基于netty-socketio的web推送服务
在WEB项目中,服务器向WEB页面推送消息是一种常见的业务需求。PC端的推送技术可以使用socket建立一个长连接来实现。传统的web服务都是客户端发出请求,服务端给出响应。但是现在直观的要求是允许特定时间内在没有客户端发起请求的情况下服务端主动推送消息到客户端。最近的预警系统中,需要服务端向预警系统推送商品行情和K线相关的数据,所以对常用的WEB端推送方式进行调研。常见的手段主要包括以下几种:
- 轮询(俗称“拉”,polling):Ajax 隔一段时间向服务器发送请求,询问数据是否发生改变,从而进行增量式的更新。轮询的时间间隔成了一个问题:间隔太短,会有大量的请求发送到服务器,会对服务器负载造成影响;间隔太长业务数据的实时性得不到保证。连。使用轮询的优点是实现逻辑简单,缺点是无效请求的数量多,在用户量较大的情况下,服务器负载较高。因此轮询的方式通常在并发数量较少、并且对消息实时性要求不高的情况下使用。
- 长轮询技术(long-polling):客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息或超时(设置)才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。长轮询技术的优点是消息实时性高,无消息的情况下不会进行频繁的请求;缺点是服务端维持和客户端的连接会消耗掉一部分资源。
- 插件提供socket方式:比如利用Flash XMLSocket,Java Applet套接口,Activex包装的socket。优点是对原生socket的支持,和PC端和移动端的实现方式相似;缺点是浏览器需要安装相应的插件。
- WebSocket:是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。其优点是更好的节省服务器资源和带宽并达到实时通讯;缺点是目前还未普及,浏览器支持不好;
综上,考虑到浏览器兼容性和性能问题,采用长轮询(long-polling)是一种比较好的方式。netty-socketio是一个开源的Socket.io服务器端的一个java的实现, 它基于Netty框架。 项目地址为: https://github.com/mrniko/netty-socketio
以下基于Netty-socketIO实现一个简单的聊天室功能,首先引入依赖:
<dependency> <groupId>com.corundumstudio.socketio</groupId> <artifactId>netty-socketio</artifactId> <version>1.7.3</version> </dependency>
定义Listen,用户监听Oncennect、disconnect和OnMSG事件:
@Service("eventListenner") public class EventListenner { @Resource(name = "clientCache") private SocketIOClientCache clientCache; @Resource(name = "socketIOResponse") private SocketIOResponse socketIOResponse; @OnConnect public void onConnect(SocketIOClient client) { System.out.println("建立连接"); } @OnEvent("OnMSG") public void onSync(SocketIOClient client, MsgBean bean) { System.out.printf("收到消息-from: %s to:%s\n", bean.getFrom(), bean.getTo()); clientCache.addClient(client, bean); SocketIOClient ioClients = clientCache.getClient(bean.getTo()); System.out.println("clientCache"); if (ioClients == null) { System.out.println("你发送消息的用户不在线"); return; } socketIOResponse.sendEvent(ioClients,bean); } @OnDisconnect public void onDisconnect(SocketIOClient client) { System.out.println("关闭连接"); } }
定义消息发送类:
@Service("socketIOResponse") public class SocketIOResponse { public void sendEvent(SocketIOClient client, MsgBean bean) { System.out.println("推送消息"); client.sendEvent("OnMSG", bean); } }
定义Cache用于保存所有和Web端的连接:
@Service("clientCache") public class SocketIOClientCache { //String:EventType类型 private Map<String,SocketIOClient> clients=new ConcurrentHashMap<String,SocketIOClient>(); //用户发送消息添加 public void addClient(SocketIOClient client,MsgBean msgBean){ clients.put(msgBean.getFrom(),client); } //用户退出时移除 public void remove(MsgBean msgBean) { clients.remove(msgBean.getFrom()); } //获取所有 public SocketIOClient getClient(String to) { return clients.get(to); } }
定义Server:
//继承InitializingBean,使Spring加载完配置文件,自动运行如下方法 @Service("chatServer") public class ChatServer implements InitializingBean{ @Resource private EventListenner eventListenner; public void afterPropertiesSet() throws Exception { Configuration config = new Configuration(); config.setPort(9098); SocketConfig socketConfig = new SocketConfig(); socketConfig.setReuseAddress(true); socketConfig.setTcpNoDelay(true); socketConfig.setSoLinger(0); config.setSocketConfig(socketConfig); config.setHostname("localhost"); SocketIOServer server = new SocketIOServer(config); server.addListeners(eventListenner); server.start(); System.out.println("启动正常"); } }
定义MSGbean:
public class MsgBean { private String from; private String to; private String content; public String getFrom() { return from; } public void setFrom(String from) { this.from = from; } public String getTo() { return to; } public void setTo(String to) { this.to = to; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } @Override public String toString() { return "MsgBean [from=" + from + ", to=" + to + ", content=" + content + "]"; } }
定义Main:
public class Main { public static void main(String[] args) { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("classpath:/applicationContext-push.xml"); context.start(); context.getBean("chatServer"); } }
HTML页面:
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Socketio chat</title> <script src="./jquery-1.7.2.min.js" type="text/javascript"></script> <script type="text/javascript" src="./socket.io.js"></script> <style> body { padding: 20px; } #console { height: 400px; overflow: auto; } .username-msg { color: orange; } .connect-msg { color: green; } .disconnect-msg { color: red; } .send-msg { color: #888 } </style> </head> <body> <h1>Netty-socketio chat demo</h1> <br /> <div id="console" class="well"></div> <form class="well form-inline" onsubmit="return false;"> <input id="from" class="input-xlarge" type="text" placeholder="from. . . " /> <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">Send</button> <button type="button" onClick="sendDisconnect()" class="btn">Disconnect</button> </form> </body> <script type="text/javascript"> var socket = io.connect('http://localhost:9098'); socket.on('connect',function() { output('<span class="connect-msg">Client has connected to the server!</span>'); }); socket.on('OnMSG', function(data) { output('<span class="username-msg">' + data.content + ' : </span>'); }); socket.on('disconnect',function() { output('<span class="disconnect-msg">The client has disconnected! </span>'); }); function sendDisconnect() { socket.disconnect(); } function sendMessage() { var from = $("#from").val(); var to = $("#to").val(); var content = $('#content').val(); socket.emit('OnMSG', { from : from, to : to, content : content }); } function output(message) { var currentTime = "<span class='time' >" + new Date() + "</span>"; var element = $("<div>" + currentTime + " " + message + "</div>"); $('#console').prepend(element); } </script> </html>
不忘初心,方得始终