当前位置: 首页 > news >正文

嵌入式LINUX——————TCP并发服务器

一、服务器

1.服务器分类

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

二、TCP并发服务器的构建

1.如何构建?     

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

#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)多线程(并发程度高、不太安全)

#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)清楚集合表     

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

        4)select:   

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

        参数:

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

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

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

                exceptfds:其他事件

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

                NULL:不设置超时时间

        返回值:

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

                失败:-1

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

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

#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)创建文件描述符集合表;       

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

                参数:

                        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            水平触发

epoll函数里面的data里存放很多相关内容,需将fd直接放入此类型结构体里进行调用

                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返回检测到的事件结果

步骤:        1.创建文件描述符集合表;
2.添加关注的文件描述符到集合
3.epoll通知内核开始监测
4.epoll返回监测到的事件结果

3.在数据量比较小的时候,select的比epoll的性能差不多,甚至更好,更小的时候,优势体现不明显,对于IO:如果处理的任务有耗时任务,此时应该考虑增加进线程,把耗时的任务给进线程去做

并发函数对比

IO多路复用——并发服务器

并发服务器的性能对比

线程池+epoll 

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

http://www.dtcms.com/a/336346.html

相关文章:

  • Python 设计模式详解 —— 掌握软件设计的通用解决方案
  • PWM输入捕获(测量按键按下时间、测量PWM波)
  • 25. 能否创建一个包含可变对象的不可变对象
  • YOLOV5训练自己的数据集并用自己的数据集检测
  • 2025-08-17 李沐深度学习16——目标检测
  • PAT 1068 Find More Coins
  • ACPI TABLE 方式加载device driver--以spi controller为例
  • 认识信号量机制、以及用信号量来实现进程互斥于进程同步
  • 计算机网络 TCP time_wait 状态 详解
  • VirtualBox-4.3.10-93012-Win.exe 安装教程附详细步骤(附安装包下载)
  • 为何她总在关键时“失联”?—— 解密 TCP 连接异常中断
  • TensorRT-LLM.V1.1.0rc1:Dockerfile.multi文件解读
  • LeetCode 刷题【44. 通配符匹配】
  • 多墨智能-AI一键生成工作文档/流程图/思维导图
  • 《WINDOWS 环境下32位汇编语言程序设计》第3章 使用MASM
  • Redis面试精讲 Day 23:Redis与数据库数据一致性保障
  • 什么是回表?
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘scikit-image’问题
  • Hooks useState的使用
  • leetcode热题100——day33
  • 视频内容提取与AI总结:提升学习效率的实用方法
  • 【深度学习新浪潮】近三年图像处理领域无监督学习的研究进展一览
  • 科目二的四个电路
  • 《Vuejs设计与实现》第 14 章(内建组件和模块)
  • 概率dp|math
  • Android中切换语言的方法
  • 基于Netty的高并发WebSocket连接管理与性能优化实践指南
  • ReactNode 类型
  • 第12章《学以致用》—PowerShell 自学闭环与实战笔记
  • “让机器人更智慧 让具身体更智能”北京世界机器人大会行业洞察