news 2026/10/22 9:30:17

Python零基础网络编程入门:动手实现TCP客户端与服务端通信

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python零基础网络编程入门:动手实现TCP客户端与服务端通信

网络编程这四个字,我第一次看到的时候觉得特别高大上,脑子里浮现的是黑客大战、服务器机房、满屏跳动的日志——直到自己真正写了第一行代码,才恍然大悟:原来网络编程做的就是一件事——让两个程序隔着千山万水也能“说话”。这篇文章就是给完全零基础的你准备的,我打算用Python自带的一个叫socket的模块,从概念到代码一步步带你写一个真正能跑起来、能互发消息的网络程序。写完之后你不仅看得懂,还能跑起来,甚至能跟朋友在一台电脑上玩一次“远程对话”。不管你是学计算机的、转行的、还是做前端想补网络知识,这篇文章都适合你。

1. 网络编程到底是什么——先把概念理清楚

很多新手一上来就去看协议栈、看三次握手,结果还没等弄清楚socket是什么,就已经被一堆名词劝退了。我自己的经验是:先建立一张“地图”,搞清楚各个模块之间的位置关系,再深入到细节,效率会高得多。

1.1 用一句人话解释网络编程

网络编程,说的就是让我们自己写的代码能够利用网络跟远处(或者本机)的另一段代码进行通信。你可以把它理解成“打电话”,不过打电话的是程序,而那个电话号码叫做IP地址和端口号。

平时我们在浏览器里输入网址,看到网页,背后其实是浏览器作为“客户端”,向远处的一台“服务器”发了一条请求,服务器把网页数据返回回来,浏览器渲染成你看到的样子。在你没有学习网络编程之前,这一整套流程都是别人封装好的;而学会了socket之后,你就能亲手复刻一个简化版的过程——自己写一个服务端程序,再写一个客户端程序,让它们互相收发消息。

这么说可能还是有点抽象,我举一个更简单的类比:socket就是两台设备之间通信的“插座”。你插上它,接上线,数据就以一连串0和1的形式在上面流动。服务端这边等着别人插进来,客户端这边主动去插,就这么简单。

1.2 IP、端口、协议三件套

要理解socket,离不开三个基础概念:IP地址、端口、协议。这三者合起来,决定了“数据发给谁”、“发给哪个应用”、“按什么格式传输”。

  • IP地址:设备在网络里的“门牌号”。局域网内常用192.168开头的地址,我们自己本机测试时则用127.0.0.1,这个地址被称为“回环地址”,它永远指向当前这台电脑自己。你可以把它理解为给“自己”打电话——不用出门,就可以验证自己的“接听系统”是否正常。
  • 端口号:一台电脑上可以同时运行很多个网络程序,数据到了IP地址之后,总得知道是交给哪个程序吧?端口就是这么个门牌里的“房间号”。一个端口对应一个正在监听的服务进程,比如常见的80端口默认用于网页服务,443端口用于HTTPS加密网页服务。
  • 协议:通信双方的语言规则。这里最常见的是TCP和UDP。TCP像“挂号信”——确保送达、有顺序、丢了会重发;UDP像“明信片”——写出去就完事儿了,速度快但可能丢失,适合视频通话、实时游戏等场景。

这三者组合,比如127.0.0.1:9999,代表的就是“本机的9999号端口”,谁写到这个地址的数据,就会送到监听这个端口的程序手里。

1.3 TCP和UDP怎么选

初学网络编程,第一个绕不开的选择题就是:用TCP好还是UDP好?我直接说经验:初学者应该先从TCP开始。原因一目了然——TCP的连接过程是有反馈的,你写错了代码,报错也报得清清楚楚;UDP是无连接的,数据包发出去之后根本不知道对方有没有收到,出了问题反而更难排查。

TCP的可靠机制可以让你的学习曲线平缓很多。你只需要记住:TCP连接建立之后,服务端和客户端之间就相当于了一条稳定的“管道”,你在这头写数据,那头就能按顺序收到。等到你把这个过程跑熟练了,再去玩UDP也不迟。

对比维度TCPUDP
连接方式需建立连接无连接
可靠性可靠,丢包会重传不可靠,丢包不处理
速度相对较慢相对较快
场景文件传输、网页、消息视频、语音、游戏
学习难度略复杂但反馈清晰简单但排查困难

2. 动手前的环境准备与工具选型

有句老话叫“工欲善其事,必先利其器”。网络编程的“器”不需要多复杂,一个能写Python的编辑器加一台电脑就够了。但有几个细节提前准备好,能省掉不少后顾之忧。

