我有一个TCP服务器和UDP服务器,在同一个端口上,使用select()函数为多个客户端提供服务 . 通常,一切都很好,客户端可以与两个服务器通信,但有时,在极少数情况下,我收到错误消息,这是由于非套接字上的recv()错误套接字操作 . 如果我在这种情况下打印套接字描述符,它通常是1(stdout)或0(stdin) . 所以它应该抛出这个错误,但为什么我的代码首先尝试从那些套接字recv()是我的问题 .

这是我的代码片段

  1. TCP套接字创建
// TCP port setup
    int sockfd; // listening socket descriptor
    int newsockfd; // newly accept()ed socket descriptor
    struct sockaddr_storage remoteaddr; // client address
    socklen_t addrlen;

    char buf_tcp[256]; // buffer for client data
    char buf_copy_tcp[256];    
    int recv_bytes;

    char remoteIP[INET6_ADDRSTRLEN];

    int yes=1; // for setsockopt() SO_REUSEADDR
    int i, k, rv_getaddrinfo, rv_setsockopt, rv_bind, rv_listen, rv_select;

    struct addrinfo hints, *servinfo, *ptr;

    // get a socket and bind it
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    rv_getaddrinfo = getaddrinfo(NULL, PORT, &hints, &servinfo);
    if (rv_getaddrinfo != 0) 
    {
        fprintf(stderr, "CLI Server TCP error: %s\r\n", gai_strerror(rv_getaddrinfo));
        exit(1);
    }

    for(ptr=servinfo; ptr!=NULL; ptr=ptr->ai_next) 
    {
        sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        if (sockfd == -1)  
        {
            fprintf(stdout, "CLI Server TCP error: socket\r\n");
            continue;
        }

        // lose the pesky "address already in use" error message
        rv_setsockopt = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
        if(rv_setsockopt == -1)
        {
            fprintf(stdout, "CLI Server TCP error: setsockopt\r\n");
            exit(1);
        }

        rv_bind = bind(sockfd, ptr->ai_addr, ptr->ai_addrlen);
        if (rv_bind == -1) 
        {
            close(sockfd);
            fprintf(stdout, "CLI Server TCP error: bind\r\n");
            continue;
        }

        break;
    }

    // if we got here, it means we didn't get bound
    if (ptr == NULL) 
    {
        fprintf(stdout, "CLI Server TCP error: failed to bind\r\n");
        exit(2);
    }

    freeaddrinfo(servinfo); // all done with this

    // listen
    rv_listen = listen(sockfd, 10);
    if (rv_listen == -1) 
    {
        fprintf(stdout, "CLI Server TCP error: listen\r\n");
        exit(3);
    }
  1. UDP套接字创建
// UDP port setup
    int sockfd_udp; // listening socket descriptor
    struct sockaddr_storage remoteaddr_udp; // client address
    socklen_t addrlen_udp;

    char buf_udp[256]; // buffer for client data
    char buf_copy_udp[256];    
    int recv_bytes_udp;

    char remoteIP_udp[INET6_ADDRSTRLEN];

    int yes_udp=1; // for setsockopt() SO_REUSEADDR
    int j, rv_getaddrinfo_udp, rv_setsockopt_udp, rv_bind_udp;

    struct addrinfo hints_udp, *servinfo_udp, *ptr_udp;

    // get a socket and bind it
    memset(&hints_udp, 0, sizeof(hints_udp));
    hints_udp.ai_family = AF_UNSPEC;
    hints_udp.ai_socktype = SOCK_DGRAM;
    hints_udp.ai_flags = AI_PASSIVE;

    rv_getaddrinfo_udp = getaddrinfo(NULL, PORT, &hints_udp, &servinfo_udp);
    if (rv_getaddrinfo_udp != 0) 
    {
        fprintf(stderr, "CLI Server UDP error: %s\r\n", gai_strerror(rv_getaddrinfo_udp));
        exit(1);
    }

    for(ptr_udp=servinfo_udp; ptr_udp!=NULL; ptr_udp=ptr_udp->ai_next) 
    {
        sockfd_udp = socket(ptr_udp->ai_family, ptr_udp->ai_socktype, ptr_udp->ai_protocol);
        if (sockfd_udp == -1)  
        {
            fprintf(stdout, "CLI Server UDP error: socket\r\n");
            continue;
        }

        // lose the pesky "address already in use" error message
        rv_setsockopt_udp = setsockopt(sockfd_udp, SOL_SOCKET, SO_REUSEADDR, &yes_udp, sizeof(int));
        if(rv_setsockopt_udp == -1)
        {
            fprintf(stdout, "CLI Server UDP error: setsockopt\r\n");
            exit(1);
        }

        rv_bind_udp = bind(sockfd_udp, ptr_udp->ai_addr, ptr_udp->ai_addrlen);
        if (rv_bind_udp == -1) 
        {
            close(sockfd_udp);
            fprintf(stdout, "CLI Server UDP error: bind\r\n");
            continue;
        }

        break;
    }

    // if we got here, it means we didn't get bound
    if (ptr_udp == NULL) 
    {
        fprintf(stdout, "CLI Server UDP error: failed to bind\r\n");
        exit(2);
    }

    freeaddrinfo(servinfo_udp); // all done with this

