2017-04-04 102 views
0

我正在嘗試使用C++中的Winsock製作一個IRC聊天機器人。我是一名編程新手,對於使用套接字進行編程來說是新手。C++ winsock irc客戶端問題

我正在嘗試連接到我的Twitch頻道。我可以成功連接,並傳遞幾個緩衝區(即我的機器人的密碼或oauth標記,用戶名,以及我試圖加入的頻道)。

但是,當我撥打recv()時,沒有數據從Twitch服務器發送。

#define WIN32_LEAN_AND_MEAN 
#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define DEFAULT_BUFLEN 512 

#include <stdio.h> 
#include <stdlib.h> 
#include <stdint.h> 
#include <WinSock2.h> 
#include <Ws2tcpip.h> 
#include <cstdlib> 
#include <iostream> 

#pragma comment(lib, "Ws2_32.lib") 
#pragma comment (lib, "Mswsock.lib") 
#pragma comment (lib, "AdvApi32.lib") 


using namespace std; 

int main() 
{ 
    string Buffer; 
    char buffers[1024 * 8] = { "0" }; 
    string oauth = "oauthtoken"; 
    string nick = "text_adventure_bot"; 
    char recvbuf[DEFAULT_BUFLEN]; 
    int recvbuflen = DEFAULT_BUFLEN; 
    int iResult; 
    string hostname = "irc.chat.twitch.tv"; 
    struct addrinfo *result = NULL, 
     *ptr = NULL, 
     hints; 


    WSABUF DataBuf; 
    WSADATA wsadata; 
    WORD wVersionRequested; 

    WORD DllVersion = MAKEWORD(2, 1); 
    iResult = WSAStartup(MAKEWORD(2, 2), &wsadata); 
    if(iResult != 0) 
    { 
     MessageBoxA(NULL, "Winsock startup failed", "Error", MB_OK | MB_ICONERROR); 
     exit(1); 
    } 

    SOCKADDR_IN addr; //the ip 
    int sizeofaddr = sizeof(addr); 
    addr.sin_addr.s_addr = inet_addr("52.25.27.117"); 
    addr.sin_port = htons(6667); 
    addr.sin_family = AF_INET; 

    SOCKET sock = socket(AF_INET, SOCK_STREAM, NULL); 

    if (connect(sock, (SOCKADDR*)&addr, sizeofaddr) != 0) 
    { 
     cout << "Connection error" << endl; 
    } 

    cout << "connected" << endl; 

    Buffer = "PASS " + oauth; 

    send(sock, Buffer.c_str(), (int)strlen(Buffer.c_str()), 0); 
    recv(sock, buffers, 1024 * 8, 0); 
    cout << Buffer.c_str() << endl << buffers << endl << endl; 

    Buffer + "NICK " + nick; 

    send(sock, Buffer.c_str(), strlen(Buffer.c_str()), 0); 
    recv(sock, buffers, 1024 * 8, 0); 
    cout << Buffer.c_str() << endl << buffers << endl << endl; 

    while (true) { 
     recv(sock, buffers, 1024 * 8, 0); 
     cout << buffers << endl << endl; 
     if (buffers[0] == 'PING') { 
      Buffer = "PONG :" + hostname + "\r\n"; 
      send(sock, Buffer.c_str(), strlen(Buffer.c_str()), 0); 
      cout << Buffer.c_str() << endl << buffers << endl << endl; 
     } 
    } 
    return 0; 
} 

當我運行這個,我看到的是我的變量被傳遞,然後無限量的零。

回答

2

您的代碼有許多問題。

  1. 你是不是檢查socket()錯誤的返回值(我假設你調用WSAStartup()事先吧?)。

  2. 您的PASSNICK命令結束時沒有發送任何換行符。 IRC是一個基於行的協議。這就是爲什麼你沒有從服務器獲取任何數據 - 它正在等待你先完成你的命令。

  3. IRC中的各種保留字符必須轉義。

  4. 你兩次發送PASS命令,當你正在設置NICK命令時使用+運營商,而不是運營商=的。

  5. 您未發送任何USERJOIN命令。

  6. 您不應該使用strlen()來計算std::string的長度。爲此,它有自己的length()size()方法。

  7. 您忽略了send()recv()的返回值。 TCP是一個字節流,但是您沒有考慮到send()recv()可以返回比請求更少的字節。您需要在循環中調用它們,直到您發送/接收到您期望的所有字節爲止。

嘗試一些更喜歡這個:

#include <windows.h> 
#include <winsock.h> 

#include <iostream> 
#include <string> 
#include <algorithm> 

void replaceStr(std::string &str, const std::string &oldStr, const std::string &newStr) 
{ 
    std::string::size_type index = 0; 
    do 
    { 
     index = str.find(oldStr, index); 
     if (index == std::string::npos) 
      return; 

     str.replace(index, oldStr.length(), newStr); 
     index += newStr.length(); 
    } 
    while (true); 
} 

std::string quote(const std::string &s) 
{ 
    std::string result = s; 
    replaceStr(result, "\x10", "\x10""\x10"); 
    replaceStr(result, "\0", "\x10""0"); 
    replaceStr(result, "\n", "\x10""n"); 
    replaceStr(result, "\r", "\x10""r"); 
    return result; 
} 

std::string unquote(const std::string &s) 
{ 
    std::string result = s; 
    std::string::size_type len = result.length(); 
    std::string::size_type index = 0; 
    while (index < len) 
    { 
     index = result.find("\x10", index); 
     if (index = std::string::npos) 
      break; 

     result.erase(index, 1); 
     --len; 

     if (index >= len) 
      break; 

     switch (result[index]) 
     { 
      case '0': 
       result[index] = '\0'; 
       break; 
      case 'n': 
       result[index] := '\n'; 
       break; 
      case 'r': 
       result[index] = '\r'; 
       break; 
     } 

     ++index; 
    } 

    return result; 
} 

std::string fetch(std::string &s, const std::string &delim) 
{ 
    std::string result; 
    std::string::size_type pos = s.find(delim); 
    if (pos == std::string::npos) 
    { 
     result = s; 
     s = ""; 
    } 
    else 
    { 
     result = s.substr(0, pos); 
     s.erase(0, pos+delim.length()); 
    } 
    return result; 
} 

bool sendStr(SOCKET sock, const std::string &s) 
{ 
    const char *ptr = s.c_str(); 
    int len = s.length(); 

    while (len > 0) 
    { 
     int ret = send(sock, ptr, len, 0); 
     if (ret == SOCKET_ERROR) 
     { 
      std::cout << "send() error: " << WSAGetLastError() << std::endl; 
      return false; 
     } 
     ptr += ret; 
     len -= ret; 
    } 

    return true; 
} 

bool sendCmd(SOCKET sock, const std::string &cmd) 
{ 
    std::cout << "Sending: " << cmd << std::endl; 
    return sendStr(sock, quote(cmd)) && sendStr(sock, "\r\n"); 
} 

int main() 
{ 
    int exitCode = -1; 

    WSADATA wsa; 
    int ret = WSAStartup(MAKEWORD(2, 0), &wsa); 
    if (ret != 0) 
    { 
     std::cout << "Winsock init error: " << ret << std::endl; 
     goto done; 
    } 

    SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    if (sock == INVALID_SOCKET) 
    { 
     std::cout << "socket() error: " << WSAGetLastError() << std::endl; 
     goto done; 
    } 

    SOCKADDR_IN addr = {0}; 
    addr.sin_family = AF_INET; 
    addr.sin_addr.s_addr = inet_addr("52.25.27.117"); //the ip 
    addr.sin_port = htons(6667); 

    if (connect(sock, (SOCKADDR*)&addr, sizeof(addr)) != 0) 
    { 
     std::cout << "connect() error: " << WSAGetLastError() << std::endl; 
     goto cleanup: 
    } 

    std::cout << "connected" << std::endl; 

    std::string oauth = ...; 
    std::string nick = ...; 
    std::string user = ...; 
    std::string channel = ...; 

    sendCmd("PASS " + oauth); 
    sendCmd("NICK " + nick); 
    sendCmd("USER " + user); 
    sendCmd("JOIN " + channel); 

    char buf[1024]; 
    std::string LineBuffer; 
    std::string::size_type StartIdx = 0; 

    do 
    { 
     int ret = recv(sock, buf, sizeof(buf), 0); 
     if (ret == SOCKET_ERROR) 
     { 
      std::cout << "recv() error: " << WSAGetLastError() << std::endl; 
      goto cleanup; 
     } 

     if (ret == 0) 
     { 
      std::cout << "Server disconnected" << std::endl; 
      break; 
     } 

     LineBuffer.append(buf, ret); 

     do 
     { 
      std::string pos = LineBuffer.find('\n', StartIdx); 
      if (pos == std::string::npos) 
       break; 

      std::string::size_type len = pos; 
      if ((pos > 0) && (LineBuffer[pos-1] == '\r')) 
       --len; 

      std::string msg = unquote(LineBuffer.substr(0, len)); 
      LineBuffer.erase(0, pos+1); 
      StartIdx = 0; 

      std::string senderNick; 
      std::string senderHost; 

      if (!msg.empty() && (msg[0] == ':')) 
      { 
       std::string tmp = fetch(msg, ' '); 
       tmp.erase(0, 1); // remove ':' 
       senderNick = fetch(tmp, '!'); 
       senderHost = tmp; 
      } 

      std::cout << "Received: " << msg << std::endl; 

      if (msg == "PING") 
       sendCmd("PONG :" + hostname); 
     } 
     while(true); 
    } 
    while (true); 

    exitCode = 0; 

cleanup: 
    closesocket(sock); 

done: 
    return exitCode; 
} 
+0

是的,我是用我調用WSAStartup試圖複製,我認爲最relavent我編輯的評論,顯示缺少的部分代碼片段。感謝您的深入解答:-) – user2350585

+0

也有任何插座教程,我應該看看?我在這裏和那裏觀看了幾個YouTube Vids ... – user2350585

0

首先,你忽略了返回值recv,所以你不知道你收到了多少字節。

其次,你沒有實際實現IRC消息協議(參見RFC1459的2.3節)。所以你沒有理由認爲緩衝區的第一個字節將包含IRC協議消息的第一個字節。只有IRC消息協議的實際實現才能生成緩衝區,其第一個字節是IRC消息的第一個字節。

同樣的,你不能做到這一點:

cout << buffers << endl << endl; 

operator<<(const char *)爲流需要一個指向一個C風格的字符串。在解析從TCP連接收到的數據並從中產生一個C風格的字符串之前,您不能將其視爲C風格的字符串。

另外:

if (buffers[0] == 'PING') { 

你的真正用意PING作爲多字節字符常量?就我所知,沒有多字節字符命名爲PING。而且,在任何情況下,IRC服務器都會發送文字四字符字符串「PING」,而不是單個「PING」字符。