zoukankan      html  css  js  c++  java
  • 使用Java建立聊天客户端

    ---------------siwuxie095

       

       

       

       

       

       

       

    关于 聊天服务器,详见本人博客的分类:来一杯Java

    里面的 使用ServerSocket建立聊天服务器(二)

       

    本人博客(任选一个)链接:

    https://www.baidu.com/s?ie=UTF-8&wd=siwuxie095

       

       

       

    使用ServerSocket建立聊天服务器(二) 中的 ChatSocket.java

    和 ChatManager.java 略作修改:

       

       

    ChatSocket.java:

       

    package com.siwuxie095.socket;

       

    import java.io.BufferedReader;

    import java.io.BufferedWriter;

    import java.io.IOException;

    import java.io.InputStreamReader;

    import java.io.OutputStream;

    import java.io.OutputStreamWriter;

    import java.io.UnsupportedEncodingException;

    import java.net.Socket;

       

    /**

    * run()循环执行的读取的工作,即当前的服务器会不断的从客户端读取内容

    * 将读取到的内容发送到集合Vector中的所有客户端(除了自身)

    *

    * output()方法中,代码段(1)等效于代码段(2)

    *

    * @author siwux

    *

    */

       

    //创建用于Socket通信的线程:ChatSocket

    public class ChatSocket extends Thread {

     

    Socket socket;

     

    //创建构造方法,传入Socket对象

    public ChatSocket(Socket socket) {

    this.socket=socket;

    }

     

     

     

    public void output(String out) {

     

    //(1)

    // try {

    // socket.getOutputStream().write((out+" ").getBytes("UTF-8"));

    // } catch (UnsupportedEncodingException e) {

    // e.printStackTrace();

    // } catch (IOException e) {

    // System.out.println("断开了一个客户端连接");

    // ChatManager.getChatManager().remove(this);

    // e.printStackTrace();

    // }

     

     

    //(2)

    try {

       

    //对当前的Socket执行 数据输出相关功能的包装

    //使用getOutputStream()获取输出流,通过输出流向外输出数据

    //返回值是OutputStream类型,创建以接收返回值

    OutputStream os=socket.getOutputStream();

     

       

    //创建一个BufferedWriter作为数据的输出,传入匿名对象,指定编码,层层包装

    BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(os,"UTF-8"));

       

    //BufferedWriter输出字符串

    //同时加一个换行符,表示一行已完结,这样flush时才会强制输出

    //如果不加,数据只会传到当前socket的缓冲区,而发不到其他的客户端的界面上

    bw.write(out+" ");

    //因为带缓冲,所以需要强制输出,不然无法输出

    bw.flush();

       

    }catch (UnsupportedEncodingException e) {

    e.printStackTrace();

    } catch (IOException e) {

    System.out.println("断开了一个客户端连接");

    ChatManager.getChatManager().remove(this);

    e.printStackTrace();

    }

       

    }

     

    //复写run()方法

    @Override

    public void run() {

     

    output("你已经连接到了服务器");

     

    try {

     

    //Socket的输入流进行包装

    //在指定InputStreamReader时,指定编码的字符集

    //有异常抛出,用 try catch 捕获

    BufferedReader br=new BufferedReader(

    new InputStreamReader(

    socket.getInputStream(),"UTF-8"));

     

    String line=null;

    //读取从客户端发送给服务器的数据

    while ((line=br.readLine())!=null) {

    System.out.println(line);

    //通过静态方法,将自己传入,同时传入line

    ChatManager.getChatManager().publish(this, line);

    }

     

    //关闭流

    br.close();

     

    System.out.println("断开了一个客户端连接");

    ChatManager.getChatManager().remove(this);

     

    } catch (IOException e) {

    System.out.println("断开了一个客户端连接");

    ChatManager.getChatManager().remove(this);

    e.printStackTrace();

    }

     

     

    }

    }

       

       

       

    ChatManager.java:

       

    package com.siwuxie095.socket;

       

    import java.util.Vector;

       

       

    /**

    * ChatManager 将不同的socket所新建的ChatSocket线程管理起来

    * 由于一个聊天服务器只能有一个聊天的管理器:ChatManager

    * 所以要把这个类作单例化处理

    * 单例化的第一步就是让这个类的构造方法变成 private 类型

    *

    * @author siwux

    *

    */

    public class ChatManager {

     

    //让构造方法变成private类型,完成单例化的第一步

    private ChatManager(){}

     

    //为当前类创建一个实例

    private static final ChatManager cm=new ChatManager();

     

    //创建方法 getChatManager(),返回ChatManager类型

    public static ChatManager getChatManager() {

    return cm;

    }

     

     

     

    //至此,完成了这个类(ChatManager)的单例化

    //接下来就是对ChatSocket线程进行管理

     

     

    //创建一个Vector,指定泛型为ChatSocket

    Vector<ChatSocket> vector=new Vector<>();

     

    //创建add()方法,为当前集合添加一个新的ChatSocket对象

    //在创建ChatSocket时使用,即ServerListener.java中使用

    public void add(ChatSocket cs) {

    //调用Vectoradd()方法,传入cs即可

    vector.add(cs);

    }

     

     

    public void remove(ChatSocket cs) {

    vector.remove(cs);

    }

     

     

    //创建publish()方法,其中的某个ChatSocket线程可以调用publish()

    //向其他的客户端(其他的ChatSocket线程)发送信息

    //传入线程本身和需要发送的信息

    public void publish(ChatSocket cs,String msg) {

     

    //因为要发送给集合Vector中的其他所有线程,要使用遍历

    for (int i = 0; i < vector.size(); i++) {

    //获取循环中的第 i 个对象

    ChatSocket csx=vector.get(i);

     

    //当前发送信息的线程就不用再接收这条信息,

    //判断发送消息的对象是不是当前对象

    if (!cs.equals(csx)) {

    csx.output(msg);

    }

    }

    }

     

     

     

     

     

     

       

    }

       

       

       

       

       

       

    聊天客户端:

       

    工程名:MyJavaChatClient

    包名:com.siwuxie095.main、com.siwuxie095.view

    类名:StartClient.java(主类)、ChatManagerX.java、MainWindow.java

       

       

    工程结构目录如下:

       

       

       

       

       

    StartClient.java(主类):

       

    package com.siwuxie095.main;

       

    import java.awt.EventQueue;

       

    import com.siwuxie095.view.MainWindow;

       

    public class StartClient {

       

    public static void main(String[] args) {

     

    //将窗体MainWindow.java的主方法里的代码复制粘贴到这里(StartClient.java)

    //MainWindow.java里的主方法删除,这样就可以在主程序中将窗体执行出来了

    EventQueue.invokeLater(new Runnable() {

    public void run() {

    try {

    MainWindow frame = new MainWindow();

    frame.setVisible(true);

    //在创建Window时传递对MainWindow的引用

    ChatManagerX.getCM().setWindow(frame);

    } catch (Exception e) {

    e.printStackTrace();

    }

    }

    });

     

    }

       

    }

       

       

       

    ChatManagerX.java:

       

    package com.siwuxie095.main;

       

    import java.io.BufferedReader;

    import java.io.IOException;

    import java.io.InputStreamReader;

    import java.io.OutputStreamWriter;

    import java.io.PrintWriter;

    import java.net.Socket;

    import java.net.UnknownHostException;

       

    import com.siwuxie095.view.MainWindow;

       

    //需要将这个类单例化

    public class ChatManagerX {

       

    //让构造方法变成private类型,完成单例化的第一步

    private ChatManagerX(){}

     

    //创建一个 private static final 修饰的实例

    private static final ChatManagerX instance=new ChatManagerX();

     

    //创建公有的 getChatManager()获取上面的私有实例

    //这样,当前的ChatManager类就只会有唯一的一个实例

    public static ChatManagerX getCM() {

    return instance;

    }

     

     

    //现在界面中只能向ChatManager发送数据

    //为了让接收到的数据放到界面上,最简便的方法即在本地引用MainWindow

    MainWindow window;

    //实现setWindow()方法

    public void setWindow(MainWindow window) {

    this.window = window;

    //ChatManager中操作window.appendText()在界面中显示数据

    window.appendText("提示:文本框已经和ChatManager绑定了");

    }

     

     

    //创建一个Socket对象

    Socket socket;

    //创建输入流 BufferedReader

    BufferedReader reader;

    //创建输出流 PrintWriter 这里不使用 BufferedWriter

    PrintWriter writer;

     

    //具体实现连接服务器的操作

    public void connect(String ip) {

     

    //呼叫连接时创建一个新的线程

    //对网络的处理放到线程中执行

    //(先建立连接,之后循环从服务器读取数据)

    new Thread(){

     

     

    @Override

    public void run() {

     

    try {

    //传入ip 并设置端口为 12345

    socket=new Socket(ip, 12345);

     

    //获取输入和输出流,层层包装

    writer=new PrintWriter(

    new OutputStreamWriter(

    socket.getOutputStream(),"UTF-8"));

     

    reader=new BufferedReader(

    new InputStreamReader(

    socket.getInputStream(),"UTF-8"));

     

    //创建完输入输出流,需要循环监听当前输入流是否有数据

    String line;

    while ((line=reader.readLine())!=null) {

    //通过window.appendText()将数据输出到界面中

    window.appendText("收到:"+line);

    }

     

    //读取数据为空,则连接结束,关闭流

    writer.close();

    reader.close();

    //置空,更安全

    writer=null;

    reader=null;

     

    } catch (UnknownHostException e) {

    e.printStackTrace();

    } catch (IOException e) {

    e.printStackTrace();

    }

    }

       

     

     

    }.start();

    }

     

    //点击发送按钮时的操作

    public void send(String out) {

    //发送时,writer不为空才能发送

    if (writer!=null) {

    //为当前的socket输出内容时,需要添加 进行换行

    //这时flush才能强制把这一行输出,

    //即这一行完结了,flush才会把它输出

    //

    //如果不加 数据实际上是传出去了,

    //但只是传到了当前socket的缓冲区域

    //只有关闭了socketsocket才会把它强制输出,这是不可取的

    //因为聊天要保证实时性,所以要加一个换行符

    writer.write(out+" ");

    //flush强制刷新输出

    writer.flush();

    }else {

    //提示信息

    window.appendText("提示:当前的连接已经中断...");

    }

     

     

    }

    }

       

       

       

    MainWindow.java:

       

    package com.siwuxie095.view;

       

    import java.awt.event.ActionEvent;

    import java.awt.event.ActionListener;

    import java.awt.event.MouseAdapter;

    import java.awt.event.MouseEvent;

       

    import javax.swing.GroupLayout;

    import javax.swing.GroupLayout.Alignment;

    import javax.swing.JButton;

    import javax.swing.JFrame;

    import javax.swing.JPanel;

    import javax.swing.JTextArea;

    import javax.swing.JTextField;

    import javax.swing.LayoutStyle.ComponentPlacement;

    import javax.swing.border.EmptyBorder;

       

    import com.siwuxie095.main.ChatManagerX;

       

    /**

    * 添加 默认序列版本ID

    * 添加 一个TextAreaJTextArea

    * 选择根容器 contentPane,将布局改为 GroupLayout

    *

    * JTextArea 上下方各添加一个JTextFieldJButton

    *

    * @author siwux

    *

    */

    public class MainWindow extends JFrame {

       

    /**

    * 添加默认序列版本ID

    * Add default serial version ID

    */

    private static final long serialVersionUID = 1L;

    private JPanel contentPane;

     

    //将文本控件等声明到类当中,作为整个类的变量

    JTextArea txt;

    private JTextField ip;

    private JTextField send;

       

     

       

    /**

    * Create the frame.

    */

    public MainWindow() {

    //添加属性 setAlwaysOnTop(true)

    //这样打开多个窗口都可以显示在其他应用的上方

    setAlwaysOnTop(true);

     

    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    setBounds(100, 100, 504, 353);

    contentPane = new JPanel();

    contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));

    setContentPane(contentPane);

     

    txt = new JTextArea();

    txt.setText("Ready...");

     

    ip = new JTextField();

    ip.setText("127.0.0.1");

    ip.setColumns(10);

     

    JButton button = new JButton("连接到服务器");

    button.addMouseListener(new MouseAdapter() {

    @Override

    public void mouseClicked(MouseEvent arg0) {

    //使用ChatManagerconnect()和按钮的监听事件绑定

    //通过ip.getText()获得需要连接的服务器的IP地址

    //通过IP来建立Socket连接

    ChatManagerX.getCM().connect(ip.getText());

     

    }

    });

    button.addActionListener(new ActionListener() {

    public void actionPerformed(ActionEvent arg0) {

    }

    });

     

    send = new JTextField();

    send.setText("你好");

    send.setColumns(10);

     

    JButton btnNewButton = new JButton("发送");

    btnNewButton.addMouseListener(new MouseAdapter() {

    @Override

    public void mouseClicked(MouseEvent arg0) {

    //使用ChatManagersend()和按钮的监听事件绑定

    //传入send文本框的文本

    //第一个send()到服务器

    //第二个appendText()添加到中间的显示界面

    ChatManagerX.getCM().send(send.getText());

    appendText("我说:"+send.getText());

    //当前文本发送出去后,需要将当前的文本框清空

    send.setText("");

     

    }

    });

    btnNewButton.addActionListener(new ActionListener() {

    public void actionPerformed(ActionEvent e) {

    }

    });

     

     

    //下面这一大段是自动生成的代码,不用管

    GroupLayout gl_contentPane = new GroupLayout(contentPane);

    gl_contentPane.setHorizontalGroup(

    gl_contentPane.createParallelGroup(Alignment.LEADING)

    .addGroup(gl_contentPane.createSequentialGroup()

    .addContainerGap()

    .addGroup(gl_contentPane.createParallelGroup(Alignment.LEADING)

    .addComponent(txt, GroupLayout.DEFAULT_SIZE, 417, Short.MAX_VALUE)

    .addGroup(gl_contentPane.createSequentialGroup()

    .addComponent(send, GroupLayout.PREFERRED_SIZE, 302, GroupLayout.PREFERRED_SIZE)

    .addPreferredGap(ComponentPlacement.RELATED)

    .addComponent(btnNewButton, GroupLayout.DEFAULT_SIZE, 109, Short.MAX_VALUE))

    .addGroup(gl_contentPane.createSequentialGroup()

    .addComponent(ip, GroupLayout.PREFERRED_SIZE, 321, GroupLayout.PREFERRED_SIZE)

    .addPreferredGap(ComponentPlacement.RELATED)

    .addComponent(button, GroupLayout.PREFERRED_SIZE, 90, Short.MAX_VALUE)))

    .addContainerGap())

    );

    gl_contentPane.setVerticalGroup(

    gl_contentPane.createParallelGroup(Alignment.LEADING)

    .addGroup(gl_contentPane.createSequentialGroup()

    .addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)

    .addComponent(ip, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)

    .addComponent(button))

    .addPreferredGap(ComponentPlacement.RELATED)

    .addComponent(txt, GroupLayout.DEFAULT_SIZE, 201, Short.MAX_VALUE)

    .addPreferredGap(ComponentPlacement.RELATED)

    .addGroup(gl_contentPane.createParallelGroup(Alignment.BASELINE)

    .addComponent(send, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)

    .addComponent(btnNewButton)))

    );

    contentPane.setLayout(gl_contentPane);

    }

     

     

     

    //为当前类创建一个appendText()方法,传入一个String

    public void appendText(String in) {

    //添加到中间的显示界面

    txt.append(" "+in);

    }

    }

       

       

    先将服务器运行:

       

       

       

       

    再运行客户端:

       

       

       

       

    客户端界面:

       

       

       

       

    点击 连接到服务器,服务器 弹出提示:

       

       

       

       

    点击 确定,客户端弹出服务器发出的消息:

       

       

       

       

    再次运行另一个客户端,两个客户端之间发送消息:

       

       

       

       

       

    此时,服务器的控制台也会打印所有经过服务器的消息:

       

       

       

       

       

    整个流程:

       

    两个客户端 通过 IP 和 端口 向服务器发起 Socket 通信请求,

    服务器监听到请求并同意

       

    两个客户端就是两个 Socket 对象,服务器创建两个 ChatSocket 线程,

    分别处理这两个连接,且这两个线程由 ChatManager 管理

       

    这两个 ChatSocket 线程先向两个客户端发出消息:"你已经连接

    到了服务器",实际上这是服务器发送给客户端的消息

       

    客户端之间发送消息:客户端 A 发出消息后,服务器接收到该消息,

    并将其转发到客户端 B,即 服务器 相当于 中转站

       

    如果有多个客户端,服务器依然是中转站,将一个客户端的消息转发

    给其他客户端(相当于 群聊)

       

       

       

       

       

    【made by siwuxie095】

  • 相关阅读:
    ARTS第十一周
    ARTS第十周
    ARTS第九周
    一.Java技术现象
    ARTS第八周
    2019书单
    10.枚举的使用
    9.文件输入与输出
    软件模块化设计
    8.String API
  • 原文地址:https://www.cnblogs.com/siwuxie095/p/6656109.html
Copyright © 2011-2022 走看看