TCP并发服务器的实现

news/2024/9/19 5:29:46/ 标签: 服务器, 网络, linux

一请求一线程

问题

当客户端数量较多时,使用单独线程为每个客户端处理请求可能导致系统资源的消耗过大和性能瓶颈。

资源消耗:
  • 线程创建和管理开销:每个线程都有其创建和销毁的开销,特别是在高并发环境中,这种开销会显著增加。
  • 内存消耗:每个线程通常需要分配一定的栈空间,这会增加内存使用量。
  • 上下文切换:操作系统需要频繁地切换线程上下文,这会消耗CPU资源。
性能瓶颈:
  • 线程竞争:大量线程会导致线程之间竞争共享资源,如内存和CPU时间,降低整体性能。
  • 调度开销:操作系统调度大量线程时的开销可能会影响应用程序的响应时间和吞吐量。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <errno.h>
#include <unistd.h>#define BUFFER_LENGTH 1024// 客户端处理线程的例程
void *client_routine(void* arg) {int clientfd = *(int*)arg;  // 获取传入的客户端套接字描述符while (1) {char buffer[BUFFER_LENGTH];  // 定义接收缓冲区int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);  // 接收数据if (len < 0) {// 接收数据出错perror("recv error");close(clientfd);  // 关闭客户端套接字break;} else if (len == 0) {// 客户端关闭连接close(clientfd);  // 关闭客户端套接字break;} else {// 打印接收到的数据printf("Recv: %s, %d byte(s)\n", buffer, len);}}return NULL;
}int main(int argc, char* argv[]) {if (argc < 2) {// 参数错误,未提供端口号printf("usage: %s port\n", basename(argv[0]));return -1;}int port = atoi(argv[1]);  // 从命令行参数获取端口号// 创建监听用的套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket creation failed");return 1;}// 配置套接字地址struct sockaddr_in addr;memset(&addr, 0, sizeof(struct sockaddr_in));  // 清空地址结构addr.sin_family = AF_INET;addr.sin_port = htons(port);  // 转换端口号为网络字节序addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用的接口if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))) {perror("bind failed");return 2;}if (listen(sockfd, 5) < 0) {perror("listen failed");return 3;}while (1) {struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));  // 清空客户端地址结构socklen_t client_len = sizeof(client_addr);// 接受客户端连接int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);if (clientfd < 0) {perror("accept failed");continue;}// 为每个客户端创建一个线程pthread_t thread_id;if (pthread_create(&thread_id, NULL, client_routine, &clientfd) != 0) {perror("pthread_create failed");close(clientfd);  // 创建线程失败时关闭客户端套接字}// 可选:分离线程以避免线程资源泄漏pthread_detach(thread_id);}// 关闭监听套接字(实际上这部分代码永远不会到达)close(sockfd);return 0;
}

使用ifconfig查看服务器程序所在主机的IP地址。

首先启动所写的tcp服务器,即确保tcp_server_test.cpp已经编译并运行在虚拟机上,监听指定的端口(8888)。

打开三个网络调试助手(NetAssist),在每个助手中配置远端主机地址为你的tcp服务器地址(在虚拟机用ifconfig查看),端口设置为 8888,点击连接。可以分别向tcp服务器写数据。

利用epoll

优点:

高效:

epoll采用事件驱动的方式,仅在有事件发生时通知应用程序,避免了轮询带来的性能开销。

可扩展性

能够处理大量的文件描述符,适合高并发应用。

边缘触发

支持边缘触发(EPOLLET),在数据到达时通知一次,适合需要高效处理大量事件的场景。

缺点

复杂性

编程模型较为复杂,需要正确处理事件并维持数据流动性,可能导致代码较难维护。

资源消耗

虽然epoll高效,但在高负载情况下,资源使用仍然会增加,如内存和系统调用次数。

边缘触发处理

需要确保处理所有数据,否则可能错过事件,增加了编程的复杂性。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <sys/epoll.h>#define BUFFER_LENGTH 1024
#define EPOLL_SIZE 1024int main(int argc, char* argv[]) {if (argc < 2) {// 参数错误,未提供端口号printf("usage: %s port\n", basename(argv[0]));return -1;}int port = atoi(argv[1]);  // 从命令行参数获取端口号// 创建监听用的套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket creation failed");return 1;}// 配置套接字地址struct sockaddr_in addr;memset(&addr, 0, sizeof(struct sockaddr_in));  // 清空地址结构addr.sin_family = AF_INET;addr.sin_port = htons(port);  // 转换端口号为网络字节序addr.sin_addr.s_addr = INADDR_ANY;  // 绑定到所有可用的接口if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in))) {perror("bind failed");close(sockfd);return 2;}if (listen(sockfd, 5) < 0) {perror("listen failed");close(sockfd);return 3;}// 创建 epoll 实例int epfd = epoll_create1(0);  // 使用 epoll_create1(0) 代替 epoll_create(0)if (epfd < 0) {perror("epoll_create failed");close(sockfd);return 4;}struct epoll_event events[EPOLL_SIZE] = {0};// 添加监听套接字到 epoll 实例struct epoll_event ev;ev.events = EPOLLIN | EPOLLET;  // 设置为边缘触发模式ev.data.fd = sockfd;if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) < 0) {perror("epoll_ctl failed");close(sockfd);close(epfd);return 5;}while (1) {// 等待事件发生int nready = epoll_wait(epfd, events, EPOLL_SIZE, -1);if (nready < 0) {perror("epoll_wait failed");break;  // 退出循环}for (int i = 0; i < nready; i++) {if (events[i].data.fd == sockfd) {struct sockaddr_in client_addr;memset(&client_addr, 0, sizeof(struct sockaddr_in));  // 清空客户端地址结构socklen_t client_len = sizeof(client_addr);// 接受客户端连接int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);if (clientfd < 0) {perror("accept failed");continue;}// 将新的客户端套接字添加到 epoll 实例中,并设置为边缘触发模式ev.events = EPOLLIN | EPOLLET;ev.data.fd = clientfd;if (epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev) < 0) {perror("epoll_ctl failed");close(clientfd);}} else {// 处理客户端套接字的事件int clientfd = events[i].data.fd;char buffer[BUFFER_LENGTH];  // 定义接收缓冲区int len;// 处理所有可用的数据while ((len = recv(clientfd, buffer, BUFFER_LENGTH, 0)) > 0) {buffer[len] = '\0';  // 添加字符串结束标志printf("Recv: %s, %d byte(s)\n", buffer, len);}if (len < 0) {perror("recv error");}// 客户端关闭连接或出错close(clientfd);  // 关闭客户端套接字epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, NULL);}}}// 关闭监听套接字和 epoll 实例close(sockfd);close(epfd);return 0;
}

