评论

收藏

[Java] Java实现多线程聊天室

编程语言 编程语言 发布于:2021-09-16 18:02 | 阅读数:325 | 评论:0

这篇文章主要为大家详细介绍了Java实现多线程聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
本文实例为大家分享了Java实现多线程聊天室的具体代码,供大家参考,具体内容如下
用多线程来实现,功能会比单线程聊天室更加齐全,也更人性化一点。
多线程版本的聊天室
1. 功能分析:

  • 实现用户注册,上线,下线
  • 实现群聊和私聊
  • 统计当前在线人数
2. 服务端实现
1.维护所有的在线用户
2.注册功能:客户端名称,添加到服务器的客户端集合里
3.群聊功能:客户端发送消息,所有的客户端都能接收到
4.私聊功能:客户端与指定客户端进发送和接收消息
5.退出功能: 从服务器客户端集合中移除客户端
3. 客户端实现
1.注册功能:创建Socket对象,给服务器发送注册执行(消息)
2.群聊功能:客户端发送和接收数据
3.私聊功能:客户端指定客户端(用户),发送和接收数据
4.退出功能:给服务器发送退出指令(消息)
5.命令行的交互式输入输出
4.实现思路:
首先,要实现服务端与客户端之间的连接
这里是使用套接字建立TCP连接:
(1)服务器端先实例化一个描述服务器端口号的ServerSocket对象
(2)客户端要创建Socket对象来连接指定的服务器端
(3)服务器端调用ServerSocket类的accept()方法来监听连接到服务器端的客户端信息
(4)若服务器端与客户端连接成功,双方将返回一个Socket对象,此时双方可以进行通信
(5)服务器端与客户端使用I/O流进行连接,服务端的输出流连接客户端的输入流,客户端的输出流连接服务端的输入流
(6)使用close()方法关闭套接字(一定要记得关闭)
2.因为是拥有一个服务端来实现多个客户端的连接,此处还要解决的是多线程的问题。
每个客户端需要两个线程,来分别处理向服务端发送消息和向服务端接收消息
而服务端,当每增加一个客户端与服务端连接,服务端都要多创建一个线程来处理与客户端的连接
5. 图解析
DSC0000.jpg

6. 服务端代码实现
Server类
package test.Server;
 
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * package:test.Server
 * Description:服务器端
 * @date:2019/8/14
 * @Author:weiwei
 **/
