socket编程中select()函数服务器端的使用

根据网络视频整理:

问题1:socket编程中,为何select放在while(1)中?

因为select需要将监听到多个客户端分别 fd = accept()一下,然后再进行读写判断。

select()在服务器端和客户端的区别:

服务器端多了一个以的形式监听“建立连接的”lfd。(lfd = socket();)其他读写的fds(fd = accept();)

客户端中只有监听读、写的连接。

多路IO转接,通过内核来监听多个客户端的连接,监听对端发送数据,代替accept函数的监听阻塞功能了。

故:

1)不需要阻塞等待连接(accept不会阻塞了)。

2)不需要阻塞去读对端(read不会阻塞了),这样server.c就可以解放去做其他事情了。

内核一旦给返回了,一定是事件发生了。

服务器通过accept监听客户端1,首先是建立连接的的请求,然后是读写请求;

服务器通过accept监听客户端2,首先是建立连接的的请求,然后是读写请求;

但是会在select()这里进行阻塞,select()函数是在accept()函数前面的,帮忙解决了accept的阻塞,read函数的阻塞。

 listenfd也应该在读事件中(服务器监听客户端,相当于读操作),listenfd只需要一个就行了,每accept()一次,会多一个客户端的fd

内核监听是通过select函数来监听的。

#include <sys/select.h>

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

参数解释:

nfds:监听的文件描述符(fd)中,最大的那个+1;(多个fd,多路复用的体现)+1的实质是指监听了前n+1个描述符0,1,2,3

readfds:文件描述符可读事件,数据类型是个位图,本质上是一个long数组的各个位。

writefds:文件描述符可写事件

exceptfds:文件描述符异常事件

timeout:阻塞时间,如果监听的事件在timeout时间内都没有来,则停止阻塞,则NULL,则select永久等待。

readfds\writefds\exceptfds都是传入传出参数

struct timeval{     //都是相对时间

long tv_sec;  //秒

long tv_usec;  //微秒

};

timeout的值决定了select函数的返回状态:

1)传入NULL,就将select函数置于阻塞状态,一定要等到监视的文件描述符有变化才会返回

2)将时间设为0秒0毫秒,就成为一个非阻塞的函数,不管文件描述符有无变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值。

3)设为大于0的值,即为等待的超时时间,即在timeout时间内阻塞,如为超时,且有事件到来则返回,否则超时后不管怎么一定返回,返回值同2)

(ps:select是通过timeout值来避免阻塞的吧。。。,只要不设为NULL,就不会阻塞)

返回值:

1)如果成功,则返回的是所监听的所有监听集合满足条件的总数:是读+写+异常的和

2)等待超时,没有可读写的文件,则返回0

3)失败,则返回-1,并设置errno

void FD_ZERO(fd_set *set);//将set清空为0//位操作 0000  0000//每次都要清零

void FD_SET(int fd,fd_set *set);//将fd设置到set集合中,即将文件描述符中fd的位置置1

int  FD_ISSET(int fd,fd_set *set);//判断fd是否在集合中//返回1,满足条件;返回0,不满足条件

void FD_CLR(int fd,fd_set *set);//将fd在set中清除,即将描述符在位图中的对应位置置0,对端关闭了,将描述符清除。

fd_set readfds;
FD_ZERO(&readfsd);
FD_SET(lfd,&readfsd);//监听连接文件描述符lfd,相当于读事件。
FD_SET(fd1,&readfsd);//监听fd1的读事件
FD_SET(fd2,&readfsd);//监听fd2的读事件
FD_SET(fd3,&readfsd);//监听fd3的读事件
n = select(fds+1,&readfds,NULL,NULL,NULL);//readfds是传入传出参数,有事件发生则该值发生变化

    FD_ISSET(fd1,&readfsd);
    accept....

 readfds、writefds、exceptfds是传入传出参数,如上,当传入到select后,readfds传入的有3值,假如此时只有fd1和fd2中有读事件发生,则select函数返回,则readfds中传出的是2值

readfds传入

fd3

fd2

fd1

lfd

位图

...

0

1

1

1

1

readfds传出

fd3

fd2

fd1

lfd

位图

...

0

0

1

1

1

 传入的时候位图全是1,传出的时候,没有读事件发生的置0了。

所以,如上述程序,readfds传入前位图中有4个1,假设lfd,fd1和fd2有读事件了,则select返回值n=3.位图中fd3被置0了。因此会使用数组来存储要监听的文件描述符。

文件描述符上限是1024,所以select同时监听的文件描述符最大是1024个。

一般用的时候,会定义数组来存储所监听的fd,否则,select去找的话,会从3遍历到1023,浪费时间(0.1.2都被占用)。

因为 readfds、writefds、exceptfds是传入传出参数,所以要对上一次的这三个值进行保存,否则这次就会被改写了。即,监听集合和满足条件的集合是一个集合,为了防止下次监听的集合被改写,所以先对原先的集合进行保存。

总结:

1)select函数及以上的代码写好后,就完成了内核监听注册;

2)然后通过FD_ISSET(fd1,&readfsd);判断到底是哪个fd的事件是否发生。

3) 在设置timeout参数为NULL的情况下,select函数若返回,一定是有监听事件满足了。

具体步骤:

