当前位置: 首页 > ds >正文

基于 Socket 和多线程的简单 Echo 服务器实现

在学习网络编程的时候,很多人会遇到一个经典的例子——Echo 服务器。所谓 Echo,就是“你说什么,我就原封不动地回什么”。这篇文章我们将通过一个 C 语言 + Socket + 多线程 的小例子,来实现一个简单的 Echo 服务器,并逐步分析其中的原理。

1. 场景类比

想象一下你去了一家快递公司,那里有一个接待大厅(服务器)。

  • 大厅的门口有个接待员(accept),不断等待新的顾客(客户端)进入。

  • 每来一个顾客,就会安排一个客服人员(线程 pthread_create),专门负责和这个顾客对话。

  • 客服的工作很简单:顾客说一句话(recv),客服就原样重复一句(send)。

这就是 Echo 服务的核心逻辑

2. 代码功能概述

这份代码主要实现了以下功能:

  • 创建 TCP 服务器 Socket(绑定本地 2000 端口)。

  • 监听客户端连接请求

  • 每当有客户端连接,就创建一个线程专门负责和该客户端通信

  • 通信逻辑:客户端发来什么消息,服务器就原样返回什么

也就是说,这是一个最简单的 多线程 TCP Echo 服务器

3. 核心原理

在深入代码之前,我们先理解几个关键点:

(1)Socket

socket(AF_INET, SOCK_STREAM, 0)

  • AF_INET 表示 IPv4。

  • SOCK_STREAM 表示使用 TCP 协议。

  • 得到的 sockfd 就像一个 监听用的电话机

(2)bind 和 listen

  • bind():把这个“电话机”绑定到一个固定号码(IP:Port)。

  • listen():让电话机进入“监听模式”,等待别人拨号。

(3)accept

  • accept():接电话,一旦有客户端打进来,就生成一个新的“分机电话”clientfd,用来和这个客户端通信。

注意:服务器端有两个 socket,一个是 监听 socket(接电话用),一个是 通信 socket(聊天用)。

(4)recv 和 send

  • recv():接收客户端发来的消息。

  • send():把消息再发回去。

(5)多线程

  • pthread_create():每接入一个客户端,就新开一个线程,保证多个客户端互不干扰。

4. 代码分块解析

下面我们逐步拆解代码。

(1)创建 socket 并绑定端口

int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(2000);if(-1 == bind(sockfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr))){printf("bind failed: %s\n",strerror(errno));
}

这段代码创建了一个 TCP socket,并绑定到 0.0.0.0:2000,也就是本机的 2000 端口。

(2)开始监听

listen(sockfd,10);
printf("listen finished\n");

listen() 表示可以同时挂起最多 10 个客户端的连接请求。

(3)等待客户端连接

while(1){printf("accept\n");int clientfd = accept(sockfd,(struct sockaddr*)&clientaddr,&len);printf("accept finished\n");pthread_t thid;pthread_create(&thid, NULL, client_thread, &clientfd);
}

每当有一个客户端连接,都会生成一个新的 clientfd,然后用 pthread_create 创建一个新线程处理该连接。

(4)线程处理函数

void *client_thread(void *arg){int clientfd = *(int *)arg;while(1){char buffer[1024] = {0};int count = recv(clientfd, buffer, 1024, 0);printf("RECV: %s\n", buffer);count = send(clientfd, buffer, count, 0);printf("SEND: %d\n", count);}
}

每个线程的逻辑非常简单:

  • 读取客户端消息(recv)。

  • 原样返回(send)。

这就是 Echo 的精髓。

5. 整体运行流程图

文字流程如下:

客户端A连接 ──┐
客户端B连接 ──┼──> accept() ---> 新线程A <-> 客户端A
客户端C连接 ──┘               新线程B <-> 客户端B新线程C <-> 客户端C

服务器就像一个前台,每接入一个客户,就派一个新客服(线程)来一对一服务。

6.完整代码

