C语言TCP服务器模型 : select + 多线程与双循环单线程阻塞服务器的比较

观察到的实验现象:

启动三个客户端:

使用双循环阻塞服务器:只能accept后等待收发,同时只能与一个客户端建立连接,必须等已连接的客户端多次收发 明确断开后才能与下个客户端连接

使用IO多路复用select:可以同时接收所有的连接请求,并且连接状态一直是存活的,直到客户端关闭连接

select + 多线程服务器创作灵感:

本来是想 接收,发送 全用select

但是如果每个连接都要求处理大量数据,则响应时间不确定

最重要的,select判断依据是内核缓存是否有足够空间可写,而不是数据是否准备好

所以为了数据准备好再发送 

我使用了 接收多路复用+分线程处理数据+处理完毕在线程内直接发送 的模型

什么样的场景收发都适合用select?

IO密集型转发服务器

用于对比的双循环阻塞服务器工作原理:

进入外循环, accept后 再进入内循环 收 发 ,当客户端结束连接时 内层循环结束(使用break)

代码走完 重新进入外层循环 accept阻塞等待一个新连接

注意事项: ip地址修改为符合 你网络规范的ip 运行环境:unix-like系统 gnu_c库

select + 多线程服务器,欢迎指正:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>

#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50012
// 此结构体用于线程参数
struct t_args
{
    int fd;
    char data[1024];
};
// 用于accept返回的fd的容器
int client_sockfds[1024] = {0};
// 计数器可以理解为指针,每次用完向后挪1位
int count = 0;
// 线程执行函数
void *start_routine(void *p)
{
    // 解析参数
    struct t_args ta = *((struct t_args *)p);
    // fd后面要用
    int fd = ta.fd;
    // 数据打印出来表示已经获取,可以进行后续处理
    printf("%s\n", ta.data);
    // 模拟数据处理
    sleep((rand() % 3) + 1);
    // 这是处理完的结果
    char res_data[128] = "yes yes done done done";
    ssize_t send_bytes;
    // 声明写监控集
    fd_set writefds;
    // 清空重置
    FD_ZERO(&writefds);
    // 将这个fd加入写监控
    FD_SET(fd, &writefds);
    // 如果select返回,说明此fd写就绪
    int r = select(fd + 1, NULL, &writefds, NULL, NULL);
    if (r == -1)
    {
        perror("select");
    }
    if (r > 0)
    {
        // 如果写就绪
        if (FD_ISSET(fd, &writefds))
        {
            // 就把处理好的数据发送回去
            send_bytes = send(fd, res_data, strlen(res_data), 0);
            if (send_bytes == -1)
            {
                perror("send");
            }
            if (send_bytes > 0)
            {
                printf("%s\n", res_data);
            }
        }
    }
    free(p);
    pthread_exit(NULL);
}
void handler(void *p)
{
    pthread_t tid;
    // 创建线程,传参fd
    if (pthread_create(&tid, NULL, start_routine, p))
    {
        perror("pthread_create");
    }
    // 分离
    if (pthread_detach(tid))
    {
        perror("pthread_detach");
    }
}