2.1 Python为什么适合新手入门

网上讲网络编程的语言很多,C、Java、Go都能做,但我觉得对新手来说,Python是绝对的第一选择。理由很简单:Python自带的socket模块语法极其简洁,一个服务端大概十行左右就能跑起来,你不需要先跟内存指针、线程模型搏斗,就能把网络通信的骨架看明白。

当然,Python也不是万能的,它的多线程性能在极端高并发下不如Go或者C。但你想理解的是网络编程的底层逻辑,而不是追求百万并发,那么Python就是最趁手的“解剖刀”——代码少,逻辑透明,适合用来搞懂原理。

2.2 环境要求与调试工具

这里我用的是Python 3,建议3.8以上版本,安装的时候记得勾选“Add Python to PATH”,否则命令行里找不到python命令。环境方面没有其他硬性要求,Windows、macOS、Linux都可以。

除了编辑器,我再推荐两个特别基础的调试工具:

  • 命令行ping命令:用于检测网络连通性,比如ping 127.0.0.1可以用来测试本机网络协议栈是否正常。
  • curl命令:它可以作为最简单的“客户端”去访问一个网络服务,验证你的服务端程序是否在正常监听。

这两个工具都用不到额外的软件,是操作系统自带的。我在后面的实操环节会具体演示怎么用它们来验证程序。

2.3 本机测试的三块“地基”

做网络编程开发,90%的调试都是在自己的电脑上完成的。这时候有四个地址你得会看:

  • 127.0.0.1:回环地址,只代表本机,任何发往这个地址的数据不会真正进入外部网络。
  • localhost:一个域名,通常解析到127.0.0.1。
  • 0.0.0.0:表示本机上的任意IPv4地址。服务端绑定这个地址,意味着局域网内其他电脑也能连进来。
  • 局域网IP(比如192.168.1.5):这是你的电脑在局域网里的真实地址,手机要连接你电脑上的服务就需要用这个地址。

新手最常犯的错误就是把服务端绑死到了127.0.0.1,结果手机想通过局域网IP连接,怎么都连不上——因为服务端只在“回环电话线”上等着,压根没监听网卡。这是我的亲身经历,朋友们,绑0.0.0.0才是真正“开放待客”。

3. 第一个程序:TCP服务端与客户端

理论铺垫得差不多了,现在开始真正的实操。这一步按照我的习惯,先写服务端,再写客户端,因为服务端是“接电话的人”,逻辑上更容易理解。

首先,打开你的终端或者IDE,新建一个文件,名字就随便起,比如server.py和client.py。

3.1 服务端代码怎么写

服务端的任务可以概括为四件事:创建socket、绑定地址、开始监听、接收连接。

下面是最简版本的服务端代码:

import socket # 1. 创建socket对象,AF_INET表示IPv4地址,SOCK_STREAM表示使用TCP协议 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 绑定IP和端口号,空字符串表示绑定到本机所有可用IP,端口选1024以上的即可 server_socket.bind(("", 9999)) # 3. 开始监听,参数5表示允许等待连接的队列长度 server_socket.listen(5) print("服务端已启动,等待客户端连接...") # 4. 接受客户端连接,accept会阻塞等待,直到有客户端连入 client_socket, client_address = server_socket.accept() print(f"收到来自 {client_address} 的连接") # 5. 接收客户端发送的数据,缓冲区大小设为1024字节 data = client_socket.recv(1024) print(f"收到消息:{data.decode('utf-8')}") # 6. 给客户端回一条消息 client_socket.send("你好,客户端,服务端已收到你的消息!".encode("utf-8")) # 7. 关闭本次连接和服务器socket client_socket.close() server_socket.close()

我来说几个关键点。第2步里的bind,第一个参数传空字符串,其实就是让服务端监听本机所有网络接口,这样无论从127.0.0.1还是局域网IP都能连上。端口为什么选9999?因为0到1023的端口通常需要管理员权限,而且容易被系统服务占用。1024到65535之间随便选一个,只要别跟现有服务冲突即可。

accept()这个函数是我觉得整个代码里最重要也最容易让人困惑的。它是一个阻塞函数,意思是代码执行到这里会暂停,一直“瞪大眼睛”盯着,直到有客户端连上来才继续往下走。这种“卡住”的行为在初学时容易误以为是程序死了,其实是它在等待。

3.2 客户端代码怎么写

有了服务端,客户端就简单多了——它只需要三步:创建socket、发起连接、收发数据。

