Linux 网络编程之socket、select

博客搬家:https://blog.youkuaiyun.com/zheyufuck/article/details/52549889

使用socket和select实现并发型服务器

本文介绍使用select实现并发型服务器的实战,若有错误之处,还请不吝指点。

1. socket介绍

  • socket系统调用包括了:socket()、bind()、listen()、accept()、connect()。
  • socket()系统调用创建一个新的socket文件描述符。
  • bind()系统调用将socke()返回的文件描述符绑定到一个特定的地址,使得客户端能够定位到该socket上。
  • listen()监听接入连接,允许一个流socket接受来自其他的socket连接。
  • accept()在监听流socket上接受来自一个对等应用程序的接入。
  • connect()建立与另一个socket之间的连接。

    2. select介绍

select函数原型:int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

  • readfds是用来检测是否有输入就绪的文件描述符集合。
  • writefds是用来检测是否有输出就绪的文件描述集合。
  • exceptfds是用来检测异常情况是否发生的文件描述符。
  • timeout用来控制select()阻塞行为的。设置为NULL时,select()会一直阻塞。又或者指向一个timeval结构体:
struct timeval{
    time_t tv_sec;//second
    suseconds_t tv_usec;//Microsecond(long int)
}

如果结构体timeval的两个域都为0,此时select不会阻塞,只是简单地轮询指定的文件描述符集合,看看是否有就绪的文件描述符并立即返回。

  • 所有关于文件描述符集合的操作都是通过四个宏来完成的:

    • void FD_ZERO(fd_set * fdset);//将fdset所指向的集合初始化为空
    • void FD_SET(int fd,fd_set * fdset);//将文件描述符fd添加到右fdset所指向的集合中
    • void FD_CLR(int fd ,fd_set * fdset);//将文件描述符fd从fdset集合中移除。
    • int FD_ISSET(int fd ,fd_set *fdset);//如果文件描述符fd是fdset所指向集合的成员,则返回TRUE。
  • 文件描述符集合有个最大容量限制,由常量FD_SETSIZE来决定。