3.多客户端服务器的select()函数

struct timeval timeout; // timeout for select(), 1ms
timeout.tv_sec  = 0;
timeout.tv_usec = 1000;
fd_set master; // master file descriptor list
fd_set read_fds; // temp file descriptor list for select()
int fdmax; // maximum file descriptor number  

FD_ZERO(&master); // clear the master and temp sets
FD_ZERO(&read_fds);

// add the listener to the master set
FD_SET(sockfd, &master);
FD_SET(sockfd_udp, &master);

// keep track of the biggest file descriptor
if(sockfd > sockfd_udp)
    fdmax = sockfd; // so far, it's this one
else
    fdmax = sockfd_udp; // so far, it's this one  

while(1)
{
    read_fds = master; // copy it

    rv_select = select(fdmax+1, &read_fds, NULL, NULL, &timeout);
    if (rv_select == -1) 
    {
        fprintf(stdout, "CLI Server TCP error: select\r\n");
        exit(4);
    }

    // run through the existing connections looking for data to read
    for(i=0; i<=fdmax; i++) 
    {
        if (FD_ISSET(i, &read_fds)) 
        { // we got one!!
            if (i == sockfd) 
            {
                // handle new tcp connections
                addrlen = sizeof(remoteaddr);
                newsockfd = accept(sockfd, (struct sockaddr *)&remoteaddr, &addrlen);

                if (newsockfd == -1) 
                    fprintf(stdout, "CLI Server TCP error: accept\r\n");
                else 
                {
                    FD_SET(newsockfd, &master); // add to master set
                    if (newsockfd > fdmax)      // keep track of the max
                        fdmax = newsockfd;

                    inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*)&remoteaddr), remoteIP, INET6_ADDRSTRLEN);
                    fprintf(stdout, "CLI Server TCP: new connection from %s on socket %d\r\n", remoteIP, newsockfd);
                }
            } 
            else if (i == sockfd_udp) 
            {
                // handle new udp connections
                addrlen_udp = sizeof(remoteaddr_udp);
                if ((recv_bytes_udp = recvfrom(i, buf_udp, sizeof(buf_udp), 0, (struct sockaddr *)&remoteaddr_udp, &addrlen_udp)) < 0) 
                    fprintf(stdout, "CLI Server UDP error: recvfrom\r\n");
                else 
                {   // handle data from a client
                    inet_ntop(remoteaddr_udp.ss_family, get_in_addr((struct sockaddr*)&remoteaddr_udp), remoteIP_udp, INET6_ADDRSTRLEN);
                    for(j=0; j<=recv_bytes_udp; j++)
                    {
                        if( (buf_udp[j] == '\r') | (buf_udp[j] == '\n') )
                            buf_udp[j] = '\0';
                    }
                    fprintf(stdout, "CLI Server UDP: received %s from connection %s\r\n", buf_udp, remoteIP_udp);
                }
            } 
            else 
            {   // handle data from a client
                if ((recv_bytes = recv(i, buf_tcp, sizeof(buf_tcp), 0)) <= 0) 
                {   // got error or connection closed by client
                    if (recv_bytes == 0) // connection closed
                        fprintf(stdout, "CLI Server TCP: socket %d hung up\r\n", i);
                    else 
                        fprintf(stdout, "CLI Server TCP error: recv from socket %d\r\n", i);

                    if(i > sockfd_udp) // temporary fix as recv error occurs on any open file descriptors.  
                        close(i); // bye!

                    FD_CLR(i, &master); // remove from master set
                } 
                else 
                {
                    for(k=0; k<=recv_bytes; k++)
                    {
                        if( (buf_tcp[k] == '\r') | (buf_tcp[k] == '\n') )
                            buf_tcp[k] = '\0';
                    }
                    fprintf(stdout, "CLI Server TCP: received %s from socket %d\r\n", buf_tcp, i);
                }
            } // END handle data from client
        } // END got new incoming connection
    } // END looping through file descriptors

    usleep(2000);
}

在select()调用之后,我在for循环中运行所有套接字描述符 . 但if(FD_ISSET())条件应该清除非套接字,如0,1等 . 在while(1)循环中使用FD_SET()的唯一地方是接受()新的TCP客户端,那些套接字描述符必须be> TCP套接字描述符,因此不可能在主集中设置0(stdin),1(stdout) . 但他们不知何故 . 为什么我的代码认为它应该从非套接字recv()然后生成该错误 . 谁在我的select()主集中设置描述符0和1 .

此错误很少见,但仍然存在 . 在我的早期版本的代码中,当我收到错误时,我关闭()该套接字描述符 . 所以我关闭了stdin,stdout和其他一些文件描述符(我用open()创建) . 这搞砸了我的代码所以为了掩盖上面的问题,我使用if(i> sockfd_udp)来关闭()描述符,这样只关闭客户端套接字描述符(通过accept()) .

任何人都可以发现问题吗?