【网络编程】

IO模型

分类

在UNIX/Linux下主要有4种I/O 模型:
阻塞I/O:
最常用、最简单、效率最低
非阻塞I/O:
可防止进程阻塞在I/O操作上,需要轮询
I/O 多路复用:
允许同时对多个I/O进行控制
信号驱动I/O:
一种异步通信模型

阻塞IO

阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。
缺省情况下,套接字建立后所处于的模式就是阻塞I/O 模式。
前面学习的很多读写函数在调用过程中会发生阻塞。
读操作中的read、recv、recvfrom
写操作中的write、send
其他操作:accept、connect

写操作也是有阻塞的:当缓冲区满的时候,写操作就会阻塞,
直到缓冲区中有足够的空间能容纳下本次的写操作时,
写操作的阻塞就会解除,只不过一般情况下,我们一般不考虑写阻塞的问题。

以读阻塞为例:
当程序执行到读操作时,如果缓冲区中有内容,则直接读走继续向下执行。
如果缓冲区中没有内容,进程就会进入休眠态(阻塞),直到缓冲区中有内容了
内核唤醒该进程,去缓冲区中把内容读走,继续向下执行。

以管道为例,演示读阻塞

读端:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
   
	int fd1 = open("fifo1", O_RDONLY);
	int fd2 = open("fifo2", O_RDONLY);
	int fd3 = open("fifo3", O_RDONLY);
	
	char buff[128] = {
   0};

	while(1){
   
		memset(buff, 0, 128);
		read(fd1, buff, 128);
		printf("fd1:[%s]\n", buff);

		memset(buff, 0, 128);
		read(fd2, buff, 128);
		printf("fd2:[%s]\n", buff);

		memset(buff, 0, 128);
		read(fd3, buff, 128);
		printf("fd3:[%s]\n", buff);
	}

	close(fd1);
	close(fd2);
	close(fd3);

	return 0;
}

三个一样的写端:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
   
	int fd = open("fifo1", O_WRONLY);
	
	char buff[128] = {
   0};

	while(1){
   
		memset(buff, 0, 128);
		fgets(buff, 128, stdin);
		buff[strlen(buff)-1] = '\0';
		write(fd, buff, 128);
	}

	close(fd);

	return 0;
}

非阻塞IO

以读操作为例:
当程序执行到读操作时,如果缓冲区中有内容,则直接读走继续向下执行。
如果缓冲区中没有内容,进程不会进入休眠态,而是立即返回一个错误。但是这种操作,需要轮询,来不停的测试是否有数据可读,这种轮询的操作,是十分占用CPU的,一般不推荐使用。有一部分函数,是自带非阻塞标志位的
如:waitpid的WNOHANG recv和recvfrom的MSG_DONTWAIT O_NONBLOCK
但是大部分的函数是没有非阻塞标志位的,这时可以使用 fcntl 函数来设置非阻塞的属性

fcntl 函数说明
功能:
	控制文件描述符的状态
头文件:
	#include <unistd.h>
	#include <fcntl.h>
函数原型:
	int fcntl(int fd, int cmd, ... /* arg */ );
参数:
	fd:文件描述符
	cmd:指令
		F_GETFL		获取文件描述符的状态  arg被忽略
		F_SETFL		设置文件描述符的状态  arg是一个int类型的参数
					文件描述符的状态中 O_NONBLOCK 表示非阻塞
	...:可变参
返回值:
	cmd是F_GETFL 成功 返回文件描述符的状态  失败 返回 -1 重置错误码
	cmd是F_SETFL 成功 0 失败 -1 重置错误码

使用 fcntl 设置非阻塞
int value = fcntl(fd, F_GETFL);//先获取原来的状态信息
value |= O_NONBLOCK;//添加非阻塞属性
fcntl(fd, F_SETFL, value);//再设置回去

读端:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
   
	int fd1 = open("fifo1", O_RDONLY);
	int fd2 = open("fifo2", O_RDONLY);
	int fd3 = open("fifo3", O_RDONLY);
	
	//设置非阻塞
	int flag = fcntl(fd1, F_GETFL);
	flag |= O_NONBLOCK;
	fcntl(fd1, F_SETFL, flag);
	flag = fcntl(fd2, F_GETFL);
	flag |= O_NONBLOCK;
	fcntl(fd2, F_SETFL, flag);
	flag = fcntl(fd3, F_GETFL);
	flag |= O_NONBLOCK;
	fcntl(fd3, F_SETFL, flag);

	char buff[128] = {
   0};

	while(1){
   
		memset(buff, 0, 128);
		read(fd1, buff, 128);
		printf("fd1:[%s]\n", buff);

		memset(buff, 0, 128);
		read(fd2, buff, 128);
		printf("fd2:[%s]\n", buff);

		memset(buff, 0, 128);
		read(fd3, buff, 128);
		printf("fd3:[%s]\n", buff);
		//sleep(1);//此处的sleep是为了演示现象时防止刷屏的
				//把sleep去掉 可以看到 CPU基本被 读端进程占满了
	}

	close(fd1);
	close(fd2);
	close(fd3);

	return 0;
}

三个一样的写端

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>

int main(int argc, const char *argv[])
{
   
	int fd = open("fifo1", O_WRONLY);
	
	char buff[128] = {
   0};

	while(1){
   
		memset(buff, 0, 128);
		fgets(buff, 128, stdin);
		buff[strlen(buff)-1] = '\0';
		write(fd, buff, 128);
	}

	close(fd);

	return 0;
}

多路IO复用

基本思路:
构造一个关于文件描述符的表,将所有要监视的文件描述符都添加到这个表里
将这个表传给一个函数 (select poll epoll),这个函数默认也是阻塞的。当监视的文件描述符中有一个或多个准备就绪时,该函数就会返回。并且会告诉我们,哪些文件描述符准备就绪了,我们已经知道哪些文件描述符就绪了,再去执行对应的IO操作,就不会再阻塞了
在这里插入图片描述

select 函数说明
功能:
	实现多路IO复用
头文件:
	#include <sys/select.h>
函数原型:
	int select(int nfds, fd_set *readfds, fd_set *writefds,
                fd_set *exceptfds, struct timeval *timeout);
参数:
	nfds:		要监视的最大的文件描述符+1
	readfds:	要监视的 读 文件描述符集合	不关心可以传 NULL
	writefds:	要监视的 写 文件描述符集合	不关心可以传 NULL
	exceptfds:	要监视的 异常 文件描述符集合 不关心可以传 NULL
	timeout:	超时时间 后面在说 传 NULL 表示一直阻塞 直到有文件描述符就绪
返回值:
	成功  就绪的文件描述符的个数
	失败  -1 重置错误码
	超时  0

注意事项:
	1.select只能监视小于 FD_SETSIZE(1024) 的文件描述符
	2.select返回时会将集合修改,所以循环中调用select时
		每次需要重置要监视的文件描述符集合
	3.我们一般只关心读文件描述符结合 写和异常传NULL 即可

操作集合的四个宏
	void FD_CLR(int fd, fd_set *set);	//将文件描述符在集合中删除
	int  FD_ISSET(int fd, fd_set *set);	//判断 文件描述符是否还在集合中
										//在 返回非0  不在 返回0
	void FD_SET(int fd, fd_set *set);	//将文件描述符添加到集合中
	void FD_ZERO(fd_set *set);			//清空文件描述符集合

读端代码:

#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/select.h>
#include <stdlib.h>

int main(int argc, const char *argv[])
{
   
	int fd1 = open("fifo1", O_RDONLY);
	int fd2 = open("fifo2", O_RDONLY);
	int fd3 = open("fifo3", O_RDONLY);
	
	//构建要监视的文件描述符集合
	fd_set readfds;//母本
	FD_ZERO(&readfds);//先清空
	fd_set readfds_temp;//用来给select修改用
	FD_ZERO(&readfds_temp);//先清空
	
	int max_fd = 0;//保存最大文件描述符
	int ret = 0;
	//将要监视的文件描述符添加到集合中
	FD_SET(fd1, &readfds);
	max_fd = max_fd>fd1?max_fd:fd1;//更新最大文件描述符
	FD_SET(fd2, &readfds);
	max_fd = max_fd>fd2?max_fd:fd2;//更新最大文件描述符
	FD_SET(fd3, &readfds);
	max_fd = max_fd>fd3?max_fd:fd3;//更新最大文件描述符

	char buff[128] = {
   0};
	int i = 0;

	while(1){
   
		readfds_temp = readfds;//select每次返回会将没有就绪的文件描述符在集合中擦除
							//所以每次需要重置集合
		if(-1 == (ret = select(max_fd+1, &readfds_temp, NULL, NULL, NULL))){
   
			perror("select error");
			exit(-1);
		}
		//遍历文件描述符集合 确定哪些就绪
		for(i = 3; i < max_fd+1 && ret!=0 ; i++){
   
			if(FD_ISSET(i, &readfds_temp)){
   
				memset(buff, 0, 128);
				read(i, buff, 128);
				printf("buff:[%s]\n", buff);
				ret--;//此处的ret--是为了减少遍历次数 提高效率的
			}
		}

		//select返回时 集合中剩余的就是就绪的
      //使用上面的方式处理更好些
		#if 0
		if(FD_ISSET(fd1, &readfds_temp)){
   
			memset(buff, 0, 128);
			read(fd1, buff, 128);
			printf("fd1:[%s]\n", buff);
		}
		if(FD_ISSET(fd2, &readfds_temp)){
   
			memset(buff, 0, 128);
			read(fd2, buff, 128);
			printf("fd2:[%s]\n", buff);
		}
		if(FD_ISSET(fd3, &readfds_temp)){
   
			memset(buff, 0, 128);
			read(fd3, buff, 128);
			printf("fd3:[%s]\n", buff);
		}
		#endif
	}

	close(fd1);
	close(fd2);
	close(fd3);

	return 0;
}

三个一样的写端

#include <stdio.h>
#include <sys/stat.h>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
linux C语言 网络编程教程及源码 一、网络应用层编程 1、Linux网络编程01——网络协议入门 2、Linux网络编程02——无连接和面向连接的区别 3、Linux网络编程03——字节序和地址转换 4、Linux网络编程04——套接字 5、Linux网络编程05——C/S与B/S架构的区别 6、Linux网络编程06——UDP协议编程 7、Linux网络编程07——广播 8、Linux网络编程08——多播 9、Linux网络编程09——TCP编程之客户端 10、Linux网络编程10——TCP编程之服务器 11、Linux网络编程11——tcp、udp迭代服务器 12、Linux网络编程12——tcp三次握手、四次挥手 13、Linux网络编程13——connect()、listen()和accept()三者之间的关系 14、Linux网络编程14——I/O复用之select详解 15、Linux网络编程15——I/O复用之poll详解 16、Linux网络编程16——I/O复用之epoll详解 17、Linux网络编程17——tcp并发服务器(多进程) 18、Linux网络编程18——tcp并发服务器(多线程) 19、Linux网络编程——tcp高效并发服务器(select实现) 20、Linux网络编程——tcp高效并发服务器(poll实现) 21、Linux网络编程——tcp高效并发服务器(epoll实现) 二、网络底层编程(黑客模式) 1、Linux网络编程1——啥叫原始套接字 2、Linux网络编程2——原始套接字编程 3、Linux网络编程3——原始套接字实例:MAC头分析 4、Linux网络编程4——原始套接字实例:MAC地址扫描器 5、Linux网络编程5——IP数据报格式详解 6、Linux网络编程6——TCP、UDP数据包格式详解 7、Linux网络编程7——原始套接字实例:发送UDP数据包 8、Linux网络编程8——libpcap详解 9、Linux网络编程9——libnet详解

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值