【Linux网络编程】- SOCKET IO复用技术

目录

一:服务器收到多个客户端信息 方案设计

二:阻塞和非阻塞 

三:IO模型 

四:常用的多路IO复用模型有三种

五:epoll代码学习


一:服务器收到多个客户端信息 方案设计

1. 一个客户端 对应 服务器上的一个进程:accept函数之后通过fork开子进程

缺陷:进程间不能通信,一定需要借助IPC技术

2. 一个客户端 对应 服务器上的一个线程:accept函数之后pthread_create创建子线程

扫描二维码关注公众号,回复: 14417831 查看本文章

缺陷:因为线程可以做到数据共享,数据不安全,解决方案是互斥量\信号量

主要问题:操作系统承载线程是有上限,上限会根据不同的硬件的配置有所差别,但一定不能到达

百万级别。

3.因此需要学习IO复用,本节做出主要介绍

二:阻塞和非阻塞 

以快递物流为例:

 

1.阻塞:空出大脑可以安心睡觉。(不占用CPU宝贵的时间片)

2.非阻塞:浪费时间,浪费电话费,占用快递员时间(占用CPU,系统资源)

3.为什么需要前后置服务器

一个简单的改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。 

举个例子:

4.epoll的作用--多路复用型IO 

三:IO模型 

•阻塞I/O
•非阻塞I/O
•I/O复用(select和poll)
•信号驱动I/O
•异步I/O

1.阻塞I/O模型

•最流行的I/O模型是阻塞I/O模型,缺省时,所有的套接口都是阻塞的

 2.非阻塞I/O模型

•当我们把一个套接口设置为非阻塞方式时,即通知内核:当请求的I/O操作非得让进程睡眠不能完成时,不要让进程睡眠,而应返回一个错误

应用程序连续不断地查询内核,看看某操作是否准备好,这对cpu时间是极大的浪费,一般只在专门提供某种功能的系统中才会用到 

四:常用的多路IO复用模型有三种

1.select函数

select函数作用

这个函数允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒进程

【仅仅知道有IO事件发生,却并不知道是哪几种流,只能做无差别轮询所有的流,找到能读出数据或者写入数据的流,对它们进行操作。select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长,select底层用的是有限制长度的数组】 

2.Poll模型

•Poll函数和select类似,但它是用文件描述符而不是条件的类型来组织信息的.
•也就是说,一个文件描述符的可能事件都存储在struct pollfd中.与之相反,select用事件的类型来组织信息,而且读,写和错误情况都有独立的描述符掩码.poll函数是POSIX:XSI扩展的一部分,它起源于UNIX System V

【poll的本质和select没区别(轮询的方式没有改变,从头到尾遍历,数据的存储结构修改了,数组->链表,虽然存储问题解决,但是遍历的问题还是没有解决),select好比数组,而poll是对数组的升级变成容器,看似升级实则无用,它将用户传入的数据拷贝到内核空间,然后查询每个fd对应的设备状态,但是它没有最大的连接数限制,原因是因为它是基于链表来存储的】 

3.Epoll 

epoll在Linux2.6内核正式提出,是基于事件驱动的I/O方式,相对于select来说

,epoll没有描述符个数限制,使用一个文件描述符管理多个描述符,

将用户关心的文件描述符的事件存放到内核的一个事件表中,

这样在用户空间和内核空间的copy只需一次。

Linux中提供的epoll相关函数如下:

int epoll_create(int size);

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

【epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的IO事件通知给程序员/主进程,epoll实际上是事件驱动(每个事件关联上fd),此时我们对这些流的操作就是有意义的,复杂度降低了变成了O(1)】 

五:epoll代码学习
 主要函数

 服务器完整代码:

#include<iostream>
#include <sys/epoll.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include<stdio.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>

using namespace std;