import socket # 1. 创建客户端socket client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 连接服务端,这里填入服务端的IP和端口 server_address = ("127.0.0.1", 9999) client_socket.connect(server_address) # 3. 发送数据给服务端 client_socket.send("你好,服务端,我是客户端!".encode("utf-8")) # 4. 接收服务端的回复 response = client_socket.recv(1024) print(f"收到服务端消息:{response.decode('utf-8')}") # 5. 关闭连接 client_socket.close()

这段代码里最核心的就是connect(),它做的事情是向目标地址发起一个连接请求。如果服务端没启动,这里就会直接抛出一个ConnectionRefusedError,明确告诉你连接被拒了。

客户端这里值得注意的秘密是:客户端并不需要手动绑定端口号。操作系统会在你调用connect的时候自动分配一个空闲的端口给这个客户端socket,你不需要关心它,等你断开连接,这个端口就被回收了。

3.3 先跑谁、怎么跑、用什么验证

实操的顺序一定要记住:先启动服务端,再启动客户端。

打开两个终端窗口,一个先执行python server.py,你会看到“服务端已启动,等待客户端连接...”这句话,然后程序就“卡”在accept那里了,这是正常的。接着在另一个终端执行python client.py,两边就会各自打印出收到消息的内容,最后程序都自动结束。

如果你手边没有PyCharm,用命令行跑也是一模一样的。

这里我建议你做一个验证操作:在启动服务端之后,先别急着跑客户端,打开另一个终端,执行下面这个命令:

curl 127.0.0.1:9999

curl会作为客户端向服务端发起请求。不过因为我们的服务端程序是按照“先收消息再回消息”的逻辑写的,curl这种“发了请求就等响应的”方式会有不同的表现,你可以亲自跑一下观察现象,这能加深对连接和监听的理解。

4. 核心机制拆解:监听、连接与数据的流动

代码能跑起来之后,更重要的就是明白“为什么”。这部分的每个函数,背后都对应着操作系统在为你做事。只有理解了这一层,你之后调试复杂问题才有方向感。

4.1 listen、accept、connect在操作系统里发生了什么

上面代码里的listen(5),很多人以为它是在“接收连接”,其实不是。listen做的唯一一件事是把你的socket变成“被动模式”——告诉操作系统:“我准备好了,有人要连我的话,你先帮我接着。”

accept才是真正的“接收”。它做的事情是从操作系统维护的“已完成连接队列”里取出一个已经建立起连接的socket。这里的秘诀在于:accept返回的新socket,跟原来那个监听socket,是两个东西。

监听socket只负责“接线”——不断迎接新连接;而accept返回的socket才负责“通话”——真正收发数据。这就好比你公司前台有一个总机号码,客人打进来,前台接了电话再转给具体的同事,之后的对话就和总机没关系了。

那connect呢?客户端执行之后,操作系统会在后台帮它完成三次握手的过程。对写应用代码的我们来说,connect返回到代码逻辑继续执行,就说明连接已经被接受了。

4.2 为什么recv和send必须用缓冲区

代码里我写recv(1024),这个1024是缓冲区大小,意思是“我一次最多接收1024个字节的数据”。它不是一个必须收满1024的意思,而是“这次调用最多给你读1024字节,有多少读多少”。

这个误区太常见了。我见过很多新手以为recv(1024)就是客户端必须发满1024字节才触发,于是在调试里遇到消息发了一半收不到的现象,百思不得其解。实际上,recv会尽量读取缓冲区里已到达的数据,可能只读到几个字节就返回了。所以,一次send不一定对应一次recv,一次recv收到的数据也可能是一次send的一部分——这个认知偏差是后面各种“灵异现象”的源头。

4.3 粘包问题初探:为什么收到的数据“粘连”在一起

顺着上面的话题,必然要提到网络编程里非常经典的现象——粘包。简单说,当你连续发送两条消息时,比如先发一个“你好”,再发一个“世界”,接收方可能一次recv就收到了“你好世界”。你明明发了两次,接收方却只读到了粘在一起的一段数据。

原因在于TCP是一个“流”协议,数据在传输管子里没有任何边界。你可以想象成自来水管里流的水,你倒了两杯水进去,但水流是连续的,对方用一个杯子接水时,接起来的水可能同时包含了两杯的部分。“消息边界消失”了,这就是粘包的本质。

解决粘包的办法倒不复杂,常见的有这么几种:

  • 在每条消息末尾加一个特殊分隔符,比如换行符\n,接收方按行解析。
  • 在消息开头固定用4个字节存消息长度,接收方先读长度再读相应字节。
  • 发完一条消息后休眠一小段时间(不推荐,但调试时偶尔能糊弄过去)。

