Linux网络编程 -- select实现多路IO转接服务器

server.c

#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 8888

int main()
{
    int opt = 1;
    int i, j, n, maxi;
    int nready, client[FD_SETSIZE];   //自定义数组client, 防止遍历1024个文件描述符  FD_SETSIZE默认为1024
    int maxfd, listenfd, connfd, sockfd;
    char buf[BUFSIZ], clie_IP[INET_ADDRSTRLEN];

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

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    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;   

    maxi = -1;  //指向数组首地址的前一个
    for (i = 0; i < FD_SETSIZE; ++i)
    {
        client[i] = -1;     //将存放客户端的文件描述符的数组初始化为 -1
    }

    FD_ZERO(&allset);           //将allset清空
    FD_SET(listenfd, &allset);  //将listenfd 放入allset中

    while (1)
    {
        read_set = allset;

        //select 返回所有监听的文教描述符满足条件的总个数  
        //参数1:所监听的所有文件描述符中,最大的文件描述符+1
        //参数2/3/4:所监听的文件描述符 可读、可写、异常时间  
        nready = select(maxfd+1, &read_set, NULL, NULL, NULL);

        if (nready < 0)
        {
            perr_exit("select error");
        }

        if (FD_ISSET(listenfd, &read_set))  //说明有新的客户端连接请求
        {
            clie_addr_len = sizeof(clie_addr);
            connfd = Accept(listenfd, (struct sockaddr *)&clie_addr, &clie_addr_len);

            printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET, &clie_addr.sin_addr, clie_IP, sizeof(clie_IP)),
                    ntohs(clie_addr.sin_port));

            for (i = 0; i < FD_SETSIZE; ++i)
            {
                if (client[i] < 0)      //定位到client数组中没有存入文件描述符的位置
                {
                    client[i] = connfd; //将新连接的客户端的文件描述符保存如client
                    break;
                }
            }

            if (FD_SETSIZE == i)        //防止文件描述符的个数超过数组的容量
            {
                printf("client numbers over\n");
                exit(1);
            }

            FD_SET(connfd, &allset);    //将conf保存到allset集合中

            if (connfd > maxfd)         //更新最大的文件描述符
            {
                maxfd = connfd;
            }

            if (i > maxi)         //更新client数组的内容的长度
            {
                maxi = i;
            }

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

        for (i = 0; i <= maxi; ++i)  ///检测client数组中有哪些文件描述符满足了条件 */
        {
            if ((sockfd = client[i]) < 0)   //该元素还没有保存文件描述符
            {
                continue;
            }

            if (FD_ISSET(sockfd, &read_set))    //该文教描述符是否满足读事件
            {
                n = Read(sockfd, buf, sizeof(buf));
                if (0 == n)     ///表示该客户端关闭链接,此时,服务器端也要关闭对应链接 
                {
                    Close(sockfd);
                    FD_CLR(sockfd, &allset);    //将该文件描述符从allset集合中清除
                    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;
                }
            }
        }
    }

    Close(listenfd);

    return 0;
}

wrap.h

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>

//输出错误信息并退出
void perr_exit(const char *str)
{
    perror(str);
    exit(-1);
}    

//创建套接字 带出错处理
int Socket(int family, int type, int protocol)
{
    int n;

    n = socket(family, type, protocol);
    if (n < 0)
    {
        perr_exit("socket error");
    }

    return n;
}


int Bind(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

    n = bind(fd, sa, salen);
    if (n < 0)
    {
        perr_exit("bing error");
    }

    return n;
}

int Listen(int fd, int backlog)
{
    int n;

    n = listen(fd, backlog);
    if (n < 0)
    {
        perr_exit("listen error");
    }

    return n;
}

int Connect(int fd, const struct sockaddr *sa, socklen_t salen)
{
    int n;

    n = connect(fd, sa, salen);
    if (n < 0)
    {
        perr_exit("connect error");
    }

    return n;
}

int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{
    int n;

again:
    n = accept(fd, sa, salenptr);
    if (n < 0)
    {
        if (errno == ECONNABORTED || errno == EINTR)
        {
            goto again;
        }
        else
        {
            perr_exit("accept error");
        }
    }

    return n;
}

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

again:
    n = read(fd, ptr, nbytes);
    if (-1 == n)
    {
        if (EINTR == errno)
        {
            goto again;
        }
        else
        {
            return -1;
        }
    }

    return n;
}

ssize_t Write(int fd, const void *ptr, size_t nbytes)
{
    ssize_t n;

again:
    n = write(fd, ptr, nbytes);
    if (-1 == n)
    {
        if (EINTR == errno)
        {
            goto again;
        }
        else
        {
            return -1;
        }
    }

    return n;
}

int Close(int fd)
{
    int n;

    n = close(fd);
    if (-1 == n)
    {
        perr_exit("close error");
    }

    return n;
}

猜你喜欢

转载自blog.csdn.net/Zhoujy1996/article/details/81455372