int main()
{
	struct epoll_event epollevent;
	//事件结构体数组 编写代码中用作判断使用
	struct epoll_event epolleventArray[5];
	int epollfd = 0;
	int epollwaitfd = 0;

	char buf[50] = { 0 };
	struct sockaddr_in addr;
	int len = 0;
	int acceptfd = 0;

	//初始化网络 识别当前计算机是否可以联网
	//第一个参数:采用IPV4 IP地址 第二个参数:网络分配TCP 
	int socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if (socketfd == -1)
	{
		perror("socket error");
	}
	else
	{
		cout << "socketfd = " << socketfd << endl;
		//确定用IPV4地址
		addr.sin_family = AF_INET;
		//服务器开放自己的IP地址给客户端连接使用   INADDR_ANY生成默认的可以联网的IP地址
		addr.sin_addr.s_addr = INADDR_ANY;
		//绑定服务器端口号0-65535  10000以下系统默认使用
		addr.sin_port = htons(10086);

		len = sizeof(addr);

		int opt_val = 1;
		//解决 address already is use 报错
		//端口复用 设置,一定在bind函数前
		setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt_val,sizeof(opt_val));

		//bind 绑定ip地址 绑定端口号
		if (bind(socketfd, (struct sockaddr*)&addr, len) == -1)
		{
			perror("bind error");
		}

		if (listen(socketfd, 10) == -1)
		{
			perror("listen error");
		}

		cout << "网络搭建成功" << endl;

		cout << "epoll创建" << endl;
		//事件结构体初始化
		bzero(&epollevent, sizeof(epollevent));
		//绑定当前准备好的socketfd(服务器可使用的网络通道文件描述符)上线使用/acceptfd发数据使用
		epollevent.data.fd = socketfd;
		//绑定有可能触发的事件 当前是socketfd 如果有事件发生一定就是 客户端连接
		epollevent.events = EPOLLIN;
		
		//创建epoll
		epollfd = epoll_create(5);
		//epoll事件队列添加socketfd 它感兴趣的事件是epollevent
		epoll_ctl(epollfd, EPOLL_CTL_ADD, socketfd,&epollevent);

		while (1)
		{
			cout << "epoll wait........." << endl;
			//阻塞式函数 等待事件发生
			epollwaitfd = epoll_wait(epollfd, epolleventArray, 5, -1);
			if (epollwaitfd < 0)
			{
				perror("epoll_wait error");
			}
			for (int i = 0; i < epollwaitfd; i++)
			{
				//判断是否有客户端上线
				if (epolleventArray[i].data.fd == socketfd)
				{
					cout << "服务器有客户端连接........." << endl;
					//服务器等待客户端连接 阻塞式函数 acceptfd在服务器代表已经连接成功的客户端
					acceptfd = accept(socketfd, NULL, NULL);
					cout << "有客户端成功连接 acceptfd = " << acceptfd << endl;

					epollevent.data.fd = acceptfd;
					epollevent.events = EPOLLIN;
					epoll_ctl(epollfd, EPOLL_CTL_ADD, acceptfd, &epollevent);
				}
				else if(epolleventArray[i].events & EPOLLIN)
				{
					//有客户端发来数据
					cout << "有事件发生 但不是socketfd 是客户端" << acceptfd << endl;
					bzero(buf, sizeof(buf));
					int res = read(epolleventArray[i].data.fd, buf, sizeof(buf));
					if (res > 0)
					{
						cout << "服务器收到客户端发来的数据.....buf = " << buf << endl;

					}
					else if(res <= 0)
					{
						cout << "客户端掉线............." << acceptfd << endl;
						
						//从epoll中删除该fd
						epollevent.data.fd = epolleventArray[i].data.fd;
						epollevent.events = EPOLLIN;
						epoll_ctl(epollfd, EPOLL_CTL_DEL, epolleventArray[i].data.fd, &epollevent);
						//关闭这个fd所对应的网络通道
						close(epolleventArray[i].data.fd);
					}

				}
			}
		}
	}
	return 0;
}

客户端完整代码:

#include<iostream>
#include <sys/types.h>         
#include <sys/socket.h>
#include<stdio.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
 
using namespace std;
 
int main()
{
	char buf[50] = { 0 };
	struct sockaddr_in addr;
	int len = 0;
	//初始化网络 识别当前计算机是否可以联网
	//第一个参数:采用IPV4 IP地址 第二个参数:网络分配TCP 
	int socketfd = socket(AF_INET, SOCK_STREAM, 0);
	cout << "客户端 socketfd = " << socketfd << endl;
	if (socketfd == -1)
	{
		perror("socket error");
	}
	else
	{
		//确定用IPV4地址
		addr.sin_family = AF_INET;
		//客户端主动寻找服务器IP地址 127.0.0.1本机回环地址 192.168.75.128
		addr.sin_addr.s_addr = inet_addr("192.168.75.128");
		//绑定服务器端口号0-65535  10000以下系统默认使用
		addr.sin_port = htons(10086);
 
		len = sizeof(addr);
 
		//主动去连接服务器 IP和端口
		if (connect(socketfd, (struct sockaddr*)&addr, len) == -1)
		{
			perror("connect error");
		}
		else
		{
			cout << "客户端连接服务器成功" << endl;
		}
 
		while (1)
		{
			cin >> buf;
			int res = write(socketfd, buf, sizeof(buf));
			cout << "客户端发送 res = " << res << endl;
			bzero(buf, sizeof(buf));
		}
	}
 
	return 0;
}

结果:一个服务器与多个客户端 可以实现通信

ps -aux查看一下

服务器就一个进程 对应客户端三个进程,符合基本业务流程 

猜你喜欢

转载自blog.csdn.net/m0_56051805/article/details/125924428