【发布时间】:2009-04-21 20:57:29
【问题描述】:
一般编程:在服务器套接字的 accept() 方法中究竟发生了什么。在底层,服务器套接字与客户端套接字有何不同?
【问题讨论】:
标签: sockets
一般编程:在服务器套接字的 accept() 方法中究竟发生了什么。在底层,服务器套接字与客户端套接字有何不同?
【问题讨论】:
标签: sockets
在低级别套接字只是套接字,无论它们是在服务器还是客户端应用程序中使用。两者的区别在于每种应用程序的系统调用。
服务器套接字将调用bind() 与端口关联。他们希望与一个端口相关联,以便其他程序知道从哪里到达他们。客户端套接字可以调用bind(),但几乎从不这样做,因为没有太多意义。如果套接字不调用bind(),操作系统只会为其选择一个临时端口,这对客户端来说很好,因为他们正在调用;没有人需要给他们打电话。
服务器套接字调用listen()。这在其他答案中得到了很好的解释。
服务器套接字调用accept(),我认为这是您问题的症结所在,因为它一开始有点神秘。要掌握的重要一点是,在调用accept() 时,内核将传回一个new 套接字。它现在与原始侦听套接字分开,并且是您的服务器将用来与其对等方通信的。
理解监听套接字如何在接受的连接执行其操作时继续监听的关键在于理解 tcp 连接依赖于 (1) 本地地址 (2) 本地端口 (3) 外部地址的 4 元组(4)国外港口。这些定义了一个独特的连接。在accept() 传回新套接字之前,内核使用这些值来创建各种结构,以便与 tcp/ip 堆栈协作,该元组的所有流量都将进入连接的套接字。即使您的服务器可能有上千个与本地地址 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 与客户端打开的套接字对象并没有太大区别。从现在开始,它是相当对称的。
【讨论】: