代码演示:多路I/O复用之网络编程通信进阶总结

测试通过虚拟机:Ubuntu 16.04

温馨提示(必读):

1.下方是对I/O多路复用步步进阶之旅,包含的系统调用用select、poll、epoll及其它们的优化版本的总结

2.下方总结仅列出了服务端的演示代码,如需进行测试,可以在linux下开启多个终端,一个运行服务端,其余的先用ifconfig命令获取ip地址,然后用命令:(nc “获取的ip地址” +你设置的端口号 ) 来进行连接测试

3.测试功能:客户端输入小写字母,经过服务端处理变成大写字母,显示在客户端中。

4.下方总结中头文件包含的#include “wrap.h” 是自定义的错误处理包裹函数,测试时需要加上,头文件和实现代码下方附上:

wrap.h代码演示:
#ifndef __WRAP_H_
#define __WRAP_H_
#include <sys/socket.h>
#include <stdlib.h>
void perr_exit(const char *s);
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr);
int Bind(int fd, const struct sockaddr *sa, socklen_t salen);
int Connect(int fd, const struct sockaddr *sa, socklen_t salen);
int Listen(int fd, int backlog);
int Socket(int family, int type, int protocol);
ssize_t Read(int fd, void *ptr, size_t nbytes);
ssize_t Write(int fd, const void *ptr, size_t nbytes);
int Close(int fd);
ssize_t Readn(int fd, void *vptr, size_t n);
ssize_t Writen(int fd, const void *vptr, size_t n);
ssize_t my_read(int fd, char *ptr);
ssize_t Readline(int fd, void *vptr, size_t maxlen);

#endif

wrap.c代码演示:
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>

void perr_exit(const char *s)
{
	perror(s);
	exit(-1);
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
	int n;

again:
	if ((n = accept(fd, sa, salenptr)) < 0) {
		if ((errno == ECONNABORTED) || (errno == EINTR))
			goto again;
		else
			perr_exit("accept error");
	}
	return n;
}

int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

	if ((n = bind(fd, sa, salen)) < 0)
		perr_exit("bind error");

    return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;
    n = connect(fd, sa, salen);
	if (n < 0) {
		perr_exit("connect error");
    }

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

	if ((n = listen(fd, backlog)) < 0)
		perr_exit("listen error");

    return n;
}

int Socket(int family, int type, int protocol)
{
	int n;

	if ((n = socket(family, type, protocol)) < 0)
		perr_exit("socket error");

	return n;
}

ssize_t Read(int fd, void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ( (n = read(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}

	return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
	ssize_t n;

again:
	if ((n = write(fd, ptr, nbytes)) == -1) {
		if (errno == EINTR)
			goto again;
		else
			return -1;
	}
	return n;
}

int Close(int fd)
{
    int n;
	if ((n = close(fd)) == -1)
		perr_exit("close error");

    return n;
}

/*参三: 应该读取的字节数*/                          //socket 4096  readn(cfd, buf, 4096)   nleft = 4096-1500
ssize_t Readn(int fd, void *vptr, size_t n)
{
	size_t  nleft;              //usigned int 剩余未读取的字节数
	ssize_t nread;              //int 实际读到的字节数
	char   *ptr;

	ptr = vptr;
	nleft = n;                  //n 未读取字节数

	while (nleft > 0) {
		if ((nread = read(fd, ptr, nleft)) < 0) {
			if (errno == EINTR)
				nread = 0;
			else
				return -1;
		} else if (nread == 0)
			break;

		nleft -= nread;   //nleft = nleft - nread 
		ptr += nread;
	}
	return n - nleft;
}

ssize_t Writen(int fd, const void *vptr, size_t n)
{
	size_t nleft;
	ssize_t nwritten;
	const char *ptr;

	ptr = vptr;
	nleft = n;
	while (nleft > 0) {
		if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
			if (nwritten < 0 && errno == EINTR)
				nwritten = 0;
			else
				return -1;
		}
		nleft -= nwritten;
		ptr += nwritten;
	}
	return n;
}

static ssize_t my_read(int fd, char *ptr)
{
	static int read_cnt;
	static char *read_ptr;
	static char read_buf[100];

	if (read_cnt <= 0) {
again:
		if ( (read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {   //"hello\n"
			if (errno == EINTR)
				goto again;
			return -1;
		} else if (read_cnt == 0)
			return 0;

		read_ptr = read_buf;
	}
	read_cnt--;
	*ptr = *read_ptr++;

	return 1;
}

/*readline --- fgets*/    
//传出参数 vptr
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
	ssize_t n, rc;
	char    c, *ptr;
	ptr = vptr;

	for (n = 1; n < maxlen; n++) {
		if ((rc = my_read(fd, &c)) == 1) {   //ptr[] = hello\n
			*ptr++ = c;
			if (c == '\n')
				break;
		} else if (rc == 0) {
			*ptr = 0;
			return n-1;
		} else
			return -1;
	}
	*ptr = 0;

	return n;
}

多进程TCP通信:

server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include <ctype.h>
#include "wrap.h"
void  do_wait(int signum)
{
	while(waitpid(0, NULL, WNOHANG) > 0);
	return;
}
int main(int argc, char* argv[])
{
	//- 创建连接套接字 socket
	int lfd = Socket(AF_INET, SOCK_STREAM, 0);	
	//- 创建sockaddr_in结构体 进行bind
	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(6666);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	//- 设置监听
	Listen(lfd, 128);
	//- 设置循环
	struct sockaddr_in clie_addr;
	socklen_t clie_len = sizeof(clie_addr);
	pid_t pid;
	while(1)
	{
  		//- 接收客户端建立连接 accept
		int cfd = Accept(lfd,(struct sockaddr*)&clie_addr, &clie_len);
  		//- 创建子进程 
		pid = fork();	
		//判断进程操作
		if(pid == 0)
		{
			//- 子进程
			Close(lfd);
			char buf[1024];
  			//- 业务处理 fork read write
			while(1)
			{
				int res = Read(cfd, buf, sizeof(buf));
 				// - 如果读取用户关闭信息 结束业务处理
				if(res == 0)
				{
					printf("客户端断开连接\n");
					Close(cfd);
					return 0;
				}
				//大小写转换
				int i;
				for(i = 0; i < res; i++)
				{

					buf[i] = toupper(buf[i]);
				}
				Write(cfd, buf, res);

			}
		}
		else
		{
			//- 主进程
 			// - 回收子进程
			Close(cfd);
			signal(SIGCHLD, do_wait);
		}
	}
	Close(lfd);
	return 0;
}

多线程TCP通信:

server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include "wrap.h"
struct s_info
{
	struct sockaddr_in clie_addr;
	int cfd;
};
void* do_work(void* argv)
{
	//参数强制类型转换
	struct s_info* si = (struct s_info*)argv;
	char ip[16];
	inet_ntop(AF_INET,&(si->clie_addr).sin_addr.s_addr,ip,sizeof(ip));
	int port = ntohs(si->clie_addr.sin_port);
	printf("IP地址: %s  端口:%hu\n", ip,port);
	char buf[100];
	while(1)
	{
		int res = Read(si->cfd, buf, sizeof(buf));
		//判断是否客户端退出
		if(res == 0)
		{
			printf("客户端断开连接\n");
			Close(si->cfd);
			break;
		}
		int i;
		for(i = 0; i< res; i++)
		{
			buf[i] = toupper(buf[i]);
		}
		Write(si->cfd, buf, res);
	}
	return NULL;
}
int main(int argc, char* argv[])
{
	//- 创建连接套接字 socket
	int lfd = Socket(AF_INET, SOCK_STREAM, 0);	
	//- 创建sockaddr_in结构体 进行bind
	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(6666);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	//- 设置监听
	Listen(lfd, 128);
	//- 设置循环
	struct sockaddr_in clie_addr;
	socklen_t clie_len = sizeof(clie_addr);
	while(1)
	{
  		//接受客户端 建立连接 accept
		int cfd = Accept(lfd,(struct sockaddr*)&clie_addr, &clie_len);
		//为结构体s_info 进行赋值 为函数调用传参
		struct s_info si;
		si.clie_addr = clie_addr;
		si.cfd = cfd;
		pthread_t tid; 
		pthread_create(&tid,NULL, do_work,(void*)&si);	
		//分离态
		pthread_detach(tid);
	}
	Close(lfd);
	return 0;
}

select(跨平台、限制1024)

1.创建套接字lfd、绑定、监听

2.创建一个文件描述符总表allset并初始化(FD_ZERO(&allset)),再来一个临时文件描述符表rset和内核交互

3.将监听的lfd加入到总表allset中

4.死循环:

  • 用总表给临时表赋值,用以下一步传入select,内核将获取rset中文件描述符状态并将结果返回rest中
  • 调用select来委托内核进行检测
  • 判断是不是监听的FD_ISSET(lfd,&rset)
    • 接受新连接accept请求,获取新连接描述符cfd
    • 将cfd加入到总表allset中FD_SET(cfd,&allset)
    • 更新最大文件描述符信息maxfd(select第一个参数处使用maxfd+1)
  • 处理客户端业务
    • 循环处理lfd+1文件描述符到maxfd文件描述符的业务
    • 判断是不是监听的FD_ISSET(文件描述符,&rset)
      • 读取数据
      • 判断数据读取为空时,从总表中除去这个文件描述符并关闭这个文件描述符
server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>

void sys_err(const char *str)
{
	perror(str);
	exit(1);
}

int main(int argc, char *argv[])
{
	int lfd = socket(AF_INET,SOCK_STREAM,0);

	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(6666);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
	listen(lfd,128);

	fd_set rset,allset;
	FD_ZERO(&allset);
	FD_SET(lfd,&allset);
	int maxfd = lfd;
	int i;
	while(1)
	{
		rset =allset;
		int ready = select(maxfd+1,&rset,NULL,NULL,NULL);
		if(ready < 0){
			perror("select err");
			exit(-1);
		}
		if(FD_ISSET(lfd,&rset)){
			struct sockaddr_in clie_addr;
			socklen_t clie_len = sizeof(clie_addr);
			int cfd = accept(lfd,(struct sockaddr*)&clie_addr,&clie_len);
			FD_SET(cfd,&allset);
			
			if(cfd > maxfd){
				maxfd = cfd;
			}
			if(--ready == 0){
				continue;
			}
		}

		char buf[1024];
		for(i = lfd+1;i <= maxfd; i++){
			if(FD_ISSET(i,&rset)){
				int res = read(i,buf,sizeof(buf));
				if(res == 0){
					printf("断开!\n");
					close(i);
					FD_CLR(i,&allset);
				}
				int k;	
				for(k = 0;k < res;k++){
					buf[k] = toupper(buf[k]);
				}
				write(i,buf,res);
				write(1,buf,res);

			}
		}

	}
	close(lfd);
	return 0;
}

select优化版

server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include "wrap.h"


int main(int argc, char* argv[])
{
	int i;
	//- 创建连接套接字 socket
	int lfd = Socket(AF_INET, SOCK_STREAM, 0);	
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof(opt));
	//- 创建sockaddr_in结构体 进行bind
	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(6666);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	//- 设置监听
	Listen(lfd, 128);
	//创建连接文件描述符 数组 同时记录数组有效数据的个数
	int maxi = -1;
	int client[FD_SETSIZE];
	for(i = 0; i < FD_SETSIZE;i++)
	{
		client[i] = -1;
	}
	//初始化位图
	fd_set allset;
	fd_set rset;
	FD_ZERO(&allset);
	//将lfd添加到位图
	FD_SET(lfd, &allset);
	//- 设置循环
	int maxfd = lfd;
	struct sockaddr_in clie_addr;
	socklen_t clie_len = sizeof(clie_addr);
	while(1)
	{
		rset = allset;
		//设置select
		int ready = select(maxfd + 1, &rset, NULL, NULL, NULL);
		if(ready < 0)
		{
			perr_exit("select error");
		}
		if(FD_ISSET(lfd,&rset))
		{
			//客户端连接
			int cfd = Accept(lfd, (struct sockaddr*)&clie_addr, &clie_len);
			//将连接文件描述符放在数据组
			for(i = 0; i<FD_SETSIZE;i++)
			{
				if(client[i] == -1)
				{
					client[i]=cfd;
					break;
				}
			}
			//更新最大值个数
			if(i > maxi)
			{
				maxi=i;
			}
			FD_SET(cfd, &allset);
			if(cfd > maxfd)
			{
				maxfd = cfd;
			}
			if(--ready == 0)
			{
				continue;
			}
		}
		//业务处理
		char buf[1024];
		for( i = 0; i <= maxi; i++)
		{
			int fd = client[i];
			if(fd == -1)
			{
				continue;
			}
			//轮询 整个需要操作的文件描述符  判断是否有业务处理
			if(FD_ISSET(fd,&rset))
			{
				int res = Read(fd, buf, sizeof(buf));
				if(res==0)
				{
					printf("客户端断开连接\n");
					Close(fd);
					FD_CLR(fd,&allset);
					//将连接文件描述符重置为-1
					client[i] = -1;
				}
				int j;
				for(j = 0; j < res; j++)
				{
					buf[j] = toupper(buf[j]);
				}
				Write(fd, buf, res);
			}
		}
	}
	Close(lfd);
	return 0;
}

poll(不可跨平台、无连接限制【链表】)

1.创建套接字lfd、绑定、监听
2.创建struct pollfd结构体(成员:fd和events)数组,用变量maxi来记录下标(初始化0):从1开始全部赋值为-1,0将lfd写入
3.死循环:

  • poll(pollfdarr,pollfdarr中结构体元素总量,-1(阻塞等待))函数返回满足响应事件的文件描述符个数。
  • 判断pollfdarr数组0元素内revents是否接收到POLLIN常量(数据可读)
    • accept()返回一个与客户端连接的描述符cfd
    • 循环遍历1开始位的pollfdarr,将连接的文件描述符cfd添加到集合中,放在第一个结构体元素内成员fd为-1的位置,并初始化events成员为POLLIN后break结束添加操作
    • 更新maxi值 if(i>maxi) maxi =i;
    • 判断是否要处理其他事件 if(–ready == 0) continue;
  • 循环进行业务处理cfd
    • 依次判断事件是否满足条件,revents是否接收到POLLIN
      • 进行业务处理:读取数据(判断返回0时关闭连接文件描述符,并在集合内将其元素中fd成员置为-1),处理数据后写出到连接文件描述符
  • 关闭监听文件描述符lfd,结束所有操作!
server.c代码演示:
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <ctype.h>
#include <unistd.h>
#include <poll.h>
int main(int argc,char *argv[]){
	int lfd = socket(AF_INET,SOCK_STREAM,0);
	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(6666);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
	bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
	listen(lfd,128);
    
	struct pollfd fdarr[2048];
	int maxi = 0;
	int i;
	for(i=0;i < 2048;i++){
		fdarr[i].fd = -1;
	}
	fdarr[0].fd = lfd;
	fdarr[0].events = POLLIN;

	while(1){
			
		int ready = poll(fdarr,maxi+1,-1);
		if(fdarr[0].revents & POLLIN){
			struct sockaddr_in clie_addr;
			socklen_t clie_len = sizeof(clie_addr);
			int cfd = accept(fdarr[0].fd,(struct sockaddr*)&clie_addr,&clie_len);
			
			for(i = 1;i <2048;i++){
				if(fdarr[i].fd == -1){
					fdarr[i].fd = cfd;
					fdarr[i].events = POLLIN;
					break;
				}
			}
			if(i > maxi){
				maxi = i;
			}
			if(--ready == 0){
				continue;
			}
		}
		char buf[2048];
		for(i = 1;i< 2048;i++){
			if(fdarr[i].revents & POLLIN){
				int res = read(fdarr[i].fd,buf,sizeof(buf));
				if(res == 0){
					close(fdarr[i].fd);
					printf("关闭!\n");
					fdarr[i].fd = -1;
				}
				int j;
				for(j = 0;j<res;j++){
					buf[j] = toupper(buf[j]);
				}
				write(fdarr[i].fd,buf,res);
				write(1,buf,res);
			}
		}
	}
	close(lfd);
	return 0;
}

epoll(红黑树)

1.创建套接字lfd、绑定、监听

2.创建epoll树根节点epoll_create,返回一个文件描述符epfd

3.创建存储发生变化的fd对应信息的数组 struct epoll_event xxx[3000]

4.创建一个struct epoll_event 变量ev,在其中初始化lfd信息 ev.events = EPOLLIN; ev.data.fd = lfd;

5.将监听的lfd挂到epoll树上 epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev)