3. 并发型服务器的实现

  • 创建一个socket:int sfd = socket(AF_INET,SOCK_STREAM,0);
  • 将socket绑定到地址:bind(sfd,(struct sockaddr *)(&addr) ,sizeof(addr));
    struct sockaddr是通用的socket地址结构,而bind()中addr是IPv4 socket的地址结构struct sockaddr_in,因此需进行类型转换。struct sockaddr_in结构体:
    struct sockaddr_in{
                sa_family_t    sin_family;//协议族
                in_port_t      sin_port;//端口号
                struct in_addr sinaddr;//IPv4地址
                unsigned char  _pad[X];
        };
  • 设置接入监听连接:listen(sfd,3);//3为最大请求连接数

  • 接受连接 :accept(sfd,(struct sockaddr *)&cli_addr,&cli_len);

  • select()实现并发通信:至此一个流式socket创建基本完成,但此时还无法实现并发通信。实现并发通信的有很多,例如:使用多线程、多进程、select、epoll等。在这里我使用select实现并发通信。
    在接入监听连接listen()之后使用select()进行轮询是否有读写事件触发,实现并发通信。
    server.c
    #include<stdio.h>
    #include<stdlib.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<sys/socket.h>
    #include<string.h>
    #include<sys/time.h>
    #include<sys/select.h>
    #include<unistd.h>

    #define MAXSOCK 10
    #define BUFSIZE 1024

    int fd_sock[MAXSOCK];//存放所有的接入的socket文件描述符

    void initSrv();

    int main(){

        initSrv();
        return 0;
    }
    void initSrv(){

        int sfd = socket(AF_INET,SOCK_STREAM,0);//创建socket
        if(sfd == -1)
        {
            printf("start socket error\n");
            return;
        }
        printf("socket ok \n");

        /*配置IPv4地址信息*/
        struct sockaddr_in addr;
        memset(&addr,0,sizeof(struct sockaddr_in));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        addr.sin_port = htons(7777);

        int bfd;
        if((bfd = bind(sfd,(struct sockaddr *)(&addr) ,sizeof(addr)) )== -1)//绑定到固定ip端口
        {
            printf("bind error \n");
            return;
        }
        printf("bind ok ,ip is:%s,port is %d\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));

        if(listen(sfd,3) == -1)//接入监听
        {
            printf("listen error\n");
            return;
        }
        printf("listen ok\n");

        /*配置地址,用于存储accept()返回的地址信息*/
        socklen_t cli_len;
        struct sockaddr_in cli_addr;
        memset(&cli_addr,0,sizeof(cli_addr));
        cli_len =  sizeof(cli_addr);

        ssize_t num;

        fd_set fdsr;//创建select()中的文件描述符集合
        struct timeval tv;//select()中超时参数的结构体
        int maxsock = sfd;//定义select中文件描述符的最大值
        int i;
        int ready,ret;
        int curr_count = 0;
        int curr_user = 0;
        int * amount = &curr_count;
        char buf[BUFSIZE];

        while(1)
        {

            /*操作select()中文件描述符集合*/
            FD_ZERO(&fdsr);
            FD_SET(sfd,&fdsr);
            tv.tv_sec = 30;
            tv.tv_usec = 0;

            //将活动的文件描述符添加到文件描述符集合中
            for(i = 0;i<MAXSOCK;i++){

                if(fd_sock[i] != 0)
                    FD_SET(fd_sock[i],&fdsr);
            }       


            ready = select(maxsock + 1,&fdsr,NULL,NULL,&tv);//调用select

            if(ready == -1){
                printf("select error\n");
                break;
            }
            else if(ready == 0){
                printf("select timeout\n");
                continue;
            }

            /*查询listen()监听事件是否有被触发即是否有连接要接入*/
            if(FD_ISSET(sfd,&fdsr))
            {


                int cfd = accept(sfd,(struct sockaddr *)&cli_addr,&cli_len);
                if(cfd == -1)
                {
                    printf("accept error\n");
                    continue;
                }

                /*将接受接入的连接的文件描述符添加到文件描述符集合*/
                if(curr_count < MAXSOCK )
                {

                    fd_sock[curr_count++] = cfd;
                    printf("fd is %d\tip is: %s,port is %d\n",cfd,inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
                    printf("在线用户有%d个\n",curr_count);
                    send(cfd,"welcome to connect\n",19,0);
                    if(cfd > maxsock)
                        maxsock = cfd;
                }
                else {

                    printf("已达到连接最大请求\n");
                    send(cfd,"conn false",10,0);
                    close(cfd);

                }

            }

            //检查客户端是否发来消息
            for(i = 0;i<curr_count;i++)
            {

                /*查询是哪个文件描述符被触发*/
                if(FD_ISSET(fd_sock[i],&fdsr)){

                    /*读取客户端发来的信息*/
                    ret = recv(fd_sock[i],buf,sizeof(buf),0);

                    if(ret < 0){
                        printf("client %d is closed\n",i);
                        close(fd_sock[i]);
                        FD_CLR(fd_sock[i],&fdsr);
                        fd_sock[i] = 0;
                        *amount -= 1;
                        printf("当前在线用户有%d个\n",*amount);

                    }
                    else if(ret == 0){
                        printf("客户端断开连接\n");
                        close(fd_sock[i]);
                        FD_CLR(fd_sock[i],&fdsr);
                        fd_sock[i] = 0;
                        *amount-= 1;
                        printf("当前在线用户有%d个\n",*amount);
                    }
                    else{
                        if(ret < BUFSIZE)
                                memset(&buf[ret],'\0',1);
                        printf("client %d send %s\n",i,buf);

                        /*将消息转发给目标客户端*/
                        char split[] = "@";
                        char * src = strtok(buf,split);
                        char * msg = strtok(NULL,split); 
                        send(atoi(src),msg,strlen(msg),0);


                        }

                        send(fd_sock[i],"serve recv succeed\n",19,0);
                    }

                }

            }

        }

  • 运行测试如下图所示:

    • 服务器端:
      这里写图片描述
    • telnet测试:
      这里写图片描述

      这里写图片描述

参考书:Linux /UNIX 系统编程手册

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值