前言
闲言少叙,上代码!
代码编写
server服务端
/**
* 服务端
*/
public class Server {
private static ServerSocket server = null;
private static Socket ss = null;
/**
* 客户端集合
*/
private static Map<String, ServerThread> serverThreadMap = new HashMap<String, ServerThread>();
public static void main(String[] args) {
server();
}
/**
* 普通服务器连接
*/
private static void server() {
try {
//建立服务端
server = new ServerSocket(10010);
System.out.println("server端已启动!");
while (true) {
//创建接收接口
ss = server.accept();
//启动新客户监听线程
new ServerThread(server, ss).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
ss.close();
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 内部类线程,每连接一个新的客户端就启动一个对应的监听线程
*/
@SuppressWarnings("Duplicates")
private static class ServerThread extends Thread {
ServerSocket server = null;
Socket socket = null;
InputStream is = null;
OutputStream os = null;
String clientName = null;
boolean alive = true;
public ServerThread() {
}
ServerThread(ServerSocket server, Socket socket) {
this.socket = socket;
this.server = server;
}
@Override
public void run() {
//接收数据
try {
is = socket.getInputStream();
//发送
os = socket.getOutputStream();
//缓存区
byte[] b = new byte[1024];
int length = 0;
while (alive) {
//接收从客户端发送的消息
length = is.read(b);
if (length != -1) {
//文本消息
String message = new String(b, 0, length);
//JSON字符串转 HashMap
HashMap hashMap = new ObjectMapper().readValue(message, HashMap.class);
//消息类型
String type = (String) hashMap.get("type");
//新连接
if ("OPEN".equals(type)) {
clientName = (String) hashMap.get("clientName");
//添加客户端到集合容器中
serverThreadMap.put(clientName, this);
System.out.println(clientName + "连接成功!");
System.out.println("当前客户端数量:" + serverThreadMap.size());
}
//关闭
if ("CLOSE".equals(type)) {
alive = false;
System.err.println(clientName + "退出连接,关闭监听线程!");
}
//文本消息
if ("MESSAGE".equals(type)) {
String msg = (String) hashMap.get("message");
String chat = (String) hashMap.get("chat");
//群聊(广播)
if ("GROUP".equals(chat)) {
//遍历容器,给容器中的每个对象转发消息
for (ServerThread st : serverThreadMap.values()) {
//向其他客户端发送数据
if (st != this) {
st.os.write(new String(b, 0, length).getBytes());
}
}
//后台打印
System.out.println(clientName + "向所有人说:" + msg);
}
//私聊
if ("PRIVATE".equals(chat)) {
String to = (String) hashMap.get("to");
serverThreadMap.get(to).os.write(new String(b, 0, length).getBytes());
//后台打印
System.out.println(clientName + "向" + to + "说:" + msg);
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
System.err.println("与" + clientName + "连接中断,被迫关闭监听线程!");
} finally {
try {
serverThreadMap.remove(clientName);
System.out.println("当前客户端数量:" + serverThreadMap.size());
os.close();
is.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
client客户端
/**
* 客户端
*/
@SuppressWarnings("Duplicates")
public class Client {
private static String serverInetAddress = "127.0.0.1";
private static int serverPort = 10010;
private static Socket client = null;
private static OutputStream os = null;
private static InputStream is = null;
private static String thisName;
private static boolean alive = true;
/**
* 客户端连接服务器
*/
@SuppressWarnings("unused")
public static void open(String name) {
try {
thisName = name;
InetAddress inetAddress = InetAddress.getLocalHost();
//建立连接
client = new Socket(serverInetAddress, serverPort);
//数据流发送数据
os = client.getOutputStream();
sendMsg("{\"type\":\"OPEN\",\"clientName\":\"" + name + "\"}");
//数据流接收数据
is = client.getInputStream();
byte[] b = new byte[1024];
int length = 0;
while (alive) {
//接收从服务器发送回来的消息
length = is.read(b);
if (length != -1) {
onMsg(new String(b, 0, length));
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
//关流
os.close();
client.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 关闭客户端
*/
public static void close() {
sendMsg("{\"type\":\"CLOSE\"}");
alive = false;
}
/**
* 发送消息
*/
public static void sendMsg(String msg) {
try {
//调用发送
os.write(msg.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 收到消息的回调
*/
private static void onMsg(String message) {
//JSON字符串转 HashMap
HashMap hashMap = null;
try {
hashMap = new ObjectMapper().readValue(message, HashMap.class);
} catch (IOException e) {
e.printStackTrace();
}
String msg = (String) hashMap.get("message");
String chat = (String) hashMap.get("chat");
String from = (String) hashMap.get("from");
String to = (String) hashMap.get("to");
//群聊
if ("GROUP".equals(chat)) {
//后台打印
System.out.println(thisName + "收到(" + to + ")群聊消息:" + msg);
}
//私聊
if ("PRIVATE".equals(chat)) {
//后台打印
System.out.println(thisName + "收到(" + from + ")私聊消息:" + msg);
}
}
/**
* 获取thisName
*/
public static String getThisName() {
return thisName;
}
}
controller模拟调用客户端
@RequestMapping("/sendMsg/{chat}/{msg}")
public void sendMsg(@PathVariable("chat") String chat, @PathVariable("msg") String msg) {
if ("group".equals(chat.toLowerCase())) {
//群聊
Client.sendMsg("{\"type\":\"MESSAGE\",\"chat\":\"GROUP\",\"from\":\""+Client.getThisName()+"\",\"to\":\"群号:xxxx\",\"message\":\"" + msg + "\"}");
} else {
//私聊
Client.sendMsg("{\"type\":\"MESSAGE\",\"chat\":\"PRIVATE\",\"from\":\""+Client.getThisName()+"\",\"to\":\"" + chat + "\",\"message\":\"" + msg + "\"}");
}
}
@RequestMapping("/starClient/{name}")
public void starClient(@PathVariable("name") String name) {
Client.open(name);
}
@RequestMapping("/closeClient")
public void closeClient() {
Client.close();
}
效果展示
一个服务端、两个客户端(两个不同的工程、模拟两个客户端),注意,要先启动服务端,再启动客户端!
使用controller模拟启动两个客户端:
http://localhost:10086/springboot/user/starClient/张三
http://localhost:10087/starClient/李四
张三发送群聊
http://localhost:10086/springboot/user/sendMsg/group/大家好啊
张三是发送者,server不再转发此消息给张三
张三向李四发送私聊信息
http://localhost:10086/springboot/user/sendMsg/李四/老表,你好啊
张三是发送者,server不再转发此消息给张三
李四回复张三私聊信息
李四是发送者,server不再转发此消息给李四
下线、掉线
张三:http://localhost:10086/springboot/user/closeClient
李四:直接终止客户端进程
后记
这个例子服务端每次有新的客户端连接进来,就启动一个线程去监听与此客户端的通信,当有大量客户端时就不适用了,而且涉及界面时,java socket不能主动给浏览器发送消息,界面聊天只能用轮询的方式实现,不好;多客户端、涉及有界面的聊天建议使用websocket(猛戳这里 -->WebSocket+Java 私聊、群聊实例)。