Socket套接字编程(I/O复用 select)

select函数

/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);//测试某个描述符是否在集合内
int  FD_ISSET(int fd, fd_set *set);//从集合内把一个描述符移除
void FD_SET(int fd, fd_set *set);//把一个描述符加入集合
void FD_ZERO(fd_set *set);//清空描述符集合

监视readfds查看读文件描述符集合会不会被堵塞(即使end-of-file,也是可读的)。

监视writefds查看写文件描述符集合会不会被堵塞。

监视exceptfd异常文件描述符集合是否出现了异常。主要用来读取OOB数据,异常并不是指出错。

注意当一个套接口出错时,它会变得既可读又可写。

如果有了状态改变,会将其他fd清零,只有那些发生改变了的fd保持置位,以用来指示set中的哪一个改变了状态。

参数n是所有set里所有fd里,具有最大值的那个fd的值加1

struct timeval 
{long    tv_sec;  /* seconds */
 long    tv_usec; /* microseconds */
};

timeval是从调用开始到select返回前,会经历的最大等待时间。

当timeval为 NULL时,会无限等待,直到被信号打断时返回1, errno 设置成 EINTR。

当timeval.tv_sec 为0并且 timeval.tv_usec为0,不等待立即返回。

当timeval.tv_sec非0 或者timeval.tv_usec非 0,等待特定时间长度, 超时返回0。



#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(void)
{
    fd_set rfds;
    struct timeval tv;
    FD_ZERO(&rfds);
    FD_SET(0,&rfds);
    tv.tv_sec=10;
    tv.tv_usec=0;
    int ret=select(1,&rfds,NULL,NULL,&tv);
    if(ret==-1)
        printf("error!\n");
    else if(ret)
    {
        if(FD_ISSET(0,&rfds))
            printf("fd0 data is avilable!\n");
    }
    else
        printf("no data coming in 2 sec!\n");
    return 0;
}

用select对TCP客户端代码进行修改

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<signal.h>
#include <errno.h>
ssize_t readn(int fd, void *buf, size_t count)
{
    size_t nleft = count;
    ssize_t nread;
    char *bufp = (char*) buf;
    while (nleft > 0)
    {
        if ((nread = read(fd, bufp, nleft)) < 0)
        {
            if (errno == EINTR)
                continue;
            return -1;
        }
        else if (nread == 0)
            return count - nleft;
        bufp += nread;
        nleft -= nread;
    }
    return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t nleft = count;
    ssize_t nwritten;
    char *bufp = (char*) buf;
    while (nleft > 0)
    {
        if ((nwritten = write(fd, bufp, nleft)) < 0)
        {
            if (errno == EINTR)
                continue;
            return -1;
        }
        else if (nwritten == 0)
            continue;
        bufp += nwritten;
        nleft -= nwritten;
    }
    return count;
}
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
    while (1)
    {
        int ret = recv(sockfd, buf, len, MSG_PEEK);
        if (ret == -1 && errno == EINTR)
            continue;
        return ret;
    }
}
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
    int ret;
    int nread;
    char *bufp = buf;
    int nleft = maxline;
    while (1)
    {
        ret = recv_peek(sockfd, bufp, nleft);
        if (ret < 0)
            return ret;
        else if (ret == 0)
            return ret;

        nread = ret;
        int i;
        for (i = 0; i < nread; i++)
        {
            if (bufp[i] == '\n')
            {
                ret = readn(sockfd, bufp, i + 1);
                if (ret != i + 1)
                    exit(EXIT_FAILURE);
                return ret;
            }
        }
        if (nread > nleft)
            exit(EXIT_FAILURE);
        nleft -= nread;
        ret = readn(sockfd, bufp, nread);
        if (ret != nread)
            exit(EXIT_FAILURE);
        bufp += nread;
    }
    return -1;
}
int main(int argc,char *argv[])
{
    signal(SIGPIPE,SIG_IGN);
    fd_set rset;
    FD_ZERO(&rset);
    struct sockaddr_in user;
    user.sin_family = AF_INET;
    user.sin_port = htons(8001);
    user.sin_addr.s_addr = inet_addr("127.0.0.1");
    int sockfd,stdinfd,maxfd;
    char sendbuf[1024]= {0},revbuf[1024]= {0};
    sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)
    {
        printf("socket failed!\n");
        exit(0);
    }
    stdinfd=fileno(stdin);
    maxfd = sockfd+1;
    if ( connect(sockfd, (struct sockaddr*) (&user), sizeof(user)) < 0)
    {
        perror("connect failed!\n");
        exit(0);
    }
    for(;;)
    {
        FD_SET(stdinfd,&rset);
        FD_SET(sockfd,&rset);
        int ret=select(maxfd,&rset,NULL,NULL,NULL);
        if(ret==-1)
            printf("select failed!\n");
        if(ret==0)
            continue;
        if(FD_ISSET(sockfd,&rset))
        {
            int val=readline(sockfd,revbuf,sizeof(revbuf));
            if(val==0)
                break;
            printf("client revmsg:");
            fputs(revbuf,stdout);
            memset(revbuf,0,sizeof(revbuf));
        }
        if(FD_ISSET(stdinfd,&rset))
        {
            if(fgets(sendbuf,sizeof(sendbuf),stdin)==NULL)
                break;
            writen(sockfd,sendbuf,strlen(sendbuf));
            memset(sendbuf,0,sizeof(sendbuf));
        }
    }
    close(sockfd);
    return 0;
}

用select对服务器端代码进行修改


此时服务器端仅有一个监听套接字描述符


