tcp,select函数支持I/O复用

Unix函数select和poll, 用来支持Unix中I/O复用的功能,在Unix中I/O模型可以分为以下几种:

(1)阻塞I/O

(2)非阻塞I/O

(3)I/O复用(select和poll)

(4)信号驱动I/O(SIGIO)

(5)异步I/O

阻塞I/O是当应用程序和内核交换数据时,由于内核还没有准备好数据,那么应用程序必须进行阻塞,不能继续执行,直到内核的数据准备好!应用程序取到数据返回后,阻塞过程结束!但返回的结果也并不一定是正确的!这里只是举一个简单的例子!也许情况会更加的复杂!

    非阻塞I/O,例如在和内核交换数据时,如果内核的数据没有准备好,那么应用程序不会一真等待,会有一个返回信息,以判断是那里出了问题!这样有助于确认是在那个阶段出了问题!

I/O复用,我们就可以调用系统调用select和poll!在这两个系统调用中的某一个阻塞,而不是真正的阻塞I/O系统调用!

下面主要介绍I/O复用中的select函数。select函数可以指示内核等待多个事件中的任一个发生,仅在一个或多个事件发生,或者等待一个足够的时间后才唤醒进程!


select函数原型

#include<sys/types.h>

#include<sys/time.h>


int select(int maxfdp1, fd_set *readset,fd_set * writeset,fd_set *excpetset, const struct timeval *timeout);

int maxfdpl是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!

比如我们的描述符为1 4 5,那么maxfdp1就为6.描述符从0开始。


struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。

中间的三个参数readset 、writeset和excpetset指定我们要让内核测试读、写、异常条件所需的描述字!函数select使用描述字集,它一般是一个整型的数组,每个数中的每一位代表一个描述符!

fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 

fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 

fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。


系统提供了4个宏对描述符集进行操作:

#include <sys/select.h>

#include <sys/time.h>

//设置文件描述符集fdset中对应于文件描述符fd的位

void FD_SET(int fd, fd_set *fdset);  // (将一个给定的文件描述符加入集合之中)

//清除文件描述符集fdset中对应于文件描述符fd的位(设置为0)

void FD_CLR(int fd, fd_set *fdset);  // (将一个给定的文件描述符从集合中删除)

//清除文件描述符集fdset中的所有位(既把所有位都设置为0)

void FD_ZERO(fd_set *fdset);  // (清空集合)

Select()

//来检测文件描述符集fdset中对应于文件描述符fd的位是否被设置。

void FD_ISSET(int fd, fd_set *fdset);  //(检查集合中指定的文件描述符是否可以读写)

struct timeval,这个

参数是一个结构体的指针,它表示等待内核中的一组描述符任一个准备好需要花费多久的时间!其中timeval指定了秒数和微秒数。

struct timeval{

         long tv_sec;//秒数

         long tv_usec;//微秒数

};

 

   将 timeout设置为空指针时,会永远等待下去,等待固定的时间:如果timeout指向的timeval中的具体的值时,会等待一个固定的时间,不等待立刻返回,

这时timeval中的tv_sec和tv_usec为0.


返回值,

select有三个可能的返回值。

1.正常情况下返回就绪的文件描述符个数;

2.经过了timeout时长后仍无设备准备好,返回值为0;

3.如果select被某个信号中断,它将返回-1并设置errno为EINTR。

4.如果出错,返回-1并设置相应的errno。

EBADF 文件描述词为无效的或该文件已关闭

EINTR 此调用被信号所中断

EINVAL 参数n 为负值。

ENOMEM 核心内存不足


示例代码:

服务端代码:

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

#define LISTENNUM 5 //listen队列
#define PORT 1234 //端口
#define MAXDATASIZE 1024 //缓冲区

int main()
{
int i,maxi,maxsocketfd,socketfd;
int isready;
fd_set rset,allset;//select文件描述符集合
int listenfd,connectfd;
struct sockaddr_in server;
int fd[FD_SETSIZE];//最大描述符个数FD_SETSIE,最多1024
char recvbuf[MAXDATASIZE];
int sin_size;
int reclen;

if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
perror("Socket failed.\n");
exit(1);
}

int opt = SO_REUSEADDR;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//设置socket
bzero(&server,sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);

if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1)
{
perror("Bind fail.\n");
exit(1);
}

if(listen(listenfd,LISTENNUM) == -1)
{
perror("Listen fail.\n");
exit(1);
}

maxsocketfd = listenfd;
maxi = -1;
for(i = 0; i < FD_SETSIZE; i++)
{
fd[i] = -1;
}

FD_ZERO(&allset);
FD_SET(listenfd,&allset);

while(1)
{
struct sockaddr_in addr;
rset = allset;
isready = select(maxsocketfd+1,&rset,NULL,NULL,NULL);

if(FD_ISSET(listenfd,&rset))
{
printf("Accept a connection.\n");
sin_size = sizeof(struct sockaddr_in);
if((connectfd = accept(listenfd,(struct sockaddr*)&addr,(socklen_t*)&sin_size)) == -1)
{
perror("Accept error.\n");
continue;
}
for(i = 0;i < FD_SETSIZE;i++)
{
if(fd[i] < 0)
{
fd[i] = connectfd;
break;
}
}
if(i == FD_SETSIZE)
{
printf("Clients over range.\n");
exit(1);
}

FD_SET(connectfd,&allset);

maxsocketfd =  maxsocketfd > connectfd ? maxsocketfd : connectfd;
maxi = maxi > i ? maxi : i;
if(--isready <= 0) 
continue;
}
for(i =0;i <= maxi;i++)
{
if((socketfd = fd[i]) < 0)
continue;
if(FD_ISSET(socketfd,&rset))
{
printf("Have connect.\n");
if((reclen = recv(socketfd,recvbuf,MAXDATASIZE,0)) == 0)
{
close(socketfd);
FD_CLR(socketfd,&allset);
fd[i] = -1;
}
else
printf("Recv: %s\n",recvbuf);
}
}
}
close(listenfd);
}

客户端代码:

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

#define PORT 1234
#define MAXDATASIZE 1024

int main()
{
        int clifd,len,sendlen;
char sendbuf[MAXDATASIZE];
        clifd = socket(AF_INET,SOCK_STREAM,0);
        struct sockaddr_in clisock;
        clisock.sin_family = AF_INET;
        clisock.sin_port = htons(PORT);
        clisock.sin_addr.s_addr = inet_addr("192.168.1.15");
        connect(clifd,(struct sockaddr *)&clisock,sizeof(clisock));
strcpy(sendbuf,"Hello!");
sendlen = sizeof(sendbuf);
        while(1)
        {
send(clifd,sendbuf,sendlen,0);
sleep(1);
        }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值