Socket 的中文名叫作插口,咋一看还挺迷惑的。事实上,双方要进行网络通信前,各自得创建一个 Socket,这相当于客户端和服务器都开了一个“口子”,双方读取和发送数据的时候,都通过这个“口子”。这样一看,是不是觉得很像弄了一根网线,一头插在客户端,一头插在服务端,然后进行通信。创建 Socket 的时候,可以指定网络层使用的是 IPv4 还是 IPv6,传输层使用的是 TCP 还是 UDP。
服务端首先调用?socket()
?函数,创建网络协议为 IPv4,以及传输协议为 TCP 的 Socket ,接着调用?bind()
?函数,给这个 Socket 绑定一个?IP 地址和端口,绑定这两个的目的是什么?
绑定完 IP 地址和端口后,就可以调用?listen()
?函数进行监听,此时对应 TCP 状态图中的?listen
,如果我们要判定服务器中一个网络程序有没有启动,可以通过?netstat
?命令查看对应的端口号是否有被监听。
服务端进入了监听状态后,通过调用?accept()
?函数,来从内核获取客户端的连接,如果没有客户端连接,则会阻塞等待客户端连接的到来。
那客户端是怎么发起连接的呢?客户端在创建好 Socket 后,调用?connect()
?函数发起连接,该函数的参数要指明服务端的 IP 地址和端口号,然后万众期待的 TCP 三次握手就开始了。
在 TCP 连接的过程中,服务器的内核实际上为每个 Socket 维护了两个队列:
syn_rcvd
?的状态;established
?状态;当 TCP 全连接队列不为空后,服务端的?accept()
?函数,就会从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 返回应用程序,后续数据传输都用这个 Socket。
注意,监听的 Socket 和真正用来传数据的 Socket 是两个:
连接建立后,客户端和服务端就开始相互传输数据了,双方都可以通过?read()
?和?write()
?函数来读写数据。
至此, TCP 协议的 Socket 程序的调用过程就结束了,整个过程如下图:
基于 Linux 一切皆文件的理念,在内核中 Socket 也是以「文件」的形式存在的,也是有对应的文件描述符。