C++ Websocket client

Post those lines of code you feel like sharing or find what you require for your project here; or simply use them as tutorials.
Post Reply
Brainsaw
Posts: 1176
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

C++ Websocket client

Post by Brainsaw »

At the moment I'm working on a native Windows client for my browsergame "Sky of Verdun 3d" (which is a multiplayer dogfight game). I have searched quite a lot but didn't find a proper C++ websocket client implementation, so I created my own. It was not so difficult, but the details caused some grey hair.

IWebSocketClient.h

Code: Select all

 
#ifndef _I_WEB_SOCKET_CLIENT  
  #define _I_WEB_SOCKET_CLIENT
 
  #include <irrlicht.h>
 
class IWebSocketClient {
  public:
    enum enWsState {
      enWsUninitialized,
      enWsInitialized,
      enWsConnecting,
      enWsConnected,
      enWsDisconnected
    };
 
  private:
    void handleData(const char *a_sData);
 
  protected:
    unsigned int m_iSocket;
 
    irr::core::stringc m_sProtocol,
                       m_sServer,
                       m_sPage;
    time_t             m_iLastPing;
    enWsState          m_iState;
 
    unsigned short m_iPort;
 
    void parseUrl(const irr::c8 *a_sUrl);
 
    int connectToServer() ;
 
    virtual bool dataReceived(const char *a_sData) = 0;
 
    irr::u32 parseHeader(const irr::c8 *a_sData, irr::u32 &a_iLength, irr::u8 &a_iOpcode, bool &a_bFin, bool &a_bMask);
 
    void sendData(const irr::c8 *a_sBuffer, irr::s32 a_iLen = -1);
 
  public:
    IWebSocketClient();
 
    int getSocket();
 
    virtual void connectToServer(const irr::c8 *a_sUrl);
 
    void update();
 
    enWsState getState();
 
    void sendText(const irr::c8 *a_sInput);
};
 
#endif
 
IWebSocketClient.cpp

Code: Select all

 
#include <time.h>
#include <winsock2.h>
#include <net-o-matic.h>
 
#include <IWebSocketClient.h>
#include <irrlicht.h>
 
void IWebSocketClient::handleData(const char *a_sData) {
  switch (m_iState) {
    case enWsConnecting:
      if (strstr(a_sData, "Switching Protocols") &&
          strstr(a_sData, "Upgrade: websocket" ) &&
          strstr(a_sData, "Connection: Upgrade")) { printf("## Connection established.\n"); m_iState = enWsConnected; }
      break;
 
    case enWsConnected:
      if (!dataReceived(a_sData)) {
        //printf("--> %s\n", a_sData);
      }
      break;
  }
}
 
void IWebSocketClient::parseUrl(const irr::c8 *a_sUrl) {
  irr::core::stringc l_sUrl = irr::core::stringc(a_sUrl), 
                     l_sDummy;
 
  m_iPort = 80;
 
  if (l_sUrl.find("://") != -1) m_sProtocol = l_sUrl.subString(0, l_sUrl.find("://"));
 
  m_sPage = l_sUrl.subString(l_sUrl.find("://") + 3, l_sUrl.size());
 
  if (m_sPage.find(":") != -1) {
    m_sServer = m_sPage.subString(0, m_sPage.find(":"));
    l_sDummy = m_sPage.subString(m_sPage.find(":") + 1, l_sUrl.size());
    if (l_sDummy.find("/") != -1) {
      l_sDummy = l_sDummy.subString(0, l_sDummy.find("/"));
    }
    m_sPage = m_sPage.subString(m_sPage.find("/"), m_sPage.size());
    m_iPort = atoi(l_sDummy.c_str());
  }
  else {
    if (m_sPage.find("/") != -1) {
      m_sServer = m_sPage.subString(0, m_sPage.find("/"));
      m_sPage = m_sPage.subString(m_sPage.find("/") + 1, m_sPage.size());
    }
    else {
      m_sServer = m_sPage;
      m_sPage = "/";
    }
  }
 
  printf("## parseUrl: server: \"%s\", port: %i, page: \"%s\"\n", m_sServer.c_str(), m_iPort, m_sPage.c_str());
}
 
