网络编程 IO多路复用 [epoll版] (TCP网络聊天室)

//head.h            头文件

//TcpGrpSer.c     服务器端

//TcpGrpUsr.c     客户端

通过IO多路复用实现服务器在单进程单线程下可以与多个客户端交互

 API

epoll函数

#include
int epoll_create(int size);
功能:创建一个epoll句柄//创建红黑树根节点
epoll把要监测的事件文件描述符挂载到红黑树上
参数:size 没有意义,但是必须>0
返回值:成功返回根节点对应的文件描述符,失败返回-1

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:实现对于epoll的控制
参数:
epfd:epoll_create创建的句柄
op:控制方式
     EPOLL_CTL_ADD:添加要监测的事件文件描述符
     EPOLL_CTL_MOD:修改epoll检测的事件类型
     EPOLL_CTL_DEL:将文件描述符从epoll删除
fd:要操作的文件描述符
event:事件结构体
typedef union epoll_data {
               void        *ptr;
               int          fd;//使用这个
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

 struct epoll_event {
               uint32_t     events; //EPOLLIN(读) EPOLLOUT(写)
               epoll_data_t data;        /* User data variable */
           };
返回值:成功返回0,失败返回-1

int epoll_wait(int epfd, struct epoll_event *events,
                      int maxevents, int timeout);
功能:阻塞等待准备好的文件描述符
参数:
epfd:epoll句柄
events:存放就绪事件描述符的结构体数组首地址
maxevents:监听的事件最大个数
timeout:超时检测
    >0:毫秒级检测
    ==0:立即返回
    -1:不关心是否超时

返回值:
>0:准备好的文件描述符的个数
==0:超时
<0:失败

 head.h

#ifndef __HEAD_H__
#define __HEAD_H__


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

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

#include
#include

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


#define NUM 10
#define ERR_MSG(msg)                    \
	do                                  \
	{                                   \
		printf("line: %d\n", __LINE__); \
		perror(msg);                    \
	} while (0)

#define PORT 6666			// 端口号的网络字节序  1024~49151
#define IP "192.168.250.100" // ifconfig查看本机IP  (ipv4)

#endif

TcpGrpSer.c

#include "head.h"



int main(int argc, const char *argv[])
{
	// 创建流式套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}

	// 填充服务器自身的地址信息结构体
	// 真实的地址信息结构体根据地址族制定AF_INET ;
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;			 // 必须填充AF_INET
	sin.sin_port = htons(PORT);			 // 端口号的网络字节序  1024~49151
	sin.sin_addr.s_addr = inet_addr(IP); // ifconfig查看本机IP

	int reuse = 1;
	if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) // 允许端口快速被重复使用
	{
		ERR_MSG("setsockopt");
		return -1;
	}
	// 绑定连接
	if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("bind");
		return -1;
	}
	printf("bind success\n");

	// 设置监听
	if (listen(sfd, 128) < 0)
	{
		ERR_MSG("listen");
		return -1;
	}

	struct epoll_event event;
	struct epoll_event events[10]; // 存放就绪事件描述符的数组
	char buf[128] = {0};

	// 创建epoll句柄
	int epfd = epoll_create(1);
	if (epfd < 0)
	{
		ERR_MSG("epoll_create");
		exit(-1);
	}

	// 添加准备就绪事件进入epoll;
	event.events = EPOLLIN; // 读事件
	event.data.fd = sfd;	// 监听套接字放入列表
	if (epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &event) < 0)
	{
		ERR_MSG("epoll_ctl");
		exit(-1);
	}
	event.events = EPOLLIN; // 读事件
	event.data.fd = 0;		// 终端输入套接字放入列表
	if (epoll_ctl(epfd, EPOLL_CTL_ADD, 0, &event) < 0)
	{
		ERR_MSG("epoll_ctl");
		exit(-1);
	}
	// 监听事件是否发生

	int s_res = 0;
	struct sockaddr_in cin;
	socklen_t len = sizeof(cin);
	struct sockaddr_in savcin[1024];
	int flags[1024] = {0};
	int newfd = -1;
	ssize_t res = 0;

	while (1)
	{
		// 监测文件描述符是否准备就绪
		// 如果成功,s_res接收返回的事件个数,就绪的事件存储到events数组中
		s_res = epoll_wait(epfd, events, 10, -1);
		if (s_res < 0)
		{
			ERR_MSG("select");
			return -1;
		}

		// 与客户端通信
		for (int i = 0; i < s_res; i++)
		{
			if (events[i].events & EPOLLIN)
			{
				if (events[i].data.fd == sfd)
				{
					printf("客户端连技事件\n");
					newfd = accept(sfd, (struct sockaddr *)&cin, &len);
					if (newfd < 0)
					{
						perror("accept");
						return -1;
					}
					savcin[newfd] = cin;
					flags[newfd] = 1;
					printf("[%s:%d] 客户端连接成功 newfd = %d __%d__ \n",
						   inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd, __LINE__);

					event.events = EPOLLIN | EPOLLET; // 读事件
					event.data.fd = newfd;	// 新套接字放入链表
					if (epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &event) < 0)
					{
						ERR_MSG("epoll_ctl");
						exit(-1);
					}
				}

				else if (0 == events[i].data.fd)
				{
					printf("触发键盘输入事件\n");
				int sndfd=-1;
				res=scanf("%d %s",&sndfd,buf);
				while(getchar() !=10);
				if(res!=2)
				{
					printf("请输入正确数据格式:[fd(4~1023)] string\n");
		
					continue;
				}
				//判断文件是否合法
				if(flags[i])
				{
					printf("sndfd = %d 是非法文件描述符\n",sndfd);
					continue;
				}
				if(send(sndfd,buf,sizeof(buf),0)<0)
				{
					ERR_MSG("send");
				}
				bzero(buf,sizeof(buf));
				}
				else
				{

					printf("客户端交互事件\n");
					// res = scanf("%s", buf);
					// while (getchar() != 10)
						res = recv(events[i].data.fd, buf, sizeof(buf), 0);
					if (res < 0)
					{
						ERR_MSG("send");
						return -1;
					}
					else if (0 == res)
					{
						printf("[%s:%d] 客户端下线 newfd = %d __%d__ \n",
							   inet_ntoa(savcin[i].sin_addr), ntohs(savcin[i].sin_port), events[i].data.fd, __LINE__);

						close(i); // 关闭文件描述符
						flags[i] = 0;
					
					}
					printf("[%s:%d] 客户端 newfd = %d : %s, __%d__ \n",
						   inet_ntoa(savcin[i].sin_addr), ntohs(savcin[i].sin_port), events[i].data.fd, buf, __LINE__);
				}
			}
		}
	}

	if (close(sfd) < 0)
	{
		ERR_MSG("close");
		return -1;
	}

	return 0;
}

