Java 网络通信编程(2):私聊功能
·
在上期文章,我们介绍了广播功能的思路并成功实现。今天我们在原代码基础上改进,进一步拓展简易的私聊功能。
1.建立哈希表
要想指定唯一的聊天对象,我们需要给予每个客户端一个唯一特征,即 userId 。如此 userId 就与每个客户端的 Socket 对象建立了一一对应关系。对于这种映射关系,我们考虑使用哈希表,以 userId 为 key,以 socket 为 value 。userId 从 1 开始自增。
public void startServer() throws Exception {
ServerSocket server = new ServerSocket(2000);
System.out.println("启动服务器");
int userId = 1;
Map<Integer, Socket> map = new HashMap<Integer, Socket>();
//重复监听连接过来的客户端
while (true) {
//阻塞监听连接过来的客户端
Socket socket = server.accept();
map.put(userId++, socket);
//启动线程,保持通信
ServerThread serverThread = new ServerThread(socket, map);
new Thread(serverThread).start();
}
}
2.群发和私聊消息处理
为了区分群发消息和私聊消息,我们给消息内容加以格式规范。
- 群发消息:“ g:……”
- 私聊消息:“ userId(1、2……):……”
为了分割和识别服务端接收的消息在冒号前后的内容,我们需要用到字符串的 split() 方法,在括号里输入“:”,字符串会以此为界分开并存入字符串数组 str 中。str[0] 是判断消息类型的标志,str[1] 是具体的消息内容。之后我们再分析 str[0] ,若为 “g” ,则遍历哈希表,将 str[1] 作为消息广播;若不是 “g” ,则需要将字符串转为数字,将数字作为键去查询对应的 socket 。拿到 socket 后把 str[1] 送给对应输出流。
public void run() {
while (true) {
try {
//接收信息
String msg = readMsg();
System.out.println("client:" + msg);
String[] str = msg.split(":");
if (str[0].equals("g")) {//前缀为“g:”是全体消息
//广播功能
for (Map.Entry<Integer, Socket> entry : map.entrySet()) {
Socket s = entry.getValue();
if (s != socket) {//排除自己客户端
OutputStream os = s.getOutputStream();
sendMsg(os, str[1]);
}
}
} else {
//私聊功能
int userId = Integer.parseInt(str[0]);
Socket s = map.get(userId);
OutputStream os = s.getOutputStream();
sendMsg(os, str[1]);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
3.结果演示
如此我们便在保留广播功能的基础上拓展出了简易私聊功能。以下为效果演示。
3号客户端先后群发消息和私聊一号客户端,结果都符合预期。



4.参考代码
以下是改进后的服务端和通信线程完整代码。
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class MServer {
public static void main(String[] args) {
try {
new MServer().startServer();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void startServer() throws Exception {
ServerSocket server = new ServerSocket(2000);
System.out.println("启动服务器");
int userId = 1;
Map<Integer, Socket> map = new HashMap<Integer, Socket>();
//重复监听连接过来的客户端
while (true) {
//阻塞监听连接过来的客户端
Socket socket = server.accept();
map.put(userId++, socket);
//启动线程,保持通信
ServerThread serverThread = new ServerThread(socket, map);
new Thread(serverThread).start();
}
}
}
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Map;
public class ServerThread implements Runnable {
public Socket socket;
public InputStream is;
public OutputStream os;
public Map<Integer, Socket> map;
public ServerThread(Socket socket, Map<Integer, Socket> map) {
this.socket = socket;
this.map = map;
//获取客户端输入输出流
try {
is = socket.getInputStream();
os = socket.getOutputStream();
sendMsg(os, "客户端连接成功");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void run() {
while (true) {
try {
//接收信息
String msg = readMsg();
System.out.println("client:" + msg);
String[] str = msg.split(":");
if (str[0].equals("g")) {//前缀为“g:”是全体消息
//广播功能
for (Map.Entry<Integer, Socket> entry : map.entrySet()) {
Socket s = entry.getValue();
if (s != socket) {//排除自己客户端
OutputStream os = s.getOutputStream();
sendMsg(os, str[1]);
}
}
} else {
//私聊功能
int userId = Integer.parseInt(str[0]);
Socket s = map.get(userId);
OutputStream os = s.getOutputStream();
sendMsg(os, str[1]);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
//发送消息方法体
public void sendMsg(OutputStream os, String msg) throws Exception {
String str = msg + "\r\n";//拼接换行
os.write(str.getBytes());
os.flush();
}
//读取消息方法体
public String readMsg() throws Exception {
byte[] bytes = new byte[1024];//保存消息的缓存区
is.read(bytes);
String msg = new String(bytes);//将 byte 数组转为字符串
return msg.trim();//去除空位
}
}
当然这个私聊功能还有许多可以完善的地方,例如遇到不符合格式的消息报错,私聊时接收消息方不知道发送方 ID 等,读者们可提出建议或自行探索。
更多推荐




所有评论(0)