int IWebSocketClient::connectToServer() {
  unsigned long       l_ulIAdd,
                      l_uArg=0;
  struct hostent     *l_pHost;
  struct sockaddr_in  l_cAdd;
 
    if (m_iSocket == INVALID_SOCKET || m_iPort > (unsigned short)0xFFFF) return -1;
 
    memset(&l_cAdd,0,sizeof(struct sockaddr_in));
 
    l_cAdd.sin_family = AF_INET;
    l_cAdd.sin_port   = htons(m_iPort);
 
    l_ulIAdd = inet_addr(m_sServer.c_str());
 
    if (l_ulIAdd == INADDR_NONE)
    {
      l_pHost=gethostbyname(m_sServer.c_str());
      if (!l_pHost) return -1;
      l_cAdd.sin_addr.s_addr = *((int *)l_pHost->h_addr_list[0]);
    }
    else l_cAdd.sin_addr.s_addr=l_ulIAdd;
 
    l_uArg = connect(m_iSocket,(struct sockaddr *)&l_cAdd,sizeof(l_cAdd));
 
    printf("## connection returned %i (%i)\n", l_uArg, WSAGetLastError());
    if (l_uArg != 0) return -3;
 
    u_long l_iMode=1;
    ioctlsocket(m_iSocket,FIONBIO,&l_iMode); 
 
    return 1;
}
 
IWebSocketClient::IWebSocketClient() {
  int     l_iProtocol = IPPROTO_TCP,
          l_iErr;
  WSADATA l_cWsa;
 
    m_iSocket = -1;
 
    m_iLastPing = time(NULL);
 
    l_iErr = WSAStartup(MAKEWORD(2,2),&l_cWsa);
 
    if (l_iErr && l_iErr != WSAVERNOTSUPPORTED) return;
 
    m_iSocket = socket(PF_INET, SOCK_STREAM, l_iProtocol);
    printf("## socket: %i\n", m_iSocket);
 
    m_iState = enWsInitialized;
}
 
int IWebSocketClient::getSocket() {
  return m_iSocket;
}
 
void IWebSocketClient::sendData(const irr::c8 *a_sBuffer, irr::s32 a_iLen) {
  send(m_iSocket, a_sBuffer, a_iLen == -1 ? strlen(a_sBuffer) : a_iLen, 0);
}
 
void IWebSocketClient::connectToServer(const irr::c8 *a_sUrl) {
  irr::core::stringc l_sRequest = "";
 
  parseUrl(a_sUrl);
  connectToServer();
 
  l_sRequest  = "GET /";
  l_sRequest += m_sPage;
  l_sRequest += "\r\n";
  l_sRequest += "Accent: */*\r\n";
  l_sRequest += "User-Agent: webClient/2.0 http://www.bulletbyte.de\r\n";
  l_sRequest += "Host: ";
  l_sRequest += m_sServer;
  l_sRequest += "\r\n";
  l_sRequest += "Upgrade: websocket\r\n";
  l_sRequest += "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n";
  l_sRequest += "Sec-WebSocket-Version: 13\r\n";
  l_sRequest += "Connection: Upgrade\r\n\r\n";
 
  sendData(l_sRequest.c_str());
 
  m_iState = enWsConnecting;
}
 
irr::u32 IWebSocketClient::parseHeader(const irr::c8 *a_sData, irr::u32 &a_iLength, irr::u8 &a_iOpcode, bool &a_bFin, bool &a_bMask) {
  a_bFin    = (a_sData[0] & 128) != 0;
  a_bMask   = (a_sData[1] & 128) != 0;
  a_iOpcode =  a_sData[0] &  15;
  a_iLength =  a_sData[1] & 127;
 
  if (a_iLength == 126) {
    a_iLength = ntohs(*((irr::u16 *)&a_sData[2]));
    return 4;
  }
  else
    if (a_iLength == 127) {
      a_iLength = (irr::u32)htonll(*((unsigned long long *)&a_sData[2])); 
      return 10;
    }
 
  return 2;
}
 
