c语言——网络编程【多路文件IO实现 poll、epoll模型总结】内附代码

1.poll 模型

c语言——网络编程【多路文件IO实现 poll、epoll模型总结】内附代码_第1张图片

poll 函数原型 

函数原型:int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能描述:监视fds中的描述符是否激活
参数描述:
    参数 fds:是一个struct pollfd 结构体数组,该数组中存放了多个想要监视的描述符
        该结构体结构如下
            struct pollfd {
               int   fd;         /* 想要监视的描述符 */
               short events;     /* 确定fd描述,到底以何种形式进行监视 */
                   POLLIN:监视fd描述是否可读
                   POLLOUT:监视fd描述符是否可写                 
               short revents;    /* returned events */
                   一般会随着结构体初始化为0,同步为0
                   当 revents == 0的时候,表示fd这个描述符没有激活
                   当 revents == POLLIN的时候,表示fd这个描述符可读激活了
                   当 revents == POLLOUT的时候,表示fd这个描述可写激活了
            };
    参数 nfds:fds这个数组的长度
    参数 timeout:设置的最大监视时长
        注意:    单位是毫秒
                  0表示不阻塞
                  -1表示一直阻塞  
        

实例
    struct pollfd arr[5] = {0}
    arr[0].fd = 0
    arr[0].events = POLLIN
    poll(arr) 表示监视 标准输入流是否可读

 poll模型 完整代码

#include 
#include 
#include 
#include 
#include 
#include 

typedef struct sockaddr_un addr_un_t;

void insert_fd(struct pollfd* arr, int* len, struct pollfd fd) {
    arr[*len] = fd;
    (*len)++;
}

void remove_fd(struct pollfd* arr, int* len, int fd) {
    int i;
    for (i = 0; i < *len; i++) {
        if (fd == arr[i].fd) {
            break;
        }
    }
    for (int j = i; j < *len - 1; j++) {
        arr[j] = arr[j + 1];
    }
    (*len)--;
}

int main(int argc, const char *argv[]) {
    if (argc != 2) {
        printf("请输入端口号\n");
        return 1;
    }

    // 准备一个用来存放所有连接上来的客户端描述符的数组
    int client_arr[64] = {};
    int client_arr_len = 0; // 这里修正了初始化为0

    int port = atoi(argv[1]); // 将字符串转换成int类型port

    // 创建服务器套接字
    int server = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = {0};
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");

    // 为套接字绑定ip和port
    if (bind(server, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind");
        return 1;
    }

    // 监听
    listen(server, 10);

    struct pollfd arr[20] = {0};
    int len = 0;

    // 将stdin和server放到arr里面去
    struct pollfd poll_stdin = {.fd = 0, .events = POLLIN, .revents = 0};
    struct pollfd poll_server = {server, POLLIN, 0};
    insert_fd(arr, &len, poll_stdin);
    insert_fd(arr, &len, poll_server);

    while (1) {
        poll(arr, len, -1);
        for (int i = 0; i < len; i++) {
            if (arr[i].revents == 0) {
                continue; // 该描述符没激活,直接看下一个描述符
            }
            int fd = arr[i].fd;
            if (fd == server) {
                printf("有新客户端连接\n");
                // 这里应该接受客户端连接,并创建新的pollfd结构,然后调用insert_fd
                // 但由于代码不完整,这里省略了具体实现
            } else if (fd == 0) {
                char buf[64] = "";
                scanf("%63s", buf);
                printf("键盘输入数据:%s\n", buf);
            } else {
                // 剩下的都是客户端描述符了
                char buf[64];
                int res = read(fd, buf, 64);
                if (res == 0) {
                    close(fd);
                } else {
                    printf("%d#客户端发来消息:%s\n", fd, buf);
                }
            }
        }
    }

    return 0;
}

2.epoll模型 

c语言——网络编程【多路文件IO实现 poll、epoll模型总结】内附代码_第2张图片

epoll函数原型 

函数原型:int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能描述:监视 epfd 里面的描述符是否被激活
参数描述:
    参数 epfd:epoll_wait的监视列表
        epfd其实是一个文件描述符,该描述符所描述的文件,才是epoll_wait的监视列表
        所以我们要做的就是,将所有想要被epoll监视的描述符,都放到 epfd 文件中去
    参数 events:用来存放所有激活了的描述符的数组,是一个结构体数组,结构如下
        struct epoll_event {
            uint32_t     events;    /* 在poll_wait函数里面,这个数据表示激活的描述符是以何种形式激活的 */
            epoll_data_t data;      /* 该数据里面存放了具体的激活的描述符是谁 */
        };
        typedef union epoll_data {
            void    *ptr;
            int      fd;    /* 具体的激活的描述符 */
            uint32_t u32;
            uint64_t u64;
        } epoll_data_t;
        其实epoll_wait 针对当前数组只做一件事
            将激活的描述符到底是谁,以及该描述符是如何激活的,存放到 events这个数组里面去
        
    参数 maxevents:想要监视的描述符的数量
    参数 timout:设定poll_wait最大阻塞时长
        0:表示不阻塞
        -1:表示永久阻塞
        单位为毫秒

返回值:返回激活的描述符的数量

实例
    struct epoll_event arr[5]
    epoll_wait(epfd,arr) 假设此时 epfd 里面有一个 0 和 一个 server
    此时 epoll_wait 监视到 server激活了
    arr就会发生改变,改变如下
    arr[0].events == EPOLLIN
    arr[0].data.fd == server

 如何创建epfd

int epoll_create(int size);
    创建一个只能监视size个描述符的监视列表
int epoll_create1(int flags);
    flags一般传 EPOLL_CLOEXEC
    表示创建一个动态的监视列表,该监视列表的长度会随着想要监视的描述符数量的改变而改变
所以,一般来说,我们想要创建一个监视列表,直接写
    int epfd = epoll_create1(EPOLL_CLOEXEC);
    
返回值:创建出来的监视列表的描述符

 如何向epfd中存入想要监视的描述符

函数原型:int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能描述:根据op(第2个参数)的不同,针对描述符做出不同的操作
参数描述:
    参数 epfd:监视列表自己的描述符
    参数 op:具体的操作
        EPOLL_CTL_ADD:将fd描述符,添加到epfd监视列表里面去
        EPOLL_CTL_DEL:将fd描述符,从epfd中移除
        EPOLL_CTL_MOD:更改fd描述的监视方式
    参数 event: 注意,这里是一个结构体地址,用来明确想要监视的描述符fd,以何种形式去监视
        struct epoll_event {
            uint32_t     events;    /* 在poll_ctl函数里面,用来确定描述符fd的监视方式 */
                EPOLLIN :监视描述符是否可读
                EPOLLOUT:监视描述符是否可写
            epoll_data_t data;     
        };
        typedef union epoll_data {
            void    *ptr;
            int      fd;    /* 要求和epoll_ctl函数里面第3个参数fd写的一模一样 */
            uint32_t u32;
            uint64_t u64;
        } epoll_data_t;

epoll完整代码 

#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct sockaddr_in addr_in_t;

int main(int argc, const char *argv[]) {
    if (argc != 2) {
        printf("请输入端口号\n");
        return 1;
    }

    int port = atoi(argv[1]);
    int server = socket(AF_INET, SOCK_STREAM, 0);
    if (server == -1) {
        perror("socket");
        return 1;
    }

    addr_in_t addr = {0};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 或者使用其他有效的IP地址

    if (bind(server, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
        perror("bind");
        return 1;
    }

    // 监听
    listen(server, 10);

    // 创建一个可动态扩容的epoll文件描述符
    int epfd = epoll_create1(EPOLL_CLOEXEC);
    if (epfd == -1) {
        perror("epoll_create1");
        return 1;
    }

    // 将server和stdin添加到epfd里面去
    struct epoll_event server_event, stdin_event;
    server_event.data.fd = server;
    server_event.events = EPOLLIN;
    stdin_event.data.fd = 0;
    stdin_event.events = EPOLLIN;

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, server, &server_event) == -1) {
        perror("epoll_ctl for server");
        return 1;
    }

    if (epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &stdin_event) == -1) {
        perror("epoll_ctl for stdin");
        return 1;
    }

    int fd_count = 50; // 这个变量似乎没有被后续代码使用

    while (1) {
        struct epoll_event active_fds[50] = {0};
        int len = epoll_wait(epfd, active_fds, 50, -1);
        if (len == -1) {
            perror("epoll_wait");
            return 1;
        }

        // 经过epoll_wait说明有len个描述符激活了
        for (int i = 0; i < len; i++) {
            int sock = active_fds[i].data.fd;
            if (sock == server) {
                // 激活的是服务器
                printf("有新客户端连接\n");
                int client = accept(server, NULL, NULL);
                if (client == -1) {
                    perror("accept");
                    continue;
                }

                struct epoll_event client_event;
                client_event.data.fd = client;
                client_event.events = EPOLLIN;

                if (epoll_ctl(epfd, EPOLL_CTL_ADD, client, &client_event) == -1) {
                    perror("epoll_ctl for client");
                    close(client);
                    continue;
                }
            } else if (sock == 0) {
                // 激活的是标准输入流
                char buf[64] = {0};
                scanf("%63s", buf);
                while (getchar() != '\n'); // 清除输入缓冲区
                printf("键盘输入了:%s\n", buf);
            } else {
                // 激活的不是上面两个,只能是客户端了
                char buf[64] = {0};
                int res = read(sock, buf, 64);
                if (res == 0) {
                    // 客户端断开连接
                    printf("客户端断开连接\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL, sock, NULL);
                    close(sock);
                } else {
                    printf("客户端发来消息:%s\n", buf);
                }
            }
        }
    }

    return 0;
}

 3.客户端测试代码

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct sockaddr_in addr_in_t;
typedef struct sockaddr addr_t;
typedef struct sockaddr_un addr_un_t;

