TCP并发服务器的实现

news2024/9/20 9:10:30

一请求一线程

问题

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

资源消耗:
  • 线程创建和管理开销:每个线程都有其创建和销毁的开销,特别是在高并发环境中,这种开销会显著增加。
  • 内存消耗:每个线程通常需要分配一定的栈空间,这会增加内存使用量。
  • 上下文切换:操作系统需要频繁地切换线程上下文,这会消耗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 1024

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");
        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.coloradmin.cn/o/2143134.html

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

相关文章

计算机三级网络技术总结(一)

RPR环中每一个节点都执行SRP公平算法IEEE 802.11a和g将传输速率提高到54Mbps一个BGP发言人与其他自治系统中的BGP发言人要交换路由信息就要先建立TCP连接在一个区域内的路由器数一般不超过200个进入接口配置模式&#xff1a;Router(config)#interface <接口名> 封装ppp协…

CentOS上使用Mosquitto实现Mqtt主题消息发布和订阅mqtt主题消息连同时间戳记录到文件

场景 CentOS上使用rpm离线安装Mosquitto(Linux上Mqtt协议调试工具)附资源下载&#xff1a; CentOS上使用rpm离线安装Mosquitto(Linux上Mqtt协议调试工具)附资源下载-CSDN博客 上面介绍了mosquitto的离线安装。 如果业务场景中需要订阅某mqtt主题的消息并将收到消息的时间以…

婚礼弹幕上墙阳光正好,爱意正浓,打造一场出圈的唯美婚礼!

原文地址 婚礼现场的弹幕功能可以给整个场景增添温暖和喜庆的氛围。通过手机发送祝福&#xff0c;让亲友可以即时将祝福传达给新人&#xff0c;同时这些祝福以弹幕的形式在大屏幕上滚动展示&#xff0c;增加了现场互动的乐趣。墙上新闻搭配的功能则更加抢眼&#xff0c;不仅可…

idea插件推荐之Cool Request

Cool Request是一款基于IDEA的HTTP调试工具&#xff0c;可以看成是轻量版的postman&#xff0c;它会自动扫描项目代码中所有API路径&#xff0c;按项目分组管理。一个类被定义为Controller且其中的方法被RequestMapping或者XXXMapping注解标注以后就会被扫描到。 对应方法左侧会…

【C++二叉树】102.二叉树的层序遍历

107. 二叉树的层序遍历 II - 力扣&#xff08;LeetCode&#xff09; 思路分析&#xff1a; 层序遍历&#xff0c;但是要注意输出的结果是一个二维数组&#xff0c;不是一层一个值一个值的输出&#xff0c;而是要一层一层的输出。可以通过一个循环控制每一层的数据个数&#xff…

FastAdmin CMS 操作手册

FastAdmin CMS 操作手册 概述&#xff1a; 安装&#xff1a; 配置&#xff1a; 模板&#xff1a; 模板目录&#xff1a; 标签&#xff1a; 全局&#xff1a; 文章&#xff1a; 专题&#xff1a; 栏目&#xff1a; 公共参数&#xff1a; 单页&#xff1a; 特殊标签&#xff1a;…

python正则表达式如何不区分大小写

使用python的re模块做模式匹配时&#xff0c;有时需要忽略大小写&#xff0c;只需要在re.search()函数中添加参数re.IGNORECASE即可。 mystring some string pattern some pattern match re.search(pattern, mystring, re.IGNORECASE)

95、k8s之rancher可视化

一、ranker 图形化界面 图形化界面进行k8s集群的管理 rancher自带监控----普罗米修斯 [rootmaster01 opt]# docker load -i rancher.tar ##所有节点 [rootmaster01 opt]# docker pull rancher/rancher:v2.5.7 ##主节点[rootmaster01 opt]# vim /etc/docker/daemon.jso…

1.数据结构-双链表

一.双链表与单链表的对比&#xff1a; 二.双链表的初始化(带头结点)&#xff1a; 1.图解&#xff1a; 2.代码演示&#xff1a; #include<stdio.h> #include<stdlib.h> ​ //定义双链表结构体 typedef struct DNode {int data;struct DNode *prior;//前驱指针即指…