我们创建一个client数组用来表示连接池,并初始化为-1,观察读文件描述符的状态


当有第一个连接描述符申请连接时,监听套接字描述符状态变为可读


同时读文件描述符的状态更新,此时我们更改client数组对应描述符状态。


当第二个连接建立时,此时描述符的状态变化如下图


我们将第一个连接终止,此时客户端发送一个FIN,使得服务器描述符4变为可读,并把client数组对应的描述符4的内容更改为-1


将fork多进程并发服务器端代码改写单进程多连接池select模型

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

#define ERR_EXIT(m) \
        do \
        { \
                perror(m); \
                exit(EXIT_FAILURE); \
        } while(0)

ssize_t readn(int fd, void *buf, size_t count)
{
    size_t nleft = count;
    ssize_t nread;
    char *bufp = (char*)buf;

    while (nleft > 0)
    {
        if ((nread = read(fd, bufp, nleft)) < 0)
        {
            if (errno == EINTR)
                continue;
            return -1;
        }
        else if (nread == 0)
            return count - nleft;

        bufp += nread;
        nleft -= nread;
    }

    return count;
}

ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t nleft = count;
    ssize_t nwritten;
    char *bufp = (char*)buf;

    while (nleft > 0)
    {
        if ((nwritten = write(fd, bufp, nleft)) < 0)
        {
            if (errno == EINTR)
                continue;
            return -1;
        }
        else if (nwritten == 0)
            continue;

        bufp += nwritten;
        nleft -= nwritten;
    }

    return count;
}

ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
    while (1)
    {
        int ret = recv(sockfd, buf, len, MSG_PEEK);
        if (ret == -1 && errno == EINTR)
            continue;
        return ret;
    }
}

ssize_t readline(int sockfd, void *buf, size_t maxline)
{
    int ret;
    int nread;
    char *bufp = buf;
    int nleft = maxline;
    while (1)
    {
        ret = recv_peek(sockfd, bufp, nleft);
        if (ret < 0)
            return ret;
        else if (ret == 0)
            return ret;

        nread = ret;
        int i;
        for (i=0; i<nread; i++)
        {
            if (bufp[i] == '\n')
            {
                ret = readn(sockfd, bufp, i+1);
                if (ret != i+1)
                    exit(EXIT_FAILURE);

                return ret;
            }
        }

        if (nread > nleft)
            exit(EXIT_FAILURE);

        nleft -= nread;
        ret = readn(sockfd, bufp, nread);
        if (ret != nread)
            exit(EXIT_FAILURE);

        bufp += nread;
    }

    return -1;
}

void echo_srv(int conn)
{
    char recvbuf[1024];
    while (1)
    {
        memset(recvbuf, 0, sizeof(recvbuf));
        int ret = readline(conn, recvbuf, 1024);
        if (ret == -1)
            ERR_EXIT("readline");
        if (ret == 0)
        {
            printf("client close\n");
            break;
        }

        fputs(recvbuf, stdout);
        writen(conn, recvbuf, strlen(recvbuf));
    }
}
int main(int argc,char *argv[])
{
    char buf[1024]= {};
    signal(SIGPIPE, SIG_IGN);
    int listenfd;
    if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
        ERR_EXIT("socket");
    struct sockaddr_in serv;
    bzero(&serv,sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(8001);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    int on = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
        ERR_EXIT("setsockopt");

    if (bind(listenfd, (struct sockaddr*)&serv, sizeof(serv)) < 0)
        ERR_EXIT("bind");
    if (listen(listenfd, SOMAXCONN) < 0)
        ERR_EXIT("listen");
    struct sockaddr_in cli;
    socklen_t len=sizeof(cli);
    int connfd,i,maxi=0,maxfd=listenfd,client[FD_SETSIZE],nready;
    for(i=0; i<FD_SETSIZE; i++)
    {
        client[i]=-1;
    }
    printf("FD_SETSIZE max is %d\n",FD_SETSIZE);
    fd_set rset,allset;
    FD_ZERO(&rset);
    FD_ZERO(&allset);
    FD_SET(listenfd,&allset);
    for(;;)
    {
        rset=allset;
        nready=select(maxfd+1,&rset,NULL,NULL,NULL);
        if(nready==-1)
        {
            printf("select error!\n");
        }
        if(nready==0)
            continue;
        if(FD_ISSET(listenfd,&rset))
        {
            connfd=accept(listenfd, (struct sockaddr*)&cli,&len);
            if(connfd==-1)
            {
                printf("connfd error!\n");
            }
            for(i=0; i<FD_SETSIZE; i++)
            {
                if(client[i]==-1)
                {
                    client[i]=connfd;
                    if(i>maxi)
                        maxi=i;
                    break;
                }
            }
            if(i==FD_SETSIZE)
                printf("no more connect!\n");
            FD_SET(connfd,&allset);
            if(connfd>maxfd)
                maxfd=connfd;
            if(--nready<=0)
                continue;
        }
        for(i=0; i<=maxi; i++)
        {
            if(client[i]==-1)
                continue;
            if(FD_ISSET(client[i],&rset))
            {
                memset(buf,0,sizeof(buf));
                int ret=readline(client[i],buf,sizeof(buf));
                if(ret==-1)
                    printf("read error!\n");
                if(ret==0)
                {
                    printf("client closed!\n");
                    client[i]=-1;
                    FD_CLR(client[i],&allset);
                    close(client[i]);
                }
                fputs(buf,stdout);
                writen(client[i],buf,strlen(buf));
                if(--nready<=0)
                    break;
            }
        }
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_33506160/article/details/80407857