UNIX网络编程3 使用select()

select函数必需的头文件:<sys/select.h>和<sys/time.h>。这个函数允许进程指示内核等待多个事件(任何描述符字准备好读或写或有异常条件待处理)中的任一个发生,并仅在事件发生或经过某指定的时间后才唤醒进程。所以调用select的进程会在这个函数阻塞,等待事件发生或者超时。

函数原型:select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
传递给select函数的参数会告诉内核:我们所关心的文件描述符;对每个描述符我们所关心的状态;我们要等待多长时间。
从select返回后,内核告诉我们:对我们的要求,已经准备好的描述符个数。

参数说明:
第一个参数int型,指定select中监视的文件句柄数。
三个fd_set参数是类似的,分别代表监视的读、写、异常文件句柄集合。对应的监视集合中有对应的事件发生,则select返回正数,若没有事件发生并超时,select返回0,若发生错误则返回0。struct fd_set是一个存放文件描述符的集合,实际上是一个long类型的数组,每一个数组元素都能与一打开的文件句柄建立联系,但建立联系的工作要由我们手动完成,FD_SET(int fd, fd_set *fdset)建立文件句柄fd与fdset的联系,同理有FD_CLR、FD_ISSET还有FD_ZERO。
timeout参数指定超时时间,若传入NULL(即不传入时间结构),select就是阻塞状态直到有事件返回;若设置为0秒0毫秒(不是NULL),select就是非阻塞函数,不管文件描述符是否有变化都立即返回(non-blocking);若时间大于0,则select在timeout时间内阻塞,期间有事件到来就返回,否则在超时后返回。

简单的使用流程:
1)套接字句柄-socket描述符
假设服务端有一个listenfd,用于侦听客户端连接
int listenfd = socket(...);
然后bind和listen
2)将描述符加入到读(写、异常)监视集合中
FD_ZERO(&rdfds);
FD_SET(listenfd, &rdfds);
3)设置超时
struct timeval tv;
tv.tv_sec = ...;
tv.tv_usec = ...;
4)调用select
一般情况下只用到读事件
ret = select(maxfd+1, &rdfds, NULL, NULL, &tv);

if (ret < 0)
     perror("select error");
else if (ret == 0)
     printf("超时\n");
else
{
     //如果listenfd这个被监视的描述符变成可读
     if (FD_ISSET(listenfd, &rdfds))
     {
          //说明有新的client连接,将新的连接的socket描述符加入到读监视集合中
          connfd = accept(...);
          FD_SET(connfd, &allset);
          //还需要保存这个客户端socket到client队列中,用于后续的通信
     }
     //遍历client队列,查看是哪个socket读就绪
     for(...)
     {
          ...
          FD_ISSET(..)
          //如果某个客户端socket退出,则清除对它的监视
          FD_CLR(...) 
     }
}

服务端使用select的源程序:
//:selectServer.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/types.h>
#include <sys/socket.h>

#define BUF_LEN 1024
#define SERV_PORT 6000
#define FD_SIZE 100
#define MAX_BACK 100