#include <errno.h>          // 提供错误码 errno
#include <stdio.h>          // 标准输入输出库
#include <sys/socket.h>     // socket 相关函数和数据结构
#include <netinet/in.h>     // sockaddr_in 结构体、htons/htonl 等
#include <string.h>         // 字符串处理
#include <pthread.h>        // POSIX 线程库// ==============================
// 客户端线程函数
// ==============================
void *client_thread(void *arg){// 取出传入的参数(clientfd 是客户端连接的 socket 描述符)int clientfd = *(int *)arg;while(1){char buffer[1024] = {0};           // 缓冲区,用于接收数据// 接收客户端发送的数据int count = recv(clientfd, buffer, 1024, 0);printf("RECV: %s\n", buffer);// 将接收到的数据原样发送回客户端(Echo)count = send(clientfd, buffer, count, 0);printf("SEND: %d\n", count);}
}// ==============================
// 主函数
// ==============================
int main(){// 1. 创建一个 TCP socketint sockfd = socket(AF_INET, SOCK_STREAM, 0);// 2. 配置服务器地址结构体struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;              // 使用 IPv4servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定到本机所有网卡 IPservaddr.sin_port = htons(2000);            // 监听端口 2000// 3. 绑定 socket 和端口if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){// 如果绑定失败,打印错误信息printf("bind failed: %s\n", strerror(errno));}// 4. 开始监听,最大等待队列长度为 10listen(sockfd, 10);printf("listen finished\n");// 用于存储客户端信息struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);#if 0   // 方式一:只接受一次客户端请求,然后收发一次数据后退出printf("accept\n");int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("accept finished\n");char buffer[1024] = {0};int count = recv(clientfd, buffer, 1024, 0);printf("RECV:%s\n", buffer);count = send(clientfd, buffer, count, 0);printf("SEND:%d\n", count);#elif 0 // 方式二:循环处理多个客户端,但串行执行(一个客户端处理完才处理下一个)while(1){printf("accept\n");int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("accept finished\n");char buffer[1024] = {0};int count = recv(clientfd, buffer, 1024, 0);printf("RECV:%s\n", buffer);count = send(clientfd, buffer, count, 0);printf("SEND: %d\n", count);}#else   // 方式三(最终版本):每个客户端使用一个线程来处理while(1){printf("accept\n");// 阻塞等待客户端连接int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("accept finished\n");// 创建线程处理该客户端连接pthread_t thid;pthread_create(&thid, NULL, client_thread, &clientfd);// 注意:此处传入 &clientfd 存在风险,最好用 malloc 分配空间再传入}
#endif// 等待用户输入,防止程序提前退出getchar();printf("exit\n");return 0;
}

7. 总结与扩展

这段代码实现了一个最小可用的 多线程 TCP Echo 服务器,主要用来理解:

  • Socket 的创建与绑定。

  • accept 的作用(连接建立)。

  • recv/send 的通信逻辑。

  • pthread 的多线程处理。

0voice · GitHub

http://www.xdnf.cn/news/20075.html

相关文章:

  • [UT]记录uvm_config_db的错误:get中的第二个参数设置为this
  • 小企业环境-火山方舟和扣子
  • 【FPGA】DDS信号发生器
  • 【C++】Vector核心实现:类设计到迭代器陷阱
  • < 自用文 主机 USC 记录:> 发现正在被攻击 后的自救
  • 天然苏打水生产的原水抽取与三重除菌的3D模拟开发实战
  • AI大模型对决:谁是最强智能?
  • MySQL 清空表实战:TRUNCATE 与 DELETE 的核心差异与正确用法
  • 小白成长之路-develops -jenkins部署lnmp平台
  • 淘宝京东拼多多爬虫实战:反爬对抗、避坑技巧与数据安全要点
  • EDVAC:现代计算机体系的奠基之作
  • JMeter下载安装及使用入门
  • MySQL 行转列 (Pivot) 的 N 种实现方式:静态、动态与 GROUP_CONCAT 详解
  • linux0.12 head.s代码解析
  • Langchain4j 整合MongoDB 实现会话持久化存储详解
  • Day34 UDP套接字编程 可靠文件传输与实时双向聊天系统
  • HTML5圣诞网站源码
  • Python基础(①①Ctypes)
  • Web安全——JWT
  • 厦门创客匠人靠谱嘛?从内容交付能力看其核心优势
  • el-tree 点击父节点无效,只能选中子节点
  • [BUUCTF-OGeek2019]babyrop详解(包含思考过程)
  • C++:类和对象(上)
  • 微软rStar2-Agent:新的GRPO-RoC算法让14B模型在复杂推理时超越了前沿大模型
  • 卷积操作原来分3种
  • 2025年工科生转型必考的十大高含金量证书!
  • 腾讯云建站多少钱?2025年最新价格曝光,0基础也能做出专业网站?实测真假
  • flutter专栏--深入剖析你的第一个flutter应用
  • 从一次Crash分析Chromium/360浏览器的悬空指针检测机制:raw_ref与BackupRefPtr揭秘
  • 留学第一天,语言不通怎么办?同声传译工具推荐来了