要学Internet上的TCP/IP网络编程,必须深刻理解Socket接口。之所以另起一篇文章来讨论Socket,是因为它是网络通信架构的基础,重要性不言而喻。所谓socket通常也称作“套接字”,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。socket的英文原义是“孔”或“插座”。在这里作为通信机制,取后者意思。socket非常类似于电话插座。以一个国家级电话网为例。电话的通话双方相当于相互通信的2个进程,区号是它的网络地址;区内一个单位的交换机相当于一台主机,主机分配给每个用户的局内号码相当于socket号(JAVA程序的socket ID 由操作系统分配)。任何用户在通话之前,首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket。然后向对方拨号呼叫,相当于发出连接请求(假如对方不在同一区内,还要拨对方区号,相当于给出网络地址)。对方假如在场并空闲(相当于通信的另一主机开机且可以接受连接请求),拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机相当于关闭socket,撤消连接。
在网络上查找socket相关资料的时候,经常会把socket和电话插座拿来对比,不仅仅是因为socket的英文原义是“插座”,更重要的是,电话机发送信号和对方从电话机接收信号的过程,相当于socket发送和接收数据的过程。对于通话双方来说,通信设施的细节不重要,重要的是两端都有电话机,也就是都支持socket这样的连接方式。
webSocket机制
websocket是一个基于TCP连接的全双工通信方式,服务端和客户端可以相互推送数据。
我们可以看看websocket是如何保持客户端和服务端通信的。
这里需要注意一点,websocket在连接的时候有一个握手阶段,但是这和TCP的三次握手又是不一样的。TCP的三次握手是为了保证连接可靠,当TCP三次握手成功的时候,websocket的握手阶段才真正开始。TCP三次握手传送的是TCP报文,而websocket的握手传送的是HTTP报文,这个是不太一样的地方。
握手开始的时候,我们需要现发送一个HTTP 1.1的请求头部:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
服务端返回的成功握手请求头部如下:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
Upgrade:WebSocket表示这是一个特殊的 HTTP 请求,请求的目的就是要将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议。
一旦连接成功后,就可以在全双工的模式下在客户端和服务端之间来回传送WebSocket消息。这就意味着,在同一时间、任何方向,都可以双向发送基于文本的消息。每个消息已0×00字节开头,以0xff结尾(这样就可以解决TCP协议中的黏包问题,在TCP协议中,会存在两个缓冲区来存放发送的数据或者接收的数据,如果没有明显的分隔符,服务端无法正确识别命令),中间数据的编码是UTF-8。
关于如何使用WebSocket也不赘述,主要还是说说WebSocket带来了什么。
WebSocket的优势
传输速度收到的影响很多,我们可以从多个角度对HTTP和WebSocket进行比较。
从纯粹的字节数角度考虑
HTTP:每一次数据传输都需要有一个HTTP头部,头部的大小不一,可能只有几百B,也可能有几千B。
WebSocket只有在进行连接的时候需要发送一个HTTP请求,之后就再也不需要发送纷繁的HTTP头部信息,光从字节数上就减少了很多。而在关闭WebSocket的过程中,也不需要像建立握手的时候那么繁杂,只需要传送一个特定的字节码0×8的关闭帧就行,服务端收到之后,需要响应一个关闭帧到客户端。
从请求数的角度考虑
正常情况下,如果我们要请求多个数据,就多发多次HTTP请求,整个过程包括建立连接,关闭连接,特别是建立连接的时间在整个传输时间中还占据了比较大的比重。HTTP长连接的劣势也在上面有描述过。
WebSocket可以一直保持连接,通过Socket通道传输数据,节省掉了建立连接需要耗费的时间。
从服务器并发数的角度考虑
服务端要同时维持大量连接处于打开状态,就需要能以低性能开销接收高并发数据的架构。此类架构通常是围绕线程或所谓的非阻塞 IO 而设计的。这就与传统服务器围绕 HTTP 请求/响应循环的设计不同。这个时候,我们就会想到nodejs,使用事件机制和异步IO对请求进行处理,提高了服务器的并发能力,并且减少了线程切换带来的开销。
Java曾引入一个新的I/O API,其被称为非阻塞式的I/O。这一API使用一个选择器来避免每次有新的HTTP连接在服务器端建立时都要绑定一个线程的做法,当有数据到来时,就会有一个事件被接收,接着某个线程就被分配来处理该请求。因此,这种做法被称为每个请求一个线程(thread-per-request)模式。其允许web服务器,比如说WebSphere和Jetty等,使用固定数量的线程来容纳并处理越来越多的用户连接。在相同硬件配置的情况下,在这一模式下运行的web服务器的伸缩性要比运行在每个连接一个线程(thread-per-connection)模型下的好得多。
每个连接一个线程模式通常会有一个更好的响应时间,因为所有的线程都已启动、准备好且是等待中,但在连接的数目过高时,其会停止提供服务。在每个请求一个线程模式中,线程被用来为到达的请求提供服务,连接则是通过一个NIO选择器来处理。响应时间可能会较慢一些,但线程会回收再用,因此该方案在大容量连接方面有着更好的伸缩性。
而WebSocket对于服务端的优势就在于Socket减少了数据传输和处理的成本,使得这些异步的IO机制能够充分地扬长避短。
换句话说,WebSocket带来的并发能力提升,不仅仅因为传输机制本身,服务端一样需要做调整来适应新的机制,这样才能充分发挥WebSocket的优势。
Socket.io
Socket.IO是一个JavaScript端的框架, 提供了一个简单类似WebSocket的API,实现异步接收和发送服务端数据。Socket.io支持WebSocket,Flash Sockets,长轮询,流,持久帧(iframe)和JSONP轮询。具体使用哪个方案取决与浏览器的兼容性,尽可能使用最优方案解决。