linux学习笔记19-使用epoll

简言

  前面学习linux系统的网络通信,套接字(socket),学习是同步的阻塞的socket,正是因为是阻塞的,才有epoll的学习.

如何将socket设置为非阻塞的

将socket设置为非阻塞的
void setnonblocking(int st)
{
	int opt = fcntl(st, F_GETFL);        //fcntl是对文件描述符提供控制,并非只对socket
	if (opt < 0)
	{
		printf("fcntl failed:%s\n", strerror(errno));
		return;
	}
	opt = opt | O_NONBLOCK;         
	if (fcntl(st, F_SETFL, opt) < 0)    //socket返回的也是文件描述符,通过opt将该文件描述符设置为非阻塞的
	{
		printf("fcntl set failed:%s\n", strerror(errno));
		return;
	}
}

先贴代码(具体等下节在详细深入)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <iconv.h>

//将gbk编码字符转换utf8字符集
int gbk2utf8(char *src, size_t *srclen, char *dest, size_t *destlen)
{
	//源字符集为gbk,目标字符集为utf8
	iconv_t result = iconv_open("UTF8", "GBK"); 
	if (result == (iconv_t)-1)
	{
		printf("open iconv failed:%s\n", strerror(errno));
		return -1;
	}
	size_t rc = iconv(result, &src, srclen, &dest, destlen);
	if (rc == (size_t)-1)
	{
		printf("iconv error:%s\n", strerror(errno));
		return -1;
	}
	iconv_close(result);
	return 0;
}

int socket_create(int port)
{
	int st = socket(AF_INET, SOCK_STREAM, 0);
	int on = 1;
	//设置socket可以重用
	if (setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) 
	{
		printf("setsockopt failed:%s\n", strerror(errno));
		return -1;
	}
	struct sockaddr_in addr;
	memset(&addr, 0, sizeof(addr));
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);                                     
	addr.sin_addr.s_addr = htonl(INADDR_ANY);			    //设置ip为任意
	if (bind(st, (struct sockaddr *)&addr, sizeof(addr)) == -1)         //绑定端口号
	{
		printf("bind port (%d) failed:%s\n", port, strerror(errno));
		return -1;
	}
	if (listen(st, 2000) == -1)					    //开始监听
	{
		printf("listen failed:%s\n", strerror(errno));
		return -1;
	}
	return st;
}

int socket_accept(int st)
{
	struct sockaddr_in client_addr;
	int len = sizeof(client_addr);
	memset(&client_addr, 0, len);
	int client_st = accept(st, (struct sockaddr *)&client_addr, &len);
	if (client_st < 0)
	{
		printf("socket_accept failed:%s\n", strerror(errno));
		return -1;
	}
	else
	{
		printf("accept ip:%s\n", inet_ntoa(client_addr.sin_addr));
		return client_st;
	}
}

int socket_recv(int st)
{
	char buf[1024];
	memset(buf, 0, sizeof(buf));
	int result = recv(st, buf, sizeof(buf), 0);
	if (result < 0)
	{
		printf("socket_recv failed:%s\n", strerror(errno));
		return -1;
	}
	else
	{
		char show_buf[1024];
		size_t show_len = sizeof(show_buf);
		size_t buf_len = (size_t)result;

		//进行字符串转换
		gbk2utf8(buf, &buf_len, show_buf, &show_len);

		printf("recv msg:%s\n", show_buf);
		return st;
	}
}

//根据文件描述符,将文件(这里是套接字)设置为非阻塞
void setnonblocking(int st)
{
	int opt = fcntl(st, F_GETFL);  //
	if (opt < 0)
	{
		printf("fcntl failed:%s\n", strerror(errno));
		return;
	}
	opt = opt | O_NONBLOCK;
	if (fcntl(st, F_SETFL, opt) < 0)
	{
		printf("fcntl set failed:%s\n", strerror(errno));
		return;
	}
}

int main(int argc, char *argv[])
{
	if (argc < 2)
	{
		printf("argc < 2\n");
		return -1;
	}
	int port = atoi(argv[1]);
	int listen_st = socket_create(port);
	if (listen_st == -1)
	{
		return -1;
	}

	struct epoll_event ev, events[2000];  		//声明epoll_event变量,ev用于注册事件,events用于回传要处理的事件
	int epoll_fd = epoll_create(2001);    		//指定创建最大的句柄数量为100

	setnonblocking(listen_st);            		//将socket设置为非阻塞模式

	ev.data.fd = listen_st;              		//关联要处理事件相关的文件描述符
	ev.events = EPOLLIN | EPOLLERR | EPOLLHUP;      //设置要处理事件的类型,分别可读/错误/被挂断的事件
	int epoll_result = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_st, &ev); //注册epoll事件

	int st = 0;
	for (;;)
	{
		//epoll_wait 最后一个参数 -1为一直等待下去,0为立马返回
		//等待epoll事件的触发
		int fds = epoll_wait(epoll_fd, events, 2000, -1);  		
		if (fds == -1)
		{
			printf("epoll_wait failed:%s\n", strerror(errno));
			break;
		}
		int i;
		for (i = 0; i < fds; i++)
		{
			printf("listenfd=%d fd=%d", listen_st, events[i].data.fd);
			if (events[i].data.fd < 0)
			{
				continue;
			}
			if (events[i].data.fd == listen_st)  //监测到listen_st接收客户端请求
			{
				st = socket_accept(listen_st);
				if (st >= 0)
				{
					setnonblocking(st);
					ev.data.fd = st;			 //设置epoll event的文件描述符和要处理的事件
					ev.events = EPOLLIN | EPOLLERR | EPOLLHUP | EPOLLET;
					//将epoll事件添加到epoll池中
					epoll_ctl(epoll_fd, EPOLL_CTL_ADD, st, &ev);
					printf("client fd=%d\n", st);
					continue;
				}
			}
			if (events[i].events & EPOLLIN)      //收到socket数据
			{
				st = events[i].data.fd;
				if (socket_recv(st) < 0)
				{
					if (st > 0)
					{
						close(st);
					}
					events[i].data.fd = -1;
				}
			}
			if (events[i].events & EPOLLERR)     //发生错误
			{
				st = events[i].data.fd;
				if (st > 0)
				{
					close(st);
				}
				events[i].data.fd = -1;      //将描述符值为-1,相当于EPOLL_CTL_DEL
			}
			if (events[i].events & EPOLLHUP)     //socket中断(或关闭)
			{
				st = events[i].data.fd;
				if (st > 0)
				{
					close(st);
				}
				events[i].data.fd = -1;
			}
		}
	}
	close(epoll_fd);				    //使用完毕,一定要记得关闭
	return 0;
}

画图理解epoll  

epoll学习
秋风 2017-04-18