Linux网络select/poll/epoll:学习笔记1

本篇文章简单的介绍使用C语言:实现Linux 网络通信server端。

1.socket函数

        socket函数是网络通信中和实现信息交互的基本函数:

        参数domian:选择AF_INET(IPv4协议);

        参数type:基于字节流的SOCK_STREAM(TCP),基于报文的SOCK_DGRAM(UDP);

        参数protocol:一般设置0,支持一种具体的协议;

          函数返回的是一个int 类型的socketfd给用户态,同时在内核态分配一个tcb内存控制块, 用于专门的管理客户端的请求连接:IO可读事件。

2.bind函数

        将socket 函数返回的socketfd绑定在固定的端口上,规定具体的网络通信的协议和格式,绑定失败返回-1;

3.listen函数

        监听sockfd上的TCP网络连接请求,挑选满足条件的,内核开辟资源(半连接队列,可能导致syn泛洪问题)维持一定数量的连接请求,为后续accept函数做准备。

4.accept函数

       从半连接队列里阻塞的等待可读的IO事件发生,然后取出组成全连接队列,完成三次握手,获取客户端的IP以及端口与socketfd 存储的服务端端口和IP进行绑定,根据双方协商好的协议形成一个完整的五元组用于双方的通信,返回clientfd用于触发IO可写事件。

5.recv函数

        阻塞的等待clientfd可写IO 事件,可以设置超时时间,接收客户端发送给服务端的字节流信息。

6.send函数

        阻塞的等待clientfd可写IO 事件,服务端的发送给客户端的字节流信息。

7.实现单次网络通信代码

#include<stdio.h>
#include <errno.h>
#include<string.h>

#include<sys/socket.h>
#include <netinet/in.h>

int main()
{

    int socketfd =socket(AF_INET,SOCK_STREAM,0);
    
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(struct sockaddr_in));

    serveraddr.sin_family=AF_INET;
    serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
    serveraddr.sin_port=htons(1988);
    if(-1==bind(socketfd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr))){
        printf("ERROR BIND  %s\n",strerror(errno));
    }
    listen(socketfd,10);

    struct sockaddr_in clientaddr;
    socklen_t len=sizeof(clientaddr);

    int clientfd=accept(socketfd,(struct sockaddr*)&clientaddr,&len);
    
    char buffer[1024]={0};

    int count =recv(clientfd,buffer,1024,0);
    printf("recv : %s\n",buffer);

    count =send(clientfd,buffer,count,0);
    printf("send : %d\n",count);
    
   
    
    return 0;

}

专属学习链接:https://xxetb.xetslk.com/s/4cnbDc

8.select/poll/epoll实现IO多路复用代码

        一连接一线程:由于内核资源的限制最多可能只能做到10000个连接,且性能堪忧;

     select模型:采用IO多路复用的思想解决了连接数量的限制,但存在参数过多,每次IO响应需要将fd_set 集合的所有的IO信息拷贝到内核态,根据响应一次刷新fd_set 的位信息,然后从内核态拷贝到用户态进行轮询所有的fd_set的IO信息,找到发生IO可写事件然后处理,因此性能上存在一定的缺陷。

        poll模型:优化select的参数过多的问题,编程相对简单,但本质上和select差别不大。

        epoll模型:解决了IO多路复用的性能瓶颈,是一个改变Linux走向的重磅级产品,性能和编写上有了极大的优化,可以做到单线程达到100万并发的恐怖性能(在reactor进行介绍)。内部实现是通过RBtree红黑树管理网络IO集合,用队列管理就绪IO事件,从而达到等待IO事件的集合有序,IO可写事件顺序处理的目的,以此提升网络IO的性能。

#include <sys/socket.h>
#include <errno.h>
#include <netinet/in.h>

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <pthread.h>
#include <sys/poll.h>
#include <sys/epoll.h>


void *func_pid(void *arg)
{
	int clientfd =*(int*)arg;
	while(1)
		{
			char buff[128]={0};
			int count =recv(clientfd,buff,128,0);
			if(0==count)
			{
				break;
			}
			send(clientfd,buff,count,0);
			printf("clientfd: %d,count:%d ,buff :%s\n",clientfd,count,buff);
		}
	close(clientfd);
}

int main()
{
	int sockfd =socket(AF_INET,SOCK_STREAM,0);
	struct sockaddr_in serveraddr;
	memset(&serveraddr,0,sizeof(struct sockaddr_in));

	serveraddr.sin_family =AF_INET;
	serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
	serveraddr.sin_port=htons(2048);

	if(-1==bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(struct sockaddr)))
		{
			perror("bind");
			return -1;
			
		}
	listen(sockfd,10);
#if 0
	//local addr
	struct sockaddr_in clientaddr;

	socklen_t len =sizeof(clientaddr);
	int clienfd =accept(sockfd,(struct sockaddr*)&clientaddr,&len);
	printf("accept\n");
#if 0
	char buff[128]={0};
	int count =recv(clienfd,buff,128,0);
	send(clienfd,buff,count,0);
	printf("sockfd :%d, cliendfd: %d,count:%d ,buff :%s\n",sockfd,clienfd,count,buff);

	//现在的问题是只能收发一次,要实现多次的收发该如何编写程序?
	//通过阻塞的等待事件的发生来触发recv 从而达到一个连接的持续的收发数据
#else
	while(1)
		{
			char buff[128]={0};
			int count =recv(clienfd,buff,128,0);
			if(0==count)
{
				break;
			}
			send(clienfd,buff,count,0);
			printf("sockfd :%d, cliendfd: %d,count:%d ,buff :%s\n",sockfd,clienfd,count,buff);
		}
#endif
#elif 0
	//为每一次accpet的发生创建一个线程,
	struct sockaddr_in clientaddr;

	socklen_t len =sizeof(clientaddr);
	int clienfd =accept(sockfd,(struct sockaddr*)&clientaddr,&len);

	pthread_t pid;
	/*
		线程调用函数func_pid,在链接较少的时候或者收发的性能没有要求的时候可以考虑
	*/
	pthread_create(&pid,NULL,func_pid,&clienfd);
#elif0
	//select 的引入在一定程度上缓解了阻塞模式的性能,最大的管理的文件数1024默认,可以更改
	//由于进行两次拷贝:从内核态的IO文件拷贝到用户态,导致性能低于poll/epoll
	//	int nready =select(maxfd,rset,wset,eset,timeout)	

	//fd_set 管理的文件的队列数组,类型int 与文件描述符一致
	fd_set rfds,rset;
	FD_ZERO(&rfds);
	FD_SET(sockfd,&rfds);

	int maxfd=sockfd;

	printf("loop\n");

	while(1)
		{
			rset=rfds;//no understant 4.10
	
			int nready= select(maxfd+1,&rset,NULL,NULL,NULL);
			if(FD_ISSET(sockfd,&rset))
				{
					struct sockaddr_in clientaddr;

					socklen_t len =sizeof(clientaddr);
					int clientfd =accept(sockfd,(struct sockaddr*)&clientaddr,&len);
					printf("sockfd: %d\n", clientfd);

					FD_SET(clientfd, &rfds);//将处理完成的事件再次加入到队列
					maxfd = clientfd;
			}

		for(int i=sockfd+1;i<=maxfd;i++)//此处用来轮询遍历链接的IO
			{
				if(FD_ISSET(i,&rset))
					{
						char buff[128]={0};
						int count =recv(i,buff,128,0);
						if(0==count)
{
							printf("disconnect\n");
							FD_CLR(i,&rfds);
							close(i);
							break;
						}
						send(i,buff,count,0);
						printf(" clientfd: %d,count:%d ,buff :%s\n",i,count,buff);
					}
			}
		}
#elif 0
	//poll
	struct pollfd fds[1024]={0};

	fds[sockfd].fd=sockfd;
	fds[sockfd].events=POLLIN;

	int maxfd=sockfd;

	while(1)
		{
			int nready=poll(fds,maxfd+1,-1);
			
			if(fds[sockfd].revents&POLLIN)
				{
					struct sockaddr_in clientaddr;
					socklen_t len = sizeof(clientaddr);
			
					int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);

					printf("sockfd: %d\n", clientfd);
					fds[clientfd].fd = clientfd;
					fds[clientfd].events = POLLIN;

					maxfd = clientfd;
				}
			for(int i= sockfd+1;i<=maxfd;i++)
				{
					if(fds[i].revents&POLLIN)
						{
							char buffer[128] = {0};
										int count = recv(i, buffer, 128, 0);
										if (count == 0) {
											printf("disconnect\n");
						
											fds[i].fd = -1;
											fds[i].events = 0;
									
											close(i);
											
											continue;
										}
										
										send(i, buffer, count, 0);
										printf("clientfd: %d, count: %d, buffer: %s\n", i, count, buffer);
						}
				}
		}
#else
	//epoll
	int epfd=epoll_create(1);

	struct epoll_event ev;
	ev.events=EPOLLIN;
	ev.data.fd=sockfd;

	epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);

	struct epoll_event events[1024]={0};

	while(1)
		{	
			int nready=epoll_wait(epfd,events,1024,-1);

			
			for(int i=0;i<nready;i++)
				{
					int connectfd=events[i].data.fd;
					if(sockfd==connectfd)//将每一个客户端和服务端的IO进行来连接
						{
							struct sockaddr_in clientaddr;
							socklen_t len = sizeof(clientaddr);
	
							int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr,&len);

							ev.events=EPOLLIN|EPOLLET;
							ev.data.fd=clientfd;

							epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&ev);
							printf("clientfd :%d \n",clientfd);
					}else if(events[i].events&EPOLLIN){
						
							char buff[10] = {0};
							int count = recv(connectfd, buff, 10, 0);
							
							if (0==count ) {
								printf("disconnect\n");

								epoll_ctl(epfd, EPOLL_CTL_DEL, connectfd, NULL);		
								close(i);
					
								continue;
							}
				
				
						send(connectfd, buff, count, 0);
						printf("clientfd: %d, count: %d, buffer: %s\n", connectfd, count,buff);
					}
					
						
						
					
				}
		}
#endif
	getchar();
}


  • 28
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
selectpollepoll都是I/O多路复用机制,用于同时监听多个I/O事件的状态。它们的基本原理是通过查询所有socket连接,如果有数据到达,就通知用户进程。\[2\]这些机制都属于同步I/O,需要在事件就绪后自己负责读写,并且读写过程会阻塞。而异步I/O则不会自己读写和阻塞,而是负责将数据从内核拷贝到用户空间。\[3\] select是最早出现的I/O多路复用机制,它使用fd_set数据结构来存储需要监听的文件描述符,通过调用select函数来等待事件的发生。select的缺点是效率较低,因为每次调用select都需要将所有的文件描述符集合传递给内核,而且select的文件描述符数量有限制。\[1\] pollselect的改进版本,它使用pollfd数据结构来存储需要监听的文件描述符,通过调用poll函数来等待事件的发生。poll相对于select的优点是没有文件描述符数量的限制,但仍然需要将所有的文件描述符集合传递给内核。\[1\] epollLinux特有的I/O多路复用机制,它使用epoll_event数据结构来存储需要监听的文件描述符,通过调用epoll_ctl函数来注册事件,然后通过调用epoll_wait函数来等待事件的发生。epoll的优点是没有文件描述符数量的限制,而且在注册事件时只需要拷贝一次文件描述符到内核,而不是在等待事件时重复拷贝。epoll还支持水平触发和边沿触发两种模式,边沿触发模式可以降低同一个事件被重复触发的次数。\[1\] 总结来说,selectpollepoll都是用于实现I/O多路复用的机制,它们的选择取决于具体的应用场景和需求。select适用于连接数量多但活动连接较少的情况,poll适用于连接数量多且活动连接较多的情况,而epoll适用于连接数量多但活动连接较少的情况,并且具有更高的效率和更灵活的触发模式。\[1\] #### 引用[.reference_title] - *1* *3* [selectpollepoll简介](https://blog.csdn.net/HuYingJie_1995/article/details/130516595)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [selectpollepoll详解](https://blog.csdn.net/ljjjjjjjjjjj/article/details/129720990)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值