day33:零基础学嵌入式之网络——TCP并发服务器

一、服务器

1.服务器分类

  • 单循环服务器:只能处理一个客户端任务的服务器
  • 并发服务器:可同时处理多个客户端任务的服务器

二、TCP并发服务器的构建

1.如何构建?

(1)多进程(每一次创建都非常耗时耗空间,但是安全)

cs 复制代码
#include "head.h"
int init_tcp(const char *ip, unsigned short port)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket fail");
        return 1;
    }
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(port);
    seraddr.sin_addr.s_addr = inet_addr(ip);

    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (ret < 0)
    {
        perror("bind fail");
        return 1;
    }
    ret = listen(sockfd, 100);
    if (ret < 0)
    {
        perror("lisen fail");
        return 1;
    }
    return sockfd;
}
void do_wait(int signo)
{
    wait(NULL);
}
int main(int argc, char const *argv[])
{
    int sockfd = init_tcp("192.168.1.138", 50000);
    if (sockfd < 0)
    {
        return 1;
    }
    signal(SIGCHLD, do_wait);
    char buf[1024] = {0};
    struct sockaddr_in cliaddr;
    int clilen = sizeof(cliaddr);
    while (1)
    {
        int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
        if (connfd < 0)
        {
            perror("connect fail");
            return 1;
        }
        pid_t pid = fork();
        if (pid > 0)
        {
        }
        else if (0 == pid)
        {
            while (1)
            {

                memset(buf, 0, sizeof(buf));
                ssize_t size = recv(connfd, buf, sizeof(buf), 0);
                if (size < 0)
                {
                    perror("recv fail");
                    break;
                }
                else if (0 == size)
                {
                    printf("client connet offline");
                    break;
                }
                printf("[%s] [%d]  : %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buf);
                strcat(buf, "------ok");
                size = send(connfd, buf, sizeof(buf), 0);
                if (size < 0)
                {
                    perror("fail send");
                    break;
                }
            }
            close(connfd);
            exit(1);
        }
        else
        {
            perror("fork fail");
            return 1;
        }
    }

    close(sockfd);

    return 0;
}

(2)多线程(并发程度高、不太安全)

cs 复制代码
#include "head.h"
int init_tcp(const char *ip, unsigned short port)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket fail");
        return 1;
    }
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(port);
    seraddr.sin_addr.s_addr = inet_addr(ip);

    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (ret < 0)
    {
        perror("bind fail");
        return 1;
    }
    ret = listen(sockfd, 100);
    if (ret < 0)
    {
        perror("lisen fail");
        return 1;
    }
    return sockfd;
}
typedef struct
{
    int connfd;
    struct sockaddr_in cliaddr;
} XIN;

void do_thurance(void *arg)
{
    XIN xi = *(XIN *)arg;
    char buf[1024] = {0};
    while (1)
    {
        memset(buf, 0, sizeof(buf));
        ssize_t size = recv(xi.connfd, buf, sizeof(buf), 0);
        if (size < 0)
        {
            perror("recv fail");
            break;
        }
        else if (0 == size)
        {
            printf("client connet offline");
            break;
        }
        printf("[%s] [%d]  : %s\n", inet_ntoa(xi.cliaddr.sin_addr), ntohs(xi.cliaddr.sin_port), buf);
        strcat(buf, "------ok");
        size = send(xi.connfd, buf, sizeof(buf), 0);
        if (size < 0)
        {
            perror("fail send");
            break;
        }
    }
    close(xi.connfd);
    pthread_exit(NULL);
}

int main(int argc, char const *argv[])
{
    int sockfd = init_tcp("192.168.1.138", 50000);
    if (sockfd < 0)
    {
        return 1;
    }
    char buf[1024] = {0};
    pthread_t tid;
    struct sockaddr_in cliaddr;
    int clilen = sizeof(cliaddr);
    while (1)
    {
        int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
        if (connfd < 0)
        {
            perror("connect fail");
            return 1;
        }
        printf("client getline\n");
        XIN xi;
        xi.connfd = connfd;
        xi.cliaddr = cliaddr;
        pthread_create(&tid, NULL, do_thurance, &xi);
        pthread_detach(tid);                    //设置分离属性,线程结束,操作系统自动会回收;
    }
    close(sockfd);

    return 0;
}

