select函数 多路I/O转接服务器 linux网络编程

多路I/O转接服务器

多路IO转接服务器也叫多任务IO服务器。该类服务器实现的主旨思想是,不再由程序自己监视客户端连接,取而代之由内核替应用程序监视文件描述符。可用select函数实现。

select函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
fd_set代表集合。
参数1:所监听的所有文件描述符中,最大的文件描述符+1.
参数2:所监听的文件描述符“可读”事件 —readfds;
参数3:所监听的文件描述符“可写”事件 —writefds;
参数4:所监听的文件描述符“异常”事件 —timeout;
参数5:时间,一般设为NULL,有监听事件才返回
返回值:
成功:所监听的所有的监听集合中,满足条件的总数。
失败:
对集合fd_set的相关操作函数
void FD_CLR(int fd, fd_set *set); 将fd从集合中清除出去
int FD_ISSET(int fd, fd_set *set); 判断fd是否在集合中
void FD_SET(int fd, fd_set *set); 将fd设置到集合中去
void FD_ZERO(fd_set *set); set清空

个人思路

在这里插入图片描述

服务器端

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<errno.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/select.h>


#define SERV_PORT 6666

ssize_t Read(int fd,void *ptr,size_t nbytes)
{

    ssize_t n;
again:
    if( (n = read(fd,ptr,nbytes))==-1)
    {

        if(errno==EINTR)
            goto again;
        else
            return -1;
    }
    return n;
}

