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

news2024/12/29 1:21:58

//head.h            头文件

//TcpGrpSer.c     服务器端

//TcpGrpUsr.c     客户端

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

 API

epoll函数

#include<sys/epoll.h>
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<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<math.h>
#include<errno.h>
#include<fcntl.h>
#include<signal.h>

#include<sys/stat.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<sys/shm.h>
#include<sys/time.h>
#include<sys/sem.h>

#include<pthread.h>
#include<semaphore.h>

#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<poll.h>
#include<sys/epoll.h>
#include<sys/fcntl.h>


#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;
}

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/812363.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TypeScript算法题实战——剑指 Offer篇(5)

目录 一、平衡二叉树1.1、题目描述1.2、题解 二、数组中数字出现的次数2.1、题目描述2.2、题解 三、数组中数字出现的次数 II3.1、题目描述3.2、题解 四、和为s的两个数字4.1、题目描述4.2、题解 五、和为s的连续正数序列5.1、题目描述5.2、题解 六、翻转单词顺序6.1、题目描述…

《cuda c编程权威指南》01- 用gpu输出hello world

学习一门新语言的最好方式就是用它来编写程序。 目录 1. 使用cpu输出hello world 2. 使用gpu输出hello world 3. CUDA编程结构 1. 使用cpu输出hello world hello.cu #include <stdio.h>void helloFromCPU() {printf("hello world from cpu!\n"); }int m…

Zabbix分布式监控配置和使用

目录 1 Zabbix监控的配置流程2 添加主机组3 添加模板4 添加主机5 配置图形6 配置大屏7 新建监控项7.1 简介7.2 添加监控项7.3 查看数据7.4 图表 8 新建触发器8.1 概述8.2 添加触发器8.3 显示触发器状态 1 Zabbix监控的配置流程 在Zabbix-Web管理界面中添加一个主机&#xff0c;…

【Golang 接口自动化00】为什么要用Golang做自动化?

目录 为什么使用Golang做自动化 最终想实现的效果 怎么做&#xff1f; 写在后面 资料获取方法 为什么使用Golang做自动化 顺应公司的趋势学习了Golang之后&#xff0c;因为没有太多时间和项目来实践&#xff0c;怕止步于此、步Java缺少练习遗忘殆尽的后尘&#xff0c;决定…

【C++进阶之路】多态篇

文章目录 前言一、概念1.分类2.实现条件①重写虚函数1.1总结三重1.2 final与override ②父类的指针或者引用2.1普通调用VS多态调用 3.抽象类3.1. 纯虚函数3.2. 接口继承和实现继承 二、原理及使用1.虚函数表 —— 虚表2.默认成员函数2.1构造函数2.2析构函数 3. 多继承3.1普通的…

python速成之循环分支结构学习

循环结构 应用场景 我们在写程序的时候&#xff0c;一定会遇到需要重复执行某条或某些指令的场景。例如用程序控制机器人踢足球&#xff0c;如果机器人持球而且还没有进入射门范围&#xff0c;那么我们就要一直发出让机器人向球门方向移动的指令。在这个场景中&#xff0c;让…

AD21原理图的高级应用(六)原理图设计片段的使用

&#xff08;六&#xff09;原理图设计片段的使用 Altium Designer 的片段功能可以很方便地重复使用一些单元模块,其中包括原理图的电路模块、PCB(包括布线)和代码模块。例如在工程中需要设计电源模块,而别的工程中又恰好有比较完善的电源模块,这时就可以通过片段功能重复地使用…

一文了解 Android 车机如何处理中控的旋钮输入?

前言 上篇文章《从实体按键看 Android 车载的自定义事件机制》带大家了解了 Android 车机支持自定义输入的机制 CustomInputService。事实上&#xff0c;除了支持自定义事件&#xff0c;对于中控上常见的音量控制、焦点控制的旋钮事件&#xff0c;Android 车机也是支持的。 那…

测试|测试用例方法篇

测试|测试用例方法篇 文章目录 测试|测试用例方法篇1.测试用例的基本要素&#xff1a;测试环境&#xff0c;操作步骤&#xff0c;测试数据&#xff0c;预期结果…2.测试用例带来的好处3.测试用例的设计思路&#xff0c;设计方法&#xff0c;具体设计方法之间的关系**设计测试用…

