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. 图解析
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(); } } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
上一篇: MySQL - 异步复制,半同步复制,增强半同步复制,组复制
下一篇: 桥接模式的理解及适用场景