【问题标题】:Winsock accept() returning WSAENOTSOCK (code 10038)Winsock accept() 返回 WSAENOTSOCK(代码 10038)
【发布时间】:2011-07-15 09:25:02
【问题描述】:

希望你今天过得愉快。另一个套接字问题,改天:)

我终于安装了 MicroSoft Visual C++ (MSVC++) IDE 以及 Platform SDK,这样我就可以编译 winsock 应用程序了。

这里漏掉了一大堆东西。在 ServerSocket::accept() 函数中,它创建了一个新的 ClientSocket 实例并将它的套接字文件描述符设置为接受()ed 的那个,我也在那里检查过,它认识到描述符在那里也是有效的。

在我的 ClientSocket::recv() 函数中,我(显然)从 winsock 库中调用了 recv() 函数。我遇到的问题是我正在使用的套接字描述符被 recv() 识别为无效,但仅在从我的 ServerSocket::accept() 返回的服务器端 ClientSocket 实例上 - 客户端 ClientSocket 实例没有问题。我插入了多个调试语句,描述符是有效的。

最奇怪的是,如果我在 Windows 上使用 MinGW gcc/g++ 编译这个完全相同的代码,它运行良好!只有使用MSVC++才会出现这个问题。

string ClientSocket::recv(int bufsize) {
    if (!isConnected()) throw SocketException("Not connected.");

    cout << "SocketRecv: " << (sockfd == INVALID_SOCKET) << " " << sockfd << endl;
    vector<char> buffer(bufsize+1, 0);
    cout << "SocketRecv1: " << (sockfd == INVALID_SOCKET) << " " << sockfd << endl;
    int ret = ::recv(sockfd, &buffer[0], bufsize, 0);
    cout << "SocketRecv2: " << (sockfd == INVALID_SOCKET) << " " << sockfd << endl;

    // ret is apparently -1 because of "invalid" socket descriptor, but the
    // above statements print zero (false) on the (sockfd == INVALID_SOCKET) ! :\
    if (ret < 0) {
        #ifdef _WIN32
        switch((ret = WSAGetLastError())) {
        #else
        switch(errno) {
        #endif
            case DECONNREFUSED: // The 'd' prefix means _I_ defined it, i.e. from windows it's
                                // set to 'WSAECONNREFUSED', but from linux it's set to 'ECONNREFUSED'
                throw SocketException("Connection refused on recover.");
                break;
            case DENOTCONN:
                throw SocketException("Not connected.");
                break;
            case DECONNABORTED:
                throw SocketException("Software caused connection abort.");
                break;
            case DECONNRESET:
                throw SocketException("Connection reset by peer.");
                break;
            default:
                //usually this itoa() and char/string stuff isn't here... needed it in 
                //order to find out what the heck the problem was.
                char tmp[21];
                string tmp4 = "Unknown error reading socket. ";
                string tmp3 = tmp4 + itoa(ret, tmp, 10);
                //this throw keeps throwing "Unknown error reading socket. 10038"
                throw SocketException(tmp3); 
                break;
        }
    } else if (ret == 0) {
        connected = false;
        return "";
    }

    return &buffer[0];
}

附加信息:套接字处于阻塞模式,即尚未设置为非阻塞。我已成功调用 WSAStartup()。这发生在服务器端,在从我的 ServerSocket::accept() 返回的 ClientSocket 实例上(是的,我也检查了那里的描述符 - 很好)。客户端声明“WSAECONNRESET (10054)”或“WSAECONNABORTED (10053)”。

我想不出还有什么可能是错的。最糟糕的是,在 Windows 和 linux 上使用 MinGW gcc/g++ 都可以正常工作。

如果您想查看整个库,请将其粘贴在:(注意:600 多行!)
Socket.cxx - http://paste.pocoo.org/show/353725/
Socket.hxx - http://paste.pocoo.org/show/353726/

谢谢!!!

更新 - 根据 Ben 的解决方案,我现在使用:void ServerSocket::accept(ClientSocket&amp; sock);,并实现为:ClientSocket mysock; server.accept(mysock);

非常感谢!!!

【问题讨论】:

    标签: c++ visual-c++ sockets winsock


    【解决方案1】:

    仅仅因为您的套接字变量未设置为INVALID_SOCKET 并不意味着从 WinSock 的角度来看套接字描述符是有效的。显然,它不是,否则 WinSock 不会抱怨它。在您能够调用 recv() 之前,套接字正在关闭(客户端也出现错误很明显)。

    根本原因是ServerSocket::accept() 按值返回一个新的ClientSocket 实例。编译器必须为返回值分配对象的第二个副本,但您的 ClientSocket 类没有定义任何复制构造函数。原始套接字描述符将从第一个 ClientSocket 实例复制到第二个实例,然后在退出时释放原始实例,在第二个实例可以使用它之前关闭套接字。您需要定义一个复制构造函数来获取原始套接字描述符的所有权并将原始实例的描述符设置为INVALID_SOCKET,这样它的析构函数就不能再关闭套接字了。

    与此相关,您的 ClientSocket 类中存在句柄泄漏。您在 ClientSocket 构造函数中同时调用了 WSAStartup()socket()(这不是这两个调用的最佳位置)。当ServerSocket::accept() 接受一个新客户端时,您正在使用新的套接字描述符调用ClientSocket::setFd(),它替换了在ClientSocket 构造函数中分配的原始套接字描述符,而没有正确释放它。您应该定义第二个ClientSocket 构造函数,它接受现有的套接字描述作为输入,然后让该构造函数调用setFd() 而不是socket()。这将消除泄漏,然后复制构造函数可以在需要时获得这个单个分配的套接字描述符的所有权。

    【讨论】:

    • 为什么不首先在旧变量上调用 WSACleanup() 和 closesocket() 方法?我倾向于接受本的回答,真的。
    • 按照我的建议,实施 swap 可以同时解决泄漏问题。
    • 一开始最好不要分配未使用的套接字描述符。这是对系统资源的浪费。在setFd() 内部调用WSACleanup() 是个坏主意,因为代码将不得不再次调用WSAStartup()。应用程序不应该像这样一遍又一遍地调用WAStartup()WSACleanup()。在程序启动时调用WSAStartup()一次,然后根据需要开始使用其他API函数,然后在程序关闭时调用WSACleanup()
    【解决方案2】:

    您似乎没有关注Rule of Three。任何时候你有一个析构函数,你都需要编写或禁用复制构造函数和赋值运算符。

    在您的示例用法中:

    ClientSocket client = server.accept();
    

    变量client 是从返回值复制构造的。然后析构函数在临时变量上运行,关闭套接字。

    在 C++0x 中,您可以添加一个移动构造函数来解决这个问题。现在,您应该实现 swap 并使用它:

    ClientSocket client;
    server.accept().swap(client);
    

    或者将client作为server.accept的参数传递:

    ClientSocket client;
    server.accept(client);
    

    您可以为ClientSocket 编写一个移动复制构造函数,采用auto_ptr 的样式,但我不建议这样做。人们不希望复制构造函数窃取资源。

    【讨论】:

    • 那么,您建议如何从 ServerSocket::accept() 返回 ClientSocket?我宁愿不强制使用指针:\
    • 非常感谢。我现在正在编译它,我希望它能解决问题。
    • 哈利路亚!它工作,使用void ServerSocket::accept(ClientSocket&amp; sock); 和:ClientSocket mysock; server.accept(mysock); 谢谢!你不知道这困扰了我多久。
    • 不客气。现在去禁用复制构造函数和赋值运算符,这样就不会再咬你了。 (最简单的方法:将它们声明为private: 并且不提供任何正文)
    • 另一种选择是引入新的class ClientSocketData,它只保存数据但不释放资源。它的构造函数可以是私有的,但应该有ClientSocket(const ClienSocketData&amp;)构造函数。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-08-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多