Java实现多线程聊天室
本文实例为大家分享了Java实现多线程聊天室的具体代码,供大家参考,具体内容如下
用多线程来实现,功能会比单线程聊天室更加齐全,也更人性化一点。
多线程版本的聊天室
1. 功能分析:
- 实现用户注册,上线,下线
- 实现群聊和私聊
- 统计当前在线人数
1.维护所有的在线用户
2.注册功能:客户端名称,添加到服务器的客户端集合里
3.群聊功能:客户端发送消息,所有的客户端都能接收到
4.私聊功能:客户端与指定客户端进发送和接收消息
5.退出功能: 从服务器客户端集合中移除客户端
3. 客户端实现
1.注册功能:创建Socket对象,给服务器发送注册执行(消息)
【Java实现多线程聊天室】2.群聊功能:客户端发送和接收数据
3.私聊功能:客户端指定客户端(用户),发送和接收数据
4.退出功能:给服务器发送退出指令(消息)
5.命令行的交互式输入输出
4.实现思路:
首先,要实现服务端与客户端之间的连接
这里是使用套接字建立TCP连接:
(1)服务器端先实例化一个描述服务器端口号的ServerSocket对象
(2)客户端要创建Socket对象来连接指定的服务器端
(3)服务器端调用ServerSocket类的accept()方法来监听连接到服务器端的客户端信息
(4)若服务器端与客户端连接成功,双方将返回一个Socket对象,此时双方可以进行通信
(5)服务器端与客户端使用I/O流进行连接,服务端的输出流连接客户端的输入流,客户端的输出流连接服务端的输入流
(6)使用close()方法关闭套接字(一定要记得关闭)
2.因为是拥有一个服务端来实现多个客户端的连接,此处还要解决的是多线程的问题。
每个客户端需要两个线程,来分别处理向服务端发送消息和向服务端接收消息
而服务端,当每增加一个客户端与服务端连接,服务端都要多创建一个线程来处理与客户端的连接
5. 图解析
文章图片
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 ONLINE_CLIENT_MAP =new ConcurrentHashMap(); //静态是为了不让对象变化,final不让对象被修改,ConcurrentHashMap是线程安全的类//static final修饰后变量名应该用常量--大写字母加下划线分隔private final Socket client; public HandlerClient(Socket client) {//HandlerClient在多线程环境下调用,所以会产生资源竞争,用一个并发的HashMapthis.client = client; //为了防止变量被修改,用final修饰} //@Overridepublic void run() {try {InputStream clientInput=client.getInputStream(); //获取客户端的数据流Scanner scanner = new Scanner(clientInput); //字节流转字符流 /***消息是按行读取* 1.register:例如: register:张三* 2.群聊: groupChat: 例如:groupChat:大家好* 3.私聊: privateChat:张三:你好,还钱* 4.退出:bye*/ while(true){String data = https://www.it610.com/article/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 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 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 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; }@Overridepublic 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 = https://www.it610.com/article/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; } @Overridepublic void run(){try {InputStream clientInput = this.client.getInputStream(); Scanner scanner = new Scanner(clientInput); while(true){String data = https://www.it610.com/article/scanner.nextLine(); //按行读数据System.out.println("来自服务端消息:"+data); }} catch (IOException e) {e.printStackTrace(); }}}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。
推荐阅读
- JAVA(抽象类与接口的区别&重载与重写&内存泄漏)
- 放屁有这三个特征的,请注意啦!这说明你的身体毒素太多
- 爱就是希望你好好活着
- 昨夜小楼听风
- 知识
- 死结。
- 我从来不做坏事
- 烦恼和幸福
- 关于QueryWrapper|关于QueryWrapper,实现MybatisPlus多表关联查询方式
- MybatisPlus使用queryWrapper如何实现复杂查询