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

观察到的实验现象:

启动三个客户端:

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

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

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

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

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

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

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

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

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

IO密集型转发服务器

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

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

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

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

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

cpp 复制代码
#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;
}

双循环阻塞服务器:

cpp 复制代码
#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;
}

赠送客户端:

cpp 复制代码
#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;
}
相关推荐
罔闻_spider23 分钟前
cookie反爬----普通服务器,阿里系
运维·服务器
明月*清风31 分钟前
【数据结构专栏】二叉搜索树(Binary Search Tree)的剖析?
开发语言·数据结构·c++·visualstudio
qiaoqiaohonghu31 分钟前
c/c++ 用easyx图形库写一个射击游戏
c语言·c++·游戏
雪碧聊技术1 小时前
RabbitMQ3:Java客户端快速入门
java·开发语言·rabbitmq·amqp·spring amqp·rabbittemplate
fanxiaohui121381 小时前
浪潮信息自动驾驶框架AutoDRRT 2.0,赋能高阶自动驾驶
运维·服务器·网络·人工智能·机器学习·金融·自动驾驶
Sinsa_SI1 小时前
2024年9月中国电子学会青少年软件编程(Python)等级考试试卷(六级)答案 + 解析
开发语言·python·等级考试·电子学会·考级
济南信息学奥赛刘老师1 小时前
GESP考试大纲
开发语言·c++·算法·青少年编程
许静知1 小时前
第十章 JavaScript的应用
开发语言·javascript·ecmascript
froginwe111 小时前
SQLite Having 子句
开发语言
好开心332 小时前
js高级06-ajax封装和跨域
开发语言·前端·javascript·ajax·okhttp·ecmascript·交互