网络编程-TCP的并发服务器构建

1.TCP服务端并发模型

(1)多进程

进程资源开销大,安全性高。

cs 复制代码
#include "head.h"

#define SIN_PORT 50000
#define SIN_ADDR "192.168.0.176"

int init_tcp_ser()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    //允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:
	int optval = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SIN_PORT);
    seraddr.sin_addr.s_addr = inet_addr(SIN_ADDR);

    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if(ret < 0)
    {
        perror("bind error");
        return -1;
    }

    ret = listen(sockfd, 100);
    if(ret < 0)
    {
        perror("listen error");
        return -1;
    }

    return sockfd;
}

void wait_handler(int signo)
{
    wait(NULL);
}

int main(int argc, char const *argv[])
{
    signal(SIGCHLD, wait_handler);
    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);

    int sockfd = init_tcp_ser();
    if(sockfd < 0)
    {
        return -1;
    }

    while(1)
    {
        int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
        if(connfd < 0)
        {
            perror("connfd error");
            return -1;
        }

        pid_t pid = fork();
        if(pid > 0)
        {

        }
        else if(0 == pid)
        {
            char buff[1024] = {0};
            while(1)
            {
                memset(buff, 0, sizeof(buff));
                ssize_t cnt = recv(connfd, buff, sizeof(buff),0);
                if(cnt < 0)
                {
                    perror("recv error");
                    return -1;
                }
                else if(0 == cnt)
                {
                    printf("[%s:%d] offline\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
                    break;
                }
                printf("[%s:%d] : %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);
                
                strcat(buff,"---->ok");
                cnt = send(connfd, buff, strlen(buff), 0);
                if(cnt < 0)
                {
                    perror("send error");
                    return -1;
                }
            }
            close(connfd);
        }
    }
    close(sockfd);
    
    return 0;
}

(2)多线程

线程相对于进程资源开销小(相同资源环境下),并发量比进程大。

cs 复制代码
#include "head.h"

#define SIN_PORT 50000
#define SIN_ADDR "192.168.0.176"

int sockfd = -1;
struct addr
{
    int connfd;
    struct sockaddr_in cliaddr;
    socklen_t clilen;
}; // 移除全局实例addr_t,避免共享冲突


int init_tcp_ser()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    //允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:
	int optval = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SIN_PORT);
    seraddr.sin_addr.s_addr = inet_addr(SIN_ADDR);

    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if(ret < 0)
    {
        perror("bind error");
        return -1;
    }

    ret = listen(sockfd, 100);
    if(ret < 0)
    {
        perror("listen error");
        return -1;
    }

    return sockfd;
}

void *task(void *arg)
{
    // 正确解析参数,获取当前连接的客户端信息
    struct addr *client_addr = (struct addr *)arg;
    int connfd = client_addr->connfd;
    struct sockaddr_in cliaddr = client_addr->cliaddr;
    
    char buff[1024] = {0};
    while (1)
    {
        memset(buff, 0, sizeof(buff));
        ssize_t cnt = recv(connfd, buff, sizeof(buff)-1, 0); // 预留1字节防止溢出
        if(cnt < 0)
        {
            perror("recv error");
            break;
        }
        else if(0 == cnt)
        {
            // 使用当前连接的客户端信息,而非全局变量
            printf("[%s:%d] offline\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
            break;
        }
        printf("[%s:%d] : %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);

        // 使用snprintf替代strcat,防止缓冲区溢出
        snprintf(buff + strlen(buff), sizeof(buff) - strlen(buff), "----->ok");
        cnt = send(connfd, buff, strlen(buff), 0);
        if(cnt < 0)
        {
            perror("send error");
            break;
        }
    }
    close(connfd);
    free(client_addr); // 释放动态分配的结构体
    return NULL;
}

int main(int argc, char const *argv[])
{
    sockfd = init_tcp_ser();
    if(sockfd < 0)
    {
        return -1;
    }

    while(1)
    {
        // 为每个连接创建独立的结构体,避免共享冲突
        struct addr *client_addr = malloc(sizeof(struct addr));
        client_addr->clilen = sizeof(client_addr->cliaddr);
        
        client_addr->connfd = accept(sockfd, (struct sockaddr *)&client_addr->cliaddr, &client_addr->clilen);
        if(client_addr->connfd < 0)
        {
            perror("accept error");
            free(client_addr); // 出错时释放内存
            return -1;
        }

        pthread_t tid;
        // 传递当前连接的结构体指针,而非全局变量
        int ret = pthread_create(&tid, NULL, task, client_addr);
        if(ret != 0)  
        {
            perror("pthread_create error");
            close(client_addr->connfd);  
            free(client_addr); // 线程创建失败时释放内存
            continue;
        }

        // 将线程设置为分离状态,结束后自动回收资源
        pthread_detach(tid);
    }

    close(sockfd);
    return 0;
}

(3)线程池

为了解决多线程(多进程)模型,在服务器运行过程中,频繁创建和销毁线程(进程)带来的时间消耗问题(基于生产者和消费者编程模型,以及任务队列等,实现的一套多线程框架)。

(4)IO多路复用

对多个文件描述符的读写可以复用一个进程(在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时监测)。

三种实现方式:

①select

②poll

③epoll

select实现IO多路复用

步骤:

①创建文件描述符集合

②添加关注的文件描述符到集合

③使用select传递集合表给内核,内核开始监测事件

④当内核检测到事件时,应用层select将解除阻塞,并获得相关的事件结果

⑤根据select返回的结果做不同的任务处理
函数接口:

void FD_CLR(int fd, fd_set *set);

从文件描述符集合中移除指定的文件描述符。

int FD_ISSET(int fd, fd_set *set);

检查指定的文件描述符是否在集合中,是则返回非零值。

void FD_SET(int fd, fd_set *set);

将指定的文件描述符添加到集合中。

void FD_ZERO(fd_set *set);

清空文件描述符集合,移除所有文件描述符。

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

功能:传递文件描述符结合表给内核并等待获取事件结果

参数:

nfds : 关注的最大文件描述符+1

readfds:读事件的文件描述符集合

writefds:写事件的文件描述符集合

exceptfds:其他事件的文件描述符集合

timeout:设置select监测时的超时时间

NULL : 不设置超时时间(select一直阻塞等待)

返回值:

成功:返回内核监测的到达事件的个数

失败:-1

0 : 超时时间到达,但没有事件发生,则返回0

cs 复制代码
#include "head.h"
#include <string.h>
#include <arpa/inet.h>

#define SIN_PORT 50000
#define SIN_ADDR "192.168.0.176"
#define MAX_SIZE 1024  

int init_tcp_ser()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    //允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:
	int optval = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SIN_PORT);
    seraddr.sin_addr.s_addr = inet_addr(SIN_ADDR);

    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if(ret < 0)
    {
        perror("bind error");
        return -1;
    }

    ret = listen(sockfd, 100);
    if(ret < 0)
    {
        perror("listen error");
        return -1;
    }

    return sockfd;
}

