基于select的回声服务器

这篇博客介绍了如何利用select函数处理多个客户端的连接,包括fd_set结构体的使用和相关函数(FD_CLR, FD_ISSET, FD_SET, FD_ZERO)来管理文件描述符集。示例代码展示了一个服务器端程序,监听客户端连接并读取消息,同时处理超时情况。
摘要由CSDN通过智能技术生成

一:函数理解

(1)
要想通过select函数处理多个客户端的连接,我们得需要一个函数把这些文件描述符集中起来吧,不然怎么处理多个客户端的链接呢?先来看select函数的第234参数,这几个参数,都是fd_set类型的变量,用来把文件描述符集中起来,第2个参数是集中想读的文件描述符,第3个参数是集中想写的文件描述符,第4个参数是集中异常的文件描述符。后面会举例子,先记这么多吧。
int select(int n,fd_set * readfds,fd_set * writefds,fd_set * exceptfds,struct timeval * timeout);

(2)
我们怎么通过fd_set变量集中文件描述符?或者清除不感兴趣的文件描述符?有以下4个函数完成这些操作。

FD_CLR(inr fd,fd_set* set);用来清除描述词组set中相关fd的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组set中相关fd的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set);用来清除描述词组set的全部位

当客户端连入时,我们怎么知道是那个文件描述符发生了变化?以select函数来说,如果我们关心读事件,那么当可读时,相应的变化就会记录在select函数的第二个实参里,但是,我们不知道这个变化了的文件描述符的值是多少,所以要遍历,这也是select函数的缺点之一,所以它的第一个参数总是记录着最大的文件描述符是多少,因为后面遍历需要。

二:例程

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 100
void error_handling(char *buf);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    struct timeval timeout;
    fd_set reads, cpy_reads;

    socklen_t adr_sz;
    int fd_max, str_len, fd_num, i;
    char buf[BUF_SIZE];
    if(argc!=2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));
    
    if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
        error_handling("bind() error");
    if(listen(serv_sock, 5)==-1)
        error_handling("listen() error");

    FD_ZERO(&reads);
    FD_SET(serv_sock, &reads);
    /*目前serv_sock文件描述符的数值是最大的*/
    fd_max=serv_sock;

    while(1)
    {
        /*这个动作要有,因为select函数返回时
        cpy_reads都会发生变化,而后续有新的客户端连   入时,也需要通过reads记录相应的文件描述符,所以每次循环到select函数时,cpy_reads的值都会是所有的文件描述符的集合*/
        cpy_reads=reads;
        timeout.tv_sec=5;
        timeout.tv_usec=5000;

        if((fd_num=select(fd_max+1, &cpy_reads, 0, 0, &timeout))==-1)
            break;
        
        if(fd_num==0)
            continue;

        for(i=0; i<fd_max+1; i++)
        {
            if(FD_ISSET(i, &cpy_reads))
            {
                if(i==serv_sock)     // connection request!
                {
                    adr_sz=sizeof(clnt_adr);
                    clnt_sock=
                        accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                    FD_SET(clnt_sock, &reads);
                    if(fd_max<clnt_sock)
                        fd_max=clnt_sock;
                    printf("connected client: %d \n", clnt_sock);
                }
                else    // read message!
                {
                    str_len=read(i, buf, BUF_SIZE);
                    if(str_len==0)    // close request!
                    {
                        FD_CLR(i, &reads);
                        close(i);
                        printf("closed client: %d \n", i);
                    }
                    else
                    {
                        write(i, buf, str_len);    // echo!
                    }
                }
            }
        }
    }
    close(serv_sock);
    return 0;
}

void error_handling(char *buf)
{
    fputs(buf, stderr);
    fputc('\n', stderr);
    exit(1);
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值