JavaSE项目:聊天室,
分享于 点击 12634 次 点评:174
JavaSE项目:聊天室,
聊天室项目:
分析:/**
* 小项目:聊天室步骤分析;
* 客户端:
* main:
* 01> 使用TCP编程,创建客户端的Socket对象;
* 02> 创建键盘输入对象,用来输入用户名;
* 03> 创建通道内的流对象(输入流,输出流);
* 04> 因为有对人聊天,所以需要不断的注册用户(while循环);
* 05> 循环里面键盘输入用户名(username),将用户名发送给服务器端,用来判断用户名是否重复;
* 06> 使用new String()方法用来接收服务器的反馈;
* 07> 在客户端进行判断,如果反馈的字符串为"yes"那么用户注册成功,退出循环,否则注册失败,继续注册;
* 08> 开启子线程:客户端用来读取服务器端反馈消息的子线程;
* 09> 使用while循环和switch语句创建一个简单的菜单,每一个选项实现一个功能;
* 10> 整体用try-catch进行异常处理(注意:关闭Socket对象的时候,会抛出一个异常,因为会影响界面效果,所以会加一个catch,抛出SocketException异常,进行空处理);
* 11> 在switch语句中,每一个功能都会实现一个方法,用来约定每一种功能的消息格式,并且拥有功能的结束条件;
*
* ClientThread:(参数:InputStream)
* 01> 因为要不断接收服务器端的数据,所以需要一个while循环;
* 02> 接收数据;
* 03> 将接收的字符串以":"进行分割,得到一个字符串输出;
* 04> 得到的msgs[0]:表示发送者;
* 05> 得到的msgs[1]:表示消息的内容;
* 06> 得到的msgs[2]:表示消息类型;
* 07> 得到的msgs[3]:表示进行这个操作的时间好眠值;
* 08> 使用一个工具类,将时间毫秒值,进行转换;
* 09> 在根据消息类型,进行if判断,得到相应功能的的输出操作;
*
* 服务器端:
* main:
* 01> 创建服务器端的Socket对象;
* 02> 创建HashMap集合,用来存储用户名,以及它所对应的Socket对象;
* 03> 因为需要不断的检测用户名,所以这里需要用while循环;
* 04> 循环中,先监听客户端,之后开启检测用户名的SaveUserThread的线程;
* 05> 给整个程序抛一个IOException异常;
*
* SaveUserThread:(参数:Socket,HashMap<String, Socket>)
* 01> 创建输入输出流对象,用来接收用户名(因为需要不断的接收,所以需要while循环);
* 02> 接收客户端传来的用户名,将用户名,与HashMap集合的健进行比较,没有就将用户名,与它所对应的Socket添加到集合中;
* 03> 还要给客户端进行反馈,保存成功反馈为"yes",否则为"no";
* 04> 检测完之后需要实现一个功能:上线提醒;
* 05> 遍历HashMap集合,给排除自己的所以人发送一句话;
* 06> 但是这句话还不能直接打印到显示屏上,需要在自己的客户端进行读取和转换;
* 07> 创建一个子线程ServeThread,让服务端的读取客户端消息的子线程;
* 08> 将整个程序进行异常处理;
*
* ServeThread:(参数:Socket,HashMap<String,Socket>,String username)
* 01> 创建输入输出流对象;
* 02> 使用while循环,不断的读取客户端发来的消息;
* 03> 读取消息,将所得到的字符创赋给msgStr(String类型);
* 04> 拆分消息(以":"进行拆分),得到一个字符数组String[] msgs;
* 05> 得到的msgs[0]:表示接收者;
* 06> 得到的msgs[1]:表示消息的内容;
* 07> 得到的msgs[2]:表示消息类型;
* 08> 使用System.currentTimeNillis()得到现在的时间毫秒值;
* 09> 之后进行if条件的判断(根据消息的类型,进行判断);
* 10> 根据不同的类型,将消息进行重组,发送给需要发送的客户端;
* 11> 里面需要用到HashMap集合的get()方法,得到接收者的Socket对象;
* 12> 整个程序实现一个异常处理;
*/
代码实现:客户端主线程:
package chatroom.client;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.Scanner;
import chatroom.client.utils.InputAndOutputUtil;
import chatroom.client.utils.InputUtil;
import chatroom.configs.Configs;
//客户端
/**
* 当前程序设计不合理 原因: 将客户端,以及服务端,发消息和读消息都写在一个类中,可能会出现问题:消息阻塞的现象,
* 客户端发送端消息,如果内容够大,服务器端没有加载完,客户端还需要服务器端反馈,这个可能出现问题!
*
* 并且Java开发原则:低耦合,高内聚, 可以将部分内容放到子线程中,关键是把发送消息放在子线程还是读消息放在子线程中呢?
* 一般情况:将读消息放在线程,因为在 子线程中,一般不键盘录入的!
*
* 改进方案:客户端和服务器端分别开启两个读消息的子线程中
*/
public class ClientChatRoom {
private static Scanner sc;
private static InputStream in;
// private static ObjectInputStream in ;
private static OutputStream out;
// private static ObjectOutputStream out ;
public static void main(String[] args) {
try {
// 使用TCP编程
// 创建客户端的Socket对象
Socket s = new Socket("192.168.159.1", 8888);
//创建通道内的流对象,用来传送用户名,以及接收服务器的反馈
out = s.getOutputStream();
in = s.getInputStream();
// 创建键盘录入对象,输入用户名
sc = new Scanner(System.in);
// 客户端注册用户名
// 不断的注册
while (true) {
System.out.println("请您输入您要注册的用户名:"); // 张三 ---->数组角标越界
String username = sc.nextLine();
// 使用通道内的输出流,将用户名写给服务器端
out.write(username.getBytes());
// 接收服务器端的反馈
// 读取保存用户名线程的一个反馈
byte[] bys = new byte[1024];
int len = in.read(bys);
String fkName = new String(bys, 0, len);
//进行判断
if (fkName.equals("yes")) {
System.out.println("用户名注册成功....");
break;
} else if (fkName.equals("no")) {
System.out.println("用户名已经存在,请重新注册");
}
}
// 开启客户端的子线程
ClientThread ct = new ClientThread(in);
ct.start();
// 给用户提供选择
// 定义一个变量:
boolean flag = true;
while (flag) {
System.out.println("请输入您的选择:1 私聊,2 公聊 3 在线列表 ,4 退出 ,5 发送文件 ,6 在线隐身");
// int num = sc.nextInt() ;
// 使用工具类InputUtil中的方法inputIntType(),用来判断键盘的输入是否满足要求
int num = InputUtil.inputIntType(new Scanner(System.in));
// 使用switch语句,给用户提供选项
switch (num) {
case 1:// 私聊
privateTalk();
break;
case 2:// 公聊
publicTalk();
break;
case 3:// 在线列表
getOnList();
break;
case 4:// 退出 在客户端应该显示谁谁下线了....
exitTalk();
// 修改flag变量
flag = false;// 跳出菜单 为了关闭Socket对象
// 停掉子线程
ct.flag = false;
break;
case 5:// 发送文件
sendFile();
break;
}
}
s.close(); // 关闭客户端的Socket对象
} catch (SocketException e) { // 关闭socket对象的时候,本身就抛出一个异常,会将SocketException异常打印控制台上,不好看
// 为了让控制台不出现异常的消息字符串,所以空处理!
// 空处理
} catch (IOException e) {
e.printStackTrace();
} finally {
}
}
// 发送文件
private static void sendFile() throws IOException {
// 思路:
// 将发送的消息以及文件通过某种方式拼接成一个大的字节数组发送过去
// 规定:发送整个字节的大小10kb,文件内容可能不够10kb,用空字节补齐
// 用空字节数组,服务器端读到内容,去除两端空格
System.out.println("请输入目标用户:");
String mbUser = sc.nextLine();
System.out.println("请输入文件路径:");
String filePath = sc.nextLine();
// 将文件封装成File对象
File file = new File(filePath);
// 组装消息: 接收者:消息内容:消息类型 (文件:文件名称,和文件的大小)
String msg = mbUser + ":" + (file.getName() + "#" + file.length()) + ":" + Configs.MSG_FILE;
// 将msg转成消息字节数组
byte[] msgBytes = msg.getBytes();
// 空字节数组
byte[] emptyBytes = new byte[1024 * 10 - msgBytes.length];
// 获取文件的大小字节数组
byte[] fileBytes = InputAndOutputUtil.readFile(filePath);
// 将上面三个字节数组在内存中拼接成一个大的字节数组--->发送过去
// 创建内存操作流(针对文件不宜过大)
ByteArrayOutputStream bos = new ByteArrayOutputStream();
// 将上面字节字节写到对象对象中
bos.write(msgBytes);
bos.write(emptyBytes);
bos.write(fileBytes);
// 内存操作流中已经有这几个字节数组
// public byte[] toByteArray()
byte[] allBytes = bos.toByteArray();
out.write(allBytes);
}
private static void exitTalk() throws IOException {
// 思路:
// 客户端要做的事情:停掉客户端所在的Socket,并且将读取服务器端转发消息的子线程停掉(ClientThread)
// 服务器端要做的事情:停电服务器端读取消息的子线程ServetThread,并且将用户从集合中移出用户自己(username),给其他人XX 下线了..
// 发送的消息格式:接收者:消息内容:消息类型
String msg = "null" + ":" + "null" + ":" + Configs.MSG_EXIT;
out.write(msg.getBytes());
}
// 在线列表
private static void getOnList() throws IOException {
// 约定的消息格式:接收者:消息内容:消息类型
// 组装消息
// 发送消息到服务器读取消息的子线程中
String msg = "null" + ":" + "null" + ":" + Configs.MSG_ONLIST;
out.write(msg.getBytes());
}
// 公聊
private static void publicTalk() throws IOException {
while (true) {
// 约定的消息格式:接收者:消息内容:消息类型
System.out.println("当前您处于公聊模式 消息格式 接收者:消息内容:消息类型 -q 退出当前模式 ");
String msg = sc.nextLine();
if ("-q".equals(msg)) {
break;
}
// 组装消息 接收者:消息内容:消息类型
msg = "null" + ":" + msg + ":" + Configs.MSG_PUBLIC;
// 使用流对象发送过去
out.write(msg.getBytes());
}
}
// 私聊
private static void privateTalk() throws IOException {
while (true) {
// 约定的新的消息格式 接收者:消息内容:消息类型
System.out.println("当前您处于私聊模式 消息格式 接收者:消息内容:消息类型 -q 退出当前模式 ");
String msg = sc.nextLine();
if ("-q".equals(msg)) {
break;
}
// 约定消息格式:接收者:消息内容:消息类型
// 客户端组装消息
msg = msg + ":" + Configs.MSG_PRIVATE;
// 发送到服务器端
out.write(msg.getBytes());
}
}
}
客户端的读服务器端反馈消息的子线程:package chatroom.client;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.SocketException;
import chatroom.client.utils.InputAndOutputUtil;
import chatroom.client.utils.TimeUtil;
import chatroom.configs.Configs;
//改进之后:客户端的读服务器端反馈消息的子线程
public class ClientThread extends Thread {
private InputStream in;
public ClientThread(InputStream in) {
this.in = in;
}
// 成员位置定义一个变量
boolean flag = true;
@Override
public void run() {
try {
// 不断读取
while (flag) {
// 读取服务器端反馈的消息
byte[] bys = new byte[1024 * 10];
int len = in.read(bys);
// 客户端接收过来的消息格式:发送者:消息内容:消息类型:系统时间
String msgStr = new String(bys, 0, len).trim(); // 去除两端空格
// 以":"开始拆分消息
String[] msgs = msgStr.split(":");
String sender = msgs[0];
String msgContent = msgs[1];
int msgType = Integer.parseInt(msgs[2]);
String time = msgs[3];
// String--->long
long timeLong = Long.parseLong(time);
// 创建Date日期类对象
/*
* Date date = new Date(timeLong) ; SimpleDateFormat sdf = new
* SimpleDateFormat("yyyy-MM-dd HH:mm:ss") ; String timeStr = sdf.format(date) ;
*/// 日期文本格式
// 调用工具类,将long类型的毫秒值转换成date类型的时间格式
String timeStr = TimeUtil.changeMils2Date(timeLong, "yyyy-MM-dd HH:mm:ss");
// 整个业务逻辑都在这里 :客户端的子线程,服务器转发的消息拆分后,做出相应的展示
if (msgType == Configs.MSG_PRIVATE) {
// 私聊的逻辑
System.out.println(timeStr);
System.out.println(sender + " 对你说: " + msgContent);
} else if (msgType == Configs.MSG_ONLINE) {
// 上线提醒
System.out.println(timeStr);
System.out.println(sender + " : " + msgContent);
} else if (msgType == Configs.MSG_PUBLIC) {
// 公聊的逻辑
System.out.println(timeStr);
System.out.println(sender + " 对大家说: " + msgContent);
} else if (msgType == Configs.MSG_ONLIST) {
// 在线列表
System.out.println(timeStr);
System.out.println("当前在线用户:");
System.out.println(msgContent);
} else if (msgType == Configs.MSG_EXIT) {
// 退出逻辑
System.out.println(timeStr);
System.out.println(msgContent);
} else if (msgType == Configs.MSG_FILE) {
// 显示系统时间
System.out.println(timeStr);
// 将文件的名称和文件大小拆分出来
String[] fileInfo = msgContent.split("#");
String fileName = fileInfo[0];
long fileLength = Long.parseLong(fileInfo[1]);
// 展示内容
System.out.println(sender + "给你发送来一个文件," + fileName + "文件大小" + (fileLength / 1024) + "KB");
// 要读文件
// 使用当前通道内内输入流对象,使用内存操作输出流写
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] cacheBytes = new byte[1024];
int cacheLength = 0;
while (true) {
int len2 = in.read(cacheBytes);
bos.write(cacheBytes, 0, len2);
// 记录读取有效字节数组
cacheLength += len2;
if (cacheLength == fileLength) {
break;
}
}
// 获取文件字节数据
byte[] fileBytes = bos.toByteArray();
boolean b = InputAndOutputUtil.writeFile("D:\\" + fileName, fileBytes);
if (b) {
// 保存成功
System.out.println("当前文件保存成功" + "D:\\" + fileName);
break;
} else {
// 失败
System.out.println("文件保存失败!");
}
}
}
} catch (SocketException e) {
// 空处理
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务器端主线程:package chatroom.myserver;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
/**
* 需求:客户端不断发送消息和读取服务器端反馈的消息,服务器端不断读取消息并反馈
*
*/
public class ServerChatRoom {
public static void main(String[] args) {
try {
// 创建服务器端的Socket对象
ServerSocket ss = new ServerSocket(8888);
// 创建一个单例集合,来存储客户端所在的Socket对象
// ArrayList<Socket> list = new ArrayList<Socket>() ;
//用来存放用户名和它所对应的Socket对象
HashMap<String, Socket> hm = new HashMap<String, Socket>();
System.out.println("服务器已开启,正在等待客户端的连接...");
// 定一个变量用来记录用户端的个数
int i = 1;
// 使用while循环,表示只要有用户就需要不停的检测用户名,
while (true) {
// 监听客户端连接
Socket s = ss.accept();
System.out.println("第" + (i++) + "个客户端已经连接了...");
// 将客户端添加到集合中
// list.add(s) ;//角标从0开始,添加客户端
// 服务器端要先检验客户端输入的用户名,然后才能开启聊天线程
SaveUserThread st = new SaveUserThread(s, hm);
st.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器端保存用户名线程:package chatroom.myserver;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.HashMap;
import java.util.Set;
import chatroom.myserver.configs.Configs;
//服务器端开启单独的保存用户名线程
public class SaveUserThread extends Thread {
private Socket s;
private HashMap<String, Socket> hm;
private String username;
public SaveUserThread(Socket s, HashMap<String, Socket> hm) {
this.s = s;
this.hm = hm;
}
@Override
public void run() {
try {
// 获取通道内输入输出流
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
// 不断地保存用户名
// 不断读取客户端传递过来的username
while (true) {
byte[] bys = new byte[1024];
int len = in.read(bys);
username = new String(bys, 0, len);
System.out.println(username);
// 判断用户名是否存在,并且服务器给客户端做出反馈
//// 如果当前集合中不存在这个用户名才该用户名和它对应的Socket添加到集合中
// public boolean containsKey(Object key)
if (!hm.containsKey(username)) {// 集合中没有username,才添加
// 存进来(username用户名和它对应的通道 Socket)
hm.put(username, s);
// 给客户端的反馈用户名的校验:自己约定是否保存成功
// yes:表示成功 no 表示失败
out.write("yes".getBytes());
break;
} else {
// 给客户端的反馈用户名的校验
out.write("no".getBytes());
}
}
// 保存用户名之后--->上线提醒:遍历集合,获取每个用户名对应的通道的Socket对象,然后获取他们给自
// 输出流对象,写给自己 ,当然,排除自己!
Set<String> keySet = hm.keySet();
// 遍历所有的注册成功的用户名
for (String key : keySet) {
// 如果排除自己
if (key.equals(username)) {
continue; // 立即进入下一次循环
}
Socket socket = hm.get(key);
OutputStream os = socket.getOutputStream();
// 保存用户名的线程:
// 上线功能的发送的消息的内容要遵循服务器转发的消息格式
// 遵循服务器转格式
// 转发的消息格式:
// 发送者:消息内容:消息类型:时间
String zfMsg = username + ":" + "上线了" + ":" + Configs.MSG_ONLINE + ":" + System.currentTimeMillis();
os.write(zfMsg.getBytes());
// 之前的上线提醒的发送的格式
// os.write((username+"上线了").getBytes());
}
new ServerThread(s, hm, username).start();
// 整个保存用户名和上线提醒完成了, 这里开启聊天线程
// ServeThread:服务端的读取客户端消息的子线程
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务器端读取客户端发送来的消息的子线程:package chatroom.myserver;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Set;
import chatroom.myserver.configs.Configs;
//服务器端读取客户端发送来的消息的子线程
public class ServerThread extends Thread {
/*
* private InputStream in;
*
* public ServerThread(InputStream in) { this.in = in; }
*/
private Socket s;
// private ArrayList<Socket> list ;
private HashMap<String, Socket> hm;
private String username;
public ServerThread(Socket s, HashMap<String, Socket> hm, String username) {
this.s = s;
this.hm = hm;
this.username = username;
}
@Override
public void run() {
try {
// 获取客户端所在通道内的输入和输出流
OutputStream out = s.getOutputStream();
InputStream in = s.getInputStream();
// 不断读取
while (true) {
byte[] bys = new byte[1024 * 10];
int len = in.read(bys);
// msgStr它是客户端发送来的消息
// 之前的消息格式:接收者:消息内容:发送者
// 现在拿到客户端发送过来消息: 接收者:消息内容:消息类型
// 现在的消息格式
String msgStr = new String(bys, 0, len).trim(); // 去掉两端空格
System.out.println(msgStr);
// 拆分消息
String[] msgs = msgStr.split(":");
// 接收者
String receiver = msgs[0];
// 消息内容
String msgContent = msgs[1];
// 消息类型
// String msgType = msgs[2] ;
int msgType = Integer.parseInt(msgs[2]);
// 拆分消息之后,重新组装消息,约定服务器转发格式
// 发送者:消息内容:消息类型:时间
long time = System.currentTimeMillis();
// 服务器端读取消息的子线程应该根据不同的消息类型做出不同的处理
if (msgType == Configs.MSG_PRIVATE) {
// 私聊处理
// 要符合转发格式
// 获取接收者所在的通道内的Socket对象
Socket socket = hm.get(receiver);
// 通过socket对象获取当前接收者的通道 内的输出流
// 转发的消息格式:
// 发送者:消息内容:消息类型:时间
String zfMsg = username + ":" + msgContent + ":" + msgType + ":" + time;
//getOutputStream():返回Socket对象的输出流对象
socket.getOutputStream().write(zfMsg.getBytes());
} else if (msgType == Configs.MSG_PUBLIC) {
// 公聊处理
// 遍历集合
Set<String> keySet = hm.keySet();
for (String key : keySet) {
// 排除自己
if (key.equals(username)) {
continue;// 立即进入下一次循环
}
// 获取当前用户名所在的通道的内的Socket对象
Socket socket = hm.get(key);
// 获取通道当前用户的通道内的输出流对象
OutputStream os = socket.getOutputStream();
// 符合转发格式:组装消息: 发送者:消息内容:消息类型:系统时间
String zfMsg = username + ":" + msgContent + ":" + Configs.MSG_PUBLIC + ":" + time;
os.write(zfMsg.getBytes());
}
} else if (msgType == Configs.MSG_ONLIST) {
// 在线列表
// 创建一个字符串缓冲区对象
StringBuffer sb = new StringBuffer();
int i = 1;
// 逻辑:遍历HashMap集合 取出键值 输出 获取每一个用户,排除自己
Set<String> keySet = hm.keySet();
for (String key : keySet) {
// 排除自己
if (key.equals(username)) {
continue;
}
// 需要容器
// 拼接
sb.append((i++)).append(",").append(key).append("\n");
// 获取谁所在的通道的Socket对象 获取发送者的Socket对象,将在线用户发送给发送者
// 组装消息,转发:格式:发送者:消息内容:消息类型:时间
String zfMsg = username + ":" + sb.toString() + ":" + Configs.MSG_ONLIST + ":" + time;
hm.get(username).getOutputStream().write(zfMsg.getBytes());
}
} else if (msgType == Configs.MSG_EXIT) {
// 下线处理
// 遍历集合,排除自己
Set<String> keySet = hm.keySet();
for (String key : keySet) {
// 排除自己
if (key.equals(username)) {
continue;
}
// 获取Socket对象
Socket socket = hm.get(key);
// 组装下线
String zfMsg = username + ":" + "下线了" + ":" + Configs.MSG_EXIT + ":" + time;
socket.getOutputStream().write(zfMsg.getBytes());
}
break;
} else if (msgType == Configs.MSG_FILE) {
// 发送文件
// 将文件名称和文件大小拆分出来
String[] fileInfo = msgContent.split("#");
String fileName = fileInfo[0];
long fileLength = Long.parseLong(fileInfo[1]);
// 组装消息:发送者:消息内容:消息类型:时间
String msg = username + ":" + msgContent + ":" + Configs.MSG_FILE + ":" + time;
// 获取msg的消息字节数组
byte[] msgBytes = msg.getBytes();
// 文件两端空格去掉了,中间也会有空字节
byte[] emptyByte = new byte[1024 * 10 - msgBytes.length];
// 读取文件
// 定义一个缓冲区
byte[] cacheBytes = new byte[1024];
int cacheLength = 0; // 读取的实际的有效字节数
// 使用客户端所在通道内的输入流去读这个文件,然后在使用内存操作流,将读到的内容存储到内存操作流中
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while (true) {
int len2 = in.read(cacheBytes);
bos.write(cacheBytes, 0, len2);
cacheLength += len2;// 每次记录读到的实际有效字节数
if (cacheLength == fileLength) {// 一旦读取完毕,停掉
break;
}
}
// //要么重置一下流对象,要么重新创建一个流的对象
byte[] fileByes = bos.toByteArray(); // 获取文件字节数组
bos.reset();
// 获取到文件的所在的字节数组
bos.write(msgBytes);
bos.write(emptyByte);
bos.write(fileByes);
// 在拼成一个大的字节数组
byte[] allBytes = bos.toByteArray();
// 转发
out.write(allBytes);
// 转发出去
}
// 之前的格式
// 接收者msgs[0].发送者
// 获取接收者所在的通的内流
// 任何包装类类型都有对应的parseXXX()方法 ---> Long.parseLong(long) ; Double Byte
// Socket socket = list.get(Integer.parseInt(msgs[0]));
// socket.getOutputStream().write((msgs[2]+"对你说"+msgs[1]).getBytes());
}
// username关闭掉自己的所在Socket对象.并且从集合中将自己移出掉
hm.get(username).close(); // 关闭会SocketException,做空处理
hm.remove(username);
} catch (SocketException e) {
//空处理
} catch (Exception e) {
e.printStackTrace();
}
}
}
相关文章
- 暂无相关文章
用户点评