2.1.1 网络io与 epoll

一、一请求一线程

void *client_routine(void *arg) {

	int clientfd = *(int *)arg;

	while (1) {

		char buffer[BUFFER_LENGTH] = {0};
		int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
		if (len < 0) {
			close(clientfd);
			break;
		} else if (len == 0) { // disconnect
			close(clientfd);
			break;
		} else {
			printf("Recv: %s, %d byte(s)\n", buffer, len);
		}

	}

}
int main(int argc, char *argv[]) {

	if (argc < 2) {
		printf("Param Error\n");
		return -1;
	}
	int port = atoi(argv[1]);

	int sockfd = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY; 

	if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
		perror("bind");
		return 2;
	}

	if (listen(sockfd, 5) < 0) {
		perror("listen");
		return 3;
	}
	
	while (1) {

		struct sockaddr_in client_addr;
		memset(&client_addr, 0, sizeof(struct sockaddr_in));
		socklen_t client_len = sizeof(client_addr);
        // 请求一次 创建一个线程
		int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
		// 创建线程
		pthread_t thread_id;
		pthread_create(&thread_id, NULL, client_routine, &clientfd);

	}
	return 0;
}

二、socket——IO多路复用模型 epoll

随着客户端越来越多,则不适合使用一请求一线程的方式
首先介绍epoll实现的三个函数:

  • epoll_create
  • epoll_ctl
  • epoll_wait

(1) epoll_create()

#include <sys/epoll.h>

int epoll_create(int size);
  • size:从Linux 2.6.8以后就不再使用,但是必须设置一个大于0的值。
  • 返回值:调用成功返回一个非负值的文件描述符epollfd,调用失败返回-1。

(2) epoll_ctl()

有了epollfd之后,我们需要将我们需要检测事件的其他fd绑定到这个epollfd上,
或修改一个已经绑定上去的fd的事件类型,或者在不需要时将fd从epollfd上解绑,
这都可以使用epoll_ctl函数。
#include <sys/epoll.h>

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • epfd:调用epoll_create函数创建的epollfd
  • op:操作类型,取值有EPOLL_CTL_ADD、EPOLL_CTL_MOD和EPOLL_CTL_DEL,分别表示向epollfd上添加、修改和移除一个其他fd,当取值是EPOLL_CTL_DEL,第四个参数event忽略不计,可以设置为NULL。
  • fd: 被操作的fd
  • event: epoll_event结构体的地址
  • 返回值:调用成功返回0,调用失败返回-1,可以通过errno错误码获取具体的错误原因.

(3) epoll_event 结构体

struct epoll_event {
    uint32_t     events; // 需要检测的fd事件,取值与poll函数一样
    epoll_data_t data; // 用户自定义数据
};

typedef union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;
事件宏描述
EPOLLIN数据可读
EPOLLOUT数据可写
EPOLLRDHUPTCP连接被对端关闭,或者关闭了写操作
EPOLLPRITCP连接被对端关闭,或者关闭了写操作
EPOLLRDHUP高优先级数据可读,例如 TCP 带外数据
EPOLLERR错误
EPOLLHUP挂起
EPOLLET边缘触发模式
EPOLLONESHOT最多触发其上注册的事件一次

(4) epoll_wait()

创建epollfd,设置好某个fd上需要检测事件并将该fd绑定到epollfd上去后,就可以调用epoll_wait检测事件了。

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • epfd: epollfd
  • events: 是一个epoll_event结构数组的首地址,这是一个输出参数,函数调用成功后,events中存放的是与就绪事件相关epoll_event结构体数组
  • maxevents : 数组元素的个数
  • timeout : 超时时间,单位是毫秒,如果设置为0,epoll_wait会立即返回。
  • 返回值:调用成功会返回有事件的fd数目;如果返回0表示超时;调用失败返回-1

三、epoll 代码实现

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

#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <pthread.h>

#include <errno.h>
#include <fcntl.h>

#include <sys/epoll.h>

#define BUFFER_LENGTH		1024
#define EPOLL_SIZE			1024


void *client_routine(void *arg) {

	int clientfd = *(int *)arg;

	while (1) {

		char buffer[BUFFER_LENGTH] = {0};
		int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
		if (len < 0) {
			close(clientfd);
			break;
		} else if (len == 0) { // disconnect
			close(clientfd);
			break;
		} else {
			printf("Recv: %s, %d byte(s)\n", buffer, len);
		}

	}

}

int main(int argc, char *argv[]) {

	if (argc < 2) {
		printf("Param Error\n");
		return -1;
	}
	int port = atoi(argv[1]);

	int sockfd = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(struct sockaddr_in));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = INADDR_ANY; 

	if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
		perror("bind");
		return 2;
	}

	if (listen(sockfd, 5) < 0) {
		perror("listen");
		return 3;
	}

	int epfd = epoll_create(1);  
	struct epoll_event events[EPOLL_SIZE] = {0};

	struct epoll_event ev;
	ev.events = EPOLLIN; // 数据可读
	ev.data.fd = sockfd; 
	epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);// sockfd交给epoll管理

	
	while (1) {

		int nready = epoll_wait(epfd, events, EPOLL_SIZE, 5);
		if (nready == -1) continue;

		int i = 0;
		for (i = 0;i < nready;i ++) {

			if (events[i].data.fd == sockfd) { // listen 

				struct sockaddr_in client_addr;
				memset(&client_addr, 0, sizeof(struct sockaddr_in));
				socklen_t client_len = sizeof(client_addr);

				int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);

				ev.events = EPOLLIN | EPOLLET; 
				ev.data.fd = clientfd;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);

			} else {

				int clientfd = events[i].data.fd;
				
				char buffer[BUFFER_LENGTH] = {0};
				int len = recv(clientfd, buffer, BUFFER_LENGTH, 0);
				if (len < 0) {
					close(clientfd);

					ev.events = EPOLLIN; 
					ev.data.fd = clientfd;
					epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
					
				} else if (len == 0) { // disconnect
					close(clientfd);

					ev.events = EPOLLIN; 
					ev.data.fd = clientfd;
					epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
					
				} else {
					printf("Recv: %s, %d byte(s)\n", buffer, len);
				}
				
				
			}

		}

	}
	
	return 0;
}

四、检测IO有没有数据?

  1. 识别有数据 —— 水平触发
  2. 监测从无到有的过程 —— 边沿触发
  • LT:(Level_triggered,水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你,如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。

  • ET:(Edge_triggered,边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你,这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。

五、运行

在这里插入图片描述
成功接收到客户端数据

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值