首页 / 资讯中心 / 文章详情

Linux Socket编程入门:从零跑通服务端与客户端通信

Linux Socket编程入门:从零跑通服务端与客户端通信 ★ FEATURED ARTICLE
简介这份文档面向希望系统掌握 Linux 网络编程的开发者与计算机专业学生聚焦跨主机进程通信这一核心问题从原理到实践梳理 Socket 编程的知识脉络。内容围绕网络进程唯一标识、Socket 的“一切皆文件”设计思想展开逐一讲解 socket()、bind()、listen()、connect()、accept()、read()/write() 与 close() 等基本函数的用法并详解 TCP 三次握手建立连接与四次挥手释放连接的过程最后给出服务端与客户端通信的实践示例帮助读者建立从接口调用到协议机制的完整认知。资源包共 1 个 docx 文件约 77KB以图文笔记形式组织便于按章节顺序阅读与查阅。目前已有 241 人学习适合作为网络编程入门与复习的参考材料也可配合动手编码加深对连接生命周期与数据收发流程的理解。1. 从一次“连不上”说起Linux Socket 编程到底是个啥你有没有遇到过这种场景自己写了个小工具想让它跟另一台机器上的程序说句话结果折腾半天屏幕上只有一行冷冰冰的Connection refused。那一刻很容易怀疑人生——代码明明没写错为什么就是通不了其实问题往往不在你的逻辑而在于你还没搞懂“两个程序隔着网络怎么搭上话”这件事。Linux Socket 编程说白了就是教你怎么让两个进程通过网络互相收发数据的一套接口。你每天打开浏览器看网页、用聊天软件发消息背后都是某个进程在跟另一个进程用 socket 通信。它不是什么高深玄学而是网络编程的地基。这份资源就是带你从零认识 socket、搞懂几个核心函数、最后亲手跑通一个服务端加客户端的例子。适合刚接触网络编程、想动手又怕踩坑的新手。2. 为什么值得学先弄懂 socket 在解决什么问题2.1 网络里两个进程靠什么“认出”对方本地两个进程通信操作系统给每个进程一个 PID靠它就能找到对方。但到了网络上PID 这套就行不通了——你根本不知道对面那台机器上跑着什么进程、PID 是多少。TCP/IP 协议族给出的办法是用 IP 地址标识一台主机用“协议 端口号”标识主机上的某个应用。三者合起来就能唯一锁定网络中的一个进程。socket 就是把这套标识封装起来、让你用几个函数就能操作的接口。你可以把它想成一部电话IP 是小区地址端口是门牌号socket 就是你手里那部能拨号、能接听的话机。2.2 socket 为什么被说成“特殊文件”Unix/Linux 有个基本哲学叫“一切皆文件”打开、读写、关闭这套模式贯穿始终。socket 就是这套模式在网络上的实现你用socket()创建它用read()/write()或send()/recv()读写它最后用close()关掉它。所以学 socket 不用从零建立一套新心智你只要把平时操作文件的习惯平移过来再把“文件”换成“一条网络连接”就行。这也是为什么很多新手觉得 socket 函数名眼熟——它们和文件 IO 长得几乎一样。2.3 新手先建立哪张“函数地图”入门阶段不用把每个函数背下来先记住一条主线服务端走socket → bind → listen → accept → recv/send → close客户端走socket → connect → send/recv → close。服务端多出来的bind是给自己定个固定门牌listen是把门打开等客accept是真正迎进来一位客人客户端则直接拿着地址去connect。把这条线在脑子里画顺后面看代码就不会迷路。3. 动手前的准备环境、头文件和第一个 socket3.1 你需要一台能编译 C 程序的 Linux 环境这份资源的例子是 C 写的所以你需要一个带 g 编译器的环境。打开终端先确认工具在不在g --version如果能看到版本信息说明编译器就绪如果提示找不到命令就先用包管理器装上 g。接着建一个练习目录后面所有文件都放里面mkdir mysocket cd mysocket这一步看着简单但很多新手把文件散落在各处编译时找不到头文件就开始慌。统一目录能省掉大量“路径不对”的排查时间。3.2 认识几个必须 include 的头文件socket 编程用到的函数和结构体分散在几个头文件里写代码前先把它们请进来#include sys/socket.h // socket、bind、listen、accept、connect #include netinet/in.h // sockaddr_in 结构体、htons、htonl #include arpa/inet.h // inet_pton把字符串 IP 转成网络格式 #include unistd.h // close、read、writesys/socket.h是主角几乎所有 socket 函数都声明在这里netinet/in.h提供 IPv4 地址结构体和字节序转换函数arpa/inet.h负责 IP 字符串和二进制之间的转换。少 include 一个编译就会报“未声明”这是新手最常见的翻车点之一。3.3 第一个能编译通过的最小骨架先不急着写完整逻辑写一个只创建 socket、然后立刻关闭的最小程序确认环境没问题#include sys/socket.h #include stdio.h #include unistd.h int main() { // AF_INET 表示 IPv4SOCK_STREAM 表示 TCP0 让系统选默认协议 int fd socket(AF_INET, SOCK_STREAM, 0); if (fd -1) { printf(create socket error\n); return 1; } printf(socket created, fd %d\n, fd); close(fd); // 用完记得关和关文件一样 return 0; }保存为first.cpp编译运行g first.cpp -o first ./first看到socket created就说明你的环境通了。这里socket()返回的是一个文件描述符后续所有操作都靠它。AF_INET决定地址类型是 IPv4SOCK_STREAM决定用 TCP第三个参数传 0 表示让系统根据前两个自动选协议。这一步跑通后面才有底气往下走。4. 把服务端和客户端跑起来一个能对话的例子4.1 服务端四步走socket、bind、listen、accept服务端要做的事很固定创建 socket、绑定地址、开始监听、接受连接。下面这段代码把前三步封装进一个函数端口固定为 6666bool createserver(int domain, int soctype, int protype, int port) { m_isocket socket(domain, soctype, protype); if (m_isocket -1) { printf(create socket error\n); return false; } struct sockaddr_in servaddr; memset(servaddr, 0, sizeof(servaddr)); servaddr.sin_family domain; servaddr.sin_addr.s_addr htonl(INADDR_ANY); // 监听本机所有网卡 servaddr.sin_port htons(port); // 端口转网络字节序 if (bind(m_isocket, (struct sockaddr*)servaddr, sizeof(servaddr)) -1) { printf(bind socket error\n); return false; } if (listen(m_isocket, 10) -1) { // 最多排队 10 个连接 printf(listen socket error\n); return false; } return true; }bind把 IP 和端口绑到 socket 上INADDR_ANY表示不限网卡htons把端口从主机字节序转成网络字节序这一步千万别省否则端口会变成一个莫名其妙的数字。listen的第二个参数是等待队列长度传 10 表示最多允许 10 个还没被 accept 的连接排队。4.2 客户端三步走socket、connect、send客户端不需要 bind系统会在 connect 时自动分配本地端口。核心就是拿着服务端地址去连bool createclient(int domain, int soctype, int protype, int port) { m_isocket socket(domain, soctype, protype); if (m_isocket -1) { printf(create socket error\n); return false; } struct sockaddr_in servaddr; memset(servaddr, 0, sizeof(servaddr)); servaddr.sin_family domain; servaddr.sin_port htons(port); // 这里的 IP 要换成你服务端所在机器的地址 if (inet_pton(AF_INET, 127.0.0.1, servaddr.sin_addr) 0) { printf(inet_pton error\n); return false; } if (connect(m_isocket, (struct sockaddr*)servaddr, sizeof(servaddr)) -1) { printf(connect to server error\n); return false; } return true; }inet_pton把点分十进制的 IP 字符串转成二进制放进结构体。本机测试用127.0.0.1最省事如果服务端在另一台机器上就换成那台机器的实际 IP。connect 成功返回 0失败返回 -1失败时先检查 IP 和端口对不对再看服务端有没有真的在监听。4.3 收发数据recv 和 send 怎么用连接建立后服务端用recv收、客户端用send发。服务端循环接收并打印void recvmsg() { int connfd accept(m_isocket, NULL, NULL); // 接受一个连接 if (connfd -1) { printf(accept error\n); return; } char buff[1024] {0}; int n; while ((n recv(connfd, buff, 1023, 0)) 0) { buff[n] \0; printf(recv from client: %s\n, buff); memset(buff, 0, sizeof(buff)); } close(connfd); }accept返回的是一个全新的描述符代表这条具体连接原来的监听 socket 继续留着等下一个客人。recv返回实际读到的字节数返回 0 表示对方关闭了连接返回 -1 表示出错。客户端则用send循环发消息void sendmsg() { int msgid 0; char buff[1024] {0}; while (1) { memset(buff, 0, sizeof(buff)); sprintf(buff, msg %d\n, msgid); if (send(m_isocket, buff, strlen(buff), 0) 0) { printf(send error\n); break; } msgid; sleep(1); // 每秒发一条方便观察 } }send的返回值小于 0 就是出错常见原因是对方已经关闭连接。加个sleep是为了让输出节奏看得清实际项目里不会这么写。4.4 编译运行先起服务端再起客户端把上面的逻辑分别放进server.cpp和client.cpp各自编译g server.cpp mysocket.cpp -o server g client.cpp mysocket.cpp -o client然后开两个终端先运行./server看到等待提示后再运行./client。服务端终端会不断打印收到的消息客户端终端则显示发送成功。如果客户端报连接失败先确认服务端是不是真的起来了、端口是不是 6666、IP 有没有写错。这个例子虽小但把 socket 的主线完整走了一遍跑通它你就已经跨过了入门最难的那道坎。5. 新手避坑这几个地方最容易翻车5.1 端口被占用bind 直接失败现象是服务端一启动就打印bind socket error。原因通常是上一次运行的服务端没退干净端口还占着或者你选的端口被系统里别的程序用了。解决办法是先查谁占了端口再换一个端口或者把旧进程结束掉。入门阶段建议每次测试前确认没有残留进程养成习惯能省很多事。5.2 字节序没转端口变成奇怪数字现象是客户端连不上服务端明明在监听但用工具看端口却是一个完全不相干的数值。原因就是htons漏了或者用错了位置。主机字节序和网络字节序不一定一致端口和 IP 在塞进sockaddr_in之前必须转成网络字节序。记住一条凡是往地址结构体里填端口外面套一层htons填 IP 用htonl或inet_pton别直接赋值。5.3 客户端 IP 写死成别人的地址现象是自己在两台机器上测试客户端一直连不上。原因多半是代码里 IP 写的是示例地址没改成服务端的真实地址。入门时建议先用127.0.0.1在本机把流程跑通确认逻辑没问题再换成局域网 IP 做跨机测试。改 IP 只改一处别在多个地方硬编码。5.4 accept 返回新描述符别拿监听 socket 去读写现象是服务端能 accept但 recv 收不到数据或者行为诡异。原因是把accept的返回值和监听 socket 搞混了。监听 socket 只负责等连接真正收发数据的是 accept 返回的那个新描述符。代码里给变量起名时就把两者区分开比如listenfd和connfd能有效避免这种低级错误。5.5 忘了处理 recv 返回 0 的情况现象是客户端断开后服务端还在死循环里打转。recv返回 0 表示对端已经正常关闭连接这时候应该跳出循环并关闭 connfd而不是继续读。很多新手只判断了大于 0 和小于 0漏了等于 0 这一支结果连接断了程序还在空转。把三种返回值都处理掉逻辑才算完整。6. 下一步学什么从跑通例子到能写小工具跑通上面那个例子之后你已经摸到了 socket 编程的门。接下来不用急着啃大部头按下面这条路径往前走会比较顺。第一步把例子改成“服务端收到消息后回一句”也就是让 recv 和 send 在两端都出现体会一下双向通信。第二步试着让服务端同时处理多个客户端——可以先从最简单的“来一个连接就 fork 一个子进程”开始理解并发连接是怎么回事。第三步去翻一翻man手册里socket、bind、listen、accept、recv、send这几个函数的完整说明重点看返回值含义和常见 errno这比看任何教程都准。下面这张表可以当作你接下来查漏补缺的对照阶段目标建议动作已跑通单向发消息把例子编译运行一遍下一步双向对话服务端收到后回发一条再下一步多客户端用 fork 或线程处理并发进阶理解连接生命周期对照三次握手看 connect/accept 返回时机我第一次学 socket 的时候最大的教训是总想一口气把“高并发服务器”写出来结果连最基本的 bind 失败都排查了半天。后来我给自己定了个规矩每写一个新函数先让它单独跑通、打印出预期结果再往大程序里拼。这个习惯让我少走了很多弯路。你也可以从今天这个例子开始每改一处就重新编译运行一次看着输出变化去理解每个参数的作用。希望这份入门指南能帮你把 socket 从“听起来很玄”变成“我能动手写”。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站