TcpGrpUsr.c

#include "head.h"


int main(int argc, const char *argv[])
{
	//创建流式套接字
	int sfd = socket(AF_INET,SOCK_STREAM,0);
	if(sfd<0)
	{
		ERR_MSG("socket");
		return -1;
	}

	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse)) < 0) //允许端口快速被重复使用
	{
		ERR_MSG("setsockopt");
		return -1;
	}
	//填充服务器自身的地址信息结构体
	//真实的地址信息结构体根据地址族制定AF_INET ; man 7 ip
	struct sockaddr_in sin; 
	sin.sin_family      = AF_INET;            //必须填充AF_INET
	sin.sin_port        = htons(PORT);        //端口号的网络字节序  1024~49151
	sin.sin_addr.s_addr = inet_addr(IP);      //ifconfig查看本机IP

	if(connect(sfd,(struct sockaddr *)&sin,sizeof(sin))<0)
	{
		perror("connect");
		return -1;
	}
	printf("连接成功\n");

	//创建集合
	struct pollfd fds[2];

	fds[0].fd = 0;
	fds[0].events = POLLIN;
	fds[1].fd = sfd;
	fds[1].events = POLLIN;

	char buf[128]="";
	int res=0;

	while(1)
	{
		//阻塞方式监测集合
		res = poll(fds,2,-1);
		if(res < 0)
		{
			ERR_MSG("poll");
			return -1;
		}
		else if(0 == res)
		{
			printf("time out...\n");      //超时
			break;
		}

		//判断0文件描述符是否右POLLIN事件
		if((fds[0].revents & POLLIN))
		{
			fgets(buf,sizeof(buf),stdin);
			buf[strlen(buf)-1] = 0;
			if(send(sfd,buf,sizeof(buf),0) < 0)
			{
				ERR_MSG("send");
				return -1;
			}
			printf("发送成功\n");
		}
		//判断sfd文件描述符是否右POLLIN事件
		if(fds[1].revents & POLLIN)
		{
			//接收数据
			bzero(buf,sizeof(buf));
			res = recv(sfd,buf,sizeof(buf),0);
			if(res<0)
			{
				ERR_MSG("recv");
				return -1;
			}
			else if(res == 0)
			{
				printf("[%s:%d] 服务器下线__%d__ \n",\
					inet_ntoa(sin.sin_addr),ntohs(sin.sin_port),__LINE__);
				break;
			}
			printf("[%s:%d] cfd = %d : %s__%d__ \n",\
					inet_ntoa(sin.sin_addr),ntohs(sin.sin_port),sfd,buf,__LINE__);
		}	
	}

	if(close(sfd)<0)
	{
		ERR_MSG("close");
		return -1;
	}

	return 0;
}

网络编程 IO多路复用 [epoll版] (TCP网络聊天室)_第1张图片 

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