enum Type{
	TYPE_REGIST,
	TYPE_LOGIN
};

typedef struct Pack{
	int size;// 用来记录整个协议包的实际大小,占4字节
	enum Type type;// 占4个字节
	char buf[2048];// 占count个字节
	int count; // 用来记录指针的偏移量,也就是buf使用了多少个字节
}pack_t;

void append(pack_t* pack,const char* data){
	char* buf = pack->buf;
	int len = strlen(data);
	*(short*)(buf+pack->count) = len; // 将buf的前2个字节当做short来存储数据len
	// memcpy(buf,&len,2);
	pack->count += 2;
	
	memcpy(buf+pack->count,data,len);
	pack->count += len;

	pack->size = pack->count + 8;
}

int main(int argc, const char *argv[])
{
	if(argc != 2){
		printf("请输入端口号\n");
		return 1;
	}
	// ./server 8888
	int port = atoi(argv[1]);// 将字符串 8888 转换成int类型port


	int client = socket(AF_INET,SOCK_STREAM,0);
	addr_in_t addr = {0};
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr("192.168.60.77");

	if(connect(client,(addr_t*)&addr,sizeof(addr)) == -1){
		perror("connect");
		return 1;
	}

	while(1){
		pack_t pack = {0};
		pack.type = TYPE_LOGIN;
		char name[20] = "";
		char pswd[20] = "";
		printf("请输入账号:");
		scanf("%19s",name);
		while(getchar()!=10);

		printf("请输入密码:");
		scanf("%19s",pswd);
		while(getchar()!=10);

		// 将name 和 pswd
		// 按照之前说好的方式(前2个字节记录数据长度n,紧接着的n个字节记录数据本身)将name和pswd存入 pack.buf 里面去
		// 其实就是 把 name 和 pswd 按照既定格式,添加进入协议包
		
		append(&pack,name);
		append(&pack,pswd);


		// 当协议包里面拥有name和pswd之后,就可以将协议包发送给服务器了
		write(client,&pack,pack.size);// 协议包总共占据 pack.count + 4字节,pack.count的长度,根据 name 和 pswd 的长度动态决定
	}
	return 0;
}

 

你可能感兴趣的:(c语言,网络,开发语言)