1

我正在编写一个 bittorrent 客户端,需要通过 tcp 连接联系多个跟踪器。为了做到这一点,我编写了一个如下所示的 winsock 包装类:

class trackerSocket{
public:
    ~trackerSocket();

    int trackerInitialize(string address);
    int trackerSend(string getParams);
    int trackerRecv();

    be_node *responseDict;
    bool working;

private:
    string address;
    string port;
    string protocol;
    string page;
    SOCKET ConnectSocket;

    int parseAnnounce(string announce);
    int parseTrackerResponse(string response);
};

该程序首先将一个新的 trackerSocket 类分配给一个变量。在此函数上调用 trackerInitialize 函数,如果成功,则将该类推送到一个向量上以存储所有工作跟踪器。这是 trackerInitialize 函数:

int trackerSocket::trackerInitialize(string announce){
    WSADATA wsaData;
    int iResult;

    working = true;

    iResult = parseAnnounce(announce);
    if(iResult != 0){
        working = false;
        return iResult;
    }

    //Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if(iResult != 0){
        return 1;
    }

    struct addrinfo *result = NULL,
                    *ptr  = NULL,
                    hints;

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    //Resolve the server address and port
    iResult = getaddrinfo(address.c_str(), port.c_str(), &hints, &result);
    if(iResult != 0){
        WSACleanup();
        return 1;
    }

        ConnectSocket = INVALID_SOCKET;

    //Attempt to connect to the first address returned by
    //the call to getaddrinfo
    ptr = result;

    do{
        //Create a socket for connecting to the server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        if(ConnectSocket == INVALID_SOCKET){
            ptr = ptr->ai_next;
            continue;
        }

        //Connect to server
        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if(iResult != SOCKET_ERROR){
            break;
        } else {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            ptr = ptr->ai_next;
        }
    } while(ptr != NULL);

    freeaddrinfo(result);
    if(ConnectSocket == INVALID_SOCKET){
        working = false;
        WSACleanup();
        return 1;
    }

    return 0;
}

然后程序执行代码以生成要发送到跟踪器的消息。对于向量中的每个跟踪器类,它的 trackerSend 函数与消息一起调用,这里是 trackerSend 函数:

int trackerSocket::trackerSend(string getParams){
    int iResult;

    ostringstream os;
    os << "GET " << page << getParams << " HTTP/1.1\r\n"
        << "Host: " << address << "\r\n"
        << "Accept: text/html\r\n"
        << "\r\n";

    string sendBuf = os.str();

    //Send tracker request
    iResult = send(ConnectSocket, sendBuf.c_str(), strlen(sendBuf.c_str()), 0);
    if(iResult == SOCKET_ERROR){
        working = false;
        closesocket(ConnectSocket);
        WSACleanup();
        return 1;
    }

    return 0;
}

每次程序运行时,发送函数都会为每个跟踪器返回 -1。如果我调用 WSAGetLastError() 函数,它会返回 10093。此错误的 msdn 定义是:

尚未执行成功的 WSAStartup。应用程序没有调用 WSAStartup 或 WSAStartup 失败。应用程序可能正在访问当前活动任务不拥有的套接字(即,尝试在任务之间共享套接字),或者 WSACleanup 已被调用太多次。

我看不到 WSACleanup 被调用了太多次,所以我只能假设套接字不属于当前活动任务(不知道这意味着什么),任何人都可以看到问题吗?

这是主程序的一些代码(我在上面已经描述过):

//Store tracker URL's in vector
vector<trackerSocket> trackers;
trackerSocket *temptracker = new trackerSocket();
iResult = temptracker->trackerInitialize(announce);
if(iResult == 0){
    trackers.push_back(*temptracker);
}
if(announcelist != NULL){
    i = 0;
    while(announcelist[i]){
        if(strcmp(announcelist[i]->val.l[0]->val.s, announce.c_str()) != 0){
            temptracker = new trackerSocket();
            iResult = temptracker->trackerInitialize(announcelist[i]->val.l[0]->val.s);
            if(iResult == 0){
                trackers.push_back(*temptracker);
            }
        }
        i++;
    }
}

//Check that at least one of the tracker URL's was valid
if(trackers.size() == 0){
    printf("None of the tracker URL's provided were valid.\n");
    return 1;
}

//Generate some required values
string peerid = genPeerID();
string peerport = "12345";
int uploaded = 0;
int downloaded = 0;

//Work out how many bytes are left to download
int left = 0;
if(singlefile){
    left = length;
} else {
    for(i = 0; i < filesinfo.size(); i++){
        left += filesinfo[i].length;
    }
}

//Send GET Request to tracker
i = 0;
ostringstream os;
string getParams;
string response;
os << "info_hash=" << infohash << "&peer_id=" << peerid << "&port=" << peerport <<
    "&uploaded=" << uploaded << "&downloaded=" << downloaded << "&event=started";

getParams = os.str();
do{     
    iResult = trackers[i].trackerSend(getParams);
    if(iResult != 0){
        printf("trackerSend %d failed: %d\n", i, iResult);
        i++;
        continue;
    }
} while(i < trackers.size());
4

2 回答 2

2

从您的代码中,您不应该在发送时调用 WSACleanup,如果一个跟踪器发送失败,您将减少内部计数器,如果它达到 0,则系统将需要您没有调用的新 WSAStartup...

我建议您只调用一次 WSAStartup(例如在应用程序启动时),只调用一次 WSACleanup。(例如申请结束)

于 2012-07-25T13:28:53.597 回答
1

在从代码中休息后,我设法找出了问题所在。我相信这个SO问题和答案中描述了这个问题:

对象的 C++ 向量与指向对象的指针向量

关于 C++ 中的向量,您必须了解的是,它们必须使用对象类的复制运算符才能将它们输入到向量中。如果您在调用析构函数时自动释放这些对象中的内存分配,则可以解释您的问题:您的对象被复制到向量中然后被销毁。

如果您的对象类中有一个指向已分配缓冲区的指针,则该对象的副本将指向同一个缓冲区(如果您使用默认的复制运算符)。如果析构函数释放缓冲区,当调用复制析构函数时,原始缓冲区将被释放,因此您的数据将不再可用。

如果您使用指针,则不会发生此问题,因为您通过 new/destroy 控制元素的生命周期,并且矢量函数仅将指针复制到您的元素。

在将向量更改为存储指向 trackerSocket 类的指针而不仅仅是 trackerSocket 类的副本并更改对向量中的 trackerSocket 函数的调用 (. to ->) 之后,问题已成功修复。以下是我所做的更改:

老的:

vector<trackerSocket> trackers;
trackers.push_back(*temptracker);
trackers[i].trackerSend(getParams);

新的:

vector<trackerSocket*> trackers;
trackers.push_back(temptracker);
trackers[i]->trackerSend(getParams);
于 2012-08-05T21:59:56.897 回答