前言
闲言少叙,上代码!
代码编写
server服务端
/*** 服务端*/
public classServer {private static ServerSocket server = null;private static Socket ss = null;/*** 客户端集合*/
private static Map serverThreadMap = new HashMap();public static voidmain(String[] args) {
server();
}/*** 普通服务器连接*/
private static voidserver() {try{//建立服务端
server = new ServerSocket(10010);
System.out.println("server端已启动!");while (true) {//创建接收接口
ss =server.accept();//启动新客户监听线程
newServerThread(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 extendsThread {
ServerSocket server= null;
Socket socket= null;
InputStream is= null;
OutputStream os= null;
String clientName= null;boolean alive = true;publicServerThread() {
}
ServerThread(ServerSocket server, Socket socket) {this.socket =socket;this.server =server;
}
@Overridepublic voidrun() {//接收数据
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 classClient {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 staticString thisName;private static boolean alive = true;/*** 客户端连接服务器*/@SuppressWarnings("unused")public static voidopen(String name) {try{
thisName=name;
InetAddress inetAddress=InetAddress.getLocalHost();//建立连接
client = newSocket(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 voidclose() {
sendMsg("{\"type\":\"CLOSE\"}");
alive= false;
}/*** 发送消息*/
public static voidsendMsg(String msg) {try{//调用发送
os.write(msg.getBytes());
}catch(IOException e) {
e.printStackTrace();
}
}/*** 收到消息的回调*/
private static voidonMsg(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 staticString getThisName() {returnthisName;
}
}
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 voidcloseClient() {
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 私聊、群聊实例)。