推荐一下 

0voice · GitHub


http://www.ppmy.cn/news/1527111.html

相关文章

代码随想录打卡Day35

今天还是以看视频为主&#xff0c;主要是力扣上合适的题目不多&#xff0c;今天主要是学习0-1背包的二维数组解法和一维数组解法&#xff0c;今天题目不多&#xff0c;但是debug花了我好久时间。。。主要还是对0-1背包不够熟悉。 46. 携带研究材料&#xff08;卡码网&#xff…

大数据新视界 --大数据大厂之数据挖掘入门:用 R 语言开启数据宝藏的探索之旅

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

vue3中把封装svg图标为全局组件

在vue3中我们使用svg图标是下面这样子的 <svg style"width:30px;height:30px;"><use xlink:href"#icon-phone" fill"red"></use></svg>第次使用图标都要写这么多重复的代码&#xff0c;很不方便&#xff0c;所以&#x…

JavaSE基础——第三章 运算符

本专题主要为观看韩顺平老师《零基础30天学会Java》课程笔记&#xff0c;同时也会阅读其他书籍、学习其他视频课程进行学习笔记总结。如有雷同&#xff0c;不是巧合&#xff01; 运算符是一种特殊的符号&#xff0c;用于表示数据的运算、赋值、比较等&#xff0c;包括&#xff…

常用 Git 命令

可视化学习网站&#xff1a;Learn Git Branching 一、初始化仓库 git init&#xff1a;在当前目录下初始化一个新的 Git 仓库。 二、添加和提交更改 git add <file>&#xff1a;将指定文件添加到暂存区。可以使用通配符&#xff0c;如 git add *.py 添加所有 .py 文件…

TSRPC+Cocos

TSRPC文档: https://tsrpc.cn/docs/get-started/api.html 创建 先创建一个默认的会话项目&#xff0c;找一个文件夹在控制台运行以下代码&#xff1a; npx create-tsrpc-applatest first-api --presets browser # 或者 yarn create tsrpc-app first-api --presets browser运…

linux-L6 linux管理服务的启动、重启、停止、重载、查看状态命令

来重启一下某一个服务 1.使用命令查看所有的服务状态 Systemctl找到其中的相关的服务 systemctl status xxx_你的应用程序的服务__xxx3.重启该服务 systemctl restart xxx_你的应用程序的服务__xxx下面的是备用&#xff0c;需要用的时候&#xff0c;查看就好了 启动服务 …

java程序员入行科目一之CRUD轻松入门教程(一)

之前在操作MySQL的时候&#xff0c;都是采用Navicat&#xff0c;或者cmd黑窗口。 无论使用什么方式和MySQL交互&#xff0c;大致步骤是这样的 建立连接&#xff0c;需要输入用户名和密码编写SQL语句&#xff0c;和数据库进行交互 这个连接方式不会变&#xff0c;但是现在需要 基…

JVM程序计数器