我们是入门阶段,知道有这个坑就行了,等以后写实际项目再解决。

4.4 为什么服务端只能处理一个客户端

把上面的代码跑一遍,你会发现一个尴尬的事实:当第一个客户端连接完、程序结束之后,第二个客户端再次运行,connect就会失败。因为服务端socket已经在处理完后被close了,再也不监听了。

想要让服务端持续服务,最简单的办法是加一个while True循环,把accept和收发数据的过程包进去:

import socket server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.bind(("", 9999)) server_socket.listen(5) print("服务端已启动,可持续接收连接") while True: client_socket, client_address = server_socket.accept() print(f"收到来自 {client_address} 的连接") data = client_socket.recv(1024) print(f"收到消息:{data.decode('utf-8')}") client_socket.send("我已收到!".encode("utf-8")) client_socket.close()

不过这样写还是有问题——服务端在跟一个客户端收发消息的时候,无法接受其他客户端的连接。要想真正处理多个客户端,就得用多线程或者异步IO。这块属于进阶内容,但这篇文章里我不展开太多,你只需要知道方向是“每个新连接开一个线程”就行。

5. 常见问题与排查技巧实录

写网络程序,快大部分的调试时间其实不是花在写代码上,而是花在排查“为什么连不上”上。我把新手最容易遇到的几个问题整理成一个速查表,你可以存起来,遇到的直接照着排查。

5.1 “ConnectionRefusedError: [Errno 111]”连接被拒绝

这个错误翻译过来就是:客户端想连接目标地址,但是目标端口根本没有服务在监听。最常见的三种情况:

  1. 服务端忘了启动,或者启动之后瞬间退出了。
  2. 服务端绑定的端口和客户端连接的端口不一致,仔细比对。
  3. 服务端启动时报错,端口被其他程序占了导致绑定失败。

排查方法很简单:在服务端所在电脑上执行命令netstat -an | findstr 9999(Windows)或者netstat -an | grep 9999(Linux/macOS)。看有没有一个处于LISTEN状态的9999端口记录。如果有,再检查是不是绑到了127.0.0.1上导致别的设备连不进来。

5.2 “Address already in use”地址已被占用

bind的时候如果遇到这个报错,几乎可以断定:9999端口仍然被上一个没退干净的程序占着。这种情况在开发里实在太常见了——你的程序因为异常崩溃了,但进程还挂在后台,端口没被释放。

解决的办法:在代码里bind之前加上一个socket.SO_REUSEADDR选项,这是“允许端口复用”的意思:

server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

加上这一行,重启程序就不会再报端口占用了。我建议所有人都把它当成服务端代码的“默认配置”,省事不少。

5.3 局域网内手机/其他电脑连不上

明明在自己电脑上测试一切正常,但换个设备通过局域网IP就连接失败。这个问题的排查步骤,我从经验里总结了一个次序:

  1. 服务端bind的IP是不是空字符串或者0.0.0.0?如果绑的是127.0.0.1,换设备就是连不上。
  2. 两台设备在不在同一个Wi-Fi或者同一个网段里?不在的话物理上就不通。
  3. 电脑的防火墙是否拦截了对应端口?Windows防火墙经常默认拦截外部设备的入站连接,临时关掉防火墙试一下即可验证,确认后可以给这些端口添加入站规则。

这三个检查点,按顺序过一遍,90%的局域网连接问题就解决了。不要一上来就怀疑路由器,先查自己的代码和系统配置。

5.4 程序“卡住”了,一点输出都没有

遇到“卡住”的第一反应,不要以为是死机,想想我们的代码里有哪些阻塞调用:accept()在等客户端、recv()在等数据、connect()在等连接完成。这三个函数都会让程序“看起来像卡住一样”。

此时你要做的第一件事是检查对方的程序有没有动作。比如服务端卡在accept(),那说明客户端根本没连上来,先查IP端口;客户端卡在recv(),那说明服务端没有发数据回来,你看看是不是自己的收发逻辑写反了,或者服务端在收到数据后代码抛异常退出了。

5.5 Windows下中文乱码问题

这个属于无心插柳们遇到比较多问题:客户端和服务端之间收发中文,打印出来变成乱码。原因很简单——两端用Python的encode("utf-8")和decode("utf-8")处理,虽然代码看起来一样,但Windows默认的终端编码可能是GBK,打印的时候把UTF-8字节按GBK解码,就乱了。

