c++ winsock irc客户端问题

Posted

技术标签:

【中文标题】c++ winsock irc客户端问题【英文标题】:c++ winsock irc client questions 【发布时间】:2017-04-04 20:15:26 【问题描述】:

我正在尝试使用 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;

当我运行它时,我看到的只是我的变量被传递,然后是无限数量的零。

【问题讨论】:

【参考方案1】:

您的代码存在许多问题。

    您没有检查socket() 的返回值是否有错误(我假设您正在事先调用WSAStartup(),对吗?)。

    您没有在 PASSNICK 命令的末尾发送任何换行符。 IRC 是基于线路的协议。这就是为什么您没有从服务器获取任何数据的原因 - 它正在等待您先完成您的命令。

    IRC 中的各种保留字符必须转义。

    您发送了两次PASS 命令,因为您在设置NICK 命令时使用的是+ 运算符而不是= 运算符。

    您没有发送任何USERJOIN 命令。

    您不应该使用strlen() 来计算std::string 的长度。为此,它有自己的length()size() 方法。

    您忽略了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::size_type 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;

【讨论】:

是的,我使用的是 wsastartup 我试图复制我认为最相关的代码片段我编辑了注释以显示缺失的部分。感谢您的深入回答:-) 还有我应该看的套接字教程吗?我在这里和那里看过一些 YouTube 视频......【参考方案2】:

首先,您忽略了recv 的返回值,因此您不知道收到了多少字节。

其次,您实际上没有在任何地方实现 IRC 消息协​​议(请参阅 RFC1459 的第 2.3 节)。所以你没有理由假设你的缓冲区的第一个字节将包含 IRC 协议消息的第一个字节。只有 IRC 消息协​​议的实际实现才能产生一个缓冲区,其第一个字节是 IRC 消息的第一个字节。

同样,你不能这样做:

    cout << buffers << endl << endl;

流的operator&lt;&lt;(const char *) 需要一个指向C 风格字符串的指针。在解析从 TCP 连接接收到的数据并从中生成 C 样式字符串之前,不得将其视为 C 样式字符串。

还有:

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

您真的是指PING 是一个多字节字符常量吗?据我所知,没有名为 PING 的多字节字符。而且,在任何情况下,IRC 服务器都会发送文字的四个字符串“PING”,而不是单个“PING”字符。

【讨论】:

以上是关于c++ winsock irc客户端问题的主要内容,如果未能解决你的问题,请参考以下文章

VC++ Winsock2 错误 10049。尝试构建 IRC 机器人

如何检查客户端是不是通过 C++ 中的 Winsock 断开连接?

Winsock - 从 C++ 中的 Java 客户端读取整数

C++ winsock 错误

如何使用 C++ 在 Winsock 中将大字符串从客户端发送到服务器

带有 winsock 的简单 C++ 服务器-客户端应用程序