从零开始学Linux网络TCP编程,其实没你想的那么难
我最早接触Linux下的TCP编程时,也是两眼一抹黑,连man socket都看得半懂不懂。后来才发现,很多0基础的朋友卡住,不是卡在代码本身,而是卡在“不知道代码跑起来之后到底发生了什么”。这篇文章想把这件事讲透——你不需要先成为C语言大师,也不需要背下几百个Linux命令,只需要一台能联网的电脑,跟着我一步步敲下来,就能亲手写出一对真正在网络上通信的程序,理解TCP协议在操作系统层面到底替你做了什么。
这篇文章适合完全没碰过Linux网络编程的初学者,也适合那些会用ping和curl、但想深入理解底层原理的人。我会用最直白的方式解释TCP的核心机制,再用完整可运行的代码带你走一遍流程。所有代码我都实测过,你照着敲就能跑。
1. 先把环境搞定:一台能练手的Linux从哪里来
学Linux网络编程,第一步不是写代码,而是先给自己准备一个趁手的实验环境。这一步看起来简单,但很多初学者恰恰是在这里被劝退的——Windows上和Linux上编译运行C程序的方式完全不同,如果你还在用记事本写代码,那后面的路会很难走。
1.1 三种环境方案,按需选一种就行
我试过的方式有三种,从省事到折腾依次排开:
- 虚拟机方案(推荐新手):在Windows或macOS上装VMware或VirtualBox,再装一个Ubuntu或CentOS镜像。好处是完全隔离,随便折腾,坏了就重装,代价是占内存,建议给虚拟机分2GB以上内存。
- WSL方案(Windows用户最快捷):Windows商店里装Windows Subsystem for Linux,跑Ubuntu发行版。启动快、文件互通方便,适合写代码和跑小实验,但现在还存在一些网络栈的细微差异,如果做的实验涉及复杂的抓包和虚拟网卡,不如虚拟机干净。你在热搜词里看到的“windows linux更新子系统安装向导提前结束”,多半是网络问题或者旧版本残留导致的,重装最新版基本能解决。
- 云服务器方案(随时在线):买一台按量计费的云主机,用SSH远程登录。好处是环境固定,将来学部署的时候还能直接用,坏处是如果只是想本机练手,没必要花这个钱。
我的建议很直接:如果你是0基础、纯粹想学TCP编程,WSL或者虚拟机二选一,选哪个都行。别在这个选择上纠结超过半小时,纠结本身就是消耗。
1.2 装好之后需要检查的三件事
无论选了哪种方案,装完系统之后先确认三件事,缺一个后面都可能踩坑:
- 确认gcc编译器存在,在终端里输入
gcc --version,能输出版本号就说明OK,如果提示找不到,用sudo apt install build-essential装一下。 - 确认Python存在,输入
python3 --version,因为后面我会用Python脚本辅助测试网络连接,有它在能省很多事。 - 确认能访问外网,输入
ping baidu.com,能通就说明网络配置没问题。如果ping不通,多半是DNS或网卡没配好,这时候先别急着写代码,去把网络搞定再说。
我当时用的是一台Ubuntu 22.04虚拟机,所有代码都在这个环境下跑通。下面所有示例也都默认你在这个环境里操作。
1.3 用到的最小命令集
很多教材一上来就让初学者背几十个“Linux常用命令”,其实对TCP编程来说,前期只需要六个命令就够了:
ls # 查看当前目录下的文件 cd # 切换目录 gcc # 编译C程序 ./可执行文件 # 运行当前目录下的程序 ip addr # 查看本机IP地址 netstat -tunap # 查看端口监听状态和已建立的连接后面所有实验都会围绕这几个命令展开,你不需要额外记别的。遇到不懂的命令,先man 命令名查手册,这才是Linux最核心的学习方法——不是背命令,而是学会查手册。
2. TCP到底在做什么:用打电话的类比一次讲清
TCP(Transmission Control Protocol)是网络世界里最常用的传输协议,HTTP网页访问、文件传输、远程登录,底层都是它。但理解TCP不能光靠背概念,我习惯把TCP通信比作一次打电话的过程,这个类比基本能覆盖TCP 90%的核心机制。
2.1 打电话模型:三次握手和四次挥手
想象你打电话给朋友的过程,默认是“先拨号,对方接起,然后开始说话”。TCP建立连接的过程——三次握手,就是这段对话:
- 你(客户端)说:“喂,你能听到吗?”——发送SYN包。
- 朋友(服务端)回答:“能听到,你听得到我吗?”——回复SYN+ACK包。
- 你回复:“听到了,我们开始聊吧。”——发送ACK包。
三次握手完成,连接建立。注意这里有个容易误解的细节:TCP连接是两个方向的通信通道同时建立的,所以三次握手里第二步的“回应”同时包含了“我对你的确认”和“我这边也想建立连接”的意思,用SYN+ACK两个标志位合在一起,省了一次往返。
断开连接的四次挥手,对应的是挂电话的场景:
- 你说:“我说完了,先挂了。”——发送FIN包。
- 朋友说:“好的我收到了。”——回复ACK包。
- 等朋友把自己要说的话也说完,他说:“我也说完了,挂了吧。”——发送FIN包。
- 你回复:“收到,挂。”——发送ACK包。
有了这个类比垫底,再去看代码里socket、bind、listen、accept这些函数,就不会觉得它们是一堆死的API,而是“拨号”“绑定电话号码”“打开来电等待”“接起电话”这些具体动作的翻译。
2.2 IP和端口:地址精确到进程的“门牌号”
打电话需要知道对方的电话号码,网络通信也一样。TCP通信需要两个关键信息:
- IP地址:定位到“哪一台电脑”,相当于城市里的街道地址。
- 端口号:定位到“这台电脑上的哪个程序”,相当于楼里的房间门牌。
一个完整的TCP连接由四元组唯一确定:本地IP、本地端口、远端IP、远端端口。为什么需要端口?因为一台服务器上可能同时跑着web服务、SSH服务、数据库服务,它们共用同一个IP,但监听不同的端口(默认分别是80、22、3306),系统靠端口号把数据分发给正确的程序。
这里有个很实用的理解:客户端程序发起连接时,系统会自动分配一个随机的临时端口(比如45678),不需要你手动指定。所以你写客户端代码时不用管本地端口,只管远端IP和端口就行。
2.3 数据是有序可靠的:TCP的序号和确认机制
TCP和UDP最大的区别就是“可靠”。什么叫可靠?就是发送方发出的每段数据,接收方收到后会回一个确认号(ACK),发送方如果没收到确认,就会重发。同时每一段数据都有序号,接收方按序号重排,即使数据到达的顺序被打乱了,也能拼回正确的顺序。
打个比方,你寄快递时填了包裹编号,收件人按编号整顿。即使快递到了中转站顺序乱了,收件人也能按照编号排序,不会把第5页内容当成第1页读。TCP就是干这件事的,它在操作系统内核里自动完成这一切,你的应用程序只需要调用send()和recv(),内核会帮你搞定分包、重传、排序。
2.4 为什么代码上服务端是“被动”的
理解了上面的机制,再看服务端程序里那五个函数,逻辑就顺了。socket()负责创建电话机,bind()给电话机绑一个号码(IP+端口),listen()把电话机设为响铃等待状态,accept()是接起电话的动作,recv()/send()是对话过程。整个生命周期里,服务端永远是被动方——它先准备好,然后等待客户端主动来“拨号”。这就是为什么服务端程序的顺序永远是固定的那套模板,你先背下来,后面再慢慢理解每一行。
3. 第一段能跑通的TCP代码:逐行拆解
现在进入正题,写一对最简单的TCP服务端和客户端程序。我用C语言来写,因为Linux网络编程最经典、最贴近底层的语言就是C,而且你将来在嵌入式Linux或者看开源项目源码的时候,用的都是这套API。如果你更偏好Python,我也会在后面给出等价版本,但建议至少先把C版跑通一遍,理解会深刻得多。
3.1 服务端代码:socketserver.c
先看服务端,打开终端,在home目录下新建一个tcp_demo文件夹,用文本编辑器把下面代码保存为server.c:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main() { // 1. 创建socket,AF_INET表示IPv4,SOCK_STREAM表示TCP int server_fd = socket(AF_INET, SOCK_STREAM, 0); if (server_fd < 0) { perror("socket"); return 1; } // 2. 绑定IP和端口 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; // IPv4地址族 server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡 server_addr.sin_port = htons(8888); // 端口8888 if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("bind"); close(server_fd); return 1; } // 3. 开始监听,backlog为5表示等待队列最多容纳5个连接 if (listen(server_fd, 5) < 0) { perror("listen"); close(server_fd); return 1; } printf("Server listening on port 8888...\n"); // 4. 接受客户端连接 struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len); if (client_fd < 0) { perror("accept"); close(server_fd); return 1; } printf("Client connected: %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); // 5. 接收数据并原样回复 char buffer[1024]; while (1) { memset(buffer, 0, sizeof(buffer)); int n = recv(client_fd, buffer, sizeof(buffer) - 1, 0); if (n <= 0) { printf("Client disconnected.\n"); break; } printf("Received: %s", buffer); send(client_fd, buffer, n, 0); // 回显给客户端 } // 6. 关闭连接 close(client_fd); close(server_fd); return 0; }这段代码的逻辑非常直观:创建socket → 绑定端口 → 监听 → 接受连接 → 循环收发数据 → 关闭。用recv()接收客户端发来的数据,然后把收到的内容原封不动地send()回去,所以这个程序叫echo server,回显服务器。
3.2 客户端代码:socketclient.c
再新建一个client.c文件:
#include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main() { // 1. 创建客户端socket int client_fd = socket(AF_INET, SOCK_STREAM, 0); if (client_fd < 0) { perror("socket"); return 1; } // 2. 指定要连接的服务端地址,这里连接本机 struct sockaddr_in server_addr; memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(8888); // 服务端端口 inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 本机回环地址 // 3. 主动连接服务端 if (connect(client_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { perror("connect"); close(client_fd); return 1; } printf("Connected to server.\n"); // 4. 发送消息并接收响应 const char* msg = "Hello TCP!\n"; send(client_fd, msg, strlen(msg), 0); char buffer[1024]; memset(buffer, 0, sizeof(buffer)); recv(client_fd, buffer, sizeof(buffer) - 1, 0); printf("Server echo: %s", buffer); // 5. 关闭连接 close(client_fd); return 0; }注意看客户端不需要bind()。前面说过,发起连接时操作系统会自动分配本地端口,所以客户端程序只需要三个步骤:socket()→connect()→send()/recv()。
3.3 编译运行,看到效果
打开两个终端窗口,一个跑服务端,一个跑客户端:
# 终端1 gcc server.c -o server ./server # 终端2 gcc client.c -o client ./client正常情况下,终端1会输出Server listening on port 8888...,当客户端运行后,会接着输出Client connected: 127.0.0.1:xxxxx和Received: Hello TCP!,终端2会输出Connected to server.和Server echo: Hello TCP!。
如果你的程序能跑到这里,恭喜你,你的Linux网络编程生涯正式迈出了第一步。哪怕你还不完全理解每一行的含义,先把这个“跑通”的感觉记住,它比任何理论都能增强信心。
3.4 C语言版存在的问题,以及为什么还要学Python版
上面这段C代码有个非常大的缺陷:它一次只能服务一个客户端。第一个客户端断开之前,accept()不会返回第二次,其他客户端只能排队等着。这在生产环境里显然不行。当然解决方法是fork()多进程或者pthread多线程,或者用select/poll/epoll,但那是进阶内容,这里先不展开。为了让你更轻松地理解TCP流程,我用Python写一个多客户端版本——Python的socket封装比C友好太多,而且英文报错信息也更直观,很适合用来理解阻塞、连接、收发的概念。
这是我强烈建议你做的第二个实验:用Python再造一个轮子,对比两种语言的差异。
4. 用Python复刻一遍:理解更进一步
Python标准库里的socket模块把C的socket API几乎一比一搬了过来,只是封装得更顺手。用Python写同样的echo服务端,代码量少一半,重点更突出。
4.1 Python多客户端echo服务端
保存为server.py:
import socket # 创建TCP socket(IPv4, TCP) server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 允许地址重用,避免重启时提示端口被占用 server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 绑定IP和端口,监听0.0.0.0:8888 server_socket.bind(("0.0.0.0", 8888)) # 开始监听 server_socket.listen(5) print("Python server listening on port 8888...") while True: # 接受一个客户端连接 client_socket, client_addr = server_socket.accept() print(f"Client connected: {client_addr}") # 为了支持多个客户端,每个连接用线程处理 import threading def handle_client(sock, addr): try: while True: data = sock.recv(1024) if not data: break print(f"Received from {addr}: {data.decode().strip()}") sock.sendall(data) # 回显 except ConnectionResetError: print(f"Client {addr} reset the connection.") finally: sock.close() print(f"Client {addr} disconnected.") t = threading.Thread(target=handle_client, args=(client_socket, client_addr)) t.daemon = True t.start()这段代码加入了线程处理,所以多个客户端可以同时连接。注意SO_REUSEADDR这个选项——如果不加,程序退出后重启会报“Address already in use”,这是因为TCP四次挥手过程中端口会进入TIME_WAIT状态,内核默认60秒后才释放端口。这条经验在C语言版里同样适用,我当年因为这个报错卡了半小时。
4.2 Python客户端
保存为client.py:
import socket client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect(("127.0.0.1", 8888)) # 发一条消息 client_socket.sendall(b"Hello from Python client!\n") # 接收服务端回显 response = client_socket.recv(1024) print("Server says:", response.decode().strip()) client_socket.close()运行方式很简单,终端1执行python3 server.py,终端2执行python3 client.py。我建议多开几个终端同时运行多个client.py,你会发现每个客户端都能正常收发,这就是线程版的威力。对比C版一次只能服务一个连接,Python版明显更适合用来观察“多客户端并发”的现象。
4.3 两个核心API的深化理解
跑通Python版之后,有两个API值得停下来细想:
recv(1024)里的1024是“最多接收多少字节”。也就是说,如果对方一次发送了3000字节,你需要调用3次recv()才能全部读完。TCP是流式协议,它不保留消息边界,这是TCP初学者最容易误解的地方——TCP不是发一条收一条,而是一个连续的字节流,发3条消息可能被一次recv()收走,发1条大消息也可能被拆成多次接收。sendall()和send()的区别:sendall()会保证全部发送完成才返回,而send()在系统发送缓冲区满的时候可能只发送一部分,需要你循环发送。所以互联网上的代码教程都建议用sendall(),就是省得自己写发送循环。
理解了这两个坑,你对TCP的“流”特性就有了直观认识,后面遇到粘包、半包问题时不会慌。
5. 抓包验证:看见三次握手和四次挥手
学了写代码只能算“会用”,想真正理解TCP,我建议用抓包工具把三次握手的过程亲眼“看”一遍。人在看到直观证据之后,记忆会深刻得多。
5.1 Wireshark抓包示例
虚拟机里可以用Wireshark,或者用Linux命令行工具tcpdump。这里给一个最简单的方法:Linux终端下直接抓本机回环流量。
先装工具:
sudo apt install tcpdump -y然后在一个终端执行抓包命令,只抓IP为127.0.0.1且端口为8888的TCP包:
sudo tcpdump -i lo tcp port 8888 -S -vv-i lo表示抓回环接口,-S表示显示原始序号(不转成相对序号),-vv输出更详细的信息。另一个终端运行python3 server.py和python3 client.py。你会发现tcpdump输出里有这么几行:
IP 127.0.0.1.45212 > 127.0.0.1.8888: Flags [S], seq 1000 IP 127.0.0.1.8888 > 127.0.0.1.45212: Flags [S.], seq 2000, ack 1001 IP 127.0.0.1.45212 > 127.0.0.1.8888: Flags [.], ack 2001第一行是SYN,第二行是SYN+ACK,第三行是ACK,这就是三次握手。你还能看到断开时的FIN包,抓包结果里会出现Flags [F.]和Flags [.],对应四次挥手。把理论笔记上的东西真正“看”到,这个价值是巨大的。
5.2 为什么顺序号和确认号是那样跳的
细心的人会发现,上面seq和ack的数值并不是从0开始——这是从某个随机初始序号开始的。TCP这么设计是为了安全,防止一个连接的数据被另一个连接误认为有效而串扰。初始序号随机化,再加上确认号=对方的序号+1,就可以精确标记“我已经收到了你发的第几个字节”,下一次该发第几个字节,整个链路就是有序的。
这些细节面试时经常被问,但更重要的是,抓过包之后你对TCP的“可靠性”不再是背诵定义,而是真的知道它靠什么机制实现了。
5.3 查看连接状态的实用命令
不想抓包时,也可以用系统自带的工具快速看连接状态:
netstat -tunap输出里每个连接都有State列,常见状态有LISTEN(监听中)、ESTABLISHED(已建立连接)、TIME_WAIT(主动关闭方等待)、SYN_SENT(客户端正在发SYN,还没收到回应)。如果你写的客户端连接不上服务端,第一步就该用这个命令看服务端到底有没有在监听,而不是盯着代码翻来找去。这个排查思路比任何代码技巧都实用——先确认监听状态,再确认防火墙,最后才是看代码逻辑。
6. 新手最常踩的四个坑,以及排错思路
初学者写TCP程序,失败率最高的时期不是完全跑不动,而是“代码看着一样,但就是通不了”。我总结了四个最典型的问题,基本覆盖90%的初学者场景。
6.1 端口被占用
现象:服务端启动直接报错Address already in use。
原因:程序上次运行没正常退出,或者别的进程占用了同一端口。解决办法分两种:
# 查看哪个进程占了8888端口 lsof -i :8888 # 或者用netstat查 netstat -tunap | grep 8888找到进程号后用kill -9 进程号杀掉。但更优雅的方案是代码里加上SO_REUSEADDR选项(Python版里已经演示过了),这样即使端口处于TIME_WAIT状态重启也不会报错。C语言版在bind()之前加一句setsockopt就行,这一步我在面试里也常问,能说出原因的人不多。
6.2 防火墙拦路
现象:服务端在本机运行,程序没报错,但局域网里其他电脑连不上。
排查思路:先确认服务端监听地址是0.0.0.0而不是127.0.0.1,127.0.0.1只监听回环接口,局域网里其他机器当然连不上。再检查防火墙:
# Ubuntu下的防火墙操作 sudo ufw status # 查看状态 sudo ufw allow 8888/tcp # 放行8888端口很多教材默认在回环地址上测试,所以没暴露过这个问题。一旦拿到真实服务器上部署,这就是第一个拦路虎。记住一个原则:排查顺序是“监听地址 → 防火墙 → 代码逻辑”,不要反过来。
6.3 粘包和半包问题
现象:客户端发送三次数据,服务端却只recv()过来两条,或者一条数据被拆成两次收到。
原因:TCP是流协议,不保证“一次send对应一次recv”。如果多次发送的数据很小,内核可能会合并发送,这就是粘包;如果单次数据很大,超过MTU(最大传输单元,通常1500字节左右),就会被拆成多个包,这就是半包。
解决办法不是改TCP参数,而是自己定义应用层协议。最简单的方案有两个:
- 固定长度:每条消息固定N字节,不足补零,接收方也按N字节截取。
- 带长度头:每条消息开头放4字节的长度值,然后放正文,接收方先读长度,再读指定字节的正文。
这个问题在做真正的项目时一定会遇到,提前理解能少走很多弯路。我当年写第一个聊天室时,就是因为没处理粘包,显示的消息总是乱成一团。
6.4 recv阻塞导致程序卡死
现象:程序运行到recv()就不再往下走了,感觉像是死机。
原因:recv()默认是阻塞的——如果没有数据到达,它会一直等待。这不是bug,而是设计。如果对端永远不发数据,这个调用就永远不会返回,程序看起来就“卡住了”。
理解了这个机制,你才会明白为什么服务器程序通常用循环来recv(),为什么客户端发完消息后需要shutdown()或者close()来通知服务端“我说完了”,服务端才能从recv()返回0并退出。要解决阻塞问题,可以把socket设为非阻塞模式,或者用select/poll/epoll做事件驱动,这些都是后话。初学者首先要能识别出“这里卡住是因为没有数据来”,而不是怀疑代码写错了。
7. 进阶路线图:从echo服务到什么才算入门
如果你的echo服务器已经跑通,并且理解了三次握手、粘包这些概念,那就可以沿着下面这条路线继续往前走了。这个路线不是我拍脑袋想的,而是综合了我自己踩过的弯路和经验总结出来的合理顺序。
7.1 第一步:给你的echo服务加上并发处理
C语言版只能服务一个客户端的问题,现在就该解决了。方法有三种,难度递增:
fork()多进程:每个连接fork出一个子进程处理,简单粗暴,但进程开销大。pthread_create()多线程:线程开销比进程小,适合连接数不多的场景,代码也相对好写。select/poll/epoll事件驱动:单线程处理成千上万个连接,这是生产级服务器的标准做法,Linux面试里“epoll和select的区别”几乎是必考题。
建议先学pthread版本,把多线程的并发模型吃透,再跳到epoll。直接学epoll容易一头雾水,因为你还没建立好“连接是操作系统资源”的直觉。
7.2 第二步:自己定义一套简单的应用层协议
回声服务没有实际业务,下一个项目建议做成一个极简聊天室:多个客户端连接服务端,任何一个客户端发送的消息,服务端都广播给其他所有客户端。这个项目的价值在于你会遇到四个经典问题:
- 如何管理客户端列表(哪些socket在连接状态)
- 如何广播消息(遍历客户端列表逐个send)
- 如何识别客户端断开(recv返回0或错误)
- 如何并发处理(线程或IO多路复用)
做完了这个聊天室,你基本就掌握了TCP服务端开发的核心套路。很多所谓“进阶”的项目,例如即时通讯软件、物联网设备网关,本质都是聊天室换了一个业务壳。
7.3 第三步:回到Linux本身学运维视角
学习网络TCP的同时,建议同步补充Linux本身的知识,这在面试和实际工作中都很重要。热搜词里有一堆“linux常用命令大全”、“linux面试题”、“linux系统管理”,你可以结合这些来学,但不要贪多。我的建议是优先搞懂四个主题:
- 进程管理(
ps、kill、top)——排查服务器问题的基础 - 文件权限(
chmod、chown)——部署服务时离不开 - systemd或supervisor——守护你的服务进程,让它崩溃后自动重启
- 网络配置(
ip、ip addr、路由)——比ifconfig更现代,很多运维面试直接考
我见过不少人代码写得不错,但一上服务器就连netstat都不会用,排查问题效率极低。编程和运维是两条腿,都要站得住。
7.4 推荐的学习资源和工具档位
书籍方面,经典两本:Richard Stevens的《Unix网络编程 卷1:套接字联网API》适合当字典查,陈硕的《Linux多线程服务端编程》则实打实地教你生产级别的服务器怎么设计。视频资源的话,现在B站、慕课上都能搜到Linux socket编程的免费课程,挑播放量高且评价好的即可,关键是跟着动手敲,光看不练等于白看。
工具档位最低配置是:一台虚拟机或WSL、一个文本编辑器(vim/nano都行)、gcc、Python3、tcpdump、Wireshark(可选)。有这些,足够支撑你从零基础学到epoll阶段。
8. 一点个人经验总结
学了这么多年Linux网络编程,回头看最让我受益的一句话是:“先把一个程序跑起来,再谈理解”。初学者最大的障碍往往不是智商,而是面对陌生命令和报错时觉得自己“没准备好”。其实你不需要全部准备好才能行动,你需要的是先迈出一小步。
我强烈建议你把今天的C版和Python版代码都亲手敲一遍,不要复制粘贴,哪怕慢,也要一个字母一个字母地敲进去。敲的过程里,你会注意到htons、inet_pton这些函数,会习惯C语言的结构体初始化方式,这些手感是粘贴代码永远换不来的。
最后分享一个小技巧:当你遇到bind: Address already in use这种报错时,不要慌,先执行netstat -tunap | grep 8888看是哪个进程占了端口,再执行kill -9 进程号。等你哪天能够不看笔记、独立完成一个支持多客户端并发收发消息的聊天室时,再回头看看这篇入门教程,你会发现自己已经不再是那个面对socket函数一脸茫然的0基础选手了。