【Windows】使用 WMI 获取系统版本信息

目录 获取系统版本信息代码 获取系统版本信息 通过 RtlGetNtVersionNumbers 获取系统版本的方法可能不适用于所有情况&#xff0c;而且将要过时&#xff08;被废弃&#xff09;。下面介绍一种通过 WMI 查询并根据版本号进行划分的系统版本解析工具&#xff0c;其他方法还有通过…

学成在线练习(HTML+CSS)

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

如何用MATLAB计算多边形的几何中心

在MATLAB中&#xff0c;计算多边形的几何中心&#xff08;又称质心或重心&#xff09;可以通过以下步骤实现。假设你有一个多边形&#xff0c;其顶点按照顺时针或逆时针顺序排列在一个矩阵中。具体步骤如下&#xff1a; 定义多边形顶点&#xff1a;首先&#xff0c;你需要将多边…

FPGA随记——状态机

1. 概念 状态机&#xff08;State Machine&#xff09;&#xff1a; 有限状态机&#xff08;Finite State Machine&#xff0c;简称FSM&#xff09; 在有限个状态之间按一定规律转换的时序电路。 2 状态机模型 组成元素&#xff1a;输入、状态、状态转移条件、输出&#xff…

BSN六周年:迈向下一代互联网

当前&#xff0c;分布式技术作为现代计算机科学和信息技术的重要组成部分&#xff0c;在云计算、区块链等技术的推动下&#xff0c;正以多样化的形式蓬勃发展。 ​而区块链作为一种特殊的分布式系统&#xff0c;近年来也在各个领域得到了广泛关注。通过在区块链上运行智能合约…

情系河北 缘聚苏州——2024苏州河北同乡迎国庆贺中秋大团圆联谊活动圆满举办

金秋送爽&#xff0c;月朗中天。9月15日晚&#xff0c;由苏州市河北商会志愿者工作委员会牵头&#xff0c;苏州河北以及各地市老乡群联合主办的第二届“情系河北 缘聚苏州”--2024年苏州河北同乡迎国庆贺中秋大团圆联谊活动在香雪海饭店&#xff08;吴中大道店&#xff09;二楼…

C++自学笔记35(文件操作)

讲解常见文件操作函数 fopen FILE* fp fopen&#xff08;char const* _FileName,char const* _Mode&#xff09; 第一个参数&#xff1a;文件名&#xff0c;字符串表示 第二个参数&#xff1a;文件打开模式3种 1.rb&#xff08;read birary&#xff09;&#xff1a;读文件…

【算法】动态规划—编辑距离

题目 给你两个单词 word1 和 word2&#xff0c; 请返回将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作&#xff1a; 插入一个字符删除一个字符替换一个字符 思路分析 编辑距离问题就是给定两个字符串 s1 和 s2&#xff0c;只能用三种操作…

STM32外设-0.96寸OLED显示屏

江科大OLED教程&#xff1a;01-快速上手&#xff08;上集&#xff09;_哔哩哔哩_bilibili 01 0.96寸OLED显示屏原理 1.1 0.96寸OLED显示屏简介 适用器件&#xff1a; 0.96寸OLED显示屏&#xff08;驱动芯片&#xff1a;SSD1306 / SSD1315&#xff09; 1.3寸OLED显示屏&…

针对国内AIGC市场,国内目前出台了那些法律法规?

针对国内AIGC市场&#xff0c;特别是AI生成与合成内容方面&#xff0c;中国已经出台了一系列法律法规来规范其发展和应用。 图片源自“央视新闻” 以下是一些主要的法律法规&#xff1a; 一、国家层面的法律法规 《中华人民共和国网络安全法》 施行时间&#xff1a;2017年6月…

【课程学习】信号检测与估计II

b站 文章目录 1-概述贝叶斯方法1-概述 线性、正交、平稳、高斯 研究线性模型,采用正交化方法,假设信号平稳,考虑信号的统计特性是高斯的。 本学期考虑,非线性、非正交、非平稳、非高斯。 阵列处理 1980-1990 MUSIC 稀疏性 2006-2012 LASS 时频分析 1995-2000 小波 贝叶斯 2…