最简单的解决办法:在代码文件第一行加# -*- coding: utf-8 -*-,同时用终端打印时统一用response.decode("utf-8"),不要依赖终端自动编码。如果终端还是乱,就检查终端编码设置,把代码页切到UTF-8(Windows的chcp 65001命令)。

6. 从跑通到吃透:几个值得继续深挖的方向

到这里,一个最简单的TCP网络通信程序已经跑通了。不过这只是网络编程的九牛一毛——你的目标应该是理解得越来越深。我也给你列几个“下一步”的具体方向。

第一步是试试改造:把单次收发改成循环收发,让客户端可以反复输入并发送消息,服务端不断回应,也就是一个“命令行聊天室”的雏形。

第二步是多客户端:用threading模块给每个连接开一个线程,这样多个客户端可以同时连着服务端互发消息。这一步能让你第一次真正理解什么叫并发连接。

第三步是尝试传输文件:读文件、按块发送、服务端接收后写入文件。文件传输是网络编程里最经典的任务之一,也是以后各种项目的基础。

第四步是UDP编程:用socket.SOCK_DGRAM创建socket,它的收发方式跟TCP完全不同,没有连接,直接sendto和recvfrom,你去对比一次就能深刻体会“连接”两个字的分量。

最后是“协议设计”:试着给自己写的消息加一个简单的json包头,比如{"type": "msg", "length": 10, "content": "hello"},按照自定义的格式去解析,这会让你对互联网应用层协议的设计有直观感受。

我个人到现在还记得第一次用自己写的代码,在局域网里让另一台电脑打印出“Hello”时的兴奋感。那种“数据穿过网线、越过路由器,最后在我面前的屏幕上出现”的确定感,正是网络编程最迷人的地方。你现在手里的代码,就是通往这种体验的第一把钥匙——别急着追求高深理论,先把这十几行跑通、跑熟、跑出自己的感觉,再往下走,你会发现自己已经能和很多“听起来很厉害”的名词平等对话了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/22 9:30:12

从Socket到套接字:一个被误读三十年的网络编程术语

先说结论:我从一个骂过"套接字"的人,变成了替它说话的人。第一次在《计算机网络》教材里看到"套接字"这三个字时,我和大多数人一样愣住了。一个好好的 Socket,翻成"插座"不好吗?哪怕叫&…

作者头像 李华
网站建设 2026/10/22 9:30:02

纯Java企业级Agent执行沙箱:BizBuddy架构与工程实践

1. 为什么“纯 Java”做 Agent Harness 是个反直觉但值得深挖的决定“用纯 Java 做企业级 Agent Harness 平台”——这个标题刚看到时,我下意识皱了下眉。身边太多团队在聊 LLM 应用落地,一开口就是 Python LangChain FastAPI,再配上点 Doc…

作者头像 李华
网站建设 2026/10/22 9:29:18

YOLO26+ByteTrack实战:从检测到跟踪的智慧园区人流统计系统搭建指南

1. 先想清楚:多目标跟踪到底解决什么问题 我最早做园区人流统计的时候,以为这活儿很简单:放一个摄像头在门口,检测到人,人数加一,完事。真到现场跑起来才发现,事情完全不是这样。 单帧检测只能…

作者头像 李华
网站建设 2026/10/22 9:28:14

从Markdown到生成式UI:AI应用流式渲染的完整实践指南

“为什么我的 AI 应用渲染一个 Markdown 流都要卡半天?”这是我最近在做一个生成式 AI 产品原型时,被问得最多的问题。很多开发者一开始都会想:AI 输出文本嘛,用 Markdown 渲染不就行了?但真到了实际场景里&#xff0c…

作者头像 李华
网站建设 2026/10/22 9:28:14

LACP链路聚合详解:原理、配置实战与故障排查指南

1. 什么是LACP:先别急着敲命令,搞懂它在解决什么问题做网络运维和系统集成这些年,我见过太多人一上来就敲channel-group 1 mode active,配置是配上了,但问一句“LACP到底干了啥”,瞬间卡壳。这其实很危险—…

作者头像 李华
网站建设 2026/10/22 9:25:53

从单点助手到多Agent生态:跨平台AI编程智能体架构实践

如果一年前有人跟我说,AI编程的下一代形态不是某个更聪明的IDE插件,而是一群各司其职的智能体在一个系统里互相派活、接力完成任务,我大概率会觉得这是PPT里的故事。直到我们把代号为Agency Agents的跨平台AI编程智能体体系真正跑起来&#x…

作者头像 李华