Java如何实现多线程聊天应用程序?S1(服务端编程)

2021年3月27日13:49:13 发表评论 700 次浏览

先决条件:在套接字编程中引入线程

在上面的文章中, 创建了一个简单的日期时间服务器, 该服务器使用线程同时处理多个用户请求。它解释了网络编程中线程化的基本概念。可以对相同的概念进行很小的修改即可使用, 以扩展上述思想并创建类似于Facebook Messenger, whatsapp等的聊天应用程序。

下一篇文章介绍了此类应用程序的实现, 并提供了详细的说明, 限制及其解决方案。

在S1中,我们将讨论服务器端编程(Server.java),在S2中讨论客户端编程(Client.java)。

服务器端编程(Server.java)

服务器类:主服务器实现很容易, 并且与上一篇文章相似。以下几点将有助于理解Server的实现:

  1. 服务器运行无限循环以继续接受传入的请求。
  2. 当请求到来时, 它分配一个新线程来处理通信部分。
  3. 服务器还将客户端名称存储到向量中, 以跟踪连接的设备。向量存储与当前请求相对应的线程对象。助手类使用这个向量查找要向其发送邮件的收件人的姓名。由于此向量包含所有流, 因此处理程序类可以使用它成功地将消息传递到特定客户端。
  4. 调用start()方法。

ClientHandler类:与上一篇文章类似, 我们创建了一个用于处理各种请求的帮助器类。这次, 我们与套接字和流一起引入了名称变量。这将保留连接到服务器的客户端的名称。以下几点将有助于理解ClientHandler的实现:

每当处理程序收到任何字符串时, 它都会将其分解为消息和收件人部分。为此, 它使用Stringtokenizer, 并以"#"作为分隔符。在此假设字符串始终为以下格式:

message # recipient

然后, 它在连接的客户端列表中搜索收件人的名称, 并将其作为矢量存储在服务器中。如果在客户端列表中找到收件人的名称, 则会在其输出流上转发邮件, 并在邮件的发送方名称之前添加发件人的名称。

// Java implementation of  Server side
// It contains two classes : Server and ClientHandler
// Save file as Server.java
  
import java.io.*;
import java.util.*;
import java.net.*;
  
// Server class
public class Server 
{
  
     // Vector to store active clients
     static Vector<ClientHandler> ar = new Vector<>();
      
     // counter for clients
     static int i = 0 ;
  
     public static void main(String[] args) throws IOException 
     {
         // server is listening on port 1234
         ServerSocket ss = new ServerSocket( 1234 );
          
         Socket s;
          
         // running infinite loop for getting
         // client request
         while ( true ) 
         {
             // Accept the incoming request
             s = ss.accept();
  
             System.out.println( "New client request received : " + s);
              
             // obtain input and output streams
             DataInputStream dis = new DataInputStream(s.getInputStream());
             DataOutputStream dos = new DataOutputStream(s.getOutputStream());
              
             System.out.println( "Creating a new handler for this client..." );
  
             // Create a new handler object for handling this request.
             ClientHandler mtch = new ClientHandler(s, "client " + i, dis, dos);
  
             // Create a new Thread with this object.
             Thread t = new Thread(mtch);
              
             System.out.println( "Adding this client to active client list" );
  
             // add this client to active clients list
             ar.add(mtch);
  
             // start the thread.
             t.start();
  
             // increment i for new client.
             // i is used for naming only, and can be replaced
             // by any naming scheme
             i++;
  
         }
     }
}
  
// ClientHandler class
class ClientHandler implements Runnable 
{
     Scanner scn = new Scanner(System.in);
     private String name;
     final DataInputStream dis;
     final DataOutputStream dos;
     Socket s;
     boolean isloggedin;
      
     // constructor
     public ClientHandler(Socket s, String name, DataInputStream dis, DataOutputStream dos) {
         this .dis = dis;
         this .dos = dos;
         this .name = name;
         this .s = s;
         this .isloggedin= true ;
     }
  
     @Override
     public void run() {
  
         String received;
         while ( true ) 
         {
             try
             {
                 // receive the string
                 received = dis.readUTF();
                  
                 System.out.println(received);
                  
                 if (received.equals( "logout" )){
                     this .isloggedin= false ;
                     this .s.close();
                     break ;
                 }
                  
                 // break the string into message and recipient part
                 StringTokenizer st = new StringTokenizer(received, "#" );
                 String MsgToSend = st.nextToken();
                 String recipient = st.nextToken();
  
                 // search for the recipient in the connected devices list.
                 // ar is the vector storing client of active users
                 for (ClientHandler mc : Server.ar) 
                 {
                     // if the recipient is found, write on its
                     // output stream
                     if (mc.name.equals(recipient) && mc.isloggedin== true ) 
                     {
                         mc.dos.writeUTF( this .name+ " : " +MsgToSend);
                         break ;
                     }
                 }
             } catch (IOException e) {
                  
                 e.printStackTrace();
             }
              
         }
         try
         {
             // closing resources
             this .dis.close();
             this .dos.close();
              
         } catch (IOException e){
             e.printStackTrace();
         }
     }
}

输出如下:

New client request received : Socket[addr=/127.0.0.1, port=61818, localport=1234]
Creating a new handler for this client...
Adding this client to active client list
New client request received : Socket[addr=/127.0.0.1, port=61819, localport=1234]
Creating a new handler for this client...
Adding this client to active client list

局限性:

尽管服务器的上述实现设法处理了大多数情况, 但是上面定义的方法仍存在一些缺点。

  • 从以上程序可以清楚地看到, 如果客户数量增加, 搜索时间将会增加在处理程序类中。为了避免这种增加, 可以使用两个哈希图。一个以名称为键, 并在活动列表中作为值索引。另一个以索引为键, 关联的处理程序对象为值。这样, 我们可以快速查找两个哈希图以匹配收件人。留给读者来实施此技巧以提高实施效率。
  • 需要注意的另一件事是该实现用户断开与服务器的连接时无法正常工作。因为在此实现中未处理断开连接, 将引发很多错误。可以像以前的基本TCP示例中一样轻松实现。读者还可以在程序中实现此功能。

客户端程序(Client.java)与以前的文章有很大的不同, 因此将在本系列的第2集中对此进行讨论。

相关文章:多线程聊天应用程序|S2

如果发现任何不正确的地方, 或者想分享有关上述主题的更多信息, 请写评论。

木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: