写点什么

手把手教会你 | 多用户 - 服务器聊天室应用软件开发

作者:TiAmo
  • 2023-05-05
    江苏
  • 本文字数:3510 字

    阅读完需:约 12 分钟

手把手教会你 | 多用户-服务器聊天室应用软件开发

01.项目分析


这个项目利用 Socket、ServerSocket、DatainputStream、DataOutputStream、Thread 以及 GUI 技术,开发一个简单聊天室应用程序。图 1 显示这个聊天室程序本机模拟运行后的典型结果。其中可以看到服务器接受两个用户以及显示两个用户的对话过程。下图是在本机操作系统窗口运行一个聊天用户的截图。



图 1 聊天室两个用户对话截图


类的设计:

在下面的代码解释中将详细讨论主要类和方法的设计目的编写技术。这里将项目中的两大类:服务端类 ChatServer 以及用户端类 ChatClient 做一个概括性描述。

ChatServer——利用 ServerSocket 创建连接、监控用户端连接端口、利用 API 类 Vector 管理和协调多用户端的请求与对话操作,以及各种异常处理。

ChatClient——利用 Socket 创建对服务端 ServerSocket 的连接和异常处理、运行时得到用户名以及显示、利用 GUI 组件创建聊天室窗口、布局、显示聊天内容以及事件处理。利用线程执行对多聊天用户的协调处理和操作等。ChatClient 包括如下内部类:

WindowExitHandler——执行按下 Exit 按钮时的关闭聊天室窗口操作。

TextActionHandler——执行将输入的聊天信息显示到每个用户聊天室窗口的功能。

ChatClientReceive——执行聊天用户线程之间聊天信息的协调性显示以及运行。


实战项目测试和模拟运行:

建议你首先在本机对这个实战项目进行模拟试运行。服务端程序可在 Eclipse 或者本机操作系统运行;然后打开两到三个本机操作系统窗口,按照图 1 下图运行用户端程序。


以下是用户端程序的主要代码部分:

class TextActionHandler implements ActionListener {//处理文本框聊天信息事件和发送信息到服务器    public void actionPerformed(ActionEvent e) {      try  {        if (e.getSource() == sendText) { //如果是文本框触发事件          remoteOut.writeUTF(username + "->" + sendText.getText()); //发送信息          receivedText.append("\n" + username + "->" + sendText.getText());//显示到本机          sendText.setText(""); //清除文本框内容        }      }      catch(IOException x) {        System.out.println(x.getMessage() + " : connection to peer lost.");      }    }  }}class ChatClientReceive extends Thread { //处理用户接收聊天室信息线程  private ChatClient chat;  ChatClientReceive(ChatClient chat) { //构造方法    this.chat=chat;  }  public synchronized void run() { //应用协调覆盖run()方法    String s;    DataInputStream remoteIn=null;    try {      remoteIn= new DataInputStream(chat.sock.getInputStream()); //创建接收信息数据流      while(true) {        s = remoteIn.readUTF(); //按UTF方式读入信息        chat.receivedText.append("\n" + s); //添加到文本窗口      }    }    catch(IOException e) {      System.out.println(e.getMessage() + " : connection to peer lost.");    }  }}
复制代码

用户端主方法创建 GUI 窗口以及 ChatClient 对象如下:

//聊天室用户端主方法代码public static void main(String args[]) {  JFrame frame= new JFrame("Connecting to Dear "+args[0]); //利用指令参数指定用户名  ChatClient chat=new ChatClient(frame,args[0],"localhost"); //创建用户  frame.add("Center",chat); //注册聊天室窗口  frame.setSize(350,600); //指定大小  frame.setResizable(false); //不可变更  frame.setVisible(true); //显示窗口  chat.client(); //调用client()方法  }
复制代码

代码中利用指令行参数 args[0]来指定用户名。所以必须在操作系统窗口利用 java 指令运行这个用户端程序。例如:

java ChatClient UserName //打入具体用户名,如 Emily
复制代码

如下是聊天室服务器端程序的主要代码:

//...public class ChatServer { //聊天室服务器端程序  private static final int port = 1688; //指定端口  private boolean connected = true; //假设连接无误  private Vector<DataOutputStream> clients=new Vector<DataOutputStream>(); //创建用户队列  public static void main(String args[]) {    new ChatServer().server(); //创建无名服务器对象并调用方法server()  }void server() { //自定义方法执行连接用户和聊天室操作    ServerSocket serverSock = null;    try {      InetAddress serverAddr=InetAddress.getByName(null); //地址初始化      System.out.println("Welcome to Chat Server. The server is running..."); //显示运行信息      System.out.println("Waiting for " + serverAddr.getHostName() + " on port "+ port);      serverSock=new ServerSocket(port, 50); //聊天室最大用户为50, 可以是任何整数    catch(IOException e) {      System.out.println(e.getMessage()+": Disconnected/Failed");    }    while(connected) {      try {        Socket socket=serverSock.accept(); //接受用户连接请求        System.out.println("Accept"+socket.getInetAddress()        getHostName()); //显示信息        //如下代码行创建输出流        DataOutputStream remoteOut= new DataOutputStream(socket.get        OutputStream());        clients.addElement(remoteOut); //将一个用户加入聊天室队列        new ServerHelper(socket,remoteOut,this).start();                  //启动这个用户的线程      }      catch(IOException e) {      System.out.println(e.getMessage()+": Disconncted/Failed");      }    }  }  synchronized Vector getClients() { //自定义方法返回用户队列    return clients;  }  synchronized void removeFromClients(DataOutputStream remoteOut){ //删除推出聊天室用户    clients.removeElement(remoteOut);  }}
复制代码

虽然在这个代码中规定聊天室最大用户容量为 50,这个数量可根据网络速度调整。代码中利用 Vector 对象来记录聊天室中的用户对象,以便向所有聊天室用户发送对话信息。每一个用户都是一个独立的线程,由其创建一个无名 ServerHelper 对象,并调用其 start()方法来启动执行。因为在调用自定义方法 getClients()和 removeFromClients()时存在多线程协调问题,所以应用协调操作 synchronized。


如下是自定义类 ServerHelper 的代码:

//聊天室服务器端用来协调处理用户聊天信息I/O的线程class ServerHelper extends Thread {  private Socket sock;  private DataOutputStream remoteOut;  private ChatServer server;  private boolean connected = true;  private DataInputStream remoteIn;  //构造器对服务器和输入聊天室信息的用户初始化  ServerHelper(Socket sock,DataOutputStream remoteOut,ChatServer server) throws IOException {    this.sock=sock;    this.remoteOut=remoteOut;    this.server=server;    remoteIn=new DataInputStream(sock.getInputStream());  }  public synchronized void run() { //覆盖run()方法并协调运行    String s;    try {      while(connected) {        s = remoteIn.readUTF(); //读入一个用户输入信息        broadcast(s); //传播这个信息到所有聊天室用户      }    }    catch(IOException e) {      System.out.println(e.getMessage()+"connection failed");    }  }  private void broadcast(String s) { //自定义方法执行聊天室信息传播    Vector clients=server.getClients(); //得到聊天室所有用户    DataOutputStream dataOut=null; //声明输出流    for(Enumeration e=clients.elements(); e.hasMoreElements(); ) { //对每一个聊天室用户      dataOut=(DataOutputStream)(e.nextElement()); //得到用户输出流对象      if(!dataOut.equals(remoteOut)) { //如果不是删除用户        try {          dataOut.writeUTF(s); //输出这个聊天室信息        }        catch(IOException x) {          System.out.println(x.getMessage()+"Failed");          server.removeFromClients(dataOut);        }      }    }  }}
复制代码

可以看到,由于在 Vector 的队列中储存有所有聊天室用户输出流信息,因而容易实现对聊天室所有用户传播对话信息。当某个用户在聊天室输入任何对话后,这个信息作为参数,传入自定义方法 broadcast()中,并利用循环和枚举技术,遍历用户队列,执行向所有用户传播这个对话信息的操作。由于方法 run()有可能在同一时间被多个用户运行的情况,代码中利用 synchronized 来实现这个协调。  

发布于: 刚刚阅读数: 3
用户头像

TiAmo

关注

有能力爱自己,有余力爱别人! 2022-06-16 加入

CSDN全栈领域优质创作者,万粉博主;阿里云专家博主、星级博主、技术博主、阿里云问答官,阿里云MVP;华为云享专家;华为Iot专家;亚马逊人工智能自动驾驶(大众组)吉尼斯世界纪录获得者

评论

发布
暂无评论
手把手教会你 | 多用户-服务器聊天室应用软件开发_多线程并发_TiAmo_InfoQ写作社区