首页 文章

如何使用select()函数进行TCP和UDP连接?

提问于
浏览
-1

我有一个只有TCP连接的功能服务器代码 . 现在我希望服务器从UDP连接接收 . 我使用端口2000用于TCP和端口2001用于UDP . 这是我的代码片段

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);

// 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;

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;

    rv_getaddrinfo = getaddrinfo(NULL, "2000", &hints, &servinfo);

    for(ptr=servinfo; ptr!=NULL; ptr=ptr->ai_next) 
    {
        sockfd = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);

        rv_setsockopt = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));

        rv_bind = bind(sockfd, ptr->ai_addr, ptr->ai_addrlen);

        break;
    }

    if (ptr == NULL) 
    {
        fprintf(stderr, "CLI Server error: failed to bind\n\r");
        exit(2);
    }

    freeaddrinfo(servinfo); // all done with this

    rv_listen = listen(sockfd, 10);
////////////////////////////////////////////////////////////////////////////////////////////////

// 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;

    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, "2001", &hints_udp, &servinfo_udp);

    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);

        rv_setsockopt_udp = setsockopt(sockfd_udp, SOL_SOCKET, SO_REUSEADDR, &yes_udp, sizeof(int));

        rv_bind_udp = bind(sockfd_udp, ptr_udp->ai_addr, ptr_udp->ai_addrlen);

        break;
    }

    if (ptr_udp == NULL) 
    {
        fprintf(stderr, "CLI UDP Server error: failed to bind\n\r");
        exit(2);
    }

    freeaddrinfo(servinfo_udp); // all done with this
//////////////////////////////////////////////////////////////////////////////////////////////////////////

    // 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

    do
    {   
        read_fds = master; // copy it

        rv_select = select(fdmax+1, &read_fds, NULL, NULL, &timeout);

        // 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 connections
                    addrlen = sizeof(remoteaddr);
                    newsockfd = accept(sockfd, (struct sockaddr *)&remoteaddr, &addrlen);

                    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: new connection from %s on socket %d\n\r", remoteIP, newsockfd);
                } 
                else if (i == sockfd_udp) 
                {
                    // handle new udp connections
                    addrlen_udp = sizeof(remoteaddr_udp);
                    recv_bytes_udp = recvfrom(i, buf_udp, sizeof(buf_udp), 0, (struct sockaddr *)&remoteaddr_udp, &addrlen_udp); 
                    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[k] == '\r') | (buf_udp[k] == '\n') )
                            buf_udp[k] = '\0';
                    }
                    fprintf(stdout, "CLI UDP Server: received %s from connection %s\n\r", 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: socket %d hung up\n\r", i);
                        }
                        else 
                        {
                            perror("CLI Server error: recv");
                            exit(6);
                        }

                        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: received %s from socket %d\n\r", buf_tcp, i);
                    }
                } // END handle data from client
            } // END got new incoming connection
        } // END looping through file descriptors
    } while(QUIT);

我正在每个阶段进行错误检查,但没有将其包含在代码段中 . 当我编译并运行它时,我可以连接到端口2000而不是2001,我的Tera术语终端关闭连接被拒绝消息 . 为什么客户端无法连接到端口2001(UDP套接字),而是连接到端口2000(TCP套接字) . 服务器只响应客户端消息,直到客户端进入QUIT .

我已经从Beej的网络编程指南selectserver.c代码中建模了这段代码 .

1 回答

  • 0

    我上面写的程序是正确的,但我对套接字编程的理解不是 . 这是一个侥幸,我设法编写正确的代码,但感谢@EJP在评论中进行了扩展讨论,以澄清我的疑问 .

    我的错误是使用Teraterm 's TCP client to connect to a UDP server. Both communications are mutually exclusive & hence can' t彼此沟通 . 所以我不得不使用UDP客户端 . Netcat使用 netcat -u <ip address> <port> 提供UDP客户端选项 . 然后我的UDP服务器能够从UDP客户端接收消息 .

    另一个错误是在DATAGRAM套接字中将bind()与connect()混淆 . 连接的DGRAM就是我在服务器和客户端上使用连接时 .

    我认为问题出在select()上,因为我错误地认为UDP和TCP套接字不能在select()中同时使用 . 但上面的代码是你为多个客户端编写UDP / TCP服务器的方法 .

    再次感谢Beej和@EJP

相关问题