linux网络编程 | c | epoll实现IO多路转接服务器

epoll实现IO多路转接服务器

可通过以下视频学习

06-opell函数实现的多路IO转接_哔哩哔哩_bilibili

通过响应式--多路IO转接实现

文章目录

1.思路&功能

**功能:**客户端输入小写字符串,服务器转成大写返回给客户端

思路:

核心思路

  1. socket()、bind()、listen()
  2. epoll_create创建红黑树,它的返回值就是树的根节点
  3. epoll_ctl将listenfd添加到树上
  4. 循环epoll_wait进行监听,它的返回值是满足监听的总个数,所以以它的返回值为遍历上限去判断事件
  5. 如果它返回的数组中data.fd等于lfd,那么就accept去连接客户端 并将新的cfd加入树中
  6. 如果不是lfd,就说明有读事件发生,就去判断读到的返回值,<0是出错 ==0是客户端关闭(这两个都要去将该cfd从树中移除 并close),>0就处理数据然后写回

2.代码实现

multi_epoll_sever.c

c 复制代码
#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/epoll.h>
#include <fcntl.h>

#define OPEN_MAX 5000
#define SERVE_PORT 9527

int main()
{
    // 所需要的变量
    int lfd, cfd, efd, ret, wait_ret, i, sockfd, len;
    char buf[1024];
    // 地址结构体
    struct sockaddr_in serve_addr, client_addr;
    socklen_t client_addr_len;
    serve_addr.sin_family = AF_INET;                // IPV4
    serve_addr.sin_port = htons(SERVE_PORT);        // 绑定端口
    serve_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定ip(ANY系统自动分配)

    // 创建socket
    lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd < 0)
    {
        perror("socket error");
        exit(1);
    }
    // bind绑定
    bind(lfd, (struct sockaddr *)&serve_addr, sizeof(serve_addr));
    // 设置上限
    listen(lfd, 128);

    // 创建红黑树
    efd = epoll_create(1); // efd就是树的根节点

    // 将lfd挂在树上
    // epoll结构体                       ep是epoll_wait所需的数组(存放满足事件的fd)
    struct epoll_event tep, ep[128]; //  tep是epoll_ctl的参数(传监听的事件)
    tep.events = EPOLLIN;
    tep.data.fd = lfd;
	
    ret = epoll_ctl(efd, EPOLL_CTL_ADD, lfd, &tep);
    if (ret < 0)
    {
        perror("epoll_ctl error");
        exit(1);
    }

    // 循环去epoll_wait进行监听
    while (1)
    {
        wait_ret = epoll_wait(efd, ep, 128, -1); // wait_ret就是实际满足事件的总个数

        // 以wait_ret为上限去遍历事件
        for (i = 0; i < wait_ret; i++)	
        {
            // sockfd用于接收满足事件的fd
            sockfd = ep[i].data.fd;

            // 如果等于lfd,那就说明有客户端要来连接,就去accept
            if (sockfd == lfd)	
            {
                client_addr_len = sizeof(client_addr);
                cfd = accept(lfd, (struct sockaddr *)&client_addr, &client_addr_len);

				// 将cfd设置为非阻塞
                int flag = fcntl(cfd, F_GETFL);
                flag |= O_NONBLOCK;
                fcntl(cfd, F_SETFL, flag);
	
                // 把新的cfd加入树中
                tep.events = EPOLLIN | EPOLLET;
                tep.data.fd = cfd;
                ret = epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &tep);
                if (ret < 0)
                {
                    perror("epoll_ctl cfd error");
                    exit(1);
                }
            }
            // 如果不是lfd,那就说明有读事件发生(读数据)
            else
            {
                len = read(sockfd, buf, sizeof(buf));
                if (len == 0) // 说明对方关闭连接(从树上摘下 & close)
                {
                    epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
                    close(sockfd);
                }
                else if (len == -1)
                {
                    perror("read error");
                    exit(1);
                }
                else // 读写数据
                {
                    for (i = 0; i < len; i++)
                        buf[i] = toupper(buf[i]);

                    write(sockfd, buf, len);
                    write(STDIN_FILENO, buf, len);
                }
            }
        }
    }

    return 0;
}
gcc 复制代码
gcc multi_epoll_sever.c -o multi_epoll_sever
cpp 复制代码
//也可以使用和其他多路IO一样的封装的warp.h warp.c的版本
//就不贴warp.c和warp.c了,大家有需要可以去select和poll的博客中查看
#include"warp.h"
#define SERV_PORT 9527
#define OPEN_MAX 20
int main(int argc,char * argv[])
{
	int i,listenfd,connfd,efd,res,nready,sockfd;
	int n,num=0;
	char buf[4096],str[INET_ADDRSTRLEN];
	socklen_t clien;
	listenfd=Socket(AF_INET,SOCK_STREAM,0);	
	int opt=1;
	setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
	struct sockaddr_in servaddr,cliaddr;
	struct epoll_event tep,ep[OPEN_MAX];

	servaddr.sin_family=AF_INET;
	servaddr.sin_port=htons(SERV_PORT);
	servaddr.sin_addr.s_addr=htonl(INADDR_ANY);

	Bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
	Listen(listenfd,128);
	
	efd=epoll_create(OPEN_MAX);
	if(efd==-1)
		perr_exit("epoll_create error");

	tep.events=EPOLLIN;
	tep.data.fd=listenfd;
	res=epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep);
	if(res==-1)
		perr_exit("epoll_ctl error");
	
	for(;;)
	{
		nready=epoll_wait(efd,ep,OPEN_MAX,-1);
		if(nready==-1)
		perr_exit("epoll_wait error");
		for(i=0;i<nready;i++)
		{
			if(!ep[i].events&EPOLLIN)
				continue;
			if(ep[i].data.fd==listenfd)
			{
				clien=sizeof(cliaddr);
				connfd=Accept(listenfd,(struct sockaddr *)&cliaddr,&clien);

				printf("received from %s at PORT %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
				printf("cfd %d---client %d\n",connfd,++num);
				tep.events=EPOLLIN;
				tep.data.fd=connfd;
				res=epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
				if(res==-1)
					perr_exit("epoll_ctl error");
			}
			else
			{
				sockfd=ep[i].data.fd;
				n=Read(sockfd,buf,4096);

				if(n==0)
				{
					res=epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);
					if(res==-1)
						perr_exit("epoll_ctl error");
					Close(sockfd);
					printf("client[%d] closed connection\n",sockfd);
				}
				else if(n<0)
				{
					perror("read n<0 error:");
					res=epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);
					Close(sockfd);
				}
				else
				{
					for(i=0;i<n;i++)
						buf[i]=toupper(buf[i]);
					Write(STDOUT_FILENO,buf,n);
					Write(sockfd,buf,n);
				}
			}
		}
	}

	Close(listenfd);
	Close(efd);
	return 0;
}