public class server {
  public static void main(String[] args) {
    try {
      int port = 6666;
 
      ServerSocket serverSocket = new ServerSocket(port);
 
      System.out.println("服务器启动..." + serverSocket.getLocalSocketAddress());  //服务器启动,打印本地地址
 
      //线程池
      ExecutorService executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
 
      while (true) {  //死循环
        Socket client = serverSocket.accept();
        System.out.println("有客户端连接到服务器:" + client.getRemoteSocketAddress());
        executorService.execute(new HandlerClient(client));
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}
HandlerClient类
package test.Server;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ConcurrentHashMap;
 
 
/**
 * Author:weiwei
 * description:HandlerClient
 * Creat:2019/3/12
 **/
public class HandlerClient implements Runnable {
 
  /**
   * 维护所有的连接到服务端的客户端对象
   */
  private static final Map<String,Socket> ONLINE_CLIENT_MAP =
      new ConcurrentHashMap<String, Socket>();  //静态是为了不让对象变化,final不让对象被修改,ConcurrentHashMap是线程安全的类
                    //static final修饰后变量名应该用常量--大写字母加下划线分隔
  private final Socket client;
  public HandlerClient(Socket client) {  //HandlerClient在多线程环境下调用,所以会产生资源竞争,用一个并发的HashMap
    this.client = client;      //为了防止变量被修改,用final修饰
  }
 
  //@Override
  public void run() {
    try {
      InputStream clientInput=client.getInputStream(); //获取客户端的数据流
      Scanner scanner = new Scanner(clientInput); //字节流转字符流
 
      /**
       *消息是按行读取
       * 1.register:<username> 例如: register:张三
       * 2.群聊: groupChat:<message> 例如:groupChat:大家好
       * 3.私聊: privateChat:张三:你好,还钱
       * 4.退出:bye
       */
 
      while(true){
        String data = scanner.nextLine();  //读数据,按行读
        if(data.startsWith("register:")){
          //注册
          String userName = data.split(":")[1];//冒号分隔,取第一个
          register(userName);
          continue;
        }
 
        if(data.startsWith("groupChat:")){
          String message = data.split(":")[1];
          groupChat(message);
          continue;
        }
 
        if(data.startsWith("privateChat:")){
          String [] segments = data.split(":");
          String targetUserName = segments[1].split("\\-")[0]; //取目标用户名
          String message = segments[1].split("\\-")[1];   //因为要取两次,所以用数组 //取发送的消息内容
          privateChat(targetUserName,message);
          continue;
        }
 
        if(data.equals("bye")){
          //表示退出
          bye();
          continue;
        }
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  /**
   * 当前客户端退出
   */
  private void bye() {
    for(Map.Entry<String,Socket> entry : ONLINE_CLIENT_MAP.entrySet()){
      Socket target = entry.getValue();
      if(target.equals(this.client)){   //在在线用户中找到自己并且移除
        ONLINE_CLIENT_MAP.remove(entry.getKey());
        break;
      }
      System.out.println(getCurrentUserName()+"退出聊天室");
    }
    printOnlineClient();//打印当前用户
  }
 
  private String getCurrentUserName(){
    for (Map.Entry<String, Socket> entry : ONLINE_CLIENT_MAP.entrySet()) {
      Socket target = entry.getValue(); //getvalue得到Socket对象
      if(target.equals(this.client)){ //排除群聊的时候自己给自己发消息的情况
        return entry.getKey();
      }
    }
    return "";
  }
 
  /**
   * 私聊,给targetUserName发送message消息
   * @param targetUserName
   * @param message
   */
  private void privateChat(String targetUserName, String message) {
    Socket target = ONLINE_CLIENT_MAP.get(targetUserName);//获取目标用户名
    if(target == null){
      this.sendMessage(this.client,"没有这个人"+targetUserName,false);
    }else{
      this.sendMessage(target,message,true);
    }
  }
 
  /**
   * 群聊,发送message
   * @param message
   */
  private void groupChat(String message) {
    for (Map.Entry<String, Socket> entery : ONLINE_CLIENT_MAP.entrySet()) {
      Socket target = entery.getValue(); //getvalue得到Socket对象
      if(target.equals(this.client)){
        continue;      //排除群聊的时候自己给自己发消息的情况
      }
      this.sendMessage(target,message,true);
    }
  }
 
  /**
   * 以userName为key注册当前用户(Socket client)
   * @param userName
   */
  private void register(String userName) {
    if(ONLINE_CLIENT_MAP.containsKey(userName)){
      this.sendMessage(this.client,"您已经注册过了,无需重复注册",false);
    }else{
      ONLINE_CLIENT_MAP.put(userName,this.client);
      printOnlineClient();
      this.sendMessage(this.client,"恭喜"+userName+"注册成功\n",false);
    }
  }
 
  private void sendMessage(Socket target,String message,boolean prefix){
    OutputStream clientOutput = null;    //value是每一个客户端
    try {
      clientOutput = target.getOutputStream();
      OutputStreamWriter writer = new OutputStreamWriter(clientOutput);
      if(prefix) {
        String currentUserName = this.getCurrentUserName();
        writer.write("<" + currentUserName + "说:>" + message + "\n");
      }else{
        writer.write( message + "\n");
      }
      writer.flush();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
  /**
   * 打印在线客户端
   */
  private void printOnlineClient(){
    System.out.println("当前在线人数:"+ONLINE_CLIENT_MAP.size()+","+"用户名如下列表:");
    for(String userName : ONLINE_CLIENT_MAP.keySet()){  //Map的key为用户名
      System.out.println(userName);
    }
  }
}
7. 客户端代码实现
Client类
package Cilent;
 
import java.io.IOException;
import java.net.Socket;
 
/**
 * package:Cilent
 * Description:客户端
 * @date:2019/8/14
 * @Author:weiwei
 **/
public class cilent {
  public static void main(String[] args) {
    try {
      //读取地址
      String host = "127.0.0.1";
      //读取端口号
      int port = 6666;
 
      Socket client = new Socket(host,port); //先写数据再读数据,读写线程分离
      new ReadDataFromServerThread(client).start();//启动读线程
      new WriteDataToServerThread(client).start();//启动写线程
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}
WriteDateToServer类
package Cilent;
 
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
 
/**
 * Author:weiwei
 * description:客户端给服务端发送数据的线程
 * 发送的数据来自命令行的交互式输入
 * Creat:2019/3/12
 **/
public class WriteDataToServerThread extends Thread{
  private final Socket client;
  public WriteDataToServerThread(Socket client){
    this.client = client;
  }
  @Override
  public void run(){
    try {
      OutputStream clientOutput = this.client.getOutputStream();
      OutputStreamWriter writer = new OutputStreamWriter(clientOutput);
      Scanner scanner = new Scanner(System.in);  //有客户端输入数据
      while(true){
        System.out.print("请输入>>");
        String data = scanner.nextLine(); //读数据
        writer.write(data+"\n");
        writer.flush();
        if(data.equals("bye")){
          System.out.println("您已下线...");
          break;
        }
      }
      this.client.close();
    } catch (IOException e) {
       // e.printStackTrace();
    }
  }
}
ReadDateFromServer类
package Cilent;
 
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import java.util.Scanner;
 
/**
 * Author:weiwei
 * description:客户端从服务端读取数据的线程
 * Creat:2019/3/12
 **/
public class ReadDataFromServerThread extends Thread {
  private final Socket client;
  public ReadDataFromServerThread(Socket client){
    this.client=client;
  }
 
  @Override
  public void run(){
    try {
      InputStream clientInput = this.client.getInputStream();
      Scanner scanner = new Scanner(clientInput);
      while(true){
        String data = scanner.nextLine();//按行读数据
        System.out.println("来自服务端消息:"+data);
      }
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持CodeAE代码之家
原文链接:https://blog.csdn.net/weixin_43224539/article/details/88662799

关注下面的标签,发现更多相似文章