c++ - Windows,C++ : two connections on one server socket?

标签 c++ sockets tcp

我不知道这是一个一般的网络问题还是一个简单的编程问题,所以我决定将其发布在这里。

尝试制作一个聊天程序,我遇到了以下情况:
- 我启动服务器程序;服务器在端口 22001 上创建一个套接字;服务器等待连接(accept() 等待);
- 我启动客户端程序;客户端连接没有错误;客户端发送没有错误;
- 服务器接收消息;
那么:
- 我在同一端口上启动另一个客户端(客户端程序的另一个实例,而不停止或断开第一个客户端的连接);
- 第二个客户端连接时没有错误(?!),尽管服务器不再处于“accept()挂起”状态;
- 第二个客户端发送消息而不显示任何错误(?!);
- 服务器没有收到来自第二个客户端的消息(?!);
实验最后一步:
- 我在服务器端断开套接字;此时,两个(?!)客户端都显示发送错误。

服务器和客户端在同一台计算机上运行,​​并配置为使用端口 22001 和计算机的 IP(例如 192.168.123.123)。
我使用带有超时的阻塞套接字进行读取和写入,并使用 select() 来使accept() 超时。我使用SO_REUSEADDR。我承认我并不完全了解 select() 是如何工作的。
我知道 tcp/ip 根据以下设置来区分连接:服务器 ip、服务器端口、客户端 ip 客户端端口。就我而言,客户端端口应该不同,而且看起来确实如此。但是 52428 是什么端口(?!)?为什么这三个端口都是相同的(请参阅下面的日志)? 远程端口通过 getpeername() 获取,本地端口通过 getsockname() 获取。
我认为服务器上的同一端口上有多个连接是可能的,但不能在同一套接字上有多个连接。我错了吗?
请注意,服务器断开连接后,客户端 1 显示错误 10053,但客户端 2 显示错误 10054。
那么这一切怎么可能呢?如何防止服务器上同一套接字上的多个连接,但允许同一端口上的多个连接?

程序的输出如下:

Server:
 1) Skt 0: SocketListen 116 created - Port 22001 IP 192.168.123.123.
 2) Skt 0: Wait for client connection/
 3) Skt 0: SocketAccept 120 created.
 4) Skt 0: Remote port is 52428 (SocketListen).
 5) Skt 0: Remote port is 52428 (SocketAccept).
 6) Skt 0: Local  port is 22001 (SocketListen).
 7) Skt 0: Local  port is 22001 (SocketAccept).
 8) Skt 0: Client connected.
 9) Skt 0: InUse.
10) Skt 0: << Src=Cli1= Dst=Cli2= Body=Text_A_0.
11) Skt 0: << Src=Cli1= Dst=Cli2= Body=Text_B_0.
12) Skt 0: closing...
13) Skt 0: Both sockets (Listen and Accept/Connect) droped.
14) Skt 0: closed.

Client 1:
 1) Socket 116 created.
 2) Socket connected - Port 22001 IP 192.168.123.123.
 3) Remote port is 52428.
 4) Local  port is 62193
 5) Sent  to  Cli2 CliString: =Text_A_0=.
 6) Sent  to  Cli2 CliString: =Text_B_0=.
 7) ERROR !!! Port 22001 cannot be reached. Error =10053.
 8) ERROR !!! setsockopt( recv_timeout ) failed.
 9) Socket closed.

Client 2:
 1) Socket 116 created.
 2) Socket connected - Port 22001 IP 192.168.123.123.
 3) Remote port is 52428.
 4) Local  port is 62194
 5) Sent  to  Cli1 CliString: =Text_C_0=.
 6) Sent  to  Cli1 CliString: =Text_D_0=.
 7) ERROR !!! Port 22001 cannot be reached. Error =10054.
 8) ERROR !!! setsockopt( recv_timeout ) failed.
 9) Socket closed.

服务器端,部分代码:

class SktSvr
{
    uint32 u32SktIdx;
    uint16 u16PortNr;
    string strIpAddr;
    WSADATA wsaData;
    struct sockaddr_in sAddr_Svr;
    SOCKET SktListen, SktAccept;
    int iRes;
    thread * pThreadRecv, * pThreadSend;
    mutex Mutex_CliId;
    static mutex Mutex_MailBoxes;
    static map< string, map< string, string > > MailBoxes; // key = destin , key = source, message
public:
    char achCliId[ MSG_SRC_STR_LEN + 1 ];
    bool bIsPrep;
    atomic<bool> bInUse;
    SktSvr( uint32 u32SktIdx_p, uint16 u16PortNr_p, char * pchAddr_p = "127.0.0.1" )
    {
        u16PortNr = u16PortNr_p;
        strIpAddr = pchAddr_p;
        u32SktIdx = u32SktIdx_p;
        bIsPrep = bInUse = false;
        pThreadRecv = pThreadSend = NULL;
        achCliId[ 0 ] = '\0';
        memset( &sAddr_Svr, 0, sizeof( sAddr_Svr ) );
        sAddr_Svr.sin_family = AF_INET; // server byte order
        wstring wstrIpAddr( strIpAddr.begin(), strIpAddr.end() );
        InetPton( AF_INET, wstrIpAddr.c_str(), &sAddr_Svr.sin_addr.s_addr ); //INADDR_ANY; // host addr
        sAddr_Svr.sin_port = htons( u16PortNr );
        char chOptVal = 1;
        if( ( iRes = WSAStartup( MAKEWORD( 2, 2 ), &wsaData ) ) != NO_ERROR )
        {
            printf( "\nSkt %d: ERROR !!! WSAStartup failed: %d.", u32SktIdx, iRes );
        }
        else if( ( SktListen = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) // default protocol
        {
            printf( "\nSkt %d: ERROR !!! Port %d can not be opened.", u32SktIdx, u16PortNr );
            printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
            WSACleanup();
        }
        else if( setsockopt( SktListen, SOL_SOCKET, SO_REUSEADDR, &chOptVal, sizeof( chOptVal ) ) == -1 )
        {
            printf( "\nSkt %d: ERROR !!! Port %d set options failed.", u32SktIdx, u16PortNr );
            printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
            WSACleanup();
        }
        else if( bind( SktListen, ( struct sockaddr * ) &sAddr_Svr, sizeof( sAddr_Svr ) ) == SOCKET_ERROR )
        {
            printf( "\nSkt %d: ERROR !!! Port %d can not be bound.", u32SktIdx, u16PortNr );
            printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
            WSACleanup();
        }
        else if( listen( SktListen, 5 ) == SOCKET_ERROR ) // size for backlog queue = 5
        {
            printf( "\nSkt %d: ERROR !!! Port %d can not listen.", u32SktIdx, u16PortNr );
            printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
            WSACleanup();
        }
        else
        {
            printf( "\nSkt %d: SocketListen %d created - Port %d IP %s.",
                u32SktIdx, SktListen, u16PortNr, strIpAddr.c_str() );
            bIsPrep = true;
        }
    }
    bool SktConn()
    {
        int iRes;
        struct timeval tmvl;
        fd_set rfds;
        FD_ZERO( &rfds );
        FD_SET( SktListen, &rfds );
        tmvl.tv_sec = TIMEOUT_SKT_CONN_S;
        tmvl.tv_usec = 0;
        if( 0 ) {}
        else if( 1 && ( iRes = select( SktListen + 1, &rfds, (fd_set*)0, (fd_set*)0, &tmvl ) ) <= 0 )
        {
            // connect timeout
            //printf( "\nSkt %d: Socket Connect timeout.", u32SktIdx );
            bInUse = false;
        }
        else if( ! FD_ISSET( SktListen, &rfds ) )
        {
            printf( "\nSkt %d: Selected another.", u32SktIdx );
        }
        else if( ( SktAccept = accept( SktListen, NULL, NULL ) ) == INVALID_SOCKET )
        {    printf( "\nSkt %d: ERROR !!! Port %d did not connect.", u32SktIdx, u16PortNr );
            printf( "\nSkt %d: Error =%ld.", u32SktIdx, WSAGetLastError() );
            bInUse = false;
        }
        else
        {
            printf( "\nSkt %d: SocketAccept %d created.", u32SktIdx, SktAccept );
            struct sockaddr_in sAddr;
            socklen_t len;
            getpeername( SktListen, ( struct sockaddr* )&sAddr, &len );
            printf( "\nSkt %d: Remote port is %d (SocketListen).", u32SktIdx, ntohs( sAddr.sin_port ) );
            getpeername( SktAccept, ( struct sockaddr* )&sAddr, &len );
            printf( "\nSkt %d: Remote port is %d (SocketAccept).", u32SktIdx, ntohs( sAddr.sin_port ) );
            int iAddrLen = sizeof( sAddr );
            if( getsockname( SktListen, ( struct sockaddr * )&sAddr, &iAddrLen ) == 0 &&
                sAddr.sin_family == AF_INET && iAddrLen == sizeof( sAddr ) )
                printf( "\nSkt %d: Local  port is %d (SocketListen).", u32SktIdx, ntohs( sAddr.sin_port ) );
            if( getsockname( SktAccept, ( struct sockaddr * )&sAddr, &iAddrLen ) == 0 &&
                sAddr.sin_family == AF_INET && iAddrLen == sizeof( sAddr ) )
                printf( "\nSkt %d: Local  port is %d (SocketAccept).", u32SktIdx, ntohs( sAddr.sin_port ) );
            bInUse = true;
        }
        return bInUse;
    }

最佳答案

单个监听套接字用于在单个端口上接受任意数量的连接。对于每个连接,都会创建一个新套接字,并由 accept() 返回,这就是“每个连接一个套接字”的用武之地。您从未听到第二个客户端发送的消息的原因是您只recv() 来自连接到第一个客户端的套接字。

如果客户端连接时监听套接字不在 accept() 调用内,则传入连接将排队。这是标准套接字行为,您不仅会在 Windows 上看到它,而且还会在 BSD、Linux 等上看到它。

接受队列的大小是一个套接字选项。

如果您不想要排队行为,则必须在接受第一个连接后立即关闭监听套接字。但几乎可以肯定,您想要的是使用 select() (或 WSAEventSelectpoll 等)来监视客户端套接字和监听套接字,当监听套接字显示事件时,再次调用 accept() 并同时使多个连接到客户端的套接字处于事件状态。

关于c++ - Windows,C++ : two connections on one server socket?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41618664/

相关文章:

c++ - 客户端和服务器无法交换数据,因为都得到 "stuck"

java - 如何在 Android 中使用 FFMPEG 减小默认摄像头捕获的视频大小?

c++ - 将 const-string 分配给常量大小的 char 数组,未使用的数组索引会发生什么?

c++ - 多次从 std::cin 读取——Linux 和 Mac OS X 上的不同行为

python - 如何将 IPv4 和 IPv6 源地址绑定(bind)到 Python 套接字?

Python Paramiko SSH session 在闲置数小时后未激活

c++ - 为什么多线程循环不是更快?

java - 如何在服务器套接字端检索客户端的 IP 和端口?

c# - PHP 到 C# - 套接字 - 文件随机损坏

linux - 通过sysctl进行性能调优,设置net.core.rmem_default (r/w)/net.ipv4.tcp_mem/net.ipv4.udp_mem有什么区别