JVM的程序计数器是线程私有的内存区域&#xff0c;它记录着当前线程执行的字节码指令地址&#xff0c;是Java虚拟机中至关重要的组件&#xff0c;确保多线程环境下程序的正确执行与流畅切换。其重要性不容忽视&#xff0c;是Java程序高效、稳定运行的基石。 一、程序计数器介绍…

敲击键盘到屏幕上打印字符计算机都做了什么

当你在 Linux 系统上按下键盘的 x 键并看到屏幕上打印出字母 x 时&#xff0c;Linux 系统内部发生了很多过事情&#xff0c;涉及硬件、操作系统内核和用户空间的多个层次。下面是一个大致的流程&#xff1a; 1. 硬件层 键盘硬件捕获按键&#xff1a;当你按下 x 键&#xff0c…

学成在线练习(HTML+CSS)

准备工作 项目目录 内部包含当前网站的所有素材&#xff0c;包含 HTML、CSS、图片、JavaScript等等 1.由于元素具有一些默认样式&#xff0c;可能是我们写网页过程中根本不需要的&#xff0c;所有我们可以在写代码之前就将其清除 base.css /* 基础公共样式&#xff1a;清除…

【PostgreSQL里vacuum但是无法回收死元组的原因】

PostgreSQL数据库里的vacuum/autvacuum在我们日长的使用中可能会遇到很多问题&#xff0c;例如vacuum被阻塞&#xff0c;vacuum时间长&#xff0c;vacuum成功执行后&#xff0c;仍旧无法回收死元组等等。&#xff0c;本文主要介绍PostgreSQL的vacuum成功执行后&#xff0c;仍旧…

力扣100题——动态规划(二)

单词划分 题目 139. 单词拆分 - 力扣&#xff08;LeetCode&#xff09; 思路 使用dp数组记录当前下标对应的字符串长度能否被正确划分 确定状态转移方程&#xff0c;当j<i时&#xff0c;d[i] d[j]&&wordDict.contains(s.substring(j, i)) 代码 public boole…

C++——多线程编程(从入门到放弃)

进程&#xff1a;运行中的程序 线程&#xff1a;进程中的进程 线程的最大数量取决于CPU的核心数 一、将两个函数添加到不同线程中 demo&#xff1a;两个函数test01()和test02()&#xff0c;实现将用户输入的参数进行打印输出1000次 将这两个函数均放到独立的线程t1和t2中&…

代码随想录训练营 Day60打卡 图论part10 SPFA算法 Bellman-Ford 之判断负权回路 Bellman-Ford 之单源有限最短路

代码随想录训练营 Day60打卡 图论part10 一、Bellman_ford 队列优化算法&#xff08;又名SPFA&#xff09; 例题&#xff1a;卡码94. 城市间货物运输 I 题目描述 某国为促进城市间经济交流&#xff0c;决定对货物运输提供补贴。共有 n 个编号为 1 到 n 的城市&#xff0c;通过…

第159天:安全开发-Python-协议库爆破FTPSSHRedisSMTPMYSQL等

案例一: Python-文件传输爆破-ftplib 库操作 ftp 协议 开一个ftp 利用ftp正确登录与失败登录都会有不同的回显 使用ftplib库进行测试 from ftplib import FTP # FTP服务器地址 ftp_server 192.168.172.132 # FTP服务器端口&#xff08;默认为21&#xff09; ftp_po…

深度学习实战电路板缺陷检测【数据集+YOLOv5模型+源码+PyQt5界面】

基于深度学习的电路板缺陷检测- 文章目录 研究背景1. 电路板制造的重要性2. 传统检测方法的局限性3. 深度学习技术的兴起4. 深度学习在缺陷检测中的应用5. 研究进展6. 面临的挑战7. 结论 代码下载链接一、效果演示1.1 图像演示1.2 视频演示1.3 摄像头演示 二、技术原理2.1 整体…

2024年9月HarmonyOS鸿蒙应用开发者高级认证全新题库(覆盖99%考题)

一个小时通过鸿蒙高级认证 1、在开发 Harmony0S 应用工程时&#xff0c; 随着业务的发展&#xff0c;现在需要创建一个模块&#xff0c; 关于在 DevEco Studio 中创建 Module &#xff0c; 下列选项哪种方式是错误的? 必对 在 hvigor 目录下&#xff0c;单击鼠标右键&#xf…

Rust程序结构与代码注释

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust编程与项目实战_夏天又到了的博客-CSDN博客 3.1 Rust程序结构 我们从一个最简单的程序入手&#xff0c;来观察一个Rust的程序结…

AD的入门操作

锦囊 1、打开AD后&#xff0c;一般默认打开上一个工程&#xff0c;这个时候如果想要打开新的工程&#xff0c;那就必须要创建一个项目&#xff0c;然后再在项目中添加原理图库和PCB库。 2、大多数情况下&#xff0c;直接使用库&#xff0c;不用自己再画原理图和封装库。 3、…