6.死循环:

  • 委托内核检测事件 epoll_wait(epfd,xxx,3000,-1),返回ret(多少个文件描述符发生变化)
  • 根据ret遍历xxx数组
    • 判断数组元素的联合体对象下的fd成员是否为lfd
      • 是lfd的话就有新接受连接请求- accept不阻塞,返回一个连接cfd
        • 更新ev内部成员信息,ev.events = EPOLLIN; ev.data.fd = cfd;
        • 让cfd上树 epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
      • else不是lfd就是处理客户端数据业务
        • 读取数据为空可以执行下树操作 epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)
server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/epoll.h>
void sys_err(const char *str)
{
	perror(str);
	exit(1);
}

int main(int argc, char *argv[])
{
	int lfd = socket(AF_INET,SOCK_STREAM,0);
	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(6666);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
	listen(lfd,128);

	int epfd = epoll_create(2048);
	struct epoll_event ev;
	struct epoll_event eparr[2048];
	ev.events = EPOLLIN;
	ev.data.fd =lfd;

	epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
	int maxi = 0;
	int i = 0;
	struct sockaddr_in clie_addr;
	socklen_t clie_len;
	while(1){
		int ready = epoll_wait(epfd,eparr,maxi+1,-1);	
		for(i = 0;i < ready; i++){
			if(eparr[i].data.fd == lfd && eparr[i].events & EPOLLIN){
				int cfd = accept(lfd,(struct sockaddr*)&clie_addr,&clie_len);
				ev.events = EPOLLIN;
				ev.data.fd = cfd;
				epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
				maxi++;
			}else{
				char buf[2048];
				int res = read(eparr[i].data.fd,buf,sizeof(buf));
				if(res == 0){
					printf("关闭连接!\n");
					epoll_ctl(epfd,EPOLL_CTL_DEL,eparr[i].data.fd,NULL);
					maxi--;
				}
				int j;
				for(j = 0;j < res;j++){
					buf[j] = toupper(buf[j]);
				}
				write(eparr[i].data.fd,buf,res);
				write(1,buf,res);
			}
		}
	}
	close(lfd); 
	close(epfd);
	return 0;
}

