""" 校园内网 TCP 消息通信实训——服务器端 ====================================== 运行位置:教师机,或者两人小组中担任“服务器”的A机。 主要功能: 1. 在本机5000端口监听TCP连接; 2. 接收客户端发送的一行UTF-8文本; 3. 在终端显示客户端IP、临时端口和消息; 4. 向客户端返回确认消息; 5. 处理完一个客户端后继续等待下一个客户端。 运行命令: python tcp_server.py 停止服务器: 在终端中按Ctrl+C。 教学重点: - bind、listen、accept、recv、sendall的作用; - IP地址和端口共同确定通信端点; - TCP是字节流,不自带“消息边界”; - 本例约定换行符\\n表示一条消息结束。 """ import socket # 0.0.0.0表示监听本机所有IPv4网卡。 # 客户端必须填写服务器通过ipconfig查到的真实IPv4地址。 HOST = "0.0.0.0" # 必须与tcp_client.py中的PORT一致。 PORT = 5000 # listen(backlog)允许操作系统暂存尚未被accept处理的连接请求数量。 # 本服务器仍按顺序处理客户端,并不是多线程并发服务器。 BACKLOG = 20 # 单条课堂消息最大字节数,防止客户端无限发送导致内存持续增长。 MAX_MESSAGE_BYTES = 4096 def recv_line(conn: socket.socket) -> str: """持续接收字节,直到读取到换行符,并返回一行UTF-8文本。""" buffer = bytearray() while b"\n" not in buffer: # recv(1024)最多读取1024字节,但不保证一次拿到完整消息。 chunk = conn.recv(1024) # recv返回空字节,表示对方已经关闭连接。 if not chunk: raise ConnectionError("客户端在消息结束前关闭了连接") buffer.extend(chunk) if len(buffer) > MAX_MESSAGE_BYTES: raise ValueError("消息过长,超过4096字节的课堂限制") # partition按照第一个换行符把数据分为:消息、分隔符、剩余字节。 line, _, _remaining = buffer.partition(b"\n") return line.decode("utf-8") def handle_client(conn: socket.socket, address: tuple[str, int]) -> None: """处理一个客户端:接收一条消息,并回复一条确认消息。""" # address通常是(客户端IPv4, 客户端临时端口)。 print(f"[CONNECT] 客户端 {address[0]}:{address[1]} 已连接") # 防止客户端连接后一直不发数据,让服务器永久阻塞。 conn.settimeout(20) message = recv_line(conn) print(f"[RECV] 来自 {address[0]}:{message}") # Python字符串必须encode为字节后才能通过socket发送。 # 回复末尾也添加换行符,告诉客户端“一条回复到这里结束”。 reply = f"服务器已收到:{message}\n" conn.sendall(reply.encode("utf-8")) print(f"[SEND] 回复 {address[0]}:{reply.strip()}") def main() -> None: """创建监听socket,并循环接待客户端。""" # AF_INET表示IPv4;SOCK_STREAM表示TCP字节流socket。 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: # 允许程序关闭后较快重新使用同一端口,方便课堂重复实验。 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # bind把本程序绑定到本机5000端口。 server.bind((HOST, PORT)) # listen把普通socket转换为监听socket。 server.listen(BACKLOG) print("=" * 60) print("校园内网 TCP 消息服务器已启动") print(f"[LISTEN] TCP {HOST}:{PORT}") print("[FORMAT] 一条UTF-8文本,以换行符结束") print("[STOP ] 按 Ctrl+C 关闭服务器") print("=" * 60) try: while True: # accept会阻塞等待连接;连接到来后返回: # conn:本次会话使用的socket;address:客户端地址。 conn, address = server.accept() # 当前客户端处理结束后,with会自动关闭conn。 with conn: try: handle_client(conn, address) except (TimeoutError, ConnectionError, UnicodeDecodeError, ValueError) as exc: print(f"[ERROR] 客户端 {address[0]} 处理失败:{exc}") except KeyboardInterrupt: print("\n[STOP] 收到Ctrl+C,TCP服务器已关闭。") if __name__ == "__main__": main()