I/O复用(一)

17 篇文章 4 订阅
12 篇文章 0 订阅
I/O复用技术允许程序同时监听多个文件描述符,适用于处理多socket、用户输入和网络连接等场景。尽管它是阻塞的,但通过多进程或线程可实现并发。在Linux下,主要的I/O复用系统调用包括select、poll和epoll。select函数通过nfds、readfds、writefds和exceptfds参数监听文件描述符的读写异常事件,并通过timeout设置超时时间。当有文件描述符就绪时,返回其数量;超时则返回0;遇到错误返回-1,并根据errno判断具体原因。
摘要由CSDN通过智能技术生成

      I/O复用使得程序能同时监听多个文件描述符,这对提高程序的性能至关重要。通常,网络程序在下列情况下需要使用I/O复用技术。

   1.客户端程序要同时处理多个socket。

   2.客户端程序要同时处理用户输入和网络连接。

   3.TCP服务器要同时处理监听socket和连接socket。

   4.服务器要同时监听多个端口,或者处理多种服务。

I/O复用虽然能同时监听多个文件描述符,但她本身是阻塞的,并且当多个文件描述符同时就绪时,如果不采取措施,程序就只能按顺序依次处理其中的每一个文件描述符。这使得服务器看起来就像是穿行工作的。如果要实现并发,只能使用多进程或多线程等。


Linux下实现I/O复用的系统调用主要有select、poll和epoll.

select:

在一段指定时间内,监听用户感兴趣的文件描述符上的可读、可写和异常事件。

select API


参数:

1.nfds参数指定被监听的文件的文件描述符的总数。它通常被设置为select监听的所有文件描述符的最大值加1,因为文件描述符是从0开始计数的。

2.readfds、writefds和exceptfds参数分别指向可读、可写和异常等事件对应的文件描述符集合。这三个参数都是输入输出型参数。

3.timeout参数用来设置select函数的超时时间。它是一个timeval结构体类型的指针,采用指针参数是因为内核将修改它以告诉应用程序select等待了多久。不过我们不能完全相信select调用返回后的timeout值,比如调用失败时timeout的值是不确定的。select成功时返回就绪文件描述符的个数。如果在超时时间内没有任何文件描述符就绪,select将返回0.select失败是返回-1并设置errno。如果在select等待期间,若程序收到信号,则select立即返回-1,并设置errno为EINTR。errno为EBADF时,文件描述符为无效或该文件已关闭。为EINVAL时,参数n为负值。为ENOMEM时,核心内存不足。

server.c

/*************************************************************************
	> File Name: server.c
	> Author: ZX
	> Mail: 18829897183@163.com 
	> Created Time: Wed 15 Mar 2017 02:15:31 AM PDT
 ************************************************************************/

#include <stdio.h>
#include <assert.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#define _SIZE_ 128
int gfd[_SIZE_];


int statup(const char* _ip, int _port)
{
	assert(_ip);

	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if(sock < 0)
	{
		perror("sock");
		exit(1);
	}

	int opt = 1;
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));//链接建立后,client方主动断开链接后短时间内都无法再建立链接。

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_addr.s_addr = inet_addr(_ip);
	local.sin_port = htons(_port);

	if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
	{
		perror("bind");
		exit(2);
	}

	if(listen(sock, 5) < 0)
	{
		perror("listen");
		exit(3);
	}

	return sock;
}

int main(int argc, char* argv[])
{
	if(argc != 3)
	{
		printf("Usage: %s [local_ip] [local_port]",argv[0]);
		exit(4);
	}

	int listen_sock = statup(argv[1],atoi(argv[2]));
	//设置位无效,全都设置为-1
	int i = 0;
	for(; i<_SIZE_; i++)
	{
		gfd[i] = -1;
	}

	int index = 0;
	gfd[index++] = listen_sock;



	while(1)
	{
		struct timeval timeout = {5,0};
		fd_set rfds;
		FD_ZERO(&rfds);//clear rfds

		int j = 0;
		int max_fd = -1;
		for(; j<_SIZE_; j++)
		{
			if(gfd[j] > 0)
			{
				FD_SET(gfd[j], &rfds);//将数组的文件描述符设置进读文件描述符集

				if(max_fd < gfd[j])
				{
					max_fd = gfd[j];
				}
			}
		}

		switch(select(max_fd+1, &rfds, NULL, NULL, &timeout))
		{
			case 0://select返回0,表示超时
				perror("time out");
				break;
			case -1://出错
				perror("select");
				break;
			default:
				{
					int k = 0;
					for(; k<_SIZE_; k++)
					{
						if(gfd[k] < 0)
						{
							continue;
						}
						if(k==0 && FD_ISSET(listen_sock, &rfds))
						{
							struct sockaddr_in peer;
							socklen_t len = sizeof(peer);
							int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
							if(sock < 0)
							{
								perror("accept");
								exit(5);
							}
							if(sock > 0)
							{
								printf("get a new client ip:%s port:%d\n",inet_ntoa(peer.sin_addr),\
										ntohs(peer.sin_port));
								int m = 0;
								for(; m<_SIZE_; m++)
								{//找到最小的无效的位置,将信道套接字(文件描述符)设置到无效位
									if(gfd[m] == -1)
									{
										gfd[m] = sock;
										break;
									}

								}
								if(m == _SIZE_)
								{//没有位置了,无法处理,就关闭文件描述符
									printf("m == _SZIE_\n");
									close(sock);
								}
							}
						}
						else if(FD_ISSET(gfd[k], &rfds))
						{
							char buf[1024];
							memset(buf, 0, sizeof(buf)-1);
							ssize_t _s = read(gfd[k], buf, sizeof(buf)-1);
							if(_s > 0)
							{
								printf("client# %s\n", buf);
							}
							else if(_s == 0)
							{
								printf("client is quit...\n");
								close(gfd[k]);
								gfd[k] = -1;
							}
							else
							{
								perror("read");
							}

						}
					}
				}
				break;
		}

	}
	return 0;
}
Makefile

.PHONY:all
all:server client

client:client.c
	gcc -o $@ $^
server:server.c
	gcc -o $@ $^

.PHONY:clean
clean:
	rm -f server client

客户端没有变化










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值