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 <sys/types.h>
#include <sys/socket.h>
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 <netinet/in.h>
#include <string.h>
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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>#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 <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>#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语言网络通信程序!每次遇到问题都可以翻回来看,一步步排查流程,问题迎刃而解。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/pingmian/84139.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tomcat 安装和配置

一、Tomcat官网 Apache Tomcat - Welcome! 选择解压到任意一个盘&#xff01;&#xff01; 二、Tomcat配置 1&#xff09;在系统变量处新建一个变量CATALINA_HOME。CATALINA_HOME环境变量的值&#xff0c;设置为Tomcat的解压安装目录 2&#xff09;找到系统变量Path&#xff0…

动态规划 熟悉30题 ---上

本来是要写那个二维动态规划嘛&#xff0c;但是我今天在问题时候&#xff0c;一个大佬就把他初一时候教练让他练dp的30题发出来了&#xff08;初一&#xff0c;啊虽然知道计算机这一专业&#xff0c;很多人从小就学了&#xff0c;但是我每次看到一些大佬从小学还是会很羡慕吧或…

基于stm32F10x 系列微控制器的智能电子琴(附完整项目源码、详细接线及讲解视频)

注&#xff1a;成品使用演示、项目源码、项目文档在文章末尾网盘链接中自取 所用硬件&#xff1a;STM32F103C8T6、无源蜂鸣器、44矩阵键盘、flash存储模块、OLED显示屏、RGB三色灯、面包板、杜邦线、usb转ttl串口 stm32f103c8t6 面包板 …

时间同步技术在电力系统中的应用

随着电力自动化技术的发展&#xff0c;时间同步不仅可以为电力系统的事后故障分析提供支持&#xff0c;而且已经参与到电力系统的实时控制中来&#xff0c;其可靠性对电力系统的稳定运行影响越来越大。在电力系统中&#xff0c;时间同步技术广泛应用于调度控制中心、发电厂、变…

XMLGregorianCalendar跟Date、localDateTime以及String有什么区别

1. java.util.Date&#xff08;已过时&#xff0c;不推荐新代码使用&#xff09; 特点 表示时间戳&#xff1a;存储自 1970-01-01 00:00:00 UTC&#xff08;Unix 纪元&#xff09; 以来的毫秒数。 问题&#xff1a; 不区分日期和时间&#xff0c;也没有时区支持&#xff08;依…

Python网页自动化Selenium中文文档

1. 安装 1.1. 安装 Selenium Python bindings 提供了一个简单的API&#xff0c;让你使用Selenium WebDriver来编写功能/校验测试。 通过Selenium Python的API&#xff0c;你可以非常直观的使用Selenium WebDriver的所有功能。 Selenium Python bindings 使用非常简洁方便的A…

玩转抖音矩阵:核心玩法与高效运营规则

一、 抖音矩阵&#xff1a;流量协同的生态网络 抖音矩阵&#xff0c;本质是运营一个相互关联、互相支持的抖音账号群。核心目标在于通过账号间的深度协同&#xff08;内容、流量、粉丝&#xff09;&#xff0c;打破单个账号的流量天花板&#xff0c;实现11>2的效果。它不仅…

C++11 constexpr和字面类型:从入门到精通

文章目录 引言一、constexpr的基本概念与使用1.1 constexpr的定义与作用1.2 constexpr变量1.3 constexpr函数1.4 constexpr在类构造函数中的应用1.5 constexpr的优势 二、字面类型的基本概念与使用2.1 字面类型的定义与作用2.2 字面类型的应用场景2.2.1 常量定义2.2.2 模板参数…

用电脑通过USB总线连接控制keysight示波器

通过USB总线控制示波器的优势 在上篇文章我介绍了如何通过网线远程连接keysight示波器&#xff0c;如果连接的距离不是很远&#xff0c;也可以通过USB线将示波器与电脑连接起来&#xff0c;实现对示波器的控制和截图。 在KEYSIGHT示波器DSOX1204A的后端&#xff0c;除了有网口…

StarRocks 全面向量化执行引擎深度解析