int main(int argc,char* argv[])
{
    int i,j,n,maxi,client_sum;
    int ret;
    int maxfd,listenfd,connfd,sockfd;
    int nready,client[FD_SETSIZE];//自定义数组,防止遍历超过1024个文件描述符
    char buf[BUFSIZ],str[INET_ADDRSTRLEN];  //8096  ,16

    struct sockaddr_in clie_addr,serv_addr;
    socklen_t clie_addr_len;
    fd_set rset,allset;//定义监听集合

    listenfd=socket(AF_INET,SOCK_STREAM,0);

    int opt=1;
    setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    //端口地址复用,当服务器主动关闭时,就算处于wait_time状态(主动关闭一方最后需要等待2MSL时间确保最后一个ACK发送成功,保证完整的4次握手关闭连接)重启后马上可以连接新的客户端

    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);

    ret=bind(listenfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));//绑定ip和端口
    if(ret==-1)
    {
        perror("bind error!");
        exit(1);
    }
    listen(listenfd,128);

    maxfd=listenfd;//记录当前最大文件描述符

    maxi=-1;
    client_sum=0;
    for(i=0;i<FD_SETSIZE;i++)//记录客户端连接的文件描述符数组,方便轮询查找
        client[i]=-1;

    FD_ZERO(&allset);//清空集合
    FD_SET(listenfd,&allset);   //监听描述符加入到集合中,如果有客户端连接,变为“可读”

    while(1)//服务器循环等待客户端连接请求
    {
        rset=allset;
        nready=select(maxfd+1,&rset,NULL,NULL,NULL);
        //成功会返回所有监听集中满足触发条件的总数
        //rset是传入传出参数,传出时是带有所有可读描述符的集合
        if(nready<0)
        {
            perror("select error");
            exit(1);
        }
        if(FD_ISSET(listenfd,&rset))
            //有可读文件描述符,优先判断其中有没有listenfd(监听描述符),如果存在说明有客户端连接请求
        {
            clie_addr_len=sizeof(clie_addr);
            connfd=accept(listenfd,(struct sockaddr*)&clie_addr,&clie_addr_len);
            printf("*******************************************************\n");
            printf("connect with client:ip %s, port: %d\n",
                    inet_ntop(AF_INET,&clie_addr.sin_addr.s_addr,str,sizeof(str)),
                    ntohs(serv_addr.sin_port));

            for(i=0;i < FD_SETSIZE;i++)
            {
                if(client[i]<0)//查找靠前且未被使用的文件描述符
                {
                    client[i]=connfd;
                    client_sum++;
                    if(i>maxi)//maxi是client数组存有文件描述符最大编号,方便下面的轮询查找
                         maxi=i;
                    printf(" set connfd:%d  in client[%d]-----maxi=%d,client_sum=%d----\n",connfd,i,maxi,client_sum);
                    printf("*******************************************************\n");
                    break;
                }
            }

            if(i==FD_SETSIZE)//超过1024个文件描述符报错
            {
                fputs("too many clients\n",stderr);
                exit(1);
            }

            FD_SET(connfd,&allset);//把新客户端连接的文件描述符加入到总集合中

            if(connfd>maxfd)//保证maxfd是序号最大的文件描述符(select函数参数要求)
                maxfd=connfd;


            if(--nready==0)//有listenfd且可读文件描述符总数为1,说明只有新客户端连接请求,不用执行下面的回应代码
                continue;
        }

        //select函数传出可读文件描述符数量,但我们并不知道具体是哪些文件描述符可读,所以需要轮询查找
        for(i=0;i<=maxi;i++)
        {
            if((sockfd=client[i])<0)//只查找数组中所有存在的文件描述符
                continue;
            if(FD_ISSET(sockfd,&rset))//判断是否在传出的可读集合中
            {
                memset(buf,0,sizeof(buf));
                n=Read(sockfd,buf,sizeof(buf));
                if(n==0)//客户端断开连接
                {
                    printf("*******************************************************\n");
                    close(sockfd);
                    FD_CLR(sockfd,&allset);//从监听集合中清楚
                    client_sum--;
                    if(client_sum==0)
                        maxi=-1;
                    else if(i==maxi)//退出的是描述符最大值时,maxi变为退出后数组描述符最大值
                    {
                        for(int k=i-1;k>=0;k--)
                        {
                            if(client[k]<0)
                                continue;
                            else
                            {
                                maxi=k;
                                break;
                            }
                        }
                    }
                    printf("client[%d]  connfd : %d exit----maxi=%d, client_sum=%d\n",i,client[i],maxi,client_sum);
                    client[i]=-1;
                    printf("*******************************************************\n");
                }
                else if(n>0)
                {
                    printf("read buf:%s---from client[%d] :connfd=%d \n",buf,i,client[i]);
                    for(j=0;j<n;j++)
                        buf[j]=toupper(buf[j]);
                    //sleep(10);//实验,处理函数时又有其他多个客户端发信号,那么会在下一次select函数中全部变为可读,依次处理
                    printf("write back buf:%s\n",buf);
                    write(sockfd,buf,n);
                    printf("--------------------\n");
                }
                if(--nready==0)//完成一次回调处理,select返回的监听集数量-1,如果为0说明没有新的需要处理的可读文件描述符
                    break;//跳出client[]数组轮询查找
            }
        }
    }
    close(listenfd);
    return 0;

}

客户端

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

#define SERV_IP "127.0.0.1"
#define SERV_PORT  6666
int main()
{
    int cfd,sfd;
    struct sockaddr_in serv_addr;
    socklen_t serv_addr_len;
    char buf[BUFSIZ],clien_ip[BUFSIZ];
    int n;
    char *find;

    memset(&serv_addr,0,sizeof(serv_addr));
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_port=htons(SERV_PORT);//转换为网络字节序
    //serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);//INADDR_ANY可以自行寻找ip
    inet_pton(AF_INET,SERV_IP,&serv_addr.sin_addr.s_addr);

    cfd=socket(AF_INET,SOCK_STREAM,0);//创建本地套接字

    // bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));//绑定,客户端隐式绑定

    connect(cfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr) );

    while(1)
    {
        memset(buf,0,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);//从输入端读一行数据存入缓存区中
        find=strchr(buf,'\n');
        if(find)
            *find='\0';//去掉fgets中的换行符
        write(cfd,buf,strlen(buf));
        n=read(cfd,buf,sizeof(buf));
        write(STDOUT_FILENO,buf,n);
        printf("\n");
    }   
    close(cfd);
    return 0;
}

测试结果

客户端的连接和退出

在这里插入图片描述

大写转换

在这里插入图片描述

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值