void IWebSocketClient::update() {
  char      l_sBuffer[0xFFFF] = "",
            l_sDummy [0xFFFF] = "";
  irr::s32  l_iDummy;
 
  do {
    memset(l_sBuffer, 0, 0xFFFF);
    memset(l_sDummy , 0, 0xFFFF);
    l_iDummy = recv(m_iSocket, (char *)l_sBuffer, 0xFFFF, 0);
 
    int l_iError=WSAGetLastError();
      if(l_iError != WSAEWOULDBLOCK && l_iError!=0) {
      m_iState = enWsDisconnected;
    }
 
    if (l_iDummy > 0) {
      if ((unsigned char)(l_sBuffer[0] & 9) == 9) {
        unsigned char s[2] = { 137, 0 };
        sendData((const char *)s, 2);
      }
 
      if (m_iLastPing + 2 < time(NULL)) {
        unsigned char s[10] = { 0x8a, 0x80, 0x51, 0x7c, 0xc3, 0xf1, 0x00 };
        sendData((const char *)s, 6);
        m_iLastPing = time(NULL);
      }
 
      irr::s32 l_iCurrentPos = 0;
      while (strlen(l_sBuffer) > 0 && l_iCurrentPos < l_iDummy) {
        switch (m_iState) {
          case enWsConnecting: {
              char *s = strstr(l_sBuffer, "\r\n\r\n");
              if (s != NULL) {
                *s = '\0'; s++;
                handleData(l_sBuffer);
                strcpy_s(l_sBuffer, &s[3]);
              }
            }
            break;
 
          case enWsConnected: {
              bool     l_bFin,
                       l_bMask;
              irr::u8  l_iOpcode;
              irr::u32 l_iLength,
                       l_iDataStart = parseHeader(&l_sBuffer[l_iCurrentPos], l_iLength, l_iOpcode, l_bFin, l_bMask);
 
              l_iCurrentPos += l_iDataStart;
              handleData(&l_sBuffer[l_iCurrentPos]);
              l_iCurrentPos += l_iLength;
            }
            break;
        }
      }
      //handleData(&l_sBuffer[iOld]);
    }
  }
  while (l_iDummy >= 0);
}
 
IWebSocketClient::enWsState IWebSocketClient::getState() {
  return m_iState;
}
 
void IWebSocketClient::sendText(const char *a_sInput) {
  char l_sBuffer[0xFFFF];
  irr::u16 l_iHeaderSize;
 
  // Setting the first byte of the header: fin flag + "text frame" opcode
  l_sBuffer[0] = 128 + 1;
 
  // Easy version: length of the string to be transmitted less than 126
  if (strlen(a_sInput) < 126) {
    l_sBuffer[1] = strlen(a_sInput);
    strcpy_s(&l_sBuffer[2], 0xFFFF - 2, a_sInput);
    l_iHeaderSize = 2;
  }
  else
    // Compilcated version: up to 0xFFFB bytes (0xFFFF - length of header)
    if (strlen(a_sInput) <= 0xFFFB) {
      l_sBuffer[1] = 126;
      *((unsigned short *)&l_sBuffer[2]) = htons(strlen(a_sInput));
      strcpy_s(&l_sBuffer[4], 0xFFFF - 4, a_sInput);
      l_iHeaderSize = 4;
    }
    else {
      // This won't work. At the moment the implementation is not able to send frames bigger than 0xFFFB bytes
      l_sBuffer[1] = 127;
      *((u_int64 *)&l_sBuffer[4]) = htonll(strlen(a_sInput));
      strcpy_s(&l_sBuffer[8], 0xFFFF - 8, a_sInput);
      l_iHeaderSize = 8;
    }
 
  send(m_iSocket, l_sBuffer, l_iHeaderSize + strlen(a_sInput), 0);
}
 
You just need to override the "dataReceived" method. The "sendText" method is used to send data to the server. It works fine in my test environment using node.js as server. Note that it can only send text frames and that (for the moment) it can't send more than 64 Kb (-10 Bytes) in one frame. It also can't handle continuous frames, but for a game this isn't necessary as you should keep the message short. I hope someone can use this.
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
sudi
Posts: 1686
Joined: Fri Aug 26, 2005 8:38 pm

Re: C++ Websocket client

Post by sudi »

Really helpful thanks
We're programmers. Programmers are, in their hearts, architects, and the first thing they want to do when they get to a site is to bulldoze the place flat and build something grand. We're not excited by renovation:tinkering,improving,planting flower beds.
Brainsaw
Posts: 1176
Joined: Wed Jan 07, 2004 12:57 pm
Location: Bavaria

Re: C++ Websocket client

Post by Brainsaw »

You're welcome. Please post some report here if you use it with another server, if you had any problems (and solutions if so).
Dustbin::Games on the web: https://www.dustbin-online.de/

Dustbin::Games on facebook: https://www.facebook.com/dustbingames/
Dustbin::Games on twitter: https://twitter.com/dustbingames
Post Reply