多路复用——select并发服务器

#include <stdio.h>
#include <string.h>
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>      
#include <sys/socket.h>
#include <arpa/inet.h>
#include "kernel.h"        //使用内核链表

#define PORT 12345

int main(void)
{
    //创建socket套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0){
        perror("socket error");
        exit(-1);
    }
    

    //更改sockfd的属性,允许地址重复:防止报“地址重复”错误
    int on = 1;
    setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
    
    //绑定IP和PORT
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(PORT);
    server.sin_addr.s_addr = htonl(INADDR_ANY);          //INADDR_ANY 实际上就是0
//  server.sin_addr.s_addr = inet_addr("192.168.10.5");


    if(bind(sockfd,(struct sockaddr *)&server,sizeof(server))){
        perror("bind error");
        exit(-1);
    }
    


    //监听,将主动连接变成被动连接
    if(listen(sockfd,30)){
        perror("listen error");
        exit(-1);
    }
    
    //初始化链表,使用内核链表
    pkernel_t p = kernel_init();

    fd_set readfds;       //创建一个名字叫readfds的集合
    int ret;
    char buf[50];
    int max = 0;
    int m = 10;
    char buff[10]="sb";
    while(1){
        //清空readfds集合,每一次循环都把集合中的文件描述符全部清空,然后再一个一个的将文件描述符再次加入集合中
        FD_ZERO(&readfds);
        
        //将文件描述符放入
         FD_SET(0,&readfds);               //将文件描述符0(即:键盘的文件描述符)放入集合
         FD_SET(sockfd,&readfds);       //将服务器的文件描述符放入集合
         max = sockfd;                           //由于集合中的文件描述的个数   等于  最大文件描述符的数值+1,所以这里先用max = sockfd;,后面再用max+1表示集合中的文件描述的个数
         
        pkernel_t pos = NULL;        //内核链表头节点
        list_for_each_entry(pos,&p->list, list)        //遍历链表
        {
            FD_SET(pos->data,&readfds);        /*从头节点开始,一个一个的将链表中的文件描述符放入readfds集合中,注意:链表中的文件描述放在链表的数据域*/
            if(pos->data > max)
                max = pos->data;        //每次有新的文件描述加入链表中,集合中的文件描述个数就会变多,所以需要让max有所变化
        }

        ret = select(max+1,&readfds,NULL,NULL,NULL);             /*等待文件描述响动,如果有文件描述符响动,则该文件描述符会被保留下来,而其他的文件描述符会被清除,注意:此时readfds集合中就只有发生响动的这一个文件描述符。max+1表示集合中最大被允许装入的文件描述符个数*/
        if(ret<0){
            perror("select error");
            exit(-1);
        }

        
        if(FD_ISSET(0,&readfds)){        /* FD_ISSET(0,&readfds)判断文件描述符0是否在readfds集合中(即:判断是否键盘的文件描述响动了)。注意:由于前面经过了select()函数的筛选,集合里面只剩下一个文件描述,只不过不知道是哪一个文件描述符响动了,所以这里需要判断*/
            bzero(buf,sizeof(buf));
            read(0,buf,sizeof(buf));
            printf("%s",buf);
        }else if(FD_ISSET(sockfd,&readfds)){          /*等待客户连接:如果键盘没有输入,那么就判断是否有客户需要连接,FD_ISSET(sockfd,&readfds)判断文件描述符sockfd是否在readfds集合中(即:判断是否有客户正在连接服务器,导致服务器的文件描述符sockfd响动了)。注意:由于前面经过了select()函数的筛选,集合里面只剩下一个文件描述,只不过不知道是哪一个文件描述符响动了,所以这里需要判断*/
        
        /*如果有客户端来连接服务器,那么sockfd会响动,则sockfd会被保留下来,
        其他的fd被清除,注意:此时readfds集合中就只有sockfd这一个文件描述符*/

        
            struct sockaddr_in client;
            int len = sizeof(client);
            int fd = accept(sockfd,(struct sockaddr *)&client,&len);        //客户端与服务器端进行连接,并产生一个新的客户文件描述fd
            if(fd<0){
                perror("accept error");
                exit(-1);
            }
            printf("客户%d上线了\n",fd);
            printf("其IP:%s PORT:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
            kernel_insert_tail(p,fd);         /*链表尾插操作,将新连接成功的客户的文件描述符fd加入链表里面。注意:是加入到链表里面而不是集合readfds里面,所以此时集合里面是没有该文件描述符的*/
      }

        /*如果前面的两个判断都没有通过,则说明是某个客户端在向服务器发送数据,那么这个客户的文件描述符fd会响动,这个客户fd被保留下来,其他的fd被清除,注意:此时readfds集合中就只剩下这个fd这一个文件描述符*/
        list_for_each_entry(pos,&p->list, list)        //由于不知道是哪一个客户的文件描述符发生了响动,所以将链表从头节点开始遍历,一个一个查询
        {
            if(FD_ISSET(pos->data,&readfds)){        //查询是集合中哪一个客户的文件描述符发生响动,如果找到,则开始接收客户端发送过来的消息
                bzero(buf,sizeof(buf));
                ret = recv(pos->data,buf,sizeof(buf),0);
                if(ret<0){
                    if(!(m--)){                //如果接收失败20次,就打印"recv error"
                        perror("recv error");
                    }
                }else if(ret==0){
                    printf("掉线了\n");
                    kernel_del(p,pos->data);
                    break;
                }else{
                    m = 10;
                    printf("客户%d : %s\n",pos->data,buf);        //打印客户端发送过来的消息
                    send(pos->data,buff,strlen(buff),0);        //返回给客户端“sb”字符串
                }
            }
        }
    }
    
    close(sockfd);
    return 0;
}
        

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值