int main(int argc, char const *argv[])
{
    // 存储每个连接的客户端地址
    struct sockaddr_in cliaddrs[MAX_SIZE];
    memset(cliaddrs, 0, sizeof(cliaddrs));
    socklen_t clilen = sizeof(struct sockaddr_in);

    int sockfd = init_tcp_ser();
    if(sockfd < 0)
    {
        return -1;
    }

    fd_set serfd;       
    fd_set serfdtmp;    

    FD_ZERO(&serfd);
    FD_SET(sockfd, &serfd);
    int maxfd = sockfd; 

    while (1)
    {
        serfdtmp = serfd;  

        int cnt = select(maxfd + 1, &serfdtmp, NULL, NULL, NULL);
        if(cnt < 0)
        {
            perror("select error");
            return -1;
        }

        // 遍历所有可能的文件描述符,检查就绪状态
        for(int fd = 0; fd <= maxfd; fd++)
        {
            if(!FD_ISSET(fd, &serfdtmp))
            {
                continue;  
            }
            // 处理新连接请求
            if(fd == sockfd)
            {
                struct sockaddr_in temp_cliaddr;
                socklen_t temp_clilen = sizeof(temp_cliaddr);
                
                int connfd = accept(sockfd, (struct sockaddr *)&temp_cliaddr, &temp_clilen);
                if(connfd < 0)
                {
                    perror("accept error");
                    continue;  
                }

                 // 保存客户端地址信息
                cliaddrs[connfd] = temp_cliaddr;

                FD_SET(connfd, &serfd);

                if(connfd > maxfd)
                {
                    maxfd = connfd;
                }


                printf("[%s:%d] online\n", inet_ntoa(cliaddrs[connfd].sin_addr), ntohs(cliaddrs[connfd].sin_port));
            }
            // 处理已连接客户端的数据
            else
            {
                char buff[1024] = {0};
                ssize_t cnt = recv(fd, buff, sizeof(buff)-1, 0);
                if(cnt < 0)
                {
                    perror("recv error");
                    FD_CLR(fd, &serfd); 
                    close(fd); 
                }
                // 客户端断开连接
                else if(cnt == 0)
                {
                    printf("[%s:%d] offline\n", inet_ntoa(cliaddrs[fd].sin_addr), ntohs(cliaddrs[fd].sin_port));
                    FD_CLR(fd, &serfd); 
                    close(fd); 
                }

                else
                {
                    printf("[%s:%d] : %s\n", inet_ntoa(cliaddrs[fd].sin_addr), ntohs(cliaddrs[fd].sin_port), buff);
                    
                    strcat(buff, "---->ok");
                    send(fd, buff, strlen(buff), 0);
                }
            }
        }
    }

    close(sockfd);
    return 0;
}
相关推荐
朱包林12 分钟前
数据库服务-日志管理-备份与恢复-主从同步
linux·运维·服务器·数据库·mysql·云计算
神色自若2 小时前
AbpVnext 阿里云ssl证书多个生产环境自动更新
服务器·阿里云·ssl
眰恦ゞLYF4 小时前
服务器类型与TCP并发服务器构建(SELECT)
服务器·select·io多路复用
我好饿14 小时前
Linux入门教程 第十五章 Linux 系统调优工具
linux·运维·网络
万花丛中一抹绿4 小时前
服务器硬件电路设计之 SPI 问答(六):如何提升服务器硬件电路中的性能?如何强化稳定性?
服务器·spi·服务器硬件电路设计
bkspiderx9 小时前
安全扫描:目标主机支持RSA密钥交换问题
网络·nginx·安全·tls·rsa·弱算法
王火火(DDoS CC防护)9 小时前
服务器网络带宽不足要怎么处理?
运维·服务器
潇凝子潇9 小时前
获取服务器指标的信息
linux·运维·服务器
FreeBuf_9 小时前
Chrome高危零日漏洞PoC公开,已被用于野外攻击
linux·运维·服务器·安全·web安全