Search code examples
c++ircwinsock2

c++ winsock irc client questions


I'm attempting to make a IRC chat bot using Winsock in C++. I am a newb at programming, and am new to programming with sockets.

I am attempting to connect to my Twitch channel. I can make the connection successfully, and pass several buffers (namely, my bot's password or oauth token, user name, and what channel I am trying to join).

However, when I call recv(), there's no data being sent from the Twitch server.

#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;
}

When I run this, all I see are my variable being passed, and then an infinite amount of zeros.


Solution

  • There are a number of problems with your code.

    1. You are not checking the return value of socket() for error (I assume you are calling WSAStartup() beforehand, right?).

    2. You are not sending any line breaks at the end of your PASS and NICK commands. IRC is a line-based protocol. That is why you are not getting any data from the server - it is waiting for you to complete your commands first.

    3. various reserved characters in IRC must be escaped.

    4. You are sending the PASS command twice, as you are using the + operator instead of the = operator when setting up your NICK command.

    5. you are not sending any USER and JOIN commands.

    6. You should not be using strlen() to calculate the length of a std::string. It has its own length() and size() methods for that purpose.

    7. You are ignoring the return values of send() and recv(). TCP is a byte stream, but you are not taking into account that send() and recv() can return fewer bytes than requested. You need to call them in a loop until you have sent/receive all of the bytes you are expecting.

    Try something more like this instead:

    #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;
    }