int main()
{
    int server_sockfd, client_sockfd;
    struct sockaddr_in server_sockaddr, client_sockaddr;
    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
    memset(&client_sockaddr, 0, sizeof(client_sockaddr));
    socklen_t client_sockaddr_len = sizeof(client_sockaddr);
    socklen_t server_sockaddr_len = sizeof(server_sockaddr);
    ssize_t recv_bytes;
    char recv_buf[1024] = {0};
    fd_set readfds;
    // 随机数种子
    srand(time(NULL));
    // 创建socket
    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sockfd == -1)
    {
        perror("socket");
    }
    // 端口复用
    int optval = 1;
    if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1)
    {
        perror("setsockopt");
    }
    // 绑定地址端口
    inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
    server_sockaddr.sin_port = htons(SERVER_PORT);
    server_sockaddr.sin_family = AF_INET;
    if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, server_sockaddr_len) == -1)
    {
        perror("bind");
    }
    // 监听
    if (listen(server_sockfd, 16) == -1)
    {
        perror("listen");
    }
    printf("server start...\n");
    // 服务器主循环
    while (1)
    {
        // 清空重置读集
        FD_ZERO(&readfds);
        // 将server_sockfd加入读集
        FD_SET(server_sockfd, &readfds);
        // 假设最大的fd是server_sockfd
        int fd_max = server_sockfd;
        int i;
        // count总是指向当前已填充fd的下一个位置
        for (i = 0; i < count; i++)
        {
            // client_sockfds[i]数组储存accept返回的fd ,> 0表示存在fd
            if (client_sockfds[i] > 0)
            {
                // 存在fd就加入读监控
                FD_SET(client_sockfds[i], &readfds);
                // 更新最大fd的值
                fd_max = fd_max > client_sockfds[i] ? fd_max : client_sockfds[i];
            }
        }
        // 此处select作用:从读集中选择读就绪
        int r = select(fd_max + 1, &readfds, NULL, NULL, NULL);
        if (r > 0)
        {
            // 如果server_sockfd是读就绪的
            if (FD_ISSET(server_sockfd, &readfds))
            {
                // 说明已经有连接在等待,则accept不会阻塞
                client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
                if (client_sockfd == -1)
                {
                    perror("accept");
                }
                // count++先读取count的值 后++,把返回的client_sockfd存到数组
                client_sockfds[count++] = client_sockfd;
                // 当连接数达到1024时,变得无法处理且有重大安全漏洞
                if (count == 1024)
                {
                    kill(getpid(), SIGKILL);
                }
            }
            // 此循环用于检查client_sockfds数组已填充部分
            for (i = 0; i < count; i++)
            {
                // 检查fd是否读就绪
                if (FD_ISSET(client_sockfds[i], &readfds))
                {
                    // 接收消息
                    recv_bytes = recv(client_sockfds[i], recv_buf, sizeof(recv_buf), 0);
                    if (recv_bytes < 0)
                    {
                        perror("recv");
                    }
                    else if (recv_bytes == 0)
                    {
                        printf("close by peer\n");
                        // 对面关我也关
                        close(client_sockfds[i]);
                        // 将数组上的fd清空
                        client_sockfds[i] = 0;
                    }
                    else
                    {
                        // 向线程传参
                        struct t_args ta;
                        ta.fd = client_sockfds[i];
                        strncpy(ta.data, recv_buf, strlen(recv_buf));
                        // 为每个线程参数动态分配内存空间
                        struct t_args *p = (struct t_args *)malloc(sizeof(ta));
                        if (p == NULL)
                        {
                            return -1;
                        }
                        // 赋值
                        *p = ta;
                        // 传入处理函数
                        handler((void *)p);
                    }
                }
            }
        }
        else if (r == -1)
        {
            perror("select");
        }
    }
    close(server_sockfd);
    return 0;
}

双循环阻塞服务器:

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>

#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50012

int main()
{
    int server_sockfd, client_sockfd;
    struct sockaddr_in server_sockaddr, client_sockaddr;
    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
    memset(&client_sockaddr, 0, sizeof(client_sockaddr));
    socklen_t client_sockaddr_len = sizeof(client_sockaddr);
    ssize_t send_bytes, recv_bytes;
    char send_buf[1024] = "How can I help you today ?";
    char recv_buf[1024] = {0};

    server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sockfd == -1)
    {
        perror("socket");
    }

    int optval = 1;
    setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    server_sockaddr.sin_family = AF_INET;
    inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
    server_sockaddr.sin_port = htons(SERVER_PORT);
    if (bind(server_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1)
    {
        perror("bind");
    }

    if (listen(server_sockfd, 16) == -1)
    {
        perror("listen");
    }

    printf("server start...\n");

    while (1)
    {
        client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
        if (client_sockfd == -1)
        {
            perror("accept");
        }
        while (1)
        {

            recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);
            if (recv_bytes == -1)
            {
                perror("recv");
            }
            else if (recv_bytes == 0)
            {
                printf("closed by peer\n");
                break;
            }
            else
            {
                printf("%s\n", recv_buf);
            }
            send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);
            if (send_bytes == -1)
            {
                perror("send");
            }
        }
    }
    close(server_sockfd);
    return 0;
}

赠送客户端:
 

#define _GNU_SOURCE
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>