(3)线程池

  • 主要解决:程序运行过程中,线程被反复创建和销毁带来的耗时问题;

(4)IO多路复用

理解:不创建进程和线程的情况下,对多个文件描述符监测复用一个进程;

二、IO多路复用

1.阻塞IO方式:

(1)多个IO之间是同步关系;

(2)多个IO之间相互影响;

2.IO多路复用

(1)步骤

1)创建文件描述符集合(数组、链表、树形结构.......);

2)添加关注的文件描述符带集合中;

3)通过函数接口,把集合传递给内核,并开始检测IO事件(输入输出、读写事件);

4)当内核检测到事件时,通过相关函数返回,做具体的相关操作;

(2)select

1)创建文件描述符集合表:fd_set

2)清楚集合表

void FD_CLR(int fd, fd_set *set);//把fd清掉

int FD_ISSET(int fd, fd_set *set);//查看fd在这个表中有没有

void FD_SET(int fd, fd_set *set);//把fd放进集合表中

void FD_ZERO(fd_set *set);//把集合表整体清空

3)把文件描述符加入到集合表中

4)select:

int select(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, struct timeval *timeout);

功能:通知内核检测的集合表并开始检测

参数:

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

readfds:关注的读事件的我文件描述符的地址

writefds:关注的写事件的我文件描述符的地址

exceptfds:其他事件

timeout:超时事件的地址;设置一个时间结点,如果都没有事件来,就直接返回;NULL:不设置超时时间

返回值:

成功:返回到达事件的个数

失败:-1

超时时间到达没有事件时:0

位图在内核中,保持最小未被使用原则

cs 复制代码
#include "head.h"
int init_tcp(const char *ip, unsigned short port)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket fail");
        return 1;
    }
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(port);
    seraddr.sin_addr.s_addr = inet_addr(ip);

    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (ret < 0)
    {
        perror("bind fail");
        return 1;
    }
    ret = listen(sockfd, 100);
    if (ret < 0)
    {
        perror("lisen fail");
        return 1;
    }
    return sockfd;
}
int main(int argc, char const *argv[])
{
    int sockfd = init_tcp("192.168.1.138", 50002);
    if (sockfd < 0)
    {
        return 1;
    }
    struct sockaddr_in cliaddr;
    int clilen = sizeof(cliaddr);
    int maxs;
    fd_set rdfds;
    fd_set tmprdfds;
    FD_ZERO(&rdfds);
    FD_SET(sockfd, &rdfds);
    int i = 0;
    maxs = sockfd;
    char buf[1024]={0};
    while (1)
    {
        tmprdfds = rdfds;
        int cnt = select(maxs + 1, &tmprdfds, NULL, NULL, NULL);
        if (cnt < 0)
        {
            perror("fail select");
            return 1;
        }
        if (FD_ISSET(sockfd, &tmprdfds))
        {
            int connfd = accept(sockfd,(struct sockaddr*)&cliaddr, &clilen);
            if (connfd < 0)
            {
                perror("fail accept");
                return 1;
            }
            FD_SET(connfd, &rdfds);
            maxs = maxs > connfd ? maxs : connfd;
        }
        // for(i=sockfd;i<maxs+1;++i)
        // {
        //     printf("%d\n",i);
        // }
        // sleep(3);
        for (i = sockfd + 1; i < maxs + 1; ++i)
        {
            if (FD_ISSET(i, &tmprdfds))
            {
                memset(buf, 0, sizeof(buf));
                ssize_t size = recv(i, buf, sizeof(buf), 0);
                if (size < 0)
                {
                    perror("recv fail");
                    continue;
                }
                if(0==size)
                {
                    printf("client offlink\n");
                    return 1;
                }
                printf("[%s] [%d]  : %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buf);
                strcat(buf, "------ok");
                size = send(i, buf, sizeof(buf), 0);
                if (size < 0)
                {
                    perror("fail send");
                    continue;
                }
            }  
        }
    }
    
    close(sockfd);

    return 0;
}
缺陷:
  • 限制了最多只能检测1024个文件描述符(底层使用数组的机制储存);
  • 在应用层每次需要遍历才可找到到达的事件的文件描述符,效率不高,还耗时;
  • 集合表存在于应用层,内核存在应用层和内核层的数据表的反复拷贝,耗时;
  • select只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式);