epoll对文件描述符的操作有两种模式

epoll对文件描述符的操作有两种模式:LTlevel trigger和ETedge triggerLT模式是默

认模式,LT模式与ET模式的区别如下:

LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处

理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理

该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

温馨提示:
  • 对于监听的sockfd,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上。如果非要使用边缘触发,网上有的方案是用while来循环accept()。
  • 对于读写的connfd,水平触发模式下,阻塞和非阻塞效果都一样,不过为了防止特殊情况,还是建议设置非阻塞。
  • 对于读写的connfd,边缘触发模式下,必须使用非阻塞IO,并要一次性全部读写完数据
block_epoll_server.c代码演示(LT模式):
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>

#define MAXLINE 10
#define SERV_PORT 9000

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int efd;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    struct epoll_event event;
    struct epoll_event resevent[10];
    int res, len;

    efd = epoll_create(10);
    //event.events = EPOLLIN | EPOLLET;     /* ET 边沿触发 */
    event.events = EPOLLIN;                 /* 默认 LT 水平触发 */

    printf("Accepting connections ...\n");

    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    event.data.fd = connfd;
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);

    while (1) {
        res = epoll_wait(efd, resevent, 10, -1);

        printf("res %d\n", res);
        if (resevent[0].data.fd == connfd) {
            len = read(connfd, buf, MAXLINE/2);         //readn(500)   
            write(STDOUT_FILENO, buf, len);
        }
    }

    return 0;
}


