一、单选题(10 题)
在网络通信中,IP 地址 的主要作用是( )。
A 标识一台主机上的某个应用程序
B 在网络中标识一台设备(大致唯一)
C 规定数据编码格式
D 替代端口号
✓ 正确
解析: IP 地址用于在网络中标识一台设备(主机),大致唯一。标识应用程序的是端口号。
端口号 的主要作用是( )。
A 替代 IP 地址
B 同一台机器上区分不同程序(进程)使用的"窗口"
C 保证传输一定可靠
D 只能是 0~255
✓ 正确
解析: 端口号用于在同一台机器上区分不同程序(进程),范围 0~65535。
下列关于 TCP 特点的描述,错误的是( )。
A 面向连接
B 可靠传输(有确认、重传、有序等机制)
C 面向字节流
D 无连接、发完就走
✓ 正确
解析: TCP 三大特点:面向连接、可靠传输、面向字节流。"无连接、发完就走"是 UDP 的特点。
创建 IPv4 + TCP 套接字时,正确的参数组合是( )。
A socket.AF_INET, socket.SOCK_DGRAM
B socket.AF_INET, socket.SOCK_STREAM
C socket.AF_UNIX, socket.SOCK_STREAM
D socket.AF_INET6, socket.SOCK_DGRAM
✓ 正确
解析: IPv4 用
AF_INET,TCP 用
SOCK_STREAM。DGRAM 对应 UDP,AF_UNIX 用于本地通信。
TCP 服务端 建立连接并收发数据,不需要 调用的是( )。
A bind
B listen
C accept
D connect
✓ 正确
解析: connect 是客户端主动连接服务端用的,服务端不需要调用它。
TCP 客户端 与服务器通信时,通常使用( )。
A bind
B listen
C connect
D accept
✓ 正确
解析: 客户端用
connect 主动发起连接;
bind/listen/accept 是服务端的方法。
服务端调用 accept() 成功后,应与哪一个套接字 进行 recv / send?( )。
A 监听套接字(listen 用的那个)
B accept 返回的新套接字
C 任意一个都可以
D 必须先 shutdown 再用监听套接字
✓ 正确
解析: accept 返回的新套接字是已建立连接的通信套接字,后续收发数据都用它。监听套接字只负责接受新连接。
Python 3 中通过 TCP 发送字符串 "你好",正确写法是( )。
A sock.send("你好")
B sock.send("你好".encode('utf-8'))
C sock.write("你好")
D sock.send(bytes("你好")) 且不指定编码
✓ 正确
解析: Python 3 中 socket 发送的是 bytes,必须先用
encode('utf-8') 将字符串编码。
下列关于 TCP recv 的说法,正确的是( )。
A 一次 recv 一定能收到对方一次 send 的完整"一条消息"
B 返回 b''(长度为 0)通常表示对端已关闭连接
C 应对监听套接字调用 recv
D recv 在 Python 3 中返回 str 类型
✓ 正确
解析: 返回
b'' 表示对端关闭连接(FIN)。TCP 是字节流,不保证消息边界。recv 返回 bytes 类型。
关于 multiprocessing.Process,下列说法正确的是( )。
A target=coding() 与 target=coding 没有区别
B 创建后调用 start() 才会真正启动子进程
C Windows 下可以不写 if __name__ == '__main__':
D 多个进程默认共享同一个全局列表的修改
✓ 正确
解析: start() 才启动子进程。
target=coding() 会在主进程中先执行函数。Windows 必须写
if __name__ 保护。进程默认不共享全局变量。
二、填空题(10 题)
TCP 三大特点:面向连接、可靠传输、面向 。
✓ 正确
解析: TCP 的三大特点:面向连接(三次握手建立连接)、可靠传输(确认/重传机制)、面向字节流(不保留消息边界)。
服务端典型流程口诀:创建 → 绑定 → 监听 → → recv/send → close。
✓ 正确
解析: 服务端调用
accept() 等待客户端连接,返回新的通信套接字用于后续数据收发。
单元素元组传参给 args 时应写成 。
✓ 正确
解析: 单元素元组必须加逗号
(x,),否则
(x) 只是括号表达式,不是元组。
查看当前进程编号常用 os. ()。
✓ 正确
解析: os.getpid() 返回当前进程的 PID(进程标识符)。
子进程中要执行的函数应传给 Process 的 参数,且不要加括号写成"先执行再传"。
✓ 正确
解析: Process(target=func) 传函数引用,
Process(target=func()) 会在主进程先执行函数。
默认情况下,多个进程之间 (共享/不共享)全局变量。
✓ 正确
解析: 进程有独立的内存地址空间,全局变量是各自进程的副本,修改不会互相影响。
recv 返回长度为 0 的字节 b'',通常表示对端已 连接。
✓ 正确
解析: recv 返回
b'' 是对端发送了 FIN 包关闭连接,本端应退出接收循环并关闭套接字。
三、简答题(7 题)
用自己的话说明:为什么 TCP 叫"面向连接",和"打电话"有什么类比?
查看参考答案 ▼
TCP 在真正传输应用数据之前,要先经过三次握手在客户端和服务器之间建立一条逻辑上的连接 ,双方都确认"对方愿意通信、状态就绪"之后,才在这条连接上传字节。这和打电话 很像:先拨号、对方接听、双方确认线路通了再说话;而不是像发短信那样随手发出去就不管对方是否收到(那更接近无连接的 UDP)。
✓ 正确
参考答案: TCP 在真正传输应用数据之前,要先经过三次握手在客户端和服务器之间
建立一条逻辑上的连接 ,双方都确认"对方愿意通信、状态就绪"之后,才在这条连接上传字节。这和
打电话 很像:先拨号、对方接听、双方确认线路通了再说话;而不是像发短信那样随手发出去就不管对方是否收到(那更接近无连接的 UDP)。
三次握手要解决什么问题?(可只答要点)
查看参考答案 ▼
① 让通信双方互相知道对方在线且愿意建立连接;② 同步双方的初始序号(seq)等连接状态,为后面可靠传输、按序重组打基础;③ 减少历史残留的旧连接请求或重复报文对本次连接的干扰。三次是在工程上既能完成双方确认、又能对齐状态的一种最小可行 握手次数。
✓ 正确
参考答案: ① 让通信双方互相知道对方在线且愿意建立连接;
② 同步双方的初始序号(seq)等连接状态,为后面可靠传输、按序重组打基础;
③ 减少历史残留的旧连接请求或重复报文对本次连接的干扰。三次是在工程上既能完成双方确认、又能对齐状态的一种
最小可行 握手次数。
为什么服务端 accept 后会得到"新套接字",而客户端通常只有一个套接字?
查看参考答案 ▼
服务端需要分工 :原来的监听套接字专门负责在门口排队接听新的连接请求(listen + 反复 accept);每成功接入一个客户端,accept 会再生成一个已与该客户端建立好连接的新套接字 ,此后的 recv/send 都在这条"专线"上进行。客户端是主动发起 connect 的一方,从头到尾只用一个套接字与服务器通信。
✓ 正确
参考答案: 服务端需要
分工 :原来的监听套接字专门负责在门口排队接听新的连接请求(listen + 反复 accept);每成功接入一个客户端,accept 会再生成一个
已与该客户端建立好连接的新套接字 ,此后的 recv/send 都在这条"专线"上进行。客户端是主动发起 connect 的一方,从头到尾只用一个套接字与服务器通信。
为什么说 TCP 是"面向字节流"?对 send / recv 有什么实际影响?
查看参考答案 ▼
TCP 上传输的是连续的字节序列 ,协议本身不保证"一条业务消息"的边界 ——你 send 两次,对端不一定两次 recv 各对齐一次发送。实际影响:应用层要自己约定分包规则 (如固定长度、先发长度再发内容、用特定分隔符、或短连接一次发完就关)。
✓ 正确
参考答案: TCP 上传输的是
连续的字节序列 ,协议本身
不保证"一条业务消息"的边界 ——你 send 两次,对端不一定两次 recv 各对齐一次发送。实际影响:应用层要自己约定
分包规则 (如固定长度、先发长度再发内容、用特定分隔符、或短连接一次发完就关)。
args 与 kwargs 给子进程传参时各要注意什么?
查看参考答案 ▼
args 必须是元组 ,按位置对应 target 函数的形参;若只有一个参数,必须写成 (x,) (逗号不能省)。kwargs 是字典 ,键名必须与被调用函数的参数名完全一致 ,值类型也要合法。
✓ 正确
参考答案: args 必须是
元组 ,按位置对应 target 函数的形参;若只有一个参数,必须写成
(x,)(逗号不能省)。
kwargs 是
字典 ,
键名必须与被调用函数的参数名完全一致 ,值类型也要合法。
为什么默认多个进程不共享全局变量?若要进程间通信通常需要什么思路?
查看参考答案 ▼
操作系统以进程 为单位分配地址空间,创建子进程时会把父进程的内存做各自独立的拷贝 ,全局变量在每个进程里其实是自己的一份副本。要在进程间交换数据,需要进程间通信(IPC) 手段,例如 multiprocessing 提供的 Queue、Pipe,或 Manager 托管对象、共享内存等。
✓ 正确
参考答案: 操作系统以
进程 为单位分配地址空间,创建子进程时会把父进程的内存做
各自独立的拷贝 ,全局变量在每个进程里其实是自己的一份副本。要在进程间交换数据,需要
进程间通信(IPC) 手段,例如
multiprocessing 提供的
Queue、
Pipe,或
Manager 托管对象、共享内存等。
阅读笔记中"四次挥手"表格,用一段话说明:为什么是四次而不是三次?
查看参考答案 ▼
因为 TCP 是全双工 通信,每个方向的数据传输需要独立关闭。一方发送 FIN 只表示"我没有数据要发了",但对方可能还有数据要传。因此关闭过程需要四步: ① 客户端发 FIN(我没数据了) ② 服务端回 ACK(我知道了)——此时服务端可能还在发剩余数据 ③ 服务端发 FIN(我也没有数据了) ④ 客户端回 ACK(我知道了) 其中②和③不能合并,因为服务端收到 FIN 后可能还需要时间处理并发送剩余数据。而三次握手时双方状态同步可以合并(SYN+ACK),所以是三次而不是四次。
✓ 正确
参考答案: 因为 TCP 是
全双工 通信,每个方向的数据传输需要独立关闭。一方发送 FIN 只表示"我没有数据要发了",但对方可能还有数据要传。因此关闭过程需要四步:
① 客户端发 FIN(我没数据了)
② 服务端回 ACK(我知道了)——此时服务端可能还在发剩余数据
③ 服务端发 FIN(我也没有数据了)
④ 客户端回 ACK(我知道了)
其中②和③不能合并,因为服务端收到 FIN 后可能还需要时间处理并发送剩余数据。而三次握手时双方状态同步可以合并(SYN+ACK),所以是三次而不是四次。
四、代码实战(6 题)
最简 TCP 一问一答(必做)
编写服务端:socket → bind → listen → accept → recv 打印 → send 回复 → close 通信套接字。 编写客户端:socket → connect → send → recv 打印 → close。 必须使用 encode('utf-8') / decode('utf-8')。
查看参考答案 ▼
# 服务端
import socket
s = socket .socket (socket .AF_INET, socket .SOCK_STREAM)
s.bind(('127.0.0.1' , 9000 ))
s.listen(128 )
conn, addr = s.accept()
data = conn.recv(1024 ).decode('utf-8' )
print ("收到:" , data)
conn.send("服务器已收到" .encode('utf-8' ))
conn.close()
s.close()
# 客户端
import socket
c = socket .socket (socket .AF_INET, socket .SOCK_STREAM)
c.connect(('127.0.0.1' , 9000 ))
c.send("你好" .encode('utf-8' ))
print (c.recv(1024 ).decode('utf-8' ))
c.close()
✓ 正确
参考答案: # 服务端
import socket
s = socket .socket (socket .AF_INET, socket .SOCK_STREAM)
s.bind(('127.0.0.1' , 9000 ))
s.listen(128 )
conn, addr = s.accept()
data = conn.recv(1024 ).decode('utf-8' )
print ("收到:" , data)
conn.send("服务器已收到" .encode('utf-8' ))
conn.close()
s.close()
# 客户端
import socket
c = socket .socket (socket .AF_INET, socket .SOCK_STREAM)
c.connect(('127.0.0.1' , 9000 ))
c.send("你好" .encode('utf-8' ))
print (c.recv(1024 ).decode('utf-8' ))
c.close()
循环 accept 接待多个客户端(重点必做)
用 while True 循环 accept,每接入一个客户端:收一条短消息、回一条"已收到"、关闭与该客户端的通信套接字。 监听套接字可保持不关闭,便于继续接下一个。
查看参考答案 ▼
while True :
conn, addr = server.accept()
data = conn.recv(1024 ).decode('utf-8' )
print (addr, data)
conn.send(b'ok' )
conn.close()
✓ 正确
参考答案: while True :
conn, addr = server.accept()
data = conn.recv(1024 ).decode('utf-8' )
print (addr, data)
conn.send(b'ok' )
conn.close()
文件上传骨架(重点必做)
服务端:accept 后以 wb 打开目标文件,循环 recv,将收到的 bytes write 进文件;当 recv 得到 b'' 时退出循环并关闭。 客户端:以 rb 读本地小文件,循环 send 分块发送,发完 close。 说明"为什么要分块"的原因(注释)。
查看参考答案 ▼
# 服务端
conn, _ = server.accept()
with open ("recv.bin" , "wb" ) as f:
while True :
chunk = conn.recv(4096 )
if not chunk:
break
f.write(chunk)
conn.close()
# 客户端
with open ("src.bin" , "rb" ) as f:
sock = socket .socket (socket .AF_INET, socket .SOCK_STREAM)
sock.connect(("127.0.0.1" , 9000 ))
while True :
chunk = f.read(4096 )
if not chunk:
break
sock.send(chunk)
sock.close()
# 分块原因:大文件无法一次读入内存,
# 分块发送可控制内存占用,避免网络拥塞。
✓ 正确
参考答案: # 服务端
conn, _ = server.accept()
with open ("recv.bin" , "wb" ) as f:
while True :
chunk = conn.recv(4096 )
if not chunk:
break
f.write(chunk)
conn.close()
# 客户端
with open ("src.bin" , "rb" ) as f:
sock = socket .socket (socket .AF_INET, socket .SOCK_STREAM)
sock.connect(("127.0.0.1" , 9000 ))
while True :
chunk = f.read(4096 )
if not chunk:
break
sock.send(chunk)
sock.close()
# 分块原因:大文件无法一次读入内存,
# 分块发送可控制内存占用,避免网络拥塞。
多进程入门(必做)
定义两个函数,分别打印不同前缀的循环输出。 用 multiprocessing.Process 创建两个子进程并 start()。 代码放在 if __name__ == '__main__': 中。
查看参考答案 ▼
import multiprocessing
def job_a():
for i in range (3 ):
print ("A" , i)
def job_b():
for i in range (3 ):
print ("B" , i)
if __name__ == "__main__" :
p1 = multiprocessing .Process (target=job_a)
p2 = multiprocessing .Process (target=job_b)
p1.start()
p2.start()
✓ 正确
参考答案: import multiprocessing
def job_a():
for i in range (3 ):
print ("A" , i)
def job_b():
for i in range (3 ):
print ("B" , i)
if __name__ == "__main__" :
p1 = multiprocessing .Process (target=job_a)
p2 = multiprocessing .Process (target=job_b)
p1.start()
p2.start()
进程传参与 PID(必做)
编写 work(name, n),打印 name 和循环序号。 分别用 args=('张三', 5) 与 kwargs 形式各启动一个子进程。 在子进程内打印 os.getpid(),在主进程打印子进程的 pid。
查看参考答案 ▼
import os
import multiprocessing
def work(name, n):
print (os .getpid(), name, n)
if __name__ == "__main__" :
p1 = multiprocessing .Process (target=work, args=("张三" , 5 ))
p2 = multiprocessing .Process (target=work, kwargs={"name" : "李四" , "n" : 10 })
p1.start()
p2.start()
print ("main:" , os .getpid())
✓ 正确
参考答案: import os
import multiprocessing
def work(name, n):
print (os .getpid(), name, n)
if __name__ == "__main__" :
p1 = multiprocessing .Process (target=work, args=("张三" , 5 ))
p2 = multiprocessing .Process (target=work, kwargs={"name" : "李四" , "n" : 10 })
p1.start()
p2.start()
print ("main:" , os .getpid())
多进程/多线程处理客户端(选做)
尝试在服务端 accept 后使用子进程(或线程)处理单个客户端,避免"一个人聊很久别人排队"(写出思路或伪代码即可)。
查看参考答案 ▼
# 思路:每 accept 一个客户端就 fork 一个子进程去处理
# 主进程继续 accept,子进程负责与该客户端通信
import socket
import multiprocessing
def handle_client(conn, addr):
"""子进程:处理单个客户端的通信"""
print (f"[子进程] 处理 {addr}" )
while True :
data = conn.recv(1024 )
if not data:
break
print (f"{addr}: {data.decode('utf-8')}" )
conn.send(b"received" )
conn.close()
print (f"[子进程] {addr} 断开" )
if __name__ == "__main__" :
server = socket .socket (socket .AF_INET, socket .SOCK_STREAM)
server.bind(('0.0.0.0' , 9000 ))
server.listen(128 )
print ("服务器启动,等待连接..." )
while True :
conn, addr = server.accept()
# 创建子进程处理该客户端
p = multiprocessing .Process (target=handle_client, args=(conn, addr))
p.start()
# 主进程继续 accept,不阻塞
# 也可以用 threading.Thread 替代 Process,
# 线程更轻量但受 GIL 限制;进程隔离更好但开销更大。
✓ 正确
参考答案: # 思路:每 accept 一个客户端就 fork 一个子进程去处理
# 主进程继续 accept,子进程负责与该客户端通信
import socket
import multiprocessing
def handle_client(conn, addr):
"""子进程:处理单个客户端的通信"""
print (f"[子进程] 处理 {addr}" )
while True :
data = conn.recv(1024 )
if not data:
break
print (f"{addr}: {data.decode('utf-8')}" )
conn.send(b"received" )
conn.close()
print (f"[子进程] {addr} 断开" )
if __name__ == "__main__" :
server = socket .socket (socket .AF_INET, socket .SOCK_STREAM)
server.bind(('0.0.0.0' , 9000 ))
server.listen(128 )
print ("服务器启动,等待连接..." )
while True :
conn, addr = server.accept()
# 创建子进程处理该客户端
p = multiprocessing .Process (target=handle_client, args=(conn, addr))
p.start()
# 主进程继续 accept,不阻塞
# 也可以用 threading.Thread 替代 Process,
# 线程更轻量但受 GIL 限制;进程隔离更好但开销更大。