边沿触发:数据从无变有,从低电平到高电平,触发一次,称读数据的上升沿触发;

数据一次收不完,但是下一次继续读,

水平触发:数据从无到有,先触发一次读,没读完,再触发读,一直到读完了,才不触发;优势在反复把数据读完;缺点:耗时,低俗模式

(3)poll

1)解决的问题 :检测的文件描述符个数不受1024限制;底层对于集合表的方式改变,变成了链表,时间复杂度O(n),其他问题未被改善,仍然需要反复拷贝、遍历、只可工作在水平触发模式;

(4)epoll

1)解决的问题: 检测的文件描述符是树形结构;时间复杂度是O(log(n)【红黑树】,也不受1024限制;将检测的文件描述符集合创建在内核,解决了内核和用户层的数据拷贝;直接返回到达事件的文件描述符集合,不需要遍历寻找;epoll可以工作在水平触摸式,也可工作在边沿触发模式;

2)步骤

a)创建文件描述符集合表;

int epoll_create(int size);

功能:创建文件描述符集合表到内核

参数

size:最多监测的文件描述符的个数

返回值:

成功返回非负的文件描述符,代表了内核的集合;

失败返回-1

b)添加关注的文件描述符到集合;

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

**功能:**对文件描述符进行操作;

参数:

epfd:文件描述符集合表的文件描述符

op:

EPOLL_CTL_ADD 新增事件

EPOLL_CTL_MOD 修改事件

EPOLL_CTL_DEL 删除事件

fd:要操作的文件描述符

events:事件相关结构体

EPOLLIN 读事件

EPOLLOUT 写事件

EPOLLET 边沿触发

LT 水平触发

typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;

struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};

返回值:

成功返回0;

失败返回-1;

c)epoll通知内核开始检测;

int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);

功能:监听事件表中的事件,并将产生的事件存放到结构体数组中

参数:

epfd:事件表文件描述符

events:存放结果事件结构体数组空间首地址

maxevents:最多存放事件个数

timeout:超时时间

-1:阻塞等待直到有事件发生

返回值:

成功返回产生事件个数

失败返回-1

d)epoll返回检测到的事件结果;

3.在数据量比较小的时候,select的比epoll的性能差不多,甚至更好,更小的时候,优势体现不明显

对于IO:如果处理的任务有耗时任务,此时应该考虑增加进线程,把耗时的任务给进线程去做

并发服务器的性能对比

线程池+epoll

对于IO:如果处理的任务有耗时任务,此时应该考虑增加进线程,把耗时的任务给进线程去做

相关推荐
清 晨3 小时前
剖析 Web3 与传统网络模型的安全框架
网络·安全·web3·facebook·tiktok·instagram·clonbrowser
国科安芯3 小时前
抗辐照芯片在低轨卫星星座CAN总线通讯及供电系统的应用探讨
运维·网络·人工智能·单片机·自动化
gx23484 小时前
HCLP--MGER综合实验
运维·服务器·网络
VB5944 小时前
[N1盒子] 斐讯盒子N1 T1通用刷机包(可救砖)
网络
-XWB-4 小时前
【安全漏洞】防范未然:如何有效关闭不必要的HTTP请求方法,保护你的Web应用
服务器·网络·http
画中鸦5 小时前
VRRP的概念及应用场景
网络
MQ_SOFTWARE5 小时前
文件权限标记机制在知识安全共享中的应用实践
大数据·网络
一个网络学徒5 小时前
MGRE综合实验
运维·服务器·网络
拾光拾趣录5 小时前
GET/POST 的区别:从“为什么登录请求不能用 GET”说起
前端·网络协议
白鹭6 小时前
基于LNMP架构的分布式个人博客搭建
linux·运维·服务器·网络·分布式·apache