【问题标题】:C++ WINSOCK tcpaccept stops accepting connections after an "attack"C++ WINSOCK tcpaccept 在“攻击”后停止接受连接
【发布时间】:2015-08-05 16:24:02
【问题描述】:

我有一个 C++ 游戏服务器,我正在使用一个网络库在 Windows 中使用 winsock。 我一直在对我的服务器进行压力测试,看看它一次可以接受多少个连接。当我使用我的游戏客户端连接时它工作正常,但在我进行如下所述的压力测试后我的游戏客户端无法再连接。

压力测试是,我使用一个简单的 for 循环程序连接到我的服务器大约 1000 次,它只是启动与我的游戏服务器的 tcp 连接并立即关闭它。它们都连接在一起。然后,之后,我尝试连接我的游戏。游戏根本连不上。

我检查了库 (见下文) 中的 tcpaccept() 函数,没有输出。由于某种原因,accept() 在我“攻击”了 1000 个连接后停止接受连接。 什么可能使我的服务器停止接受连接?

以下是我对侦听和接受连接并关闭它们的循环的总结:

bool serverIsOn = true;
double listen = tcplisten(12345, 30000, 1);
setnagle(listen, true);

...

while(serverIsOn){
    double playerSocket = tcpaccept(listen, 1);
    if(playerSocket > -1){
        cout << "Got a new connection, socket ID: " << playerSocket << endl;

        //add their sockID to list here!
        addSockIDToList(playerSocket);

    }

    //Loop through list of socks and parse their messages here..
    //If their message size == 0, we close their socket via closesocket(sockID);
    loopThroughSocketIdsAndCloseOnLeave();
}

cout << "Finished!" << endl;

这里是 tcplisten、tcpaccept、CSocket::CSocket(SOCKET)、CSocket::tcplisten(...) 和 CSocket::tcpaccept(...) 的定义:

double tcplisten(int port, int max, int mode)
{
    CSocket* sock = new CSocket();
    if(sock->tcplisten(port, max, mode))
        return AddSocket(sock);
    delete sock;
    return -1;
}

double tcpaccept(int sockid, int mode)
{
    CSocket*sock = (CSocket*)sockets.item(sockid);
    if(sock == NULL)return -1;
    CSocket*sock2 = sock->tcpaccept(mode);
    if(sock2 != NULL)return AddSocket(sock2);
    return -1;
}

...

CSocket::CSocket(SOCKET sock)
{
    sockid = sock;
    udp = false;
    format = 0;
}

bool CSocket::tcplisten(int port, int max, int mode)
{
    if((sockid = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) return false;
    SOCKADDR_IN addr;
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    if(mode)setsync(1);
    if(bind(sockid, (LPSOCKADDR)&addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
    {
        closesocket(sockid);
        return false;
    }
    if(listen(sockid, max) == SOCKET_ERROR)
    {
        closesocket(sockid);
        sockid = INVALID_SOCKET;
        return false;
    }
    return true;
}


CSocket* CSocket::tcpaccept(int mode)
{
    if(sockid == INVALID_SOCKET) return NULL;
    SOCKET sock2;
    if((sock2 = accept(sockid, (SOCKADDR *)&SenderAddr, &SenderAddrSize)) != INVALID_SOCKET)
    {
        //This does NOT get output after that 1000-'attack' test.
        std::cout << "Accepted new connection!" << std::endl;
        CSocket*sockit = new CSocket(sock2);
        if(mode >=1)sockit->setsync(1);
        return sockit;
    }

    return NULL;
}

在我的 1000 个连接压力测试之后,我可以做些什么来弄清楚为什么 accept() 不再接受连接?这与我在完成连接后关闭连接的方式有关吗?当我这样做时,我所做的只是调用:closesocket(sockID)

请询问需要的任何其他代码!

编辑: 我刚刚注意到我的“压力测试”java 程序 在连接大约 668 次 后出现异常。这是一个例外:

Exception in thread "main" java.net.ConnectException: Connection refused: connect
    at java.net.DualStackPlainSocketImpl.connect0(Native Method)
    at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
    at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:339)
    at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:200)
    at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:182)
    at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
    at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
    at java.net.Socket.connect(Socket.java:579)
    at java.net.Socket.connect(Socket.java:528)
    at java.net.Socket.<init>(Socket.java:425)
    at java.net.Socket.<init>(Socket.java:208)
    at sockettest.SocketTest.main(SocketTest.java:63)
Java Result: 1

【问题讨论】:

  • 我不确定这是否是问题,但我也注意到当有人连接到服务器时,sockID 每次都会增加一。这个库似乎没有回收套接字 ID(不确定 winsock 是否应该这样工作)。例如,如果一个玩家的 sockID 为 2 并且他们离开了,那么下一个加入的玩家的 sockID 将是 3 而不是 2。
  • 与其他平台不同,Windows 不使用 ID 号识别套接字。套接字是内核中的实际对象,因此使用对象句柄表示。一旦套接字关闭,该句柄可能会被重用。人们在关闭套接字后忘记使套接字句柄无效(将其设置为INVALID_SOCKET)是一个常见错误,然后他们作用于非无效套接字句柄并最终作用于恰好发生在的完全不同的套接字上使用相同的句柄值。这就是为什么您应该在关闭句柄后始终使其无效。
  • connect() 可能由于多种不同的原因而失败并出现“连接被拒绝”错误,但最常见的(假设防火墙没有阻止连接)是服务器套接字没有在监听端口,或者服务器套接字的待处理连接积压已满(这意味着accept() 没有被足够频繁地调用,如果有的话)。
  • 您好 Remy,使套接字 ID 句柄无效的正确代码是什么?我仍在试图弄清楚如何使用这个库。另外,我每次迭代都调用accept() 来查看是否有连接通过。它肯定是被调用的。以及如何检查积压是否已满,以及关闭套接字时清空它的正确方法是什么?
  • 我在上一条评论中说过如何(将套接字句柄设置为INVALID_SOCKET),例如:closesocket(sockid); sockid = INVALID_SOCKET;listen() 失败时,您在CSocket::tcplisten() 中这样做,但在@987654331 失败时不会这样做@ 失败。

标签: c++ sockets winsock ddos


【解决方案1】:

因为您的服务器端正在关闭套接字,所以它们很可能会在 time_wait 中等待几分钟。 Windows 具有控制最大套接字和各种状态的各种参数。我猜您的程序会在几分钟后重新开始运行,并且事件查看器中可能会出现一些警告。

另一种方法可能是简单地忽略这些套接字几分钟并希望它们消失。即当您根本没有响应时客户端调用closesocket,这意味着您不会产生time_wait。这通常有效,但并非总是如此。如果他们没有,那么你在后台慢慢地对他们调用 closesocket()。

如果你真的想要,你可以重置连接,详情请参阅TCP option SO_LINGER (zero) - when it's required,但重置连接是不正常的,所以一定要广泛阅读有关 So_linger 以及 tcp 拆除工作原理的信息。

【讨论】:

  • 我“攻击”后实际上等了大约 20 分钟,但我仍然无法通过我的游戏客户端连接到我的服务器。我也没有把它放在我的代码中,但是如果我从客户端获得了在一段时间后没有向我发送响应的连接,我认为该客户端不是游戏的一部分,我会禁止它们。但是,他们仍然可以通过 tcpaccept() 获得接受的连接。之后我只是关闭了他们的套接字。我如何忽略它们或拒绝它们一起通过 accept() 连接?
  • @JoeBid:你为什么要“禁止”一个超时的客户?如果他们的网络连接只是断了然后又恢复了怎么办?在任何情况下,您都不能在accept() 首先接受客户端连接之前拒绝它。您只需要立即关闭它。在没有accept() 接受的情况下拒绝连接的唯一方法是根本不使用accept(),而是改用WSAAccept()(这是WinSock 的Windows 特定扩展)。
  • 哦,不,我没有禁止超时的客户端我禁止没有返回我的游戏客户端应该响应的正确响应的客户端。比如我的压力测试程序。他们所做的只是连接和关闭他们的连接。但问题是,WSAAccept() 能否告诉我游戏客户端连接是否被拒绝以及为什么?如果服务器根本无法获得连接怎么办? WSAAccept() 在我的压力测试之后会得到什么吗? (正在查找如何使用它)。我真的不确定我会用这个新的 WSAAccept() 函数来解决这个问题。
  • 你有可能关闭监听套接字吗?看起来它与普通套接字在同一个列表中?运行压力测试后,netstat -na 是否仍然显示正在侦听端口 12345。这可以解释 connrefused。
  • 嗯,在我进行压力测试后,端口 12345 不再出现在该列表中!关于为什么会发生这种情况的任何想法?在程序结束之前(在 while 循环之后),我从不关闭监听套接字。
【解决方案2】:

原来这个库有它自己的关闭套接字的方法:

int closesock(int sockid)
{
    CSocket*sock = (CSocket*)sockets.item(sockid);
    if(sock == NULL)return -1;
    delete sock;
    sockets.set((int)sockid, NULL);
    return 1;
}

所以它通过套接字列表中的 sockID 获取当前套接字。 然后,如果 sockID 与有效的套接字相关,则删除 sock 对象并在套接字列表中将其设置为 NULL。

问题是我只调用了 closesocket(sockID) 而不是 closesock(sockID),后者执行了关闭套接字所需的必要操作。

感谢大家的帮助。

【讨论】:

    猜你喜欢
    • 2011-03-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多