基于TCP的网络实时聊天室(socket通信案例)
java团长
共 10908字,需浏览 22分钟
· 2020-12-11
来源:https://blog.csdn.net/Charzous
开门见山
(1) L(list):查看当前上线用户;
(2) G(group):群聊;
(3) O(one-one):私信;
(4) E(exit):退出当前聊天状态;
(5) bye:离线;
(6) H(help):查看帮助
本篇将详细记录网络实时聊天室的实现步骤
一、数据结构Map
二、保证线程安全
三、群聊核心方法
private void sendToMembers(String msg,String hostAddress,Socket mySocket) throws IOException{
PrintWriter pw;
OutputStream out;
Iterator iterator=users.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry entry=(Map.Entry) iterator.next();
Socket tempSocket = (Socket) entry.getKey();
String name = (String) entry.getValue();
if (!tempSocket.equals(mySocket)){
out=tempSocket.getOutputStream();
pw=new PrintWriter(new OutputStreamWriter(out,"utf-8"),true);
pw.println(hostAddress+":"+msg);
}
}
}
private void sendToOne(String msg,String hostAddress,Socket another) throws IOException{
PrintWriter pw;
OutputStream out;
Iterator iterator=users.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry entry=(Map.Entry) iterator.next();
Socket tempSocket = (Socket) entry.getKey();
String name = (String) entry.getValue();
if (tempSocket.equals(another)){
out=tempSocket.getOutputStream();
pw=new PrintWriter(new OutputStreamWriter(out,"utf-8"),true);
pw.println(hostAddress+"私信了你:"+msg);
}
}
}
四、聊天室具体设计
private ConcurrentHashMapString > users=new ConcurrentHashMap();
0、用户登录服务器
pw.println("From 服务器:欢迎使用服务!");
pw.println("请输入用户名:");
String localName = null;
while ((hostName=br.readLine())!=null){
users.forEach((k,v)->{
if (v.equals(hostName))
flag=true;//线程修改了全局变量
});
if (!flag){
localName=hostName;
users.put(socket,hostName);
flag=false;
break;
}
else{
flag=false;
pw.println("该用户名已存在,请修改!");
}
}
1、查看当前上线用户
if (msg.trim().equalsIgnoreCase("L")){
users.forEach((k,v)->{
pw.println("用户:"+v);
});
continue;
}
2、群聊
else if (msg.trim().equals("G")){
pw.println("您已进入群聊。");
while ((msg=br.readLine())!=null){
if (!msg.equals("E")&&users.size()!=1)
sendToMembers(msg,localName,socket);
else if (users.size()==1){
pw.println("当前群聊无其他用户在线,已自动退出!");
break;
}
else {
pw.println("您已退出群组聊天室!");
break;
}
}
}
3、私信
//一对一私聊
else if (msg.trim().equalsIgnoreCase("O")){
pw.println("请输入私信人的用户名:");
String name=br.readLine();
//查找map中匹配的socket,与之建立通信
//有待改进,处理输入的用户名不存在的情况
users.forEach((k, v)->{
if (v.equals(name)) {
isExist=true;//全局变量与线程修改问题
}
});
//已修复用户不存在的处理逻辑
Socket temp=null;
for(Map.Entry<Socket,String> mapEntry : users.entrySet()){
if(mapEntry.getValue().equals(name))
temp = mapEntry.getKey();
// System.out.println(mapEntry.getKey()+":"+mapEntry.getValue()+'\n');
}
if (isExist){
isExist=false;
//私信后有一方用户离开,另一方未知,仍然发信息而未收到回复,未处理这种情况
while ((msg=br.readLine())!=null){
if (!msg.equals("E")&&!isLeaved(temp))
sendToOne(msg,localName,temp);
else if (isLeaved(temp)){
pw.println("对方已经离开,已断开连接!");
break;
}
else{
pw.println("您已退出私信模式!");
break;
}
}
}
else
pw.println("用户不存在!");
}
4、退出当前聊天状态
//判断用户是否已经下线
private Boolean isLeaved(Socket temp){
Boolean leave=true;
for(Map.Entry
String > mapEntry : users.entrySet()) {if (mapEntry.getKey().equals(temp))
leave = false;
}
return leave;
}
5、离线
if (msg.trim().equalsIgnoreCase("bye")) {
pw.println("From 服务器:服务器已断开连接,结束服务!");
users.remove(socket,localName);
sendToMembers("我下线了",localName,socket);
System.out.println("客户端离开。");//加当前用户名
break;
}
6、查看帮助
else if (msg.trim().equalsIgnoreCase("H")){
pw.println("输入命令功能:(1)L(list):查看当前上线用户;(2)G(group):进入群聊;(3)O(one-one):私信;(4)E(exit):退出当前聊天状态;(5)bye:离线;(6)H(help):帮助");
continue;//返回循环
}
五、聊天室服务完整代码
class Handler implements Runnable {
private Socket socket;
public Handler(Socket socket) {
this.socket = socket;
}
public void run() {
//本地服务器控制台显示客户端连接的用户信息
System.out.println("New connection accept:" + socket.getInetAddress().getHostAddress());
try {
BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
pw.println("From 服务器:欢迎使用服务!");
pw.println("请输入用户名:");
String localName = null;
while ((hostName=br.readLine())!=null){
users.forEach((k,v)->{
if (v.equals(hostName))
flag=true;//线程修改了全局变量
});
if (!flag){
localName=hostName;
users.put(socket,hostName);
flag=false;//可能找出不一致问题
break;
}
else{
flag=false;
pw.println("该用户名已存在,请修改!");
}
}
// System.out.println(hostName+": "+socket);
sendToMembers("我已上线",localName,socket);
pw.println("输入命令功能:(1)L(list):查看当前上线用户;(2)G(group):进入群聊;(3)O(one-one):私信;(4)E(exit):退出当前聊天状态;(5)bye:离线;(6)H(help):帮助");
String msg = null;
//用户连接服务器上线,进入聊天选择状态
while ((msg = br.readLine()) != null) {
if (msg.trim().equalsIgnoreCase("bye")) {
pw.println("From 服务器:服务器已断开连接,结束服务!");
users.remove(socket,localName);
sendToMembers("我下线了",localName,socket);
System.out.println("客户端离开。");//加当前用户名
break;
}
else if (msg.trim().equalsIgnoreCase("H")){
pw.println("输入命令功能:(1)L(list):查看当前上线用户;(2)G(group):进入群聊;(3)O(one-one):私信;(4)E(exit):退出当前聊天状态;(5)bye:离线;(6)H(help):帮助");
continue;//返回循环
}
else if (msg.trim().equalsIgnoreCase("L")){
users.forEach((k,v)->{
pw.println("用户:"+v);
});
continue;
}
//一对一私聊
else if (msg.trim().equalsIgnoreCase("O")){
pw.println("请输入私信人的用户名:");
String name=br.readLine();
//查找map中匹配的socket,与之建立通信
users.forEach((k, v)->{
if (v.equals(name)) {
isExist=true;//全局变量与线程修改问题
}
});
//已修复用户不存在的处理逻辑
Socket temp=null;
for(Map.Entry
mapEntry : users.entrySet()){ if(mapEntry.getValue().equals(name))
temp = mapEntry.getKey();
}
if (isExist){
isExist=false;
//私信后有一方用户离开,另一方未知,仍然发信息而未收到回复,未处理这种情况
while ((msg=br.readLine())!=null){
if (!msg.equals("E")&&!isLeaved(temp))
sendToOne(msg,localName,temp);
else if (isLeaved(temp)){
pw.println("对方已经离开,已断开连接!");
break;
}
else{
pw.println("您已退出私信模式!");
break;
}
}
}
else
pw.println("用户不存在!");
}
//选择群聊
else if (msg.trim().equals("G")){
pw.println("您已进入群聊。");
while ((msg=br.readLine())!=null){
if (!msg.equals("E")&&users.size()!=1)
sendToMembers(msg,localName,socket);
else if (users.size()==1){
pw.println("当前群聊无其他用户在线,已自动退出!");
break;
}
else {
pw.println("您已退出群组聊天室!");
break;
}
}
}
else
pw.println("请选择聊天状态!");
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
六、效果演示:基于TCP的网络实时聊天室
结语
评论
【第129期】程序员的新宠:三款终端工具,让你告别Xshell!
概述 WindTerm:跨平台的SSH利器 首先介绍的是WindTerm,这是一款使用C语言开发的跨平台SSH客户端。它不仅完全免费,而且没有商业使用的限制。WindTerm支持SSH v2、Telnet、Raw Tcp等协议,而且性能出色,甚至超过了FinalShell和Electerm。功能
前端微服务
0
上班的时候,有一群摸鱼搭子非常重要...
上班的时候,有一群摸鱼搭子非常重要!一到上班时间,他们就从四面八方涌进群里冒泡...从八卦聊到股市、从职场聊到乌X兰局势,偶尔还会复读、相亲、battle...然后,下午6点钟准时消失不见...所以你要不要加入我们一起摸鱼?我们有北京、上海、深圳、广州、杭州、武汉、成都、南京等8个城市的摸鱼群,还有
产品经理日记
0
周四002 瑞超:同样落寞的境遇——北雪平vs埃尔夫斯堡
上赛季最终排名联赛第9的北雪平本赛季伊始表现不佳,4轮战罢他们仅以1胜1平2负的战绩排在倒数第三,这支历史上曾夺得13次联赛冠军、6次杯赛冠军老牌劲旅,正如英格兰赛场上的一众百年俱乐部,在低谷中不断探索着出路。球队主教练安德烈亚斯·阿尔姆曾是AIK索尔纳及赫根队的主教练,他于今年年初刚刚拿起球队教鞭
产品与体验
0
日本影山优佳最新杂志照,展现充满透明感的美丽
今天的图文分享的是影山优佳的杂志写真。元日向坂46的影山优佳,登上了写真杂志《周刊FLASH》5/7和5/14合并号的封面。影山优佳是日本艺人、女演员、前偶像。身高155厘米。2001年5月8日出生于东京都。2023年7月从组合日向坂46毕业,之后作为演员活跃的影山优佳,在《周刊FLAS
python教程
0
盘点一个使用超级鹰识别验证码并自动登录的案例
点击上方“Python共享之家”,进行关注回复“资源”即可获赠Python学习资料今日鸡汤江上几人在,天涯孤棹还。大家好,我是皮皮。一、前言前几天在Python钻石交流群【静惜】问了一个Python实现识别验证码并自动登录的问题,提问截图如下:验证码的截图如下所示:二、实现过程这里大家激烈的探讨,【
IT共享之家
0
朋友,你也不想一个人孤孤单单的上班吧?
上班的时候,有一群摸鱼搭子非常重要!一到上班时间,他们就从四面八方涌进群里冒泡...从八卦聊到股市、从职场聊到乌X兰局势,偶尔还会复读、相亲、battle...然后,下午6点钟准时消失不见...所以你要不要加入我们一起摸鱼?我们有北京、上海、深圳、广州、杭州、武汉、成都、南京等8个城市的摸鱼群,还有
产品经理日记
0
美团社招一面,比预想的简单。
面试题大全:www.javacn.site面试这件事就很玄学,有时候你觉得他可能很难,但面完之后竟然出奇的顺利,问的问题你都会;有些你觉得这次面试应该很简单,但去了之后就被问懵了,所以面试这件事有很多一部分运气的成分。所以说,在没有 Offer 之前就是多准备、楞怂面,主打一个大力出奇迹。这不,逛牛
Java中文社群
0
Eiten 一个构建投资组合的好帮手
Eiten是Tradytics的一个开源工具包,它实现了各种统计和算法投资策略,如Eigen组合、最小方差组合、最大夏普比率组合和基于遗传算法的组合。Eiten允许你用自己的股票组合建立自己的投资组合。Eiten中自带的严格测试框架使你能够对你的投资组合更有自信。1.准备开始之前,你要确保Pytho
Python实用宝典
0