> 技术文档 > Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信_linux c tcp

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信_linux c tcp


文章目录

  • Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端客户端通信
    • 前言
    • 一、网络通信基础概念
    • 二、服务端与客户端的完整流程图解
    • 三、每一步的详细讲解和代码示例
      • 1. 创建Socket(服务端和客户端都要)
      • 2. 绑定本地地址和端口(服务端用bind)
      • 3. 设置监听(服务端listen)
      • 4. 等待并接受连接(服务端accept)
      • 5. 客户端发起连接(connect)
      • 6. 数据收发(read/write)
      • 7. 关闭socket
    • 四、流程图&示意
    • 五、错误处理机制(errno、perror、strerror)
    • 六、完整最简服务端和客户端代码范例
      • 1. 服务端示例
      • 2. 客户端示例
    • 七、通用经验和常见问题排查
    • 八、图解数据流和socket关系
    • 九、总结

Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信


前言

本文面向初学者,目标是让你“一看就懂、能马上动手实践”。从零讲起,手把手梳理服务端与客户端的构建全过程,每个函数、参数、典型用法、数据流向、底层机制、注意事项全部细细拆解,让你彻底明白如何让两个程序通过网络可靠通信。


一、网络通信基础概念

  1. 什么是Socket?

    • Socket(套接字)是操作系统为进程之间通过网络发送和接收数据而提供的一套“接口”
    • 类比现实世界,就是一台机器(主机)上的“电话插孔”,只有插上电话线(建立连接)你们才能说话。
    • Socket抽象了所有底层的网络细节,为开发者提供了“像读写文件一样”进行网络通信的方式。
  2. IP和端口

    • IP:主机的唯一网络地址,相当于“电话号码”。
    • 端口:主机内部区分不同网络服务的编号,相当于“分机号”。
  3. TCP通信流程(面向连接)

    • 服务端先开启,监听一个IP+端口。
    • 客户端主动连接服务端的IP+端口。
    • 建立连接(三次握手)。
    • 双方可以互相发送和接收数据。
    • 通信完成后关闭连接。

二、服务端与客户端的完整流程图解

服务端主要流程:

  1. 创建Socket
  2. 绑定本地IP和端口(bind)
  3. 设置为监听状态(listen)
  4. 死循环等待客户端连接(accept)
  5. 收发数据(read/write)
  6. 关闭通信(close)

客户端主要流程:

  1. 创建Socket
  2. 配置服务器IP和端口
  3. 发起连接请求(connect)
  4. 收发数据(read/write)
  5. 关闭通信(close)

三、每一步的详细讲解和代码示例

1. 创建Socket(服务端和客户端都要)

作用:告诉内核“我要用网络通信”,创建一个通信端点。

#include #include int socket(int domain, int type, int protocol);
  • domain:协议族,常用AF_INET(IPv4)。
  • type:套接字类型,常用SOCK_STREAM(TCP,可靠流)。
  • protocol:协议,通常填0,表示由系统自动选择。

举例:

int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) { perror(\"socket error\"); exit(1);}
  • 返回值:成功时是一个“文件描述符”,失败返回-1。

2. 绑定本地地址和端口(服务端用bind)

作用:明确告诉操作系统“我用哪个IP+端口”来等待客户端连接。只有bind了,别人才能找到你。

#include #include struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;  // 协议族servaddr.sin_port = htons(8888);  // 端口(本地字节序转网络字节序)servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 本机所有IPint ret = bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));if (ret == -1) { perror(\"bind error\"); exit(1);}
  • htons/htonl 是将主机字节序转为网络字节序(大端)。
  • INADDR_ANY 让你的服务监听本机所有网卡(IP)。

3. 设置监听(服务端listen)

作用:让socket进入“监听”状态,准备接收连接请求。

int listen(int sockfd, int backlog);
  • sockfd:刚才创建并bind过的socket。
  • backlog:内核排队等待连接的最大数量。

举例:

if (listen(sockfd, 128) == -1) { perror(\"listen error\"); exit(1);}
  • 这时操作系统会帮你排队管理那些“想要连你”的客户端。

4. 等待并接受连接(服务端accept)

作用:阻塞等待客户端“来电”,接听一个新连接,为每个连接分配一个新的socket。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  • sockfd:监听socket。
  • addr:传出参数,获取客户端的IP+端口等信息。
  • addrlen:addr结构体大小(调用前要赋初值)。

举例:

struct sockaddr_in cliaddr;socklen_t cliaddr_len = sizeof(cliaddr);int connfd = accept(sockfd, (struct sockaddr*)&cliaddr, &cliaddr_len);if (connfd == -1) { perror(\"accept error\"); continue; // 或exit(1)}
  • connfd:每个客户端连接都会分配一个新socket,独立通信。
  • 注意:监听socket(sockfd)只负责“等电话”,不能直接收发数据,后面通信都用connfd。

5. 客户端发起连接(connect)

作用:主动“打电话”给服务端,发起三次握手,连接指定IP+端口。

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

客户端配置服务器地址:

struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(8888);inet_pton(AF_INET, \"192.168.1.100\", &servaddr.sin_addr);if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { perror(\"connect error\"); exit(1);}
  • inet_pton 用于将点分十进制IP字符串转为网络字节序整数。

6. 数据收发(read/write)

作用:收发字节流数据,就像读写文件一样。

  • write() 发送数据到对方
  • read() 从对方接收数据

例子(双方都类似):

char buf[1024];// 发送write(sockfd, \"hello\", 5);// 接收int n = read(sockfd, buf, sizeof(buf)-1);if (n > 0) { buf[n] = \'\\0\'; printf(\"收到: %s\\n\", buf);}
  • 注意:read和write返回值要判断,<=0说明对方关闭了连接或出错。
  • 服务器处理建议:每个连接完成后记得close(connfd)。

7. 关闭socket

作用:释放系统资源,断开连接。

close(sockfd); // 对服务端监听socket、通信socket、客户端socket都适用

四、流程图&示意

服务器端流程客户端流程--------------------------------------------------------socket() socket() |  |bind() connect() |  |listen() | | ---------三次握手accept() <--------+ | | |  |read()/write()  read()/write() | |  |close() close() close()

五、错误处理机制(errno、perror、strerror)

  1. 每一步系统调用都可能出错,一定要检查返回值(-1 代表失败)。
  2. 出错后操作系统会设置errno变量(int类型,存放错误码)
  3. perror(“描述”) 会直接把你的描述和错误原因一起打印到标准错误输出。
  4. strerror(errno) 把错误码转为字符串,可以写日志或文件。

例子:

if (bind(sockfd, ...) == -1) { perror(\"bind error\"); // fprintf(logfile, \"bind error: %s\\n\", strerror(errno)); exit(1);}
  • 建议每个步骤都这样处理,排查故障时一清二楚。

六、完整最简服务端和客户端代码范例

1. 服务端示例

#include #include #include #include #include #define SERVER_PORT 8888int main() { int listenfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERVER_PORT); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { perror(\"bind error\"); exit(1); } if (listen(listenfd, 128) == -1) { perror(\"listen error\"); exit(1); } printf(\"Server is listening...\\n\"); while (1) { struct sockaddr_in cliaddr; socklen_t cliaddr_len = sizeof(cliaddr); int connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len); if (connfd == -1) { perror(\"accept error\"); continue; } char buf[1024]; int n = read(connfd, buf, sizeof(buf)-1); if (n > 0) { buf[n] = \'\\0\'; printf(\"client says: %s\\n\", buf); write(connfd, buf, n); // 回显 } close(connfd); } close(listenfd); return 0;}

2. 客户端示例

#include #include #include #include #include #define SERVER_PORT 8888#define SERVER_IP \"127.0.0.1\"int main() { int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERVER_PORT); inet_pton(AF_INET, SERVER_IP, &servaddr.sin_addr); if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) { perror(\"connect error\"); exit(1); } char sendbuf[1024] = \"hello server\"; write(sockfd, sendbuf, strlen(sendbuf)); char recvbuf[1024]; int n = read(sockfd, recvbuf, sizeof(recvbuf)-1); if (n > 0) { recvbuf[n] = \'\\0\'; printf(\"server says: %s\\n\", recvbuf); } close(sockfd); return 0;}

七、通用经验和常见问题排查

  1. 端口被占用,bind出错:先用netstat -ntlp查端口占用,或改端口再试。
  2. connect失败:IP、端口写错?服务器没开?防火墙拦截?
  3. read/write出错或为0:对方关闭了连接,需及时close。
  4. 每个连接用独立socket,主循环不要关listenfd。
  5. 大项目建议引入多线程或select/epoll提升并发能力。

八、图解数据流和socket关系

| 客户端 | 网络 | 服务端 |+--------+---------------+-----------------------+| | ---connect--> | [listenfd] || | <---三次握手--| || |  | accept()产生[connfd] || |read/write| [connfd][listenfd] || | ---close----->| [connfd closed] |
  • listenfd负责排队监听,不用于通信。connfd负责与客户端通信。

九、总结

  1. 先socket(),再bind()(服务端),然后listen(),accept()连接,read/write通信,close()结束。客户端用connect()主动连接。
  2. 每一步都检查错误,配合perror/strerror打印详细信息,便于调试和维护。
  3. IP、端口、字节序、缓冲区管理要细心。
  4. 建议将代码拆分成模块,错误处理、日志、收发通信各自封装。

只要理解并掌握上面每一步、每个关键函数的使用,你就能独立搭建出稳定可靠的C语言网络通信程序!每次遇到问题都可以翻回来看,一步步排查流程,问题迎刃而解。


穿衣打扮指南