#define SERVER_IP "192.168.142.132"
#define SERVER_PORT 50012

int main()
{
    int client_sockfd;
    struct sockaddr_in server_sockaddr, client_sockaddr;
    memset(&server_sockaddr, 0, sizeof(server_sockaddr));
    memset(&client_sockaddr, 0, sizeof(client_sockaddr));
    socklen_t client_sockaddr_len = sizeof(client_sockaddr);
    ssize_t send_bytes, recv_bytes;
    char send_buf[1024] = {0};
    char recv_buf[1024] = {0};
    srand(time(NULL));
    client_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (client_sockfd == -1)
    {
        perror("socket");
    }
    inet_pton(AF_INET, SERVER_IP, &server_sockaddr.sin_addr.s_addr);
    server_sockaddr.sin_port = htons(SERVER_PORT);
    server_sockaddr.sin_family = AF_INET;
    if (connect(client_sockfd, (struct sockaddr *)&server_sockaddr, sizeof(server_sockaddr)) == -1)
    {
        perror("connect");
    }
    getsockname(client_sockfd, (struct sockaddr *)&client_sockaddr, &client_sockaddr_len);
    snprintf(send_buf, sizeof(send_buf), "%u:he###llo s???ver !!!",
             ntohs(client_sockaddr.sin_port));
    while (1)
    {
        send_bytes = send(client_sockfd, send_buf, strlen(send_buf), 0);
        if (send_bytes == -1)
        {
            perror("send");
        }
        recv_bytes = recv(client_sockfd, recv_buf, sizeof(recv_buf), 0);
        if (recv_bytes == -1)
        {
            perror("recv");
        }
        printf("%s\n", recv_buf);
        sleep(1);
    }

    close(client_sockfd);
    return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Kubesphere 自动化部署失败报错

Kubesphere 自动化部署在 push tag 阶段失败报错 git push http://****:****github.com/****/devops-java-sample.git --tags --ipv4 remote: Support for password authentication was removed on August 13, 2021. remote: Please see https://docs.github.com/get-started/g…

Netty是什么

一、Netty介绍 1、Netty是一个异步的、基于事件驱动的网络应用框架&#xff0c;用以快速开发高性能、高可靠性的网络IO程序。 2、Netty主要针对在TCP协议下&#xff0c;面向Clients端的高并发应用&#xff0c;或者Peer-to-Peer场景下的大量数据持续传输的应用。 3、Netty本质是…

银行数字化转型导师坚鹏:银行数字化转型给分行带来的8大价值

银行数字化转型给分行带来的8大价值 银行数字化转型对不仅对总行产生了深远影响、给总行带来了新质生产力&#xff0c;对分行也会产生重要价值&#xff0c;银行数字化转型导师坚鹏从以下8个方面进行详细分析&#xff0c;相信能够给您带来重要启发&#xff0c;从而加速银行分行…

【并发编程系列】使用 CompletableFuture 实现并发任务处理

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

[C#]OpenCvSharp利用MatchTemplate实现多目标匹配

【效果展示】 原图 模板图 匹配结果&#xff1a; 【实现部分代码】 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using…

RabbitMQ3.x之九_Docker中安装RabbitMQ

RabbitMQ3.x之_Docker中安装RabbitMQ 文章目录 RabbitMQ3.x之_Docker中安装RabbitMQ1. 官网2. 安装1 .拉取镜像2. 运行容器 3. 访问 1. 官网 rabbitmq - Official Image | Docker Hub 2. 安装 1 .拉取镜像 docker pull rabbitmq:3.13.0-management2. 运行容器 # latest Rabb…

从零起步:开启你的IT职业之旅

简介&#xff1a; 信息技术&#xff08;IT&#xff09;行业以其快速发展和广阔的就业前景吸引着全球众多职场新人。但对于零基础的求职者而言&#xff0c;挺进这一行业似乎是条充满挑战的道路。进入IT行业可能看起来是一项艰巨的挑战&#xff0c;尤其是对于那些没有任何相关经…

伪造靶机之iptables

伪造禁ping、网络不可达、主机不可达、协议、端口的命令 iptables -A INPUT -p icmp --icmp-type echo-request -j DROP iptables -A INPUT -s 172.18.6.89 -p icmp -j REJECT --reject-with icmp-net-unreachable iptables -A INPUT -s 172.18.6.89 -p icmp -j REJECT --re…

【Canavs与艺术】绘制蓝白绶带大卫之星勋章

【图例】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>用Canvas绘制蓝白绶带大卫之星勋章</title><style type&quo…

Python如何解决“滑动拼图”验证码(8)

前言 本文是该专栏的第67篇,后面会持续分享python爬虫干货知识,记得关注。 做过爬虫项目的同学,或多或少都会接触到一些需要解决验证码才能正常获取数据的平台。 在本专栏之前的文章中,笔者有详细介绍通过python来解决多种“验证码”(点选验证,图文验证,滑块验证,滑块…

网络协议——VRRP(虚拟路由冗余协议)原理与配置

1. VRRP概述 单网关出现故障后下联业务中断&#xff0c;配置两个及以上的网关时由于IP地址冲突&#xff0c;导致通讯时断时续甚至通信中断。VRRP组播类的网络层协议 2. 协议版本 VRRP v2: 支持认证,仅适用于IPv4网络 VRRP v3: 不支持认证&#xff0c; 适用于IPv4和IPv6两种网…

【Leetcode笔记】102.二叉树的层序遍历

目录 知识点Leetcode代码&#xff1a;ACM模式代码&#xff1a; 知识点 vector、queue容器的操作 对vector<int> vec;做插入元素操作&#xff1a;vec.push_back(x)。对queue<TreeNode*> que;做插入元素操作&#xff1a;que.push(root);。队列有四个常用的操作&…

【Python系列】 yaml中写入数据

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

普联一面4.2面试记录

普联一面4.2面试记录 文章目录 普联一面4.2面试记录1.jdk和jre的区别2.java的容器有哪些3.list set map的区别4.get和post的区别5.哪个更安全6.java哪些集合类是线程安全的7.创建线程有哪几种方式8.线程的状态有哪几种9.线程的run和start的区别10.什么是java序列化11.redis的优…

深度解读DynamIQ架构cache的替换策略

快速链接: 【精选】ARMv8/ARMv9架构入门到精通-[目录] &#x1f448;&#x1f448;&#x1f448; 思考: 在经典的 DynamIQ架构 中&#xff0c;数据是什么时候存在L1 cache&#xff0c;什么时候存进L2 cache&#xff0c;什么时候又存进L3 cache&#xff0c;以及他们的替换策略是…

ArcGIS Pro导出布局时去除在线地图水印

目录 一、背景 二、解决方法 一、背景 在ArcGIS Pro中经常会用到软件自带的在线地图&#xff0c;但是在导出布局时&#xff0c;图片右下方会自带地图的水印 二、解决方法 解决方法&#xff1a;添加动态文本--服务图层制作者名单&#xff0c;然后在布局中选定位置添加 在状…

红蓝色WordPress外贸建站模板

红蓝色WordPress外贸建站模板 https://www.mymoban.com/wordpress/5.html

【Java】打包:JAR、EAR、WAR

打包&#xff1a;JAR、EAR、WAR war 是一个 Web 模块&#xff0c;其中需要包括 WEB-INF&#xff0c;是可以直接运行的 WEB 模块。而 jar 一般只是包括一些 class 文件&#xff0c;在声明了 main_class 之后是可以用 java 命令运行的。 它们都是压缩的包&#xff0c;拿 Tomcat …

LeetCode_234(回文链表)

//时间复杂度O(n) 空间复杂度O(1)public boolean isPalindrome(ListNode head) {ListNode fast head,slow head;while (fast !null && fast.next !null){fast fast.next.next;slow slow.next;}//如果链表是奇数个结点&#xff0c;把正中的归到左边if(fast ! null){s…

操作系统的信号量操作以及实战中的踩坑分析

往期地址&#xff1a; 操作系统系列一 —— 操作系统概述操作系统系列二 —— 进程操作系统系列三 —— 编译与链接关系操作系统系列四 —— 栈与函数调用关系操作系统系列五 —— 目标文件详解操作系统系列六 —— 详细解释【静态链接】操作系统系列七 —— 装载操作系统系列…