【4】epoll和线程并发、进程并发

本文详细介绍了Linux下三种常见的服务器并发模型:epoll、多线程和多进程。epoll以其高效、无限制连接数等优点成为首选,文中展示了epoll的API使用及并发服务器模型实现。多线程模型则以资源共享和快速切换为特点,但面临线程安全问题。多进程模型通过信号处理函数实现子进程管理,每个连接创建一个新进程,确保进程间隔离,但存在资源消耗大的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

【1】epoll

epoll并发模型可以解决select缺陷,实现高效率,百万级服务器模型,被称为二十一世纪最好用的并发模型。
epoll的优点:
1、最大连接数量没有限制,上限是系统可以最大打开的文件数目;
2、epoll是一种异步通知,无需轮询,效率要比select高
3、成功返回实际事件个数,有效轮询。
API接口:

1.int epoll_create

int epoll_create(int size);

功能:创建红黑树根节点
参数:size已经废弃掉,填1即可
返回值:成功返回红黑树的一个句柄(非负数)
失败返回-1

2.int epoll_ctl

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:指定的文件描述符
event:文件描述符的属性

     typedef union epoll_data {void        *ptr;int          fd; //指定的文件描述符
​               uint32_t     u32;
​               uint64_t     u64;} epoll_data_t;

​       struct epoll_event {
​           uint32_t     events;      //文件描述符事件
​           epoll_data_t data;};
​	EPOLLIN:允许可读事件
​	EPOLLOUT:允许可写事件
​	EPOLLET:边沿触发(水平触发方式为默认方式,效率较低)
返回值:成功返回0,失败返回-1
3.int epoll_wait

int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能:等待挂在红黑树上的文件描述符发生IO事件
参数
epfd:红黑树句柄
events:从内核得到具体那些文件描述符发生IO事件,是一个集合
maxevents:最多一次返回的事件个数
timeout:超时等待,毫秒级单位
-1:阻塞 0:立即返回
返回值:成功时返回发生io事件的数量
失败时返回-1

使用epoll完成并发服务器模型:
1、创建红黑树
2、将关心的文件描述符加入红黑树上
3、等待发生IO事件的文件描述符
4、判断哪个文件描述符发生了IO事件并且作出对应逻辑处理

实例

#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <unistd.h>
   #include <string.h>

#if 0
使用epoll完成并发服务器模型:
1、创建红黑树
2、将关心的文件描述符加入红黑树上
3、等待发生IO事件的文件描述符
4、判断哪个文件描述符发生了IO事件并且作出对应逻辑处理
#endif

int main(int argc, char *argv[])
{
    //1.创建红黑树
    int epfd = epoll_create(1);
    if (epfd < 0)
    {
        perror("epfd err");
        return -1;
    }
    //创建本地套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sockfd err");
        return -1;
    }
    //2.将关心的文件描述符加入到红黑树上
    //EPOLL_CTL_ADD,struc epoll_event
    //EPOLLIN  :可读事件
    //EPOLLET:边沿检测
    //初始化对应文件描述符
    struct epoll_event event;
    event.events = EPOLLIN | EPOLLET;
    event.data.fd = sockfd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event) < 0)
    {
        perror("epoll_ctl err");
        return -1;
    }
    //填充服务器地址
    struct sockaddr_in serveraddr, clientaddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t len = sizeof(serveraddr);
    //绑定
    if (bind(sockfd, (struct sockaddr *)&serveraddr, len) < 0)
    {
        perror("bind err");
        return -1;
    }
    //监听
    if (listen(sockfd, 10) < 0)
    {
        perror("listen err");
        return -1;
    }
    int acceptfd, ret, i;
    int bytes;
    struct epoll_event events[20];
    char buf[32] = {0};
    while (1)
    {
        //3.使用epoll_wait轮询检测文件描述赋发生IO事件
        ret = epoll_wait(epfd, events, 20, -1);
        if (ret > 0)
        {
            for (i = 0; i < ret; i++)
            {
                //判断文件描述符数值
                if (events[i].data.fd == sockfd)
                {
                    //判断对应文件描述赋的IO类型
                    if (events[i].events & EPOLLIN == EPOLLIN)
                    {
                        acceptfd = accept(events[i].data.fd, (struct sockaddr *)&clientaddr, &len);
                        if (acceptfd < 0)
                        {
                            perror("accept err");
                            return -1;
                        }
                        printf("IP:%s connect\n", (char *)inet_ntoa(clientaddr.sin_addr));
                        //将新客户端通信套接字挂到树上
                        struct epoll_event event;
                        event.events = EPOLLIN | EPOLLET;
                        event.data.fd = acceptfd;
                        if (epoll_ctl(epfd, EPOLL_CTL_ADD, acceptfd, &event) < 0)
                        {
                            perror("epoll_ctl err");
                            return -1;
                        }
                    }
                }else{
                    //处理老客户端信息
                    if(EPOLLIN &events[i].events == EPOLLIN)
                    {
                        memset(buf,0,sizeof(buf));
                        bytes = recv(events[i].data.fd,buf,sizeof(buf),0);
                        if(bytes < 0)
                        {
                            perror("recv err");
                            return -1;
                        }else if(bytes ==0){
                            //客户端断开信息
                            printf("acceptfd:%d disconnect\n",events[i].data.fd);
                            //关闭对应文件描述符
                            close(events[i].data.fd);
                            //将挂在树上对应的文件描述符清楚
                            epoll_ctl(epfd,EPOLL_CTL_DEL,events[i].data.fd,NULL);
                        }else
                        {
                            printf("buf:%s",buf);
                        }
                        
                    }
                }
            }
        }else
        {
            perror("epoll_wait err");
            return -1;
        }
    }
    close(sockfd);
    return 0;
}

【2】多线程实现服务器并发模型

​ 线程函数:
​ {
​ recv(acceptfd)
​ 接收数据,判断客户端退出
​ }
​ while(1)
​ {
​ accept()成功返回后创建线程用于客户端和服务器通信
​ }
练习:使用线程知识完成服务器并发模型

#include <stdio.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>

void *pthread_callback(void *arg)
{
    int acceptfd = *(int *)arg;
    int bytes;
    char buf[32];
    while (1)
    {
        memset(buf, 0, sizeof(buf));
        if (bytes == 0)
        {
            printf("acceptfd:%d disconnect\n", acceptfd);
            close(acceptfd);
            return NULL;
        }
        else if (bytes > 0)
        {
            printf("buf:%s", buf);
        }
        else
        {
            perror("recv err");
            return NULL;
        }
    }
}

int main(int argc, char *argv[])
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sockfd err");
        return -1;
    }
    struct sockaddr_in serveraddr, clientaddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    socklen_t len = sizeof(serveraddr);

    if (bind(sockfd, (struct sockaddr *)&serveraddr, len) < 0)
    {
        perror("bind err");
        return -1;
    }
    if (listen(sockfd, 10) < 0)
    {
        perror("listen err");
        return -1;
    }

    int acceptfd;
    pthread_t tid;
    //初始化线程分离属性
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    while (1)
    {
        //等待客户端链接
        acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &len);
        if (acceptfd < 0)
        {
            perror("acceptfd err");
            return -1;
        }
        //创建线程函数,并且将通信套接字传到线程函数中
        if (pthread_create(&tid, &attr, pthread_callback, (void *)&acceptfd) != 0)
        {
            perror("pthread_create err");
            return -1;
        }
    }
    close(sockfd);
    return 0;
}

多线程实现服务器并发模型的优点:
(1)资源共享,线程间通信简单
(2)资源切换开销小,效率高
多线程实现服务器并发模型的缺点:
(1)安全性低,临界资源需要保护
(2)程序的健壮性差,单一线程崩溃可能会引起整个进程崩溃

【3】多进程实现服务器并发模型

​ 思路:
​ TCP服务器编码流程socket bind listen
​ 设置信号处理函数(SIGCHLD)

/***************************************************
# filename      : socket.c
# author        : zhuangzhen                         
# e-mail        : 1448686495@qq.com                 
# create time   : 2022-05-12 14:14:47
***************************************************/
#include <stdio.h>                                  
#include <stdlib.h>                                 
#include <string.h>                                 
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
 #include <signal.h>

void child_wait(int signum)
{
	if(signum == SIGCHLD)
	{
		wait(NULL);
	}
}
int main(int argc, char *argv[])                    
{     
 	int sockfd;
	//1.创建套接字文件
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0)
	{
		perror("socket err");
		return -1;
	}
	printf("scokfd:%d\n",sockfd);
	//2.bind绑定服务器地址,需要填充服务器地址,根据协议的不同,
	struct sockaddr_in serveraddr,clientaddr;
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port =  htons(atoi(argv[2]));//端口号网络字节序8888
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);//IP网络字节序192.168.2.66
	socklen_t len = sizeof(serveraddr);
	if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)) < 0)
	{
		perror("bind err");
		return -1;
	}
	//3.监听
	if(listen(sockfd,10) < 0)
	{
		perror("listen err");
		return -1;
	}
	//4.链接客户端,阻塞
	int acceptfd;
	pid_t pid;
	//处理僵尸进程
	signal(SIGCHLD,child_wait);
	while(1)
	{
		if((acceptfd=accept(sockfd,(struct sockaddr*)&clientaddr,&len)) < 0)
		{
			perror("accept err");
			return -1;
		}
		printf("IP:%s connect \n",(char *)inet_ntoa(clientaddr.sin_addr));
		pid=fork();
		if(pid == 0)
		{
			close(sockfd);
			//child  负责处理老客户断通信
			int bytes;
			char buf[32];
			while(1)
			{
				memset(buf,0,sizeof(buf));
				bytes=recv(acceptfd,buf,sizeof(buf),0);
				if(bytes == 0)
				{
					printf("acceptfd:%d\n",acceptfd);
					printf("IP%sdiconnect\n",(char *)inet_ntoa(clientaddr.sin_addr));
					close(acceptfd);
					exit(0);
				}else if(bytes > 0)
				{
					printf("buf:%s",buf);
				}else
				{
					perror("recv err");
					exit(0);
				}
			}
		}else if(pid < 0){
			perror("fork err");
			return -1;
		}
		close(acceptfd);
	}
	close(sockfd);
	return 0;                                       
}                                                   


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值