TCP选择功能不适用于第二次连接

时间:2018-05-27 19:02:06

标签: sockets

我在使用Windows 10的DELL E6400上使用Visual Studio Enterprise 2017.我正在编写一个充当服务器的功能。在连接一次连接后我无法接受连接。我已关闭之前的连接。我使用Wireshark并且可以看到SYN,SYN / ACK,ACK,因此逻辑上已经建立了连接。但我的程序并没有出现在select调用中(请注意,第一次连接顺利,并且在关闭第一个连接后会出现此问题。)

以下是代码序列的要点“()”表示它是一个函数调用:

gethostname()
gethostbyaddr()
server_addr->sin_family
server_addr->sin_port
socket()
setsockopt()
bind()
getsockname()
listen()
FD_ZERO();
FD_SET();
k = select()
closesocket()

以下是实际代码:

#include <stdlib.h>
#include <conio.h>
#include "EasyTcp.h"

//  On Windows, use Ws2_32.dll

/*  need to define some linux-only symbols for other systems */
#ifndef SOL_TCP
    #ifndef IPPROTO_TCP
        #define IPPROTO_TCP 6           // 6 usually = TCP
    #endif
    #define SOL_TCP     IPPROTO_TCP     /* tcp's protocol number (from /etc/protocols) */
#endif

#define LISTENING_DEPTH         3
#define MAXHOSTNAMELEN          132

void start_up(void);
SOCKET openserver( unsigned int server_port, char *server_name,
               struct sockaddr_in *server_addr );
int print_address(char *message, int len, struct sockaddr_in *addr,
                  unsigned int *host, unsigned int *port);
int print_ip_address( unsigned int ip_address );
SOCKET get_local_address( SOCKET fd, unsigned int *host, unsigned int *port);
int get_ip_address( char *name, unsigned int *host );
SOCKET accept_a_client(SOCKET listening_fd, struct sockaddr_in *addr,
    unsigned int *whole, unsigned int *fraction);



/*  Accept a connection on port local_port.
    Returns the socket or -1 if an error.
*/
SOCKET EasyAccept( int local_port )
{
    SOCKET     listening_fd = -1;      //  -1 when not connected
    SOCKET     fd;

    unsigned int sys_remote_port = 0;
    unsigned int sys_local_host = 0;
    unsigned int sys_remote_host = 0;

    struct sockaddr_in  target_addr;
    struct sockaddr_in  initiator_addr;

    unsigned int sys_local_port = local_port;    // The port to listen on
    unsigned int *whole_ptr = NULL, *fraction_ptr = NULL;   // The amount of time to listen
    char         *default_host = NULL;       // The host you want to listen to, else NULL

    listening_fd = openserver( sys_local_port, default_host, &target_addr );
    if( listening_fd >= 0 )
    {
        print_address("Listening", 1, &target_addr, &sys_local_host,
            &sys_local_port);
    }
    else
    {
        printf("openserver returned an error\n");
        return -1;
    }

    if( (fd = accept_a_client(listening_fd, &initiator_addr,
        whole_ptr,fraction_ptr)) >= 0 )
    {/* accept succeeded, fd is connection to new initiator */

        print_address("Server", 1, &initiator_addr, &sys_remote_host,
            &sys_remote_port);
        get_local_address(fd, &sys_local_host, &sys_local_port);
    }
    else
    {/* accept failed */
        printf("accept failed, fd=%p\n", (void *)fd);
        return -1;
    }

    return fd;
}

int EasyConnect( int remote_port, char *remote_server )
{
    // not done yet
    printf("EasyConnect() is not finished\n");
    _getch();
    exit(1);
}


/*  Opens a new TCP socket for the server at interface server_name,           */
/*  port server_port.  Returns in server_addr the server's internet           */
/*  address structure.  The server is NOT connected to a client on return.    */
/*  Returns an fd that is a "listening post" on which to make connections.    */
SOCKET openserver( unsigned int server_port, char *server_name,
               struct sockaddr_in *server_addr )
{
    int                     len;
    SOCKET                  fd;
    struct sockaddr_in      address1;
    struct in_addr          address0;
    struct hostent          *node_ptr;
    char                    local_node[MAXHOSTNAMELEN];
    char                    *host_name;
    char                    buffer[64];


    start_up();

    /*  get the internet name of the local host node on which we are running */
    if( gethostname(local_node, MAXHOSTNAMELEN) < 0 )
    {
        printf("openserver gethostname: %s\n", strerror(errno));
        strcpy(local_node, "Unknown Host");
    }
    if( server_name == NULL )
        host_name = local_node;             /* default to local node */
    else
        host_name = server_name;

    /*  get structure for local interface server is to use */
    if( isdigit((int)host_name[0]) )
    {/* must be an ip address, not a DNS name */
        #ifdef __sun__
            if( (*((int *)&address0) = inet_addr(host_name)) == -1 )
        #else
            if( !inet_aton(host_name, &address0) )
        #endif
            {
                printf("Invalid IP address \"%s\"\n", host_name);
                return -1;
            }
            if( (node_ptr = gethostbyaddr((char *)&address0,
                sizeof(address0), AF_INET)) == NULL )
            {
                herror(host_name);
                return -1;
            }
    }
    else if( (node_ptr = gethostbyname(host_name)) == NULL )
    {
        herror(host_name);
        return -1;
    }

    /*  set up Internet address structure for the server */
    if (server_addr == NULL)               /* user doesn't want this back */
    {
        server_addr = &address1;            /* so store it locally */
    }
    memset(server_addr, 0, sizeof(struct sockaddr_in));
    server_addr->sin_family = AF_INET; // node_ptr->h_addrtype;     /* should be AF_INET */
    server_addr->sin_port = htons( (u_short)server_port );
    if( server_name == NULL )
        server_addr->sin_addr.s_addr=htonl(INADDR_ANY); /* use any interface */
    else
        memcpy(&server_addr->sin_addr, node_ptr->h_addr, node_ptr->h_length);

    /*  open an internet TCP socket */
    if( (fd = socket(PF_INET, SOCK_STREAM, 0)) < 0 )
    {
        printf("openserver socket: %s\n", strerror(errno));
        return -1;
    }

    /*  set this socket to reuse port addresses quickly */
    if (setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, buffer, sizeof( buffer ) ) < 0)
    {
        printf( "openserver setsockopt REUSEADDR: %s\n", strerror( errno ) );
    }

    /*  bind this socket to the server's Internet address */
    if( bind(fd,(struct sockaddr *)server_addr,sizeof(struct sockaddr_in))<0 )
    {
        printf("Bind of IP address %s returned an error, port %d: %s\n",
            inet_ntoa(server_addr->sin_addr), ntohs(server_addr->sin_port),
            strerror(errno));
        //close(fd);
        return -1;
    }

    /*  now find out what local port number was assigned to this server */
    len = sizeof(struct sockaddr);
    if( getsockname(fd, (struct sockaddr *)server_addr, (void *)&len) < 0 )
    {
        printf("openserver getsockname: %s\n", strerror(errno));
        close(fd);
        return -1;
    }

    /*  set up listening backlog for connect requests from clients */
    if( listen(fd, LISTENING_DEPTH) < 0 )
    {
        printf("openserver listen: %s\n", strerror(errno));
        close(fd);
        return -1;
    }

    /*  we are now successfully established as a server */
    return fd;                              /* return fd of listening socket */
}


int print_address(char *message, int len, struct sockaddr_in *addr,
                  unsigned int *host, unsigned int *port)
{
    printf("%*s at IP address %s, port %d\n",
        len, message, addr->sin_addr.S_un.S_addr == 0 ? "any" : inet_ntoa(addr->sin_addr), ntohs(addr->sin_port));
    if( host != NULL )
        *host = ntohl(addr->sin_addr.s_addr);
    if( port != NULL )
        *port = ntohs(addr->sin_port);
    return 0;
}


int print_ip_address( unsigned int ip_address )
{
    struct sockaddr_in  x;

    x.sin_addr.s_addr = htonl(ip_address);
    printf("%s", inet_ntoa(x.sin_addr));
    return 0;
}


SOCKET get_local_address( SOCKET fd, unsigned int *host, unsigned int *port)
{
    #if defined(__osf__) || defined(_HPUX_SOURCE)
        struct sockaddr_in  addr;
        int                 n = sizeof(addr);
    #else
        struct sockaddr_in  addr;
        int                 n = sizeof(addr);
    #endif


    if( getsockname(fd, (struct sockaddr *)&addr, &n) < 0 )
    {
        perror("getsockname");
        return -1;
    }
    if( host != NULL )
        *host = ntohl(addr.sin_addr.s_addr);
    if( port != NULL )
        *port = ntohs(addr.sin_port);
    return 0;
}


int get_ip_address( char *name, unsigned int *host )
{
    struct in_addr          address0;
    struct hostent          *node_ptr;


    /*  get structure for remote host node on which server resides */
    if( isdigit((int)name[0]) )
    {/* must be an ip address, not a DNS name */
        #ifdef __sun__
            if( (*((int *)&address0) = inet_addr(name)) == -1 )
        #else
            if( !inet_aton(name, &address0) )
        #endif
            {
                printf("Invalid IP address \"%s\"\n", name);
                return -1;
            }
            if( (node_ptr = gethostbyaddr((char *)&address0,
                sizeof(address0), AF_INET)) == NULL )
            {
                herror(name);
                return -1;
            }
    }
    else if( (node_ptr = gethostbyname(name)) == NULL )
    {
        herror(name);
        return -1;
    }

    if( host != NULL )
        *host = ntohl(*(unsigned int *)node_ptr->h_addr);
    return 0;
}


/*  wait for at most whole.fraction seconds to accept a connection */
/*  returns fd >= 0 if ok, -1 on error, -2 on timeout or control-C */
SOCKET accept_a_client( SOCKET listening_fd, struct sockaddr_in *addr,
                    unsigned int *whole, unsigned int *fraction )
{
    struct sockaddr     *client;
    struct sockaddr     local_addr;
    int                 len, k;
    SOCKET              fd;
    struct timeval      local_tv, *tv;
    fd_set              readset;
    char                buffer[64];


    len = sizeof(struct sockaddr);
    if( addr == NULL )
        client = &local_addr;
    else
        client = (struct sockaddr *)addr;

    //printf("Accepting connection\n");

    fflush(stdout);
    do  {
        if( whole == NULL  ||  fraction == NULL )
            tv = NULL;                          /* no timeout given */
        else
        {/* have a timeout, must set it up each time around loop */
            tv = &local_tv;
            tv->tv_sec = *whole;
            tv->tv_usec = *fraction;
        }
        FD_ZERO(&readset);
        FD_SET(listening_fd, &readset);
        k = select(0, &readset, NULL, NULL, tv);  // for Windows the 1st parameter is not used
        if( k < 0 )
        {/* select found an error */
            if( errno == EINTR )
            {/* return forced by control-C, treat it like timeout */
                return -2;
            }
            else
            {
                printf("Select on fd %p: %s\n", (void *)listening_fd, strerror(errno));
                return -1;
            }
        }
        else if( k == 0 )
        {/* select timed out */
            return -2;
        }
        else if( (fd = accept(listening_fd, client, (void *)&len)) < 0 )
        {/* accept found an error */
            if( errno == EINTR )
                return -2;
            else
            {
                printf("Accept on fd %p: %s\n", (void *)listening_fd, strerror(errno));
                return -1;
            }
        }
        else
        {/* fd is now the newly accepted connection from a new client */
            /* turn off the Nagle Algorithm on this connection */
            if( setsockopt(fd, SOL_TCP, TCP_NODELAY, buffer, sizeof(buffer)) < 0 )
            {
                printf("accept_a_client setsockopt NODELAY: %s\n",
                    strerror(errno));
            }
            break;
        }
    }
    while( 1 );

    /*****
    printf("accept_a_client returning fd %p\n", fd);
    *****/

    return fd;
}

1 个答案:

答案 0 :(得分:0)

由于您的interface Visitor { void Visit(Target target); } class Target { void Accept(Visitor visitor) { visitor.Visit(this); // not just a field but a whole instance } } 函数未包含在已发布的代码中,因此很难确定发生了什么,但如果我可以假设它正在调用{{1在一个循环中,问题是你的main()函数形成错误。

特别是,EasyAccept()似乎调用EasyAccept(),它会创建一个新的TCP连接接受套接字,并在其上调用EasyAccept()openserver()。到现在为止还挺好。

然后,有问题的部分:bind()然后继续使用该套接字接受传入的TCP连接并返回TCP连接的套接字。

那为什么会出现这个问题呢?因为当listen()返回时,您的程序已经泄露了#34; EasyAccept()套接字 - 也就是说,套接字仍处于打开状态,但您的程序无法知道前一次调用中EasyAccept()的值是多少,因此它不能再接受TCP连接。

因此,第二次调用listening_fd时,它将尝试创建另一个接受TCP连接的套接字,但这次listening_fd调用将失败,因为已经存在TCP连接 - 接受套接字侦听要绑定的端口 - &#34;忘记&#34;第一次拨打EasyAccept()时的TCP套接字。

要解决此问题,您需要将bind()的呼叫从EasyAccept()功能移出;相反,在程序执行开始时(可能在openserver()的顶部附近)只调用EasyAccept()一次,并且在程序的持续时间内保持其返回值&# 39;执行。然后修改openserver()以将main()值作为其参数而不是EasyAccept()。这样,每次调用SOCKET都可以重用一个接受TCP连接的套接字,而不是在每次调用时错误地尝试创建一个新套接字。