linux设备驱动的poll与fasync

什么是fasync 在 Linux 驱动程序中&#xff0c;fasync 是一种机制&#xff0c;用于在异步事件发生时通知进程。它允许进程在等待设备事件时&#xff0c;不必像传统的轮询方式那样持续地查询设备状态。 具体来说&#xff0c;当进程调用 fcntl(fd, F_SETFL, O_ASYNC) 函数时&am…

lib-flexible修改配置适配更多不同分辨率

找到设置宽度的地方 然后根据你的屏幕最大多大呀&#xff0c;最小多小呀设置一下 if (width / dpr < 1980) { width 1980 * dpr; } else if (width / dpr > 5760) { width 5760 * dpr; }

Python 教程之标准库概览

概要 Python 标准库非常庞大&#xff0c;所提供的组件涉及范围十分广泛&#xff0c;使用标准库我们可以让您轻松地完成各种任务。 以下是一些 Python3 标准库中的模块&#xff1a; 「os 模块」 os 模块提供了许多与操作系统交互的函数&#xff0c;例如创建、移动和删除文件和…

【Linux】进程篇Ⅱ:进程开始、进程终止、进程等待

文章目录 五、fork 函数&#xff0c;创建进程写时拷贝 六、进程终止1. 退出码2. 如何终止程序 七、进程等待1. 概念2. wait 函数waitpid 函数 &#x1f53a; 3. 阻塞等待 五、fork 函数&#xff0c;创建进程 #include <unistd.h>   pid_t fork(void);   返回值&#xf…

动态SQL 语句-更复杂的查询业务需求也能轻松拿捏

文章目录 动态SQL 语句-更复杂的查询业务需求动态SQL-官方文档为什么需要动态SQL动态SQL-基本介绍基本介绍动态SQL 必要性解决方案分析 动态SQL 常用标签动态SQL-案例演示if 标签应用实例where 标签应用实例choose/when/otherwise 应用实例forEach 标签应用实例trim 标签应用实…

【C语言进阶篇】 你真的学会数组了嘛?数组笔试题万字解析(上)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《C语言初阶篇》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言&#x1f4ac; 数组笔试题解析&#x1f4ad; 一维数组笔试题✅ 一维数组笔试题解析✅ 一维数…

ShardingSphere 源码模块介绍

目录 Agent Db-Protocol Dialect-Exception Distribution Features Infra JDBC Kernel Mode Proxy Agent agent 模块提供了基于 Java Agent 实现的可观察框架 logging&#xff1a; 用于记录 ShardingSphere 的日志&#xff0c;支持文件metricrs&#xff1a;用于收集…

C++那些事之高性能SIMD

C那些事之高性能SIMD 最近在看相关向量化的内容&#xff0c;看起来有点头大&#xff0c;借此机会&#xff0c;学习一下高性能SIMD编程。 SIMD全称single-instruction multiple-data,单指令多数据。 在传统的计算机架构中&#xff0c;CPU一次只能处理一个数据元素。但是&#xf…

【机器学习】Feature scaling and Learning Rate (Multi-variable)

Feature scaling and Learning Rate 1、数据集2、学习率2.1 α \alpha α 9.9e-72.2 α \alpha α 9e-72.3 α \alpha α 1e-7 3、特征缩放3.1 特征缩放的原因3.2 Z-score 归一化3.3 预测3.4 损失等值线 导入所需的库 import numpy as np np.set_printoptions(precision…

【C++】类和对象-C++运算符重载

运算符重载 1.加号运算符重载 代码&#xff1a; #include <iostream> using namespace std; /******************************************/ //加号运算符重载class Person { public:int m_A;int m_B;//1、成员函数重载号(不能与下面方式2同时存在&#xff0c;否则代码报…

在docker中没有vi如何修改docker中的文件

今天在做学成在线的项目&#xff0c;遇到了一个问题&#xff0c;就是死活登不上xxl-job&#xff0c;按照之前遇到的nacos的问题&#xff0c;我怀疑很大概率是和当时的ip设置有关&#xff0c;不知道nacos的ip怎么修改的同学&#xff0c;可以看看这篇文章&#xff1a;关于docker中…