noblock_epoll_server.c代码演示(ET模式):
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>

#define MAXLINE 10
#define SERV_PORT 8000

int main(void)
{
    struct sockaddr_in servaddr, cliaddr;
    socklen_t cliaddr_len;
    int listenfd, connfd;
    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int efd, flag;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    listen(listenfd, 20);

    ///
    struct epoll_event event;
    struct epoll_event resevent[10];
    int res, len;

    efd = epoll_create(10);

    event.events = EPOLLIN | EPOLLET;     /* ET 边沿触发,默认是水平触发 */

    //event.events = EPOLLIN;
    printf("Accepting connections ...\n");
    cliaddr_len = sizeof(cliaddr);
    connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
    printf("received from %s at PORT %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
            ntohs(cliaddr.sin_port));

    flag = fcntl(connfd, F_GETFL);          /* 修改connfd为非阻塞读 */
    flag |= O_NONBLOCK;
    fcntl(connfd, F_SETFL, flag);

    event.data.fd = connfd;
    epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);      //将connfd加入监听红黑树
    while (1) {
        printf("epoll_wait begin\n");
        res = epoll_wait(efd, resevent, 10, -1);        //最多10个, 阻塞监听
        printf("epoll_wait end res %d\n", res);

        if (resevent[0].data.fd == connfd) {
            while ((len = read(connfd, buf, MAXLINE/2)) >0 )    //非阻塞读, 轮询
                write(STDOUT_FILENO, buf, len);
        }
    }

    return 0;
}

