← 网络基础学习第 15 / 55 章进阶45 分钟

模块一 · 应用层

套接字编程

浏览器和服务器怎样使用操作系统提供的网络服务?这一章从一对收发文字的程序出发,区分应用接口、套接字对象和网络协议。

本章要解决的问题:浏览器和服务器怎样使用操作系统提供的网络服务?这一章从一对收发文字的程序出发,区分应用接口、套接字对象和网络协议。

本章的证据来源:教材§2.7,纸书102–109页,及Python、Microsoft官方接口文档。本轮在本机原始笔记工程找到并阅读了tools/socket-lab.py;脚本未随网站提供,旧稿输出也不是本次重新运行的结果。

先从一个例子理解概念

假设你写一个小程序,发出“hello”,另一个程序回复“收到”。你需要告诉操作系统往哪发、从哪收,但不用亲手生成每一个TCP报文。

协议、接口与对象

协议规定通信双方遵循的规则;接口是程序调用操作系统服务的方法。套接字(socket)是程序使用网络服务的通信对象,程序通过它收发数据。

系统用句柄或描述符引用这个对象,Python还把它包装成对象。不能把套接字本身简单等同于“一个编号”。

地址与端口

IP地址用于网络中的寻址;端口帮助主机把数据交给相应的应用端点。下面只考虑IPv4、TCP、一台服务器和一个客户端,通信双方都在同一台机器上,用回环地址127.0.0.1。

回环地址让数据在本机协议栈中传递,这种演示不能代表真实网线、Wi-Fi或跨互联网连接的性能。

TCP程序:先监听,再接受连接

这是接口流程示意,不是本次运行输出:

服务端:socket() → bind() → listen() → accept() → recv()/send()
客户端:socket() → connect() → send()/recv()
调用应用请求系统做什么
socket()创建通信对象,选择地址族与协议类型
bind()指定本地地址与端口
listen()将TCP套接字置于被动监听状态
connect()尝试建立到指定对端的连接
accept()接受队列中的连接,返回用于通信的新套接字
send() / recv()提交待发送的字节、读取接收到的字节
close()释放应用持有的套接字资源,连接关闭依状态与选项处理

成功的阻塞TCP connect调用意味着连接已建立;调用也可能失败,非阻塞调用则可能在建立尚未完成时返回。因此不能只说“connect返回就完成了握手”。

TCP握手由协议栈处理。监听后的请求可以在应用调用accept之前完成建立;accept负责让应用取得连接,不能把它当成服务器发送某个握手报文的动作。原脚本也先调用客户端的阻塞connect,再调用服务器accept;代码顺序与“accept执行握手”的解释不符。具体握手与关闭状态在运输层再学。

两种服务端套接字,三个对象

继续同一个客户端的例子:

对象在谁那里用途
监听套接字服务端等待新连接
连接套接字服务端与当前客户端收发字节,由accept返回
客户端套接字客户端与服务器收发字节

这个例子合计三个套接字对象。 accept返回的连接对象就是表中的第二个,不能再加一次。原脚本TCP段实际使用srv、cli与accept返回的conn,并依次关闭这三个对象;“四个”是脚本打印说明的计数错误,旧稿又把它解释成重复创建。

监听套接字继续接收其他连接请求。服务器可以用线程、进程或事件循环同时处理多个连接,并非必须给每条连接创建一个线程。

UDP程序:保留数据报边界

数据报是一次发送的独立消息。UDP没有TCP那样的协议建连过程:

服务端:socket() → bind() → recvfrom()/sendto()
客户端:socket() → sendto()/recvfrom()

UDP服务端不使用listen/accept。这个例子中服务端显式绑定一个双方约定的端口;客户端可以让系统分配本地端口。UDP程序也可以用其他方式协商服务端实际地址与端口,不必把“服务端端口必须固定”当成协议要求。

UDP套接字也可以调用connect。 它设置默认对端等本地套接字状态,不执行TCP三次握手;协议无连接不代表接口不能记录对端。

