对sokect的工作过程一直很模糊,特别是看到一些库的网络实现,总难免有困惑。今天决定搞清楚。
网上看到的不少资料,会从各个层面跟我们解释socket:
概念:
我们知道,两个进程通信时,要先能确定信息接收的对象。在互联网中,我们通过ip确定了目标机器,通过端口号确定了机器上的某个进程。因此协议+ip+端口能唯一确定信息接收(连接)对象。
socket是操作系统提供的进程间通信的机制。
大部分应用层的协议如FTP、SMTP、POP3等都使用socket建立进程间的连接。 当两个进程要建立双向连接进行通信时,socket往往成对存在,一个在进程A(客户端),一个在进程B(服务器端)。因此也有人说:socket是进程间通信的一个端点。
实现:
简单理解Socket一文有段话:
socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
这段话其实已经点出了socket的意义和本质。
类型:
Sockets Tutorial 提到应用广泛的 socket types有两种,一种是stream socket,一种是datagram socket。
实际上,socket在创建的时候,需要指定两个参数address domain和socket type。且两个进程间的通信,只有在各自socket的domain和type相同时才能进行。
address domains主要有:unix domain和Internet domain
如果socket指定了unix domain,两个进程在同一个文件系统下通信。
如果socket指定了Internet domain,两个进程在网络中两台主机间通信。
Stream sockets会把通信的数据作为持续的字符串流,并使用tcp协议。
datagram sockets会一次读取整条message,使用udp协议。
工作:
socket到底如何工作,才是我最想知道的。一开始看一些中文的帖子,基本上都只是大概地说:创建socket,监听某个端口,接收到连接之后处理返回,关闭连接。
但我的疑问是:服务器进程一直在跑,肯定要有socket一直监听端口。
是处理完一个请求之后,就关闭这个socket,然后重开一个socket继续监听该端口?
还是说socket1一直监听端口,请求来了之后,创建另一个socket2去处理请求,然后关闭socket2?
Using Socket as a Server (Listening) Socket
Socket programming - What's the difference between listen() and accept()?这篇问答给了我答案。
大致地说,服务器端socket经历了下面几个过程:
创建socket
调用系统bind方法让socket绑定端口
调用系统listen方法让socket开始监听请求
接收请求,创建child sokect(或称client socket)连接到来的请求
三次握手
child socket与客户端通信(读写)
四次挥手
child socket关闭
listen socket继续监听
直到listen socket 关闭,释放资源。
右边是tutorialspoint上一个完整的例子: Unix Socket - Server Examples实现:
上面的流程在不同语言中都能看到相似的实现:
下面是菜鸟教程python3网络编程的简单实例:
#导入 socket、sys 模块
import socket
import sys
# 创建 socket 对象
serversocket = socket.socket(
socket.AF_INET, socket.SOCK_STREAM)
# 获取本地主机名
host = socket.gethostname()
port = 9999
# 绑定端口号
serversocket.bind((host, port))
# 设置最大连接数,超过后排队
serversocket.listen(5)
while True:
# 建立客户端连接
clientsocket,addr = serversocket.accept()
print("连接地址: %s" % str(addr))
msg='欢迎访问菜鸟教程!'+ "\r\n"
clientsocket.send(msg.encode('utf-8'))
clientsocket.close()
下面是go源码中http的处理过程,见文章3.3 Go如何使得Web工作:
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // how long to sleep on accept failure
for {
rw, e := l.Accept() //接收客户端连接
if e != nil {
if ne, ok := e.(net.Error); ok && ne.Temporary() {
...
continue
}
return e
}
tempDelay = 0
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
}
}
之前我一直很奇怪这里为什么accept返回是个rw,今天才直到原来是充当了socket的角色。。
其实在nodejs底层也是类似的实现,只是js层面上我们看不到太多。有兴趣可以看看走进Node.js 之 HTTP实现分析
当然,这是都只是从资料上看到的东西,真正像研究socket这部分,还是得看前辈推荐的教材:
《UNIX网络编程 卷1:套接字联网API(第3版)》