EPOLL中数据封装后函数回调使用:
server.c代码演示:
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <ctype.h>
#include "wrap.h"

int maxi = 0;

struct e_event
{
	//文件描述符
	int fd;
	//事件
	int event;
	//数据
	void* arg;
	//回调函数
	void (*call_back)(int epfd, int fd, void* arg);
};

//业务处理
void doWork(int epfd, int fd, void* arg)
{
	//业务处理
	char buf[1024];
	int res = Read(fd, buf, sizeof(buf));
	if (res == 0)
	{
		printf("断开连接\n");
		maxi--;
		//下树
		epoll_ctl(epfd, EPOLL_CTL_DEL, fd, (struct epoll_event*)arg);
		//free(((struct epoll_event*)arg)->data.ptr);
		Close(fd);
	}
	int j;
	for (j = 0; j < res; j++)
		buf[j] = toupper(buf[j]);
	Write(fd, buf, res);
}
//创建链接
void doAccept(int epfd, int fd, void* arg)
{
	//创建连接
	struct sockaddr_in clie_addr;
	socklen_t clie_len = sizeof(clie_addr);
	struct epoll_event tep;
	struct e_event* ev = (struct e_event*)malloc(sizeof(struct e_event));
	int cfd = Accept(fd, (struct sockaddr*)&clie_addr, &clie_len);
	ev->fd = cfd;
	ev->event = EPOLLIN;
	ev->call_back = doWork;
	tep.events = EPOLLIN;
	tep.data.ptr = (void*)ev;
	//上树
	epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tep);
	maxi++;
}

int main(int argc, char* argv[])
{
	int i;
	//- 创建连接套接字 socket
	int lfd = Socket(AF_INET, SOCK_STREAM, 0);	
	int opt = 1;
	setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof(opt));
	//- 创建sockaddr_in结构体 进行bind
	struct sockaddr_in serv_addr;
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(6666);
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	Bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
	//- 设置监听
	Listen(lfd, 128);
	struct e_event* ev = (struct e_event*)malloc(sizeof(struct e_event));
	struct epoll_event tep;
	struct epoll_event eparr[2048];
	//创建红黑二叉树
	int epfd = epoll_create(2048);
	if(epfd < 0)
	{
		perr_exit("epoll error");
	}
	//设置lfd监听事件
	tep.events = EPOLLIN;
	ev->fd = lfd;
	ev->event = EPOLLIN;
	ev->call_back = doAccept;
	//tep.data.fd = lfd;
	tep.data.ptr = ev;//
	//将lfd添加到树中
	epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep);

	while(1)
	{
		int ready = epoll_wait(epfd, eparr, maxi + 1, -1);
		if(ready < 0)
		{
			perr_exit("epoll wait error");
		}
		for(i = 0; i < ready; i++)
		{
			struct e_event* tev = (struct e_event*)eparr[i].data.ptr;
			tev->call_back(epfd, tev->fd, &eparr[i]);
		}
	}

	Close(lfd);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值