首先导入netty socketIO包
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.11</version>
</dependency>
服务器:
新建ServerRunner类实现ApplicationListener确保服务器在spring启动完成后启动,添加@Service标签确保spring可以扫描到。
package com.tomshow.kunya.common.NettySocket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Service;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;
import com.corundumstudio.socketio.listener.DataListener;
import com.corundumstudio.socketio.listener.DisconnectListener;
import com.corundumstudio.socketio.listener.ExceptionListenerAdapter;
import com.tomshow.kunya.common.chat.Chat;
import com.tomshow.kunya.common.chat.ClientInfo;
import com.tomshow.kunya.common.chat.MessageInfo;
import com.tomshow.kunya.common.util.PropertiesUtil;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
@Service("ServerRunner")
@Slf4j
public class ServerRunner implements ApplicationListener<ContextRefreshedEvent>{
//会话集合
private static final ConcurrentSkipListMap<String, ClientInfo> webSocketMap = new ConcurrentSkipListMap<>();
//静态变量,用来记录当前在线连接数。(原子类、线程安全)
private static AtomicInteger onlineCount = new AtomicInteger(0);
private SocketIOServer server;
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//端口
int WSS_PORT=Integer.parseInt(PropertiesUtil.getProperty("wss.server.port"));
//服务器ip
String WSS_HOST=PropertiesUtil.getProperty("wss.server.host");
System.out.println("WSS_PORT"+WSS_PORT+"**************************"+WSS_HOST);
try{
//避免重复启动
if( server== null){
Configuration config = new Configuration();
//服务器ip
config.setHostname(WSS_HOST);
config.setPort(WSS_PORT);
//该处进行身份验证h
config.setAuthorizationListener(handshakeData -> {
//http://localhost:8081?username=test&password=test
//例如果使用上面的链接进行connect,可以使用如下代码获取用户密码信息
//String username = data.getSingleUrlParam("username");
//String password = data.getSingleUrlParam("password");
return true;
});
config.setExceptionListener(new ExceptionListenerAdapter(){
@Override
public boolean exceptionCaught(ChannelHandlerContext ctx, Throwable e) throws Exception {
System.out.println("错误:\n"+e.getMessage());
ctx.close();
return true;
}
});
server = new SocketIOServer(config);
//添加链接事件监听
server.addConnectListener(new ConnectListener() {
@Override
public void onConnect(SocketIOClient client) {
String clientId = client.getHandshakeData().getSingleUrlParam("clientid");
log.info("web socket连接:"+clientId);
UUID session = client.getSessionId();
ClientInfo si = webSocketMap.get(clientId);
// 如果没有连接信息、则新建会话信息
if (si == null) {
si = new ClientInfo();
si.setOnline(true);
//在线数加1
log.info("socket 建立新连接、sessionId:"+session+"、clientId:"+clientId+"、当前连接数:"+onlineCount.incrementAndGet());
}
// 更新设置客户端连接信息
si.setLeastSignificantBits(session.getLeastSignificantBits());
si.setMostSignificantBits(session.getMostSignificantBits());
si.setLastConnectedTime(new Date());
//将会话信息更新保存至集合中
webSocketMap.put(clientId, si);
}
});
//添加销毁链接事件监听
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
String clientId = client.getHandshakeData().getSingleUrlParam("clientid");
webSocketMap.remove(clientId);
//在线数减1
log.info("socket 断开连接、sessionId:"+client.getSessionId()+"、clientId:"+clientId+"、当前连接数:"+ onlineCount.decrementAndGet());
}
});
//添加发送消息事件监听
server.addEventListener("message_event", MessageInfo.class, new DataListener<MessageInfo>(){
@Override
public void onData(SocketIOClient client, MessageInfo data, AckRequest ackSender) throws Exception {
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
String time = simpleDateFormat.format(new Date());
Chat chat = new Chat();
chat.setUserSendId(data.getTargetClientId());
chat.setUserReceiveId(data.getSourceClientId());
chat.setContent(data.getMsg());
chat.setCreatetime(time);
//这里保存聊天记录
String targetClientId = data.getTargetClientId();
ClientInfo clientInfo = webSocketMap.get(targetClientId);
if (clientInfo != null && clientInfo.isOnline()){
UUID target = new UUID(clientInfo.getMostSignificantBits(), clientInfo.getLeastSignificantBits());
log.info("目标会话UUID:"+target);
MessageInfo sendData = new MessageInfo();
sendData.setSourceClientId(data.getSourceClientId());
sendData.setTargetClientId(data.getTargetClientId());
sendData.setMsg(data.getMsg());
// 向当前会话发送信息
client.sendEvent("message_event", sendData);
// 向目标会话发送信息
server.getClient(target).sendEvent("message_event", sendData);
}
}
});
//需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
server.start();
System.out.println("start****************************server**************************end");
}
}catch(Exception e){
e.printStackTrace();
}
}
}
实体类:
@Component
@Setter
@Getter
public class Chat {
/**
* 发送者用户ID
*/
private String userSendId;
/**
* 接受者用户Id
*/
private String userReceiveId;
/**
* 内容
*/
private String content;
/**
* 创建时间
*/
private String createtime;
}
/**
* nettyIo 实体类
*/
@Component
@Getter
@Setter
public class ClientInfo {
private String clientId;
private boolean isOnline;
private long mostSignificantBits;
private long leastSignificantBits;
private Date lastConnectedTime;
}
@Component
@Setter
@Getter
public class MessageInfo {
//源客户端id
private String sourceClientId;
//目标客户端id
private String targetClientId;
//消息内容
private String msg;
}
客户端:
引入socket.io.js
<script src="${ctxsta}/common/moment/socket.io.js"></script>
定义连接:
<script>
var clientId= '8015',targetId = '5';
//clientId为本端通话id,避免重复。项目中可用用户id替换
//targetId 为对端通话id
var socket = io.connect('http://localhost:8085?clientid=' + clientId);
socket.on('connect', function () {
showMsg(':<span class="connect-msg">成功连接到服务器!</span>');
});
socket.on('message_event', function (data) {
showMsg('<br /><span class="username-msg">' + data.sourceClientId + ':</span> ' + data.msg);
});
socket.on('disconnect', function () {
showMsg(':<span class="disconnect-msg">服务已断开!</span>');
});
function sendDisconnect() {
socket.disconnect();
}
function sendMessage() {
var message = $('#msg').val();
$('#msg').val('');
var jsonObject = {
sourceClientId: clientId,
targetClientId: targetId,
msg: message
};
socket.emit('message_event', jsonObject);
}
function showMsg(message) {
var currentTime = "<span class='time'>" + moment().startOf('hour').fromNow() + "</span>";
var element = $("<div>" + currentTime + "" + message + "</div>");
$('#console').append(element);
}
$(document).keydown(function (e) {
if (e.keyCode == 13) {
$('#send').click();
}
});
</script>
页面:
<body>
<h1>Netty-socket.io Demo</h1><br/>
<div id="console" class="well"></div>
<form class="well form-inline" onsubmit="return false;">
<input id="msg" class="input-xlarge" type="text" placeholder="Type something..."/>
<button type="button" onClick="sendMessage()" class="btn" id="send">Send</button>
<button type="button" onClick="sendDisconnect()" class="btn">Disconnect</button>
</form>
</body>
完成。