Socket 技术简介
一、Socket 简介
在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。
我的理解就是Socket就是该模式的一个实现:即socket是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭)。
Socket()函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
客户端在调用 socket() 函数创建套接字后,并没有建立连接,所以此时套接字还处于 CLOSED 的状态;服务端在调用 listen()
函数后,套接字进入 LISTEN 状态,开始监听客户端请求。
二、Socket 的断开方式
close()/closesocket()
用来关闭套接字,并在内存中将其清除,后续将无法再继续使用,而 shutdown() 则是用来关闭连接,而不是套接字,套接字在内存中还是存在;
两者都会向网络中发送 FIN 包,表示数据传输完毕,客户端收到 FIN 数据包后便认为没有数据进行传输,随后向服务端发送 ACK 包,进入 TIME_WAIT 阶段;
不同点在于 close()/closesocket()
会立即向网络中发送 FIN 包,不管缓冲区是否还有数据,会造成缓冲区数据的糗丢失,而 shutdown()
是等待缓冲区数据传输完成后再进行发送 FIN 包;
三、 Socket 常用函数接口及其原理
图解 socket 函数:
1、使用 socket() 函数创建套接字
int socket(int af, int type, int protocol);
af-地址族(Address Family),即 IP 地址类型
四、前后端的交互问题
注解:在 WebSocket 出现之前,为实现即时通讯,通常采用的方案是轮询(Polling)和 Comet 技术,Comet 可细分为两种实现方式,长轮询机制和流技术,这两种技术实际是对轮询技术的改进;WebSocket 本质上是基于 tcp 的协议,由通信协议和编程 API 组成,可以在浏览器和服务器之间建立双向连接,以基于事件的方式,赋予浏览器通信的能力。
1、前后端双向消息传递的方式:
常见的是前端通过 http 进行数据请求交互,但是是单向的,如果考虑到后台向前台的数据传递就需要用到 websocket 了,常见的还有短轮询、长轮训和 websocket;
长轮训是对普通轮询的提高和改进,普通轮询就是客户端每隔一段时间就向服务器端发送请求,以频繁请求的方式进行保持客户端和服务端的数据同步,这种问题就是当服务端数据未更新的时候就会造成低效的网络传输;而长轮训在服务端数据没有更新时连接会保持一段时间的周期,直到数据或状态发生变化或连接过期,这样就会减少很多无效的客户端和服务端间的交互;当然数据改变频繁时就和普通轮询差不多了,且长轮询会占用消耗更多的资源,比如 CPU ,内存和宽带等。
websocket 首先会用 http 来建立连接,连接完成后此项服务交由 websocket 进行管理,之后会建立长久的连接;
流技术机制: 流技术简单的说就是客户端的页面使用一个隐藏的窗口向服务器发送一个长链接的请求,服务器接收到请求后悔不断的更新数据状态,保证连接不断和信息的时效性。这种方案需兼容不同浏览器来改进用户体验,同时如果在并发情况下发生,会对服务器造成很大压力。
2、HTTP 长轮询
由 HTTP 请求组成:长时间运行 GET 请求用于从服务器接收数据、短期内运行的 POST 请求,用于向服务器发送数据;由于传输的性质,连续发出的请求可能在同一个 HTTP 请求内连续被发送;
2、websocket
WebSocket 是一种在服务器和浏览器之间提供全双工和低延迟通道的通信协议,socket.io 提供了客户端尝试建立 WebSocket 连接,否则将回退到 HTTP 长轮询的功能。
考虑到上述几种局限性,产生了websocket,浏览器通过 JavaScript 借助现有的 HTTP 协议来向服务器发出 WebSocket 连接的请求,当连接建立后,客户端和服务器端就可以直接通过TCP连接来直接进行数据交换。这是由于 websocket 协议本质上就是一个 TCP 连接,所以在数据传输的稳定性和传输量上有所保证,且相对于以往的轮询在性能方面也有了长足的进步。
websocket 在通信时需要借助 http ,但是他是属于双向通信协议,在建立连接后,websocket 服务端和客户端都可以主动向对方发送和接收数据,另外 websocket 需要先建立连接,只有连接后才可以进行通信。
3、websocket API
new websocket(ws://localhost;9003)
,参数一: ws 和 wss ,参数二: 选填自定义协议,多协议使用数组;
ws 和 wss 来进行通信协议的确定,和 HTTP 和 HTTPS 类似,ws 表示纯文本通信,wss 表示加密通道通信(TCP + TLS),考虑到 websocket 的其他应用场景,需要自定义协议,比如保证在非 HTTP 的情况下也可以进行数据交换。
ws 协议:普通请求,占用与 HTTP 相同的 80 端口;
wss 协议:基于 SSL 的安全传输,占用与 TLS 相同的 443 端口;
HTTP 这中间设备会对 websocket 进行请求的特殊处理,比如连接的盲目升级和数据内容的随意修改等 ,wss 建立了一条端到端的安全传输通道,这个通道对中间设备模糊数据,使其无法感知到数据,也就无法对数据进行特殊处理了。
websocket 协议其实是一个 http 请求,”Upgrade:WebSocket”
表示需要将客户端的通讯协议从 http 升级到 websocket 协议,客户端到服务端的请求信息里还包括”Sec-WebSocket-Extensions”、“Sec-WebSocket-Key”
这样的头信息,服务端在接收到这些握手信息后解析这些头信息,然后生成一个 28 位的安全密钥返回给客户端,表明服务器接收到了客户端的请求,同意创建 websocket 连接。
4、socket.io
socket.io 是对 websocket 的封装,并且实现了服务器代码,socket.io 将 websocket 和轮询机制及通讯方式封装成了通用的接口,socket.io 简化了 websocket API ,websocket 是 socket.io 实现通讯的一个子集。
创建 socket.io 服务器需要依赖于已近创建好的 HTTP 服务器,因此需要首先创建一个 HTTP 的服务器,然后使用 socket 的 listen 方法创建一个 Socket.io 的服务器。
const socket = io.connect()
使用 socket.io 客户端和服务器端进行数据通信时有两种方式: 常规的 socket.send() 发送数据,另一端使用 socket.on('message',(data)=>{})
监听消息;另外一种方式就是通过 socket.emit('name',data,fn)
发送事件,另一端通过 socket.on('name',(name,fn)=>{fn(data1,data2,...)})
进行监听事件;在 emit 的时候可以传入多个参数,当传入函数的时候就可以实现另一端通过回调的方式调用该函数进行数据的传递;
服务端实现:
// 引入socke.io
const io = require('socket.io')(80)
// 监听客户端连接,回调函数会传递本次连接的socket
io.on('connection',function(socket))
// 给所有客户端广播消息
io.sockets.emit('String',data)
// 给指定的客户端发送消息
io.sockets.socket(socketid).emit('String', data)
// 监听客户端发送的信息
socket.on('String',function(data))
// 给该socket的客户端发送消息
socket.emit('String', data)
Node.js API
// socket-server.js
// 需要使用HTTP模块来启动服务器和Socket.IO
const http= require('http'),
const io= require('socket.io')
const server= http.createServer(function(req, res){
// 发送HTML的headers和message
res.writeHead(200,{ 'Content-Type': 'text/html' })
res.end('<p>Hello Socket.IO!<p>')
});
// 在8080端口启动服务器
server.listen(8080)
// 创建一个Socket.IO实例,并把它传递给服务器
const socket= io.listen(server)
// 添加一个连接监听器
socket.on('connection', function(client) {
// 连接成功,开始监听
client.on('message',function(event){
console.log('Received message from client!',event)
})
// 连接失败
client.on('disconnect',function(){
clearInterval(interval)
console.log('Server has disconnected')
})
})