图中即一个简单的socket建立连接的过程, 可以看到服务器端需要执行4个系统调用来准备好接收客户端的socket连接,而客户端在建立socket完成后,即可调用connect去主动连接服务器。
关于5个系统调用的具体含义详情可以去看unp,这里不多赘述,不过在一开始学习socket的时候我就对此处抱有疑问(也是没有细心看书导致),这里对我的疑问做一个解答。
Q:关于服务器与客户端的流程比较,为什么服务器端在调用socket创建完成一个套接字后,还需要去调用bind()、listen()、accept()这些系统调用,而客户端却在调用socket完成后直接调用connect进行连接?
A:这里的不理解主要是因为没去了解这些系统调用背后到底做了什么,而只是从字面理解。
bind
bind为建立的socket指明了端口及ip地址,当客户端没有经过bind直接connect连接时,我们通过调用netstat命令可以看到它的端口号是由内核分配的,这对于TCP客户端来说没有任何问题,但是对于服务器端来说,如果端口号随机分配,那么客户们如何知道服务器的端口号是多少,另外当服务器重启,无法保证端口号跟原来一样,于是我们需要分配一个众所周知的端口号,以形成一个约定。
另外bind在指定端口的同时还指定了IP地址,这是为了限制连接的客户,使得服务器只对部分指定的IP地址客户开放。listen
当一个套接字被建立时,它被假设为一个主动套接字,即它是一个将会调用connect进行连接的套接字,而listen将这样一个主动套接字转换成一个被动套接字。
另外listen会使得内核为转换而来的被动监听套接字维护两个队列:未完成连接队列、已完成连接队列。当一个客户执行connect后进行三次握手,第一个SYN分节到达服务器后,就会在 未完成连接队列中 中建立一项(即套接字),处于SYN_RCVD状态。当三次握手完成时,内核将之前建立的项转移到已完成连接队列中。accept
用于从已完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,那么进程被投入睡眠。结合之前listen的解释,这里的accept就很容易理解了,它的作用只是在已经建立的连接队列中找到最早的一个