socket.io 使用简介
一、Socket 编程基础
什么是 Socket
两台计算机通过网络进行通信,必须具备如下要素:
- IP 地址
- 协议
- 端口号(用于区分同一台主机上不同的应用程序。范围为 0~65535,其中 0~1023 为系统保留)
IP 地址和端口号组成了所谓的 Socket,Socket 是网络上运行的程序之间双向通信链路的终结点(英文原意为“插座”。客户软件将插头插到房间里不同编号的插座,就可以得到不同的服务),是 TCP 和 UDP 的基础(Socket 可实现 TCP / UDP)。
利用 Socket 建立网络连接的步骤
建立 Socket 连接至少需要一对套接字,其中一个运行于客户端,称为 ClientSocket,另一个运行于服务器端,称为 ServerSocket。套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
- 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
- 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
- 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。
Java 实现基于 TCP 的 Socket 通信
见百度百科 Java 示例,不赘述。 https://baike.baidu.com/item/socket/281150
二、什么是 socket.io?
HTTP 的缺陷
通信只能由客户端发起,服务器无法主动向客户端推送信息。如果服务器有连续的状态变化,客户端要获知就非常麻烦。 在构建实时 Web 应用时,可用技术如下:
- 轮询:这是最早的一种实现实时 Web 应用的方案。客户端以一定的时间间隔向服务端发出请求,以频繁请求的方式来保持客户端和服务器端的同步。
- 长轮询:长轮询则是针对普通轮询的这种缺陷的一种改进方案,其具体实现方式是如果当前请求没有数据可以返回,则继续保持当前请求的网络连接状态,直到服务端有数据可以返回或者连接超时。长轮询通过这种方式减少了客户端与服务端交互的次数,避免了一些无谓的网络连接。
- 流:流就是在客户端的页面向服务端发出一个长连接的请求。服务器端接到这个请求后作出回应并不断更新连接状态以保证客户端和服务器端的连接不过期。通过这种机制可以将服务器端的信息源源不断地推向客户端。
以上这些方案实质上都是用 ajax 的方法模拟实时效果,并不是真正的实时技术。同时每一次交互都是一次 http 请求和响应的过程,增加了网络的负载。
WebSocket
WebSocket 就是专门用来解决这个问题的。它是一个独立于 http 的实时通信协议,最初是在 HTML5 中被引用进来的,在 HTML5 规范中作为浏览器与服务器的核心通信技术被嵌入到浏览器中。WebSocket 的出现使得浏览器提供对 Socket 的支持成为可能,从而在浏览器和服务器之间提供了一个基于 TCP 连接的双向通道。 这里粗略了解下即可,感兴趣可以看阮一峰的 WebSocket 教程
socket.io
由于浏览器端对 HTML5 的支持不一,为了兼容所有浏览器,出现了 socket.io。socket.io 是 WebSocket 的父集,它将 Websocket 和轮询(Polling)机制以及其它的实时通信方式封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。 官网首页的例子:https://socket.io 看下项目主页:https://github.com/socketio/socket.io/
Socket.IO enables real-time bidirectional event-based communication.
(实时、双向、事件驱动)
- 前台支持:Javascript(socketio-client.js)
- 后台支持:一开始只有 Node.js(就是这个项目本身),我们搜 socket.io 教程,一般都是用 Node.js 来讲的。后来出现了 Java 版实现
netty-socketio
https://github.com/mrniko/netty-socketio netty-socketio 是基于 netty 的 socket.io 服务实现,可以无缝对接前端使用的 socketio-client.js。 主要 API 参见:Netty-Socketio API
三、socket.io 在本地打印中的应用
后台部分
主要逻辑:
- 启动 socket 服务器
- 添加事件监听
- 触发事件时(收到客户端的数据时),执行一系列操作,把返回数据封装到事件中,向客户端发送事件
核心代码:
// 启动 socket 服务器 final Configuration config = new Configuration(); config.setHostname("localhost"); config.setPort(9092); final SocketIOServer server = new SocketIOServer(config); server.start(); // 检查本地打印软件是否启动成功 server.addEventListener("aliveChecking", ChatObject.class, new DataListener() { @Override public void onData(SocketIOClient socketIOClient, ChatObject chatObject, AckRequest ackRequest) throws Exception { socketIOClient.sendEvent("aliveChecking", new ChatObject("")); } }); // 获取本地打印软件中的相关配置 server.addEventListener("getConfigData", ChatObject.class, new DataListener() { @Override public void onData(SocketIOClient socketIOClient, ChatObject chatObject, AckRequest ackRequest) throws Exception { JSONObject data = new JSONObject(); // 省略... socketIOClient.sendEvent("getConfigData", new ChatObject(data.toString())); } }); // server socket 常驻后台 try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } server.stop();
前台部分
主要逻辑:
- 连接服务端 socket,同时创建客户端 socket
- 添加事件监听
- 发送事件,触发回调函数,获取服务器的返回消息
核心代码:
// 连接并创建 socket 对象 nativePrintSocket = io.connect('http://localhost:9092'); // 添加事件监听 nativePrintSocket.on('aliveChecking', function () { isLoadingNativePrint = false; nativePrintSocket.emit('getConfigData', {sessionID: sessionID}); }); nativePrintSocket.on('getConfigData', function (e) { nativePrintLoadingDialog.destroy(); var data = FR.jsonDecode(e.message); // 省略... }); // 发送事件,触发回调函数 nativePrintSocket.emit('aliveChecking');
四、socket.io 聊天室 demo
在本地打印中的应用,只是实现了类似 http 的功能。并没有体现出 socket.io 的优势,可以研究下官方的聊天室 demo。https://github.com/mrniko/netty-socketio-demo