Valhalla Legends Forums Archive | C/C++ Programming | Winsock Wrapper

AuthorMessageTime
iago
Upon special request, here's my wrapper I wrote for winsock. The intention is that you make a subclass that implements the virtual members. To use it, call recv() in a loop.

I know it's not the best way to do things, but it's nice and simple :)

[code] // WS.h
// This is a wrapper for winsock that will allow a person to easily connect
// to a remote site over TCP and send/recieve data.

#ifndef WS_H
#define WS_H

#include <winsock2.h>
#include <stdio.h>
#include "Buffer.h"
//#include <ws2tcpip.h>



class WS
{
protected:
   // The socket used.. probably shouldn't be played with
   SOCKET s;
   // A couple booleans to keep track of the state
   bool bReady;
   bool bConnected;
   // This is probably useless, but I left it here anyway
   WSAData WinsockData;
   // This initializes winsock and such
   void Initialize();
   // This sets the socket to either block or not block
   void SetSocketBlocking(DWORD Block);

   // Calls the error callback if it's an important error
   void Error(char *Message, DWORD Code);

   virtual void DataSent(char *Data, DWORD Length);
   virtual void DataRecieved(char *Data, DWORD Length);
   virtual void ErrorFunc(char *Description, DWORD Code);
   virtual void Connected();
   virtual void Disconnected();

public:
   // Default constructor
   WS();
   ~WS();

   // Connects to an ip or domain name specified by the string server on the port specified
   // by Port
   bool Connect(const char *Server, WORD Port);
   // Closes the connection
   bool Close();
   // Send out Length bytes of Data
   bool Send(char *Data, DWORD Length);
   // Send out a single byte
   bool SendByte(BYTE Data);
   // Send out a single word
   bool SendWord(WORD Data);
   // Send out a single dword
   bool SendDWord(DWORD Data);
   // Send out an ascii string with or without the null terminator
   bool SendAscii(char *Data, bool SendNull = false);
   // Send out a buffer
   bool SendBuffer(Buffer Data);

   // Manually recieve Length bytes of data
   virtual DWORD Recv(char *Data, DWORD Length);
   // If there is any data waiting, recieve all of it
   virtual bool Recv();

   // Get the code for the last error
   int GetLastError();
   // Gets the equivolant textstring for the error code
   static char *GetErrorString(DWORD ErrorCode);
};

#endif
[/code]

[code] // WS.cpp
#include "WS.h"

void WS::Initialize()
{
   // Set up the member variables
   bConnected = false;
   s = INVALID_SOCKET;
   bReady = false;
   
   // Create a TCP socket
   s = socket(2, SOCK_STREAM, IPPROTO_IP);
   if(s != INVALID_SOCKET)
   {
      bReady = true;
   }
   else
   {
      Error("Could not initialize winsock.", WSAGetLastError());
   }

   SetSocketBlocking(false);
}

WS::WS()
{
   WSAStartup(0x101, &WinsockData);
   Initialize();
}

WS::~WS()
{
   Close();
   bReady = false;
   WSACleanup();
}

bool WS::Connect(const char *Server, WORD Port)
{
   // I'm not totally sure how I did this, but somehow it connects to Server on Port.
   // Server is either, "www.google.com" or "127.0.0.1"
   hostent *Host;
   sockaddr_in sock;
   char hstip[16];

   SetSocketBlocking(true);
   if(isdigit(*Server))
   {
      sock.sin_addr.s_addr = inet_addr(Server);
   }
   else
   {
      Host = gethostbyname(Server);
      if(Host)
      {
         sprintf (hstip, "%u.%u.%u.%u",
            (unsigned char) Host->h_addr_list[0][0],
            (unsigned char) Host->h_addr_list[0][1],
            (unsigned char) Host->h_addr_list[0][2],
            (unsigned char) Host->h_addr_list[0][3]);
         sock.sin_addr.s_addr = inet_addr(hstip);   
      }
      else
      {
         Error("Unable to resolve host.", WSAGetLastError());
         SetSocketBlocking(false);
         return false;
      }
   }      

   sock.sin_family = AF_INET;
   sock.sin_port = htons(Port);
   if(!(connect(s, (sockaddr*)&sock, sizeof(sockaddr_in))))
   {
      bConnected = true;
      bReady = false;
      Connected();
      SetSocketBlocking(false);
      return true;
   }
   else
   {
      bConnected = false;
      bReady = true;
      Error("Error Connecting.", WSAGetLastError());
      SetSocketBlocking(false);
      return false;
   }
}

bool WS::Close()
{
   // Make the socket block while we disconnect
   SetSocketBlocking(true);

   // Attempt to close the socket
   if(!closesocket(s))
   {   
      Disconnected();
      SetSocketBlocking(false);
      Initialize();
      return true;
   }
   else
   {
      Error("Error closing socket.", WSAGetLastError());
      SetSocketBlocking(false);
      return false;
   }
}

bool WS::Send(char *Data, DWORD Length)
{
   this->SetSocketBlocking(true);
   DWORD LengthSent = send(s, Data, Length, NULL);
   this->SetSocketBlocking(false);

   if(LengthSent != SOCKET_ERROR)
   {
      DataSent(Data, LengthSent);
      return true;
   }
   else
   {
      Error("Error sending data", WSAGetLastError());
      return false;
   }
}

DWORD WS::Recv(char *Data, DWORD Length)
{
   DWORD retLength = recv(s, Data, Length, 0);
   
   if(retLength != SOCKET_ERROR)
   {
      DataRecieved(Data, retLength);
      return retLength;
   }
   else
   {
      Error("Error recieving data.", WSAGetLastError());
      return retLength;
   }
}

int WS::GetLastError()
{
   return WSAGetLastError();
}

char *WS::GetErrorString(DWORD ErrorCode)
{
   switch(ErrorCode)
   {
      case 0: return "WSABASEERR No Error";
      case 10004: return "WSAEINTR: - Interrupted system call";
      case 10009: return "WSAEBADF: - Bad file number";
      case 10013: return "WSAEACCES: - Permission denied";
      case 10014: return "WSAEFAULT: - Bad address";
      case 10022: return "WSAEINVAL: - Invalid argument";
      case 10024: return "WSAEMFILE: - Too many open files";
      case 10035: return "WSAEWOULDBLOCK: - Operation would block";
      case 10036: return "WSAEINPROGRESS: - Operation now in progress";
      case 10037: return "WSAEALREADY: - Operation already in progress";
      case 10038: return "WSAENOTSOCK: - Socket operation on non-socket";
      case 10039: return "WSAEDESTADDRREQ: - Destination address required";
      case 10040: return "WSAEMSGSIZE: - Message too long";
      case 10041: return "WSAEPROTOTYPE: - Protocol wrong type for socket";
      case 10042: return "WSAENOPROTOOPT: - Bad protocol option";
      case 10043: return "WSAEPROTONOSUPPORT: - Protocol not supported";
      case 10044: return "WSAESOCKTNOSUPPORT: - Socket type not supported";
      case 10045: return "WSAEOPNOTSUPP: - Operation not supported on socket";
      case 10046: return "WSAEPFNOSUPPORT: - Protocol family not supported";
      case 10047: return "WSAEAFNOSUPPORT: - Address family not supported by protocol family";
      case 10048: return "WSAEADDRINUSE: - Address already in use";
      case 10049: return "WSAEADDRNOTAVAIL: - Can't assign requested address";
      case 10050: return "WSAENETDOWN: - Network is down";
      case 10051: return "WSAENETUNREACH: - Network is unreachable";
      case 10052: return "WSAENETRESET: - Net dropped connection or reset";
      case 10053: return "WSAECONNABORTED: - Software caused connection abort";
      case 10054: return "WSAECONNRESET: - Connection reset by peer";
      case 10055: return "WSAENOBUFS: - No buffer space available";
      case 10056: return "WSAEISCONN: - Socket is already connected";
      case 10057: return "WSAENOTCONN: - Socket is not connected";
      case 10058: return "WSAESHUTDOWN: - Can't send after socket shutdown";
      case 10059: return "WSAETOOMANYREFS: - Too many references, can't splice";
      case 10060: return "WSAETIMEDOUT: - Connection timed out";
      case 10061: return "WSAECONNREFUSED: - Connection refused";
      case 10062: return "WSAELOOP: - Too many levels of symbolic links";
      case 10063: return "WSAENAMETOOLONG: - File name too long";
      case 10064: return "WSAEHOSTDOWN: - Host is down";
      case 10065: return "WSAEHOSTUNREACH: - No Route to Host";
      case 10066: return "WSAENOTEMPTY: - Directory not empty";
      case 10067: return "WSAEPROCLIM: - Too many processes";
      case 10068: return "WSAEUSERS: - Too many users";
      case 10069: return "WSAEDQUOT: - Disc Quota Exceeded";
      case 10070: return "WSAESTALE: - Stale NFS file handle";
      case 10071: return "WSAEREMOTE: - Too many levels of remote in path";
      case 10091: return "WSASYSNOTREADY: - Network SubSystem is unavailable";
      case 10092: return "WSAVERNOTSUPPORTED: - Winsock DLL Version out of range";
      case 10093: return "WSANOTINITIALISED: - Successful WSASTARTUP not yet performed";
      case 10110: return "WSA_E_NO_MORE: - No more results can be returned by WSALookupServiceNext";
      case 11001: return "WSAHOST_NOT_FOUND: - Host not found";
      case 11002: return "WSATRY_AGAIN: - Non-Authoritative Host not found";
      case 11003: return "WSANO_RECOVERY: - Non-Recoverable Errors: (FORMERR, REFUSED, NOTIMP)";
      case 11004: return "WSANO_DATA: - Valid name, no data record of requested type";
      default: return "Unknown error!";
   }
}

// Will recieve up to 5000 bytes
bool WS::Recv()
{
   // Set the recieve buffer to 5000 bytes
   char RecvBuffer[5000];
   int Length = recv(s, RecvBuffer, 4999, NULL);
   if(Length == SOCKET_ERROR)
   {
      Error("Error recieving data", WSAGetLastError());
      return false;
   }
   else if(Length > 0)
   {
      DataRecieved(RecvBuffer, Length);
      return true;
   }
   return false;
}

void WS::SetSocketBlocking(DWORD Block)
{
   // Block is backwords, so fix it
   Block = !Block;
   // ioctlsocket is the big fancy function that controls the blocking
   if(ioctlsocket(s, FIONBIO, &Block) == SOCKET_ERROR)
   {
      Error("Error setting socket blocking (ioctlsocket)", WSAGetLastError());
   }
}

bool WS::SendByte(BYTE Data)
{
   return Send((char*)&Data, sizeof(BYTE));   
}

bool WS::SendWord(WORD Data)
{
   return Send((char*)&Data, sizeof(WORD));
}

bool WS::SendDWord(DWORD Data)
{
   return Send((char*)&Data, sizeof(DWORD));
}

bool WS::SendAscii(char *Data, bool SendNull)
{
   // IF SendNull is true, it will send the null-terminator after the string.
   return Send(Data, SendNull == false ? (DWORD)strlen(Data) : (DWORD)strlen(Data) + 1);
}

// This is probably the preferred function to use, but that's just my opinion because I
// wrote the buffer class :)
bool WS::SendBuffer(Buffer Data)
{
   return Send(((char*)Data.c_str()), Data.GetSize());
}


void WS::Error(char *Message, DWORD Code)
{
   // WSAEWOULDBLOCK is a worthless error for my purposes
   if(Code != WSAEWOULDBLOCK)
   {
      ErrorFunc(Message, Code);
   }
}

// By default the events are all blank
void WS::DataSent(char *Data, DWORD Length)
{
}

void WS::DataRecieved(char *Data, DWORD Length)
{
}

void WS::ErrorFunc(char *Description, DWORD Code)
{
}

void WS::Connected()
{
}

void WS::Disconnected()
{
}
[/code]
September 22, 2003, 2:44 AM
SiMi
Nice!
September 22, 2003, 3:25 AM
Skywing
Comments:

Why're you hardcoding the value of 2 for the af/pf (depending on whose documentation you go by) parameter for the socket call? You ought to use AF/PF_INET.
Additionally, I'm pretty sure that you should use IPPROTO_TCP and not IPPROTO_IP for a TCP socket.
September 22, 2003, 6:54 AM
iago
[quote author=Skywing link=board=5;threadid=2786;start=0#msg21954 date=1064213677]
Comments:

Why're you hardcoding the value of 2 for the af/pf (depending on whose documentation you go by) parameter for the socket call? You ought to use AF/PF_INET.
Additionally, I'm pretty sure that you should use IPPROTO_TCP and not IPPROTO_IP for a TCP socket.
[/quote]

I didn't think of it at the time.. all I needed it for was a tcp connection. I'm sure I can fix that later, if I decide I need more functionality.

The problem is, I coded this 100% for my needs at the time, and never really planned on releasing it. The only reason I did was because somebody asked me to, and I thought it might be useful for everybody to see :-)
September 22, 2003, 7:25 AM
Moonshine
[quote] if(isdigit(*Server))
{
sock.sin_addr.s_addr = inet_addr(Server);
}[/quote]

