Winsock accept()返回WSAENOTSOCK(代码10038)

时间:2011-03-14 22:29:10

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

希望你过得愉快。另一个插座问题,另一天:)

我终于安装了MicroSoft Visual C ++(MSVC ++)IDE,加上Platform SDK,所以我可以编译winsock应用程序。

错过了一大堆东西。在ServerSocket :: accept()函数中,它创建了一个新的ClientSocket实例,并将它的套接字文件描述符设置为accept()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& sock);,并实施为:ClientSocket mysock; server.accept(mysock);

非常感谢!!!

2 个答案:

答案 0 :(得分:4)

您似乎没有关注Rule of Three。只要有了析构函数,就需要编写或禁用copy-constructor和赋值运算符。

在您的示例用法中:

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编写移动的复制构造函数,但我不建议这样做。人们不希望复制构造函数窃取资源。

答案 1 :(得分:0)

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

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

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