recvfrom返回数据和发送方地址。数据报的源端口来自UDP首部,源IP来自IP层,不能说“UDP首部自带全部地址信息”。TCP提供字节流,不保留应用的一次send对应的一条消息边界。

一次读取能拿到多少

  • TCP的recv给出当前可读的一部分字节,上限由缓冲参数指定。应用要自行约定消息边界,例如长度前缀。
  • UDP接收针对一个数据报,但接收缓冲过小可能截断数据,或在Windows等实现中报告消息过长错误。保留边界不等于任何大小的缓冲都能收到完整内容。

发送成功与错误回报

send/sendto成功只表明本地系统接受了该次发送请求或相应数量的数据,不保证对端已收到,更不保证对端应用已处理。

Windows上,UDP收到针对先前发送的ICMP端口不可达通知后,某些后续接收操作可报告WSAECONNRESET。能否收到通知、何时通过接口交付,还受协议、系统选项及过滤影响;不能把一次历史观察扩展成“错误只能在下一次操作时出现”的通用规律。

旧稿记录的WinError 10054在本次没有复现。接口定义可直接核对Microsoft文档;真实报文与错误出现的时间则需要原脚本、抓包和运行条件。

端口0:让系统选择本地端口

bind到本地端口0,是请求系统选择可用端口。之后用getsockname读取实际绑定的地址和端口,再告诉需要连接的一方。

系统可用端口范围依系统配置而异。这个接口行为不能证明当前机器必定使用旧稿列出的端口,也不能让客户端自动知道服务器选中了哪个端口。

本章小结

结论范围
套接字是通信对象,程序通过接口使用协议服务区分对象、句柄、接口和协议
TCP服务器区分监听与连接套接字一个客户端的示例合计三个对象
accept取出连接,不等同于执行三次握手应用接口与协议栈行为不同
UDP不使用listen/accept,也可调用connectconnect不建立UDP协议连接
TCP是字节流;UDP保留数据报边界UDP仍要考虑缓冲过小
本地发送成功不能证明对端交付TCP和UDP都适用
原始脚本已阅读,运行结果未在本次复现脚本存在于原始笔记工程,网站未提供附件

自测题

7 题 · 先自己答,再展开对照

  1. 协议、套接字对象与套接字接口是什么关系?
    协议规定通信规则;套接字是应用使用网络服务的对象;接口调用让应用操作这个对象。句柄或描述符只是引用对象的方式。
  2. 一个监听TCP服务器与一个客户端通信,典型示例里有几个套接字对象?
    三个:服务端监听套接字、accept返回的服务端连接套接字、客户端套接字。不能把accept返回的对象重复加一次。
  3. accept是否等于服务器执行三次握手?
    不等于。握手由协议栈处理,连接可在应用accept之前建立;accept让应用取得队列中的连接。
  4. UDP能调用connect吗?它会建立TCP那样的连接吗?
    可以,它设置默认对端等本地状态,不执行TCP三次握手。UDP协议的无连接与接口记录对端是不同概念。
  5. send返回成功,能证明对端应用已经收到数据吗?
    不能,只能说明本地系统接受了发送请求或相应数量的数据。协议送达和应用处理都不能由这次返回值直接推出。
  6. bind时端口写0是什么意思?客户端怎样知道实际端口?
    请求系统选择可用本地端口。程序用getsockname读出实际端口,再通过约定的方式告知客户端。
  7. TCP的recv能一次收到一整条应用消息吗?UDP缓冲太小又会怎样?
    TCP不保留消息边界,可能分次返回,应用须自行定界。UDP针对一个数据报接收,但缓冲过小可能截断或报错,不能保证总能取到完整数据。

参考资料与适用范围

修订核对日期:2026-10-13。平台差异按官方文档理解;原始socket-lab脚本已作静态核对,旧稿历史观察没有在本次重新实测,网站也未打包该脚本。

正文源自 m1-09-套接字编程.md · 共 9 节 · 7 道自测题