C/S架构学习之使用epoll实现TCP特大型并发服务器

  • epoll实现TCP特大型并发服务器的流程:
  • 一、创建套接字(socket函数):
  • 通信域选择IPV4网络协议、套接字类型选择流式
c 复制代码
	int sock_fd = socket(AF_INET,SOCK_STREAM,0); //通信域选择IPV4、套接字类型选择流式
  • 二、填充服务器和客户机的网络信息结构体:
  • 1.分别定义服务器网络信息结构体变量serveraddr和客户机网络信息结构体变量clientaddr
  • 2.分别求出服务器和客户机的网络信息结构体变量的内存空间大小,以作备用;
  • 3.网络信息结构体清0
  • 4.使用IPV4网络协议AF_INET
  • 5.在终端预留服务器端主机的IP地址inet_addr(argv[1])
  • 6.在终端预留服务器端网络字节序的端口号htons(atoi(argv[2]))
c 复制代码
	struct sockaddr_in serveraddr; //定义服务器网络信息结构体变量
	struct sockaddr_in clientaddr;
    socklen_t serveraddr_len = sizeof(serveraddr);//求出服务器结构体变量的内存空间大小
    socklen_t clientaddr_len = sizeof(clientaddr);//求出客户机结构体变量的内存空间大小

    memset(&serveraddr,0,serveraddr_len); //服务器结构体清零
    memset(&clientaddr,0,clientaddr_len);//客户机结构体清零

    serveraddr.sin_family = AF_INET;  //使用IPV4网络协议
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);  //IP地址
    serveraddr.sin_port = htons(atoi(argv[2]));//网络字节序的端口号
  • 三、设置允许端口复用(setsockopt函数):
  • setsockopt函数:
  • 功能:设置套接字属性;
c 复制代码
	#include <sys/types.h>
	#include <sys/socket.h>

	int setsockopt(int sockfd, int level, int optname,
	const void *optval, socklen_t optlen);	
	/*
	参数:
	
		sockfd:套接字
		level:	选项的级别
	
			套接字API级别		SOL_SOCKET
	
			TCP级别			IPPROTO_TCP
	
			IP级别			IPPROTO_IP
	
		optname:选项的名字
	
			套接字API级别
	
				SO_BROADCAST	是否允许发送广播
	
				SO_RCVBUF		接收缓冲区的大小
	
				SO_SNDBUF		发送缓冲区的大小
	
				SO_RCVTIMEO		接收超时时间
					参数使用的是 struct timeval 结构体
					如果超时了 函数调用会立即返回-1
					并将错误码置成 EAGAIN
	
				SO_SNDTIMEO			发送超时时间
	
				SO_REUSEADDR		端口复用
	
			TCP级别
	
				TCP_NODELAY		使能/禁用Nagle算法
	
			IP级别
	
				IP_ADD_MEMBERSHIP	设置加入多播组
	
		optval:	选项的值
	
			没有特殊说明时 使用的都是int类型
	
		optlen:optval的大小
	
	返回值:
	
		成功 	0
	
		失败 	-1 	重置错误码
	*/
  • 特别注意:
  • 使用setsockopt设置允许端口复用时,其在代码的位置在填充网络信息结构体和bind之间;
c 复制代码
	int reuse = 1;
    if(-1 == (setsockopt(sock_fd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))))
    {
        perror("setsockopt error");
        exit(-1);
    }
  • 四、套接字和服务器的网络信息结构体进行绑定(bind函数):
c 复制代码
	int ret = bind(sock_fd,(struct sockaddr *)&serveraddr,serveraddr_len);
  • 五、套接字设置成被动监听(listen函数):
c 复制代码
	int ret1 = listen(sock_fd, 5);
  • 六、创建红黑树(epoll_create函数):
c 复制代码
	#include <sys/epoll.h>
	int epoll_create(int size);
	/*
	功能:
		
		创建epoll/创建epoll实例的描述符
	
	参数:
	
	    size:参数已经被忽略了,只需要填写大于0的值即可
	
	返回值:

        epoll_create 调用成功时会返回一个非负整数epfd,
    
        表示新创建的 epoll 实例的文件描述符,
    
        如果调用失败则返回 -1,并设置 errno 变量以指示具体错误原因
     */
c 复制代码
	int epfd = epoll_create(1);
    if(-1 == epfd)
    {
        perror("epoll_create error");
        exit(-1);

    }
  • 七、定义事件结构体变量和存放就绪事件描述符的数组:
  • 事件结构体epoll_event用于描述一个文件描述符上的事件;
c 复制代码
			typedef union epoll_data {
               void        *ptr;
               int          fd;  
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;   
           struct epoll_event {
               uint32_t     events;      //EPOLLIN 读 / EPOLLOUT 写
               epoll_data_t data;        //存放用户的数据
           };    
c 复制代码
    struct epoll_event event;
    struct epoll_event events[N];
  • 八、将关心的文件描述符加入到红黑树(epoll_ctl函数):
  • 功能:epoll的控制操作或者用于向 epoll 实例中添加、修改、删除事件;
  • epoll_ctl函数:
c 复制代码
	int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
	/*
	参数:
	         epfd:epoll的文件描述符
	
	         op:控制方式
	
	            EPOLL_CTL_ADD:添加
	
	            EPOLL_CTL_MOD:修改
	
	            EPOLL_CTL_DEL:删除
	
	         fd:被操作的文件描述符
	
	         event:(事件)结构体指针
	          
	
	返回值:    
				成功返回0,
	
	            失败返回-1 置位错误码
	 */
c 复制代码
	//添加要检测事件的描述符
    event.events = EPOLLIN;

    event.data.fd = sock_fd;

    //将关心的文件描述符加入到红黑树
    if(-1 == (epoll_ctl(epfd,EPOLL_CTL_ADD,sock_fd,&event)))
    {
        perror("epoll_ctl error");
        exit(-1);

    }
  • 九、等待文件描述符中的事件是否就绪,成功则返回就绪的文件描述符的个数(epoll_wait函数):
  • epoll_wait函数:
c 复制代码
	int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
	/*
	参数:

		    epfd:epoll的文件描述符
		
		    events:准备好的事件的结构体地址
		
		    maxevents:返回的最大的文件描述符的个数
		
		    timeout:超时
		
		        >0 :毫秒级别的超时时间
		
		        =0 :立即返回
		
		        =-1:不关心超时时间
	返回值:
	
		     成功返回准备好的文件描述符的个数
		
		     返回0代表超时时间到了
		
		     失败返回-1置位错误码
	*/
c 复制代码
		if(-1 == (ret = epoll_wait(epfd,events,N,-1)))
        {
            perror("epoll_wait error");
            exit(-1);
        }	
  • 十、遍历就绪的文件描述符集,判断哪些文件描述符已经准备就绪:
c 复制代码
		for(int i = 0; i < ret; ++i)
        {
        	...
        }
  • 十一、找到实际就绪的事件的文件描述符,并且接收来自客户端的数据(recv函数)和给客户端发送应答消息(send函数):
c 复制代码
			if(events[i].data.fd == sock_fd)
            {
                //获取连接成功后新的客户端
                new_fd = accept(sock_fd,(struct sockaddr *)&clientaddr,&clientaddr_len);
                if(-1 == new_fd)
                {
                    perror("accept error");
                    exit(-1);
                }
                printf("文件描述符[%d]客户端[%s:%d]连接到了服务器\n",new_fd,inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port));
                //添加要检测的文件描述符
                event.events = EPOLLIN;
                event.data.fd = new_fd;
                if(-1 == (epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event)))
                {
                    perror("epoll_ctl error");
                    exit(-1);

                }
                printf("文件描述符[%d]成功挂载在红黑树上\n",new_fd);
            }
            else
            {
                memset(buf,0,sizeof(buf));
                int old_fd = events[i].data.fd;
                if(-1 == (nbytes = recv(old_fd,buf,sizeof(buf),0)))
                {
                    perror("recv error");
                    exit(-1);
                }
                else if(0 == nbytes)
                {
                    printf("文件描述符[%d]客户端断开了服务器\n",old_fd);

                    //关闭对应的文件描述符
                    close(old_fd);
                    //剔除挂在树上对应的文件描述符
                    epoll_ctl(epfd,EPOLL_CTL_DEL,old_fd,&event);

                }
                if(!strncmp(buf,"quit",4))
                {
                    printf("文件描述符[%d]客户端退出了服务器\n",old_fd);
                    //关闭对应的文件描述符
                    close(old_fd);
                    //剔除挂在树上对应的文件描述符
                    epoll_ctl(epfd,EPOLL_CTL_DEL,old_fd,&event);
                }
                printf("文件描述符[%d]客户端发来数据[%s]\n",old_fd,buf);

                //组装应答消息
                strcat(buf,"-----k");

                //给客户端发送应答消息
                send(old_fd,buf,sizeof(buf),0); 
  • 十二、关闭套接字(close函数):
c 复制代码
	close(sock_fd);
相关推荐
LabVIEW开发5 小时前
LabVIEW QMH 队列消息处理架构
架构·labview·labview知识·labview功能·labview程序
为何创造硅基生物6 小时前
C语言 结构体内存对齐规则(通俗易懂版)
c语言·开发语言
吃好睡好便好6 小时前
在Matlab中绘制横直方图
开发语言·学习·算法·matlab
仰泳之鹅7 小时前
【C语言】自定义数据类型2——联合体与枚举
c语言·开发语言·算法
wangqiaowq7 小时前
windows下nginx的安装
linux·服务器·前端
rising start7 小时前
二、全面理解MySQL架构
mysql·架构
nashane7 小时前
HarmonyOS 6学习:CapsLock键失效诊断与长截图完整实现指南
学习·华为·harmonyos
pengyi8710157 小时前
独享IP池自动化维护方案,智能检测自动延长使用寿命
网络协议·tcp/ip·自动化
麦客奥德彪7 小时前
Android Skills
架构·ai编程
jolimark7 小时前
C语言自学攻略:小白入门三步走
c语言·编程入门·学习路线·实践项目·自学攻略