1)FD_set(lfd,&rset); maxfd=lfd;

2)select(madfd+1,&rset,,)//只是在监听lfd

3)判断FD_ISSET(lfd),//进行判断是否有连接,为1表明有客户端在请求连接

4)接着调用confd = accept(),返回一个新的文件描述符,服务器与客户端建立连接。

5)FD_SET(confd,&rset);if(confd>maxfd) maxfd = confd;

6)selcet(maxfd+1,&rset,,,);

7)FD_ISSET(confd,&rset),为1,则read.......

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

#include "wrap.h"

#define SERV_PORT 6666

int main(int argc, char *argv[])
{
    int i, j, n, maxi;

    int nready, client[FD_SETSIZE];                 /* 自定义数组client, 防止遍历1024个文件描述符  FD_SETSIZE默认为1024 */
    int maxfd, listenfd, connfd, sockfd;
    char buf[BUFSIZ], str[INET_ADDRSTRLEN];         /* #define INET_ADDRSTRLEN 16 */

    struct sockaddr_in clie_addr, serv_addr;
    socklen_t clie_addr_len;
    fd_set rset, allset;                            /* rset 读事件文件描述符集合 allset用来暂存 */

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sin_family= AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port= htons(SERV_PORT);

    Bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    Listen(listenfd, 128);

    maxfd = listenfd;                                           /* 起初 listenfd 即为最大文件描述符 */

    maxi = -1;                                                  /* 将来用作client[]的下标, 初始值指向0个元素之前下标位置 */
    for (i = 0; i < FD_SETSIZE; i++)
        client[i] = -1;                                         /* 用-1初始化client[] */

    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);                                  /* 构造select监控文件描述符集 */

    while (1) {   
        rset = allset;                                          /* 每次循环时都从新设置select监控信号集 */
        nready = select(maxfd+1, &rset, NULL, NULL, NULL);
        if (nready < 0)
            perr_exit("select error");

        if (FD_ISSET(listenfd, &rset)) {                        /* 说明有新的客户端链接请求 */

            clie_addr_len = sizeof(clie_addr);
            connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);       /* Accept 不会阻塞 */
            printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
                    ntohs(clie_addr.sin_port));

            for (i = 0; i < FD_SETSIZE; i++)
                if (client[i] < 0) {                            /* 找client[]中没有使用的位置 */
                    client[i] = connfd;                         /* 保存accept返回的文件描述符到client[]里 */
                    break;
                }

            if (i == FD_SETSIZE) {                              /* 达到select能监控的文件个数上限 1024 */
                fputs("too many clients\n", stderr);
                exit(1);
            }

            FD_SET(connfd, &allset);                            /* 向监控文件描述符集合allset添加新的文件描述符connfd */
            if (connfd > maxfd)
                maxfd = connfd;                                 /* select第一个参数需要 */

            if (i > maxi)
                maxi = i;                                       /* 保证maxi存的总是client[]最后一个元素下标 */

            if (--nready == 0)
                continue;
        } 

        for (i = 0; i <= maxi; i++) {                               /* 检测哪个clients 有数据就绪 */

            if ((sockfd = client[i]) < 0)
                continue;
            if (FD_ISSET(sockfd, &rset)) {

                if ((n = Read(sockfd, buf, sizeof(buf))) == 0) {    /* 当client关闭链接时,服务器端也关闭对应链接 */
                    Close(sockfd);
                    FD_CLR(sockfd, &allset);                        /* 解除select对此文件描述符的监控 */
                    client[i] = -1;
                } else if (n > 0) {
                    for (j = 0; j < n; j++)
                        buf[j] = toupper(buf[j]);
                    Write(sockfd, buf, n);
                    Write(STDOUT_FILENO, buf, n);
                }
                if (--nready == 0)
                    break;                                          /* 跳出for, 但还在while中 */
            }
        }
    }
    Close(listenfd);
    return 0;
}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
accept函数是在socket编程用于接受客户端连接的函数。当服务器端创建了一个监听socket后,可以调用accept函数来等待客户端的连接请求。 accept函数的原型为: ```c int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); ``` 其,sockfd是服务器端创建的监听socket的文件描述符;addr是指向struct sockaddr类型的指针,用于存储客户端的IP地址和端口号;addrlen是一个指向socklen_t类型的指针,用于存储客户端地址结构体的长度。 accept函数的工作流程如下: 1. 服务器调用listen函数socket设置为监听状态,等待客户端连接。 2. 当有客户端发起连接请求时,服务器调用accept函数进行处理。 3. accept函数会阻塞等待,直到有客户端连接请求到达。 4. 当有连接请求到达时,accept函数会创建一个新的socket,并返回该新socket的文件描述符。 5. 这个新的socket会与客户端建立连接,服务器端可以通过该socket与客户端进行通信。 6. 同时,accept函数会将客户端的IP地址和端口号存储在addr参数所指向的结构体,并将结构体的长度存储在addrlen参数。 需要注意的是,accept函数在没有新的连接请求到达时会一直阻塞等待,直到有新的连接请求才会返回。如果需要非阻塞地等待连接请求,可以通过设置socket为非阻塞模式或者使用select函数来实现。另外,accept函数一般会在一个循环使用,以便持续接受客户端的连接请求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值