int main(int argc, char **argv)
{
     int listenfd, connfd, sockfd, maxfd, maxi, i;
     int nready, client[FD_SIZE]; //接收select返回值,保存客户端套接字
     int lens;
     size_t n; //read字节数
     //fd_set的数据结构,实际上是一long类型的数组,
     //每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系
     fd_set rset, allset;
     char buf[BUF_LEN];
     socklen_t clilen;
     struct sockaddr_in servaddr, cliaddr;

     if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
     {
          printf("Create socket Error: %d\n", errno);
          exit(EXIT_FAILURE);
     }
     //bzero是传统BSD函数,属于POSIX标准,使用头文件string.h,bzero无返回值,推荐使用memset替代bzero
     bzero(&servaddr, sizeof(servaddr));
     servaddr.sin_family = AF_INET;
     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
     servaddr.sin_port = htons(SERV_PORT);

     if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) == -1)
     {
          printf("Bind Error: %d\n", errno);
          exit(EXIT_FAILURE);
     }

     if (listen(listenfd, MAX_BACK) == -1)
     {
          printf("Listen Error: %d\n", errno);
          exit(EXIT_FAILURE);
     }

     maxfd = listenfd;
     maxi = -1;
     for(i = 0; i < FD_SIZE; i ++)
     {
          client[i] = -1;
     }
     FD_ZERO(&allset);
     //把服务端侦听的listenfd放到allset中
     FD_SET(listenfd, &allset);

     while(1)
     {
          rset = allset;
          //select(最大句柄数+1, 监视的文件句柄集合, 写, 异常, 超时)
          if ((nready = select(maxfd + 1, &rset, NULL, NULL, NULL)) == -1)
          {
               printf("Select Error: %d\n", errno);
               exit(EXIT_FAILURE);
          }
          if (nready <= 0)
          {
               continue;
          }

          //如果是侦听的listenfd就绪,则说明是有新的连接进入
          if(FD_ISSET(listenfd, &rset))
          {
               clilen = sizeof(cliaddr);
               printf("Start doing...\n");
               //新连接
               if ((     ) == -1)
               {
                    printf("Accept Error: %d\n", errno);
                    continue;
               }
               for (i = 0; i < FD_SIZE; i ++)
               {
                    //保存这个新的client fd
                    if (client[i] < 0)
                    {
                         client[i] = connfd;
                         break;
                    }
               }
               if (i == FD_SIZE)
               {
                    printf("To many clients...Reject\n");
                    close(connfd);
                    continue;
               }
               FD_SET(connfd, &allset);
               if (connfd > maxfd)
               {
                    maxfd = connfd;
               }
               if (i > maxi)
               {
                    maxi = i;
               }
          }

          //否则轮训一遍,看看是哪个客户端socket的读就绪
          for (i = 0; i <= maxi; i ++)
          {
               if ((sockfd = client[i]) > 0)
               {
                    if (FD_ISSET(sockfd, &rset))
                    {
                         memset(buf, 0, sizeof(buf));
                         n = read(sockfd, buf, BUF_LEN);
                         if (n < 0)
                         {
                              printf("Error!\n");
                              close(sockfd);
                              FD_CLR(sockfd, &allset);
                              client[i] = -1;
                              continue;
                         }
                         if (n == 0)
                         {
                              printf("No data\n");
                              //close(sockfd);
                              //FD_CLR(sockfd, &allset);
                              //client[i] = -1;
                              continue;
                         }

                         printf("Server Recv: %s\n", buf);
                         if (strcmp(buf, "q") == 0)
                         {
                              close(sockfd);
                              FD_CLR(sockfd, &allset);
                              client[i] = -1;
                              continue;
                         }
                         printf("Server send: %s\n", buf);
                         write(sockfd, buf, n);
                    }
               }
          }
     }
     return 0;
}

简单的客户端程序:
//:selectClient.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

#define BUF_LEN 1024
#define SERVER_PORT 6000

int main(int argc, char const **argv)
{
     int connfd;
     int message_len;
     struct sockaddr_in remote_addr;
     char buf[BUF_LEN];
     memset(&remote_addr, 0, sizeof(remote_addr));
     memset(buf, 0, BUF_LEN);
     remote_addr.sin_family = AF_INET;
     remote_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
     remote_addr.sin_port = htons(SERVER_PORT);
     connfd = socket(AF_INET, SOCK_STREAM, 0);
     if (connfd < 0)
     {
          //perror和strerror都是C语言提供的库函数,用于获取与errno相关的错误信息
          //不同的是perror向stderr输出结果,strerror(errno)向stdout输出结果
          perror("socket error!");
          return -1;
     }
     if (connect(connfd, (struct sockaddr *)&remote_addr, sizeof(struct sockaddr)) < 0)
     {
          perror("connect error!");
          return -1;
     }
     printf("Connected! You can send message.\n");
     while(1)
     {
          gets(buf);
          if (strcmp(buf, "q") == 0)
          {
               write(connfd, buf, message_len);
               printf("Exit.\n");
               break;
          }
          if ((message_len = strlen(buf)) > 0)
          {
               write(connfd, buf, message_len);
               memset(buf, 0, BUF_LEN);
               message_len = read(connfd, buf, BUF_LEN);
               if (message_len > 0)
               {
                    printf("Receive from server: %s\n", buf);
               }
          }
     }
     //客户端主动断开连接
     close(connfd);
     return 0;
}

参考资料:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值