StarRocks 全面向量化执行引擎深度解析 StarRocks 的向量化执行引擎是其高性能的核心设计&#xff0c;相比传统行式处理引擎&#xff08;如MySQL&#xff09;&#xff0c;性能可提升 5-10倍。以下是分层拆解&#xff1a; 1. 向量化 vs 传统行式处理 维度行式处理向量化处理数…

02 Deep learning神经网络的编程基础 逻辑回归--吴恩达

1.逻辑回归 逻辑回归是一种用于解决二分类任务&#xff08;如预测是否是猫咪等&#xff09;的统计学习方法。尽管名称中包含“回归”&#xff0c;但其本质是通过线性回归的变体输出概率值&#xff0c;并使用Sigmoid函数将线性结果映射到[0,1]区间。 以猫咪预测为例 假设单个…

UDP 与 TCP 的区别是什么?

UDP&#xff08;用户数据报协议&#xff09;与TCP&#xff08;传输控制协议&#xff09;有以下区别&#xff1a; 连接方式 - UDP&#xff1a;无连接&#xff0c;发送数据前不需要建立连接&#xff0c;也不维护连接状态&#xff0c;因此UDP的通信效率较高&#xff0c;适合对实时…

6.计算机网络核心知识点精要手册

计算机网络核心知识点精要手册 1.协议基础篇 网络协议三要素 语法&#xff1a;数据与控制信息的结构或格式&#xff0c;如同语言中的语法规则语义&#xff1a;控制信息的具体含义和响应方式&#xff0c;规定通信双方"说什么"同步&#xff1a;事件执行的顺序与时序…

unipp---HarmonyOS 应用开发实战

HarmonyOS 应用开发实战指南 1. 开篇&#xff1a;为什么选择 HarmonyOS&#xff1f; 最近在开发鸿蒙应用时&#xff0c;发现很多开发者都在问&#xff1a;为什么要选择 HarmonyOS&#xff1f;这里分享一下我的看法&#xff1a; 生态优势 华为手机用户基数大&#xff0c;市场潜…

Python_day48随机函数与广播机制

在继续讲解模块消融前&#xff0c;先补充几个之前没提的基础概念 尤其需要搞懂张量的维度、以及计算后的维度&#xff0c;这对于你未来理解复杂的网络至关重要 一、 随机张量的生成 在深度学习中经常需要随机生成一些张量&#xff0c;比如权重的初始化&#xff0c;或者计算输入…

C++中的数组

在C中&#xff0c;数组是存储固定大小同类型元素的连续内存块。它是最基础的数据结构之一&#xff0c;广泛用于各种场景。以下是关于数组的详细介绍&#xff1a; 一、一维数组 1. 定义与初始化 语法&#xff1a;类型 数组名[元素个数];示例&#xff1a;int arr[5]; // 定义…

three.js 零基础到入门

three.js 零基础到入门 什么是 three.js为什么使用 three.js使用 Three.js1. 创建场景示例 2.创建相机3. 创建立方体并添加网格地面示例 5. 创建渲染器示例 6. 添加效果(移动/雾/相机跟随物体/背景)自动旋转示例效果 相机自动旋转示例 展示效果 实现由远到近的雾示例展示效果 T…

Elasticsearch的写入性能优化

优化Elasticsearch的写入性能需要从多维度入手,包括集群配置、索引设计、数据处理流程和硬件资源等。以下是一些关键优化策略和最佳实践: 一、索引配置优化 合理设置分片数与副本数分片数(Shards):过少会导致写入瓶颈(无法并行),过多会增加集群管理开销。公式参考:分…

FMC STM32H7 SDRAM

如何无痛使用片外SDRAM? stm32 已经成功初始化了 STM32H7 上的外部 SDRAM&#xff08;32MB&#xff09; 如何在开发中无痛使用SDRAM 使它像普通 RAM 一样“自然地”使用? [todo] 重要 MMT(Memory Management Tool) of STM32CubeMx The Memory Management Tool (MMT) disp…

【AIGC】RAGAS评估原理及实践

【AIGC】RAGAS评估原理及实践 &#xff08;1&#xff09;准备评估数据集&#xff08;2&#xff09;开始评估2.1 加载数据集2.2 评估忠实性2.3 评估答案相关性2.4 上下文精度2.5 上下文召回率2.6 计算上下文实体召回率 RAGas&#xff08;RAG Assessment)RAG 评估的缩写&#xff…