运行图

两个客户端访问服务器端

相关推荐
合方圆~小文7 分钟前
工业现场的视频图像采集设备
java·c语言·人工智能·数码相机·物联网·信号处理
EterNity_TiMe_1 小时前
【Linux网络】网络基础:传输层TCP协议(二)
linux·运维·网络·udp·tcp
多方通行81 小时前
关于Ubuntu的server版本登录无法输入password问题
linux·开发语言·ubuntu·编辑器·bug
aolitianya1 小时前
修改docker源
linux
charlie1145141911 小时前
嵌入式Linux应用层开发——调试专篇(关于使用GDB调试远程下位机开发板的应用层程序办法 + VSCode更好的界面调试体验提升)
linux·c语言·开发语言·vscode·imx6ull·嵌入式linux·调试技术
leaf_leaves_leaf1 小时前
WSL中不同版本Ubuntu与本机VScode联合使用
linux·vscode·ubuntu
Hali_Botebie1 小时前
ubuntu 用 ss-tproxy的最终网络结构
linux·运维·ubuntu
狄加山6752 小时前
C语言(指针基础练习)
java·c语言·算法
努力学习的小廉2 小时前
深入了解Linux —— 理解gcc编译器
linux·服务器·缓存
TANGLONG2222 小时前
【初阶数据结构和算法】八大排序算法之插入排序(直接插入排序、希尔排序及其对比)
java·c语言·数据结构·c++·算法·面试·排序算法