传统的Http是基于请求-响应式的协议,需要客户端主动向用户发送请求,才能得到服务器的响应,而在请求同步响应结束后,Http也会关闭,此时服务器便不能再向客户端主动发送消息了。即若客户端想得到服务端的消息,就必须首先发送请求才能得到消息回复。
在有些场景下,如股票价格实时显示、直播、在线聊天等场景,则需要服务器主动向客户端推送消息,显然Http协议并不太适合完全这项工作,而Netty-SocketIO是基于Netty框架下用Java实现Socket通信的组件,可用于服务器主动推送消息到客户端的情形。
文章基于Netty-Socket实现Java后台+socket.io.js前端实现服务器消息推送。
<dependency>
<groupId>com.corundumstudio.socketiogroupId>
<artifactId>netty-socketioartifactId>
<version>1.7.7version>
dependency>
localhost:8089
,并在spring Bean加载的时候就开启服务。socket服务需要添加监听事项,本文用spring注入listeners
@Service
public class SocketService implements InitializingBean{
@Autowired
private EventListennter listeners;
public void startServer() {
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(8089);
SocketIOServer server = new SocketIOServer(config);
server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
@Override
public void onConnect(SocketIOClient client) {
System.err.println(client.getRemoteAddress() + " web客户端接入");
}
});
server.addListeners(listeners);
server.start();
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("start socket");
this.startServer();
}
}
@Component
public class EventListennter {
//维护每个客户端的SocketIOClient
private Map> clients = new ConcurrentHashMap<>();
@OnConnect
public void onConnect(SocketIOClient client) {
System.err.println("建立连接");
}
@OnEvent("token")
public void onToken(SocketIOClient client, SocketIOMessage message) {
List socketList = clients.get(message.getToken());
if (null == socketList || socketList.isEmpty()) {
List list = new ArrayList<>();
list.add(client);
clients.put(message.getToken(), list);
}
System.err.println("get token Message is " + message.getToken());
}
/**
* 新事务
* @param client 客户端
* @param message 消息
*/
@OnEvent("newAlert")
public void onAlert(SocketIOClient client, SocketIOMessage message) {
//send to all users
Collection> clientsList = clients.values();
for (List list : clientsList) {
for (SocketIOClient socketIOClient : list) {
socketIOClient.sendEvent("newAlert", message);
}
}
}
/**
* 通知所有在线客户端
*/
public void sendAllUser() {
Set>> entrySet = clients.entrySet();
for (Entry> entry : entrySet) {
String key = entry.getKey();
List value = entry.getValue();
for (SocketIOClient socketIOClient : value) {
SocketIOMessage message = new SocketIOMessage();
message.setMessage("send All user Msg" + key);
socketIOClient.sendEvent("newAlert", message);
}
}
}
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
System.err.println("关闭连接");
}
}
public class SocketIOMessage {
private String token;
private String message;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
@Controller
@RequestMapping("/server")
public class SocketController {
@Autowired
private EventListennter eventListennter;
@RequestMapping("/send")
public void sendMsg() {
System.err.println("send Msg....");
eventListennter.sendAllUser();
}
}
<html>
<head>
<meta charset="UTF-8">
<title>socket testtitle>
<script src="./jquery.min.js" type="text/javascript">script>
<script type="text/javascript" src="./socket.io.js">script>
head>
<body>
<h1>Netty-socketio demoh1>
<br />
<div id="console" class="well">div>
<form class="well form-inline" onsubmit="return false;">
<input id="token" class="input-xlarge" type="text" placeholder="token . . " />
<input id="to" class="input-xlarge" type="text" placeholder="to. . . " />
<input id="content" class="input-xlarge" type="text" placeholder="content. . . " />
<button type="button" onClick="sendMessage()" class="btn">Sendbutton>
<button type="button" onClick="sendDisconnect()" class="btn">Disconnectbutton>
form>
<script type="text/javascript">
var socket = io.connect('http://localhost:8089');
socket.on('connect',function() {
alert("user connect");
console.log("user connect");
});
socket.on('newAlert', function(data) {
alert("receive alert");
console.log("receive alert..." + data.message);
});
socket.on('disconnect',function() {
alert("user disconnect");
console.log("user disconnect");
});
function sendDisconnect() {
socket.disconnect();
}
function sendMessage() {
console.log("send message token");
socket.emit('token', {
token : $('#token').val(),
message : 'message token'
});
}
script>
body>
html>
addEventListener
,这个方法的第3个传参是一个接口DataListener
,如下采用匿名类实现,DataListener需要实现onData(SocketIOClient client, SocketIOMessage data, AckRequest ackSender)
其中的AckRequest可以用来同步返回给客户端(其实这个类是封装了SocketIOClient对象),其中AckRequest.sendAckData(Ojbect obj)
底层是调用了SocketIOClient
的send方法,所以其本质与SocketIOClient是一样的。server.addEventListener("test", SocketIOMessage.class, new DataListener() {
@Override
public void onData(SocketIOClient client, SocketIOMessage data,
AckRequest ackSender) throws Exception {
System.err.println("receive from web " + data.toString());
SocketIOMessage send = new SocketIOMessage();
send.setMessage("test Ack");
send.setToken("server token");
ackSender.sendAckData(send);
}
});
SocketIOClient
的sendEvent
方法在socket.io.js客户端可用socket.on('event', function(){....})
接收处理,那么AckRequest
方法发送的消息怎么接收呢? socket.io.js
客户端的emit
函数可以传3个参数,最后1个参数便回调处理AckRequest
返回的同步消息。function sendTest() {
console.log("send test...");
socket.emit('test', {
token : $('#content').val(),
message: 'test ackData'
},
function(data) { //处理AckRequest返回的消息
console.log(data.message);
});
}