通用编程:在服务器socket的accept()方法中究竟发生了什么。在低级别上,服务器socket和客户端socket有何不同?
通用编程:在服务器socket的accept()方法中究竟发生了什么。在低级别上,服务器socket和客户端socket有何不同?
在低层级别上,无论是在服务器还是客户端应用程序中使用的套接字都只是套接字。两者之间的区别在于每种应用程序所调用的系统调用。
服务器套接字将调用bind()
与端口相关联。它们希望与端口相关联,以便其他程序知道如何联系它们。客户端套接字可以调用bind()
,但几乎从不这样做,因为这没有太多意义。如果一个套接字没有调用bind()
,操作系统将为其选择一个临时端口,这对于客户端来说是可以的,因为他们正在进行呼叫; 没有人需要打电话给他们。
服务器套接字调用listen()
。这在其他答案中已经很好地解释过了。
服务器套接字调用accept()
,我认为这是你问题的关键,因为它一开始有点神秘。理解的关键在于,在调用accept()
时,内核将返回一个新的套接字。它现在与原始的监听套接字分离,并且是服务器将用来与其对等体通信的东西。
理解监听套接字如何继续监听而被接受的连接正在执行其任务是要理解tcp连接依赖于(1)本地地址(2)本地端口(3)远程地址(4)远程端口的四元组。这些定义了一个唯一的连接。在accept()
返回新套接字之前,内核使用这些值创建各种结构,以便在与tcp/ip栈协作时,该元组的所有流量都将进入已连接的套接字。即使您的服务器可能有1000个使用本地地址192.168.1.100端口80的连接,客户端地址和端口的组合也总是不同的,因此该元组始终是唯一的。
首先,服务器套接字通常绑定到公知名称(端口,在此情况下),并使用listen()
建立自身。这就是真正的区别所在,因为客户端套接字通过connect()
建立自身。在套接字上调用listen()
会导致内核的TCP/IP实现开始接受发送到套接字绑定名称(端口)的连接。不管您是否调用了accept()
,这都会发生。
accept()
只是为您的服务器提供一种访问和与已连接到您的侦听套接字的客户端套接字进行交互的方式。
如果您真的感兴趣,我建议您阅读TCP/IP Illustrated, Volume 2。如果您想要一个不太“深入”的答案,则:
socket()
系统调用中指定的协议和bind()
系统调用中指定的寻址信息形成。listen()
系统调用时,网络堆栈会创建一个队列,其中挂起的连接被放置到队列中。对队列大小的提示以listen()
中的backlog
参数给出。accept()
来从队列中拉取新的连接。connect()
时指定服务器端点,并使用send()
或write()
发送数据来向服务器套接字发送消息。connect()
时,连接将被推送到服务器端队列中,直到服务器接受该连接。然而,这个描述只对TCP/IP套接字有效。UDP情况更简单,也非常不同,因为UDP套接字没有(必要)连接。
如果不指定编程语言,很难完全回答您的问题。我将以Python为例回答,因为据我所知,这个答案可能是代表性的。
如果s
是一个socket
对象,则服务器首先通过调用s.bind('',12345)
绑定端口。这将创建一个服务器模式下的套接字。它已准备好在端口12345上接收数据。
然后调用s.listen(10)
。这将套接字置于服务器模式。这意味着此套接字将接收到连接请求(最多同时有10个挂起请求)。
当我们到达s.accept()
时,操作系统已经知道我们正在侦听端口12345。 s.accept()
仅说明我们将如何处理接收到的请求。在Python中,s.accept()
将返回(connection,address)
,其中connection是通过另一个端口的连接。在这种情况下,connection
与客户端打开的套接字对象没有太大区别。从这里开始,它相对称。