What happens if Server is say, "123hosting.com" ?
October 9, 2003, 4:28 AM
iago
Then that's too bad for them!

They should use www.123hosting.com

Or I should add pattern matching,
Match(Addr, "*.*.*.*") .. would also fail occasionally, but eh?
October 9, 2003, 4:32 AM
Moonshine
Hehe, if you say so :P
October 9, 2003, 4:33 AM
Moonshine
Also, shouldn't Connected()/Disconnected() return something?
October 9, 2003, 4:43 AM
Arta
hmm, why not use the constructor for the contents of Initialize();? Also, that error thing isn't very nice - I'd use FormatMessage(). Rest looks nice though :)
October 9, 2003, 3:42 PM
iago
hmm, I don't know how FormatMessage() works, I've never actually used it.

And rather than connect() and disconnect() actually returning anything, when it works, the connected() or disconnected() virtual function is called.
October 9, 2003, 4:37 PM
Arta
[code]
char* CGUI::GetErrorText(int Code){
   void* Buf;
   FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, Code, 0, (char*)&Buf, 0, NULL);
   
   if(!Buf){
      Buf = (char*)LocalAlloc(LHND, 26);
      strcpy((char*)Buf, "An unknown error occured\n");
   }

   return (char*)Buf;
}
[/code]
October 9, 2003, 8:40 PM
iago
Ah handy.. will fix that later :)
October 9, 2003, 9:56 PM
St0rm.iD
very nice indeed
October 9, 2003, 11:11 PM
Skywing
[quote author=Arta[vL] link=board=5;threadid=2786;start=0#msg23571 date=1065732054]
[code]
char* CGUI::GetErrorText(int Code){
   void* Buf;
   FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER, NULL, Code, 0, (char*)&Buf, 0, NULL);
   
   if(!Buf){
      Buf = (char*)LocalAlloc(LHND, 26);
      strcpy((char*)Buf, "An unknown error occured\n");
   }

   return (char*)Buf;
}
[/code]
[/quote]
Note that your code may crash if FormatMessage fails. The fact that it may or may not modify Buf if a failure occcurs is undocumented behavior. Instead, you should check the return value.
October 10, 2003, 1:29 AM
Arta
Yes, quite true. Might want to change that.
October 10, 2003, 2:01 AM
Kp
Arta's code, as written, returns the message in an allocated buffer (either FormatMessage uses LocalAlloc or Arta invokes it manually if FormatMessage fails); in either case, the storage will need to be freed using LocalFree once the returned string is no longer in use.
October 10, 2003, 7:43 PM
iago
What's the difference between LocalAlloc() and malloc()? By the names, I would assume that LocalAlloc() does it on the function's stack, but since it has to be freed I doubt that's the case.
October 10, 2003, 9:23 PM
Kp
malloc is implemented as an attempt to be libc compatible. LocalAlloc dates back to Win16 days, when you could allocate either local or global memory. Incidentally, if you check the LocalAlloc documentation, it tells you that this is for Win16 only and you should not use it in Win32; thus, I find it odd FormatMessage uses it, since afaik FormatMessage is new enough it was created after LocalAlloc ought to have been deprecated.
October 10, 2003, 9:45 PM
Adron
Presumably laziness wins in the long run. LocalAlloc has rather few arguments and so is easier to use.
October 11, 2003, 12:28 AM
Skywing
[quote author=Adron link=board=5;threadid=2786;start=15#msg23725 date=1065832139]
Presumably laziness wins in the long run. LocalAlloc has rather few arguments and so is easier to use.
[/quote]
Last time I checked, malloc had fewer arguments... :p
October 11, 2003, 1:39 AM
Adron
[quote author=Skywing link=board=5;threadid=2786;start=15#msg23737 date=1065836387]
[quote author=Adron link=board=5;threadid=2786;start=15#msg23725 date=1065832139]
Presumably laziness wins in the long run. LocalAlloc has rather few arguments and so is easier to use.
[/quote]
Last time I checked, malloc had fewer arguments... :p
[/quote]

But malloc belongs to the C run-time library and so cannot be used by a kernel32(?) function. Well, that is, if you're using the malloc that has its own small block heap. If you're using the malloc that maps directly to the same heap that LocalAlloc uses they're of course interchangeable.
October 11, 2003, 1:52 AM

Search