fclt Logo
Client/Server Socket class
Classes Socket Client/Serveur
Not much to say about this class. Note, though, that the class transfers the ownership of the socket when a socket object is copy constructed or assigned to another socket object. Just like the std::auto_ptr class.
Pas grand chose à dire sur cette classe. A noter cependant que, comme la classe std::auto_ptr,  si vous construisez un objet Socket en utilisant le constructeur de copie ou bien si vous assignez un objet Socket à un autre en utilisant l'opérateur =, le nouvel objet devient propriétaire du socket.

// socket.h
// Based on James Pee's article 'Guidelines for Wrapping Sockets
// in Classes' published in the C/C++ Users journal.
//  
#ifndef SOCKET_H
#define SOCKET_H

#include <string>
#include <iostream>
#include <exception>
#include <winsock2.h>

namespace mlLib
{
//
// Exception class used by all classes
//

class socket_exception: public std::exception
{
  public:
    explicit socket_exception(const std::string &what);
    virtual const char* what()const throw();
  private:
    std::string msg_;
};

//
// This class is responsible for initializing winsock
//

class Winsock
{
  public:
    Winsock();
    virtual ~Winsock();
  private:
    static int refcount_;
};

//
// Utility classes
//

struct LocalHost: public Winsock
{
  LocalHost(){}
  std::string GetHostName()const;
  std::string GetIPAddress()const;
};

class Host: public Winsock
{
  public:
    Host(const std::string& address);
    
    bool IsDotted()const;
    std::string GetHostName()const;
    std::string GetIPAddress()const;
  private:
    std::string Address_;
};

//
// Base class
//

class Socket: public Winsock
{
  public:
    Socket();
    Socket(int domain,int type,int protocol);
    // Copy constructor and assignment operator transfer ownership
    // of the socket to the new Socket object.
    Socket(Socket& rhs);
    Socket& operator=(Socket& rhs);
    virtual ~Socket();

    void Bind(unsigned short port);
    Socket Accept();
    void Listen(int connections);
    void Connect(const std::string& host,unsigned short port);
    void Close();
    unsigned long BytesAvailable();
    bool Readable();
    
    //TCP read and write functions
    int Read(void* data,int size);
    void Write(const void* data,int size);
    std::string ReadLine();
    void WriteString(const std::string& data);

  protected:
    bool Initialized;
    int Domain;
    int SocketDescriptor;
    bool GetAddress(const std::string& host,unsigned short port,
        sockaddr_in* in);
    //UDP read and write functions
    int RecvFrom(int desc,void* data,int size,sockaddr* source);
    void SendTo(int desc,const void* data,int size,sockaddr* target);

  private:
    std::string ClientIP;
    Socket(int,const std::string&);
    int ReleaseOwnership();
};

//
// TCP Client
//

enum Protocol {prTCP,prUDP};

class TCPClient : public Socket
{
  public:
    TCPClient(const std::string& host,unsigned short port);
};

//
// TCP Server
//

class TCPServer : public Socket
{
  public:
    TCPServer(unsigned short port,int connections = 10);
    Socket AcceptClient();
};

//
// UDP Client
//

class UDPClient : public Socket
{
  public:
    UDPClient(const std::string& host,unsigned short port);
    int ReadFrom(void* data,int size);
    void WriteTo(const void* data,int size);

  private:
    sockaddr RemoteAddress;
    void ResolveAddress(const std::string& host,unsigned short port);
};

//
// UDP Server
//

class UDPServer : public Socket
{
  public:
    UDPServer(unsigned short port);
    int ReadFrom(void* data,int size);
    void WriteTo(const void* data,int size);
    
  private:
    sockaddr ClientAddress;
};

//end namespace mlLib
#endif


#include <windows.h>
#pragma hdrstop
#include <winsock2.h>
#include <sstream>

#include "socket.h"

/**
 * Exception implementation
 */

mlLib::socket_exception::socket_exception(const std::string &what)
    :msg_(what)
{
  
}

const char * mlLib::socket_exception::what() const throw()
{
  std::ostringstream ss;
  ss << msg_
     << " Error: "
     << WSAGetLastError();
  return ss.str().c_str();
}


/**
 * Base Winsock class implementation
 */

int mlLib::Winsock::refcount_ = 0;

mlLib::Winsock::Winsock()
{
  if(refcount_==0)
  {
    WSADATA WInitData;
    int WError = WSAStartup(MAKEWORD(2,0),&WInitData);
    if (WError != 0) throw socket_exception("Unable to load winsock.");
  }
  ++refcount_;
}

mlLib::Winsock::~Winsock()
{
  --refcount_;
  if(refcount_ == 0)WSACleanup();
}

/**
 * Utility classes implementation
 */

std::string mlLib::LocalHost::GetHostName()const
{
  char Buffer[64];
  int ret = gethostname(Buffer,sizeof(Buffer));
  if(ret != 0)throw socket_exception("Unable to retrieve local host name");
  else return std::string(Buffer);
}

std::string mlLib::LocalHost::GetIPAddress()const
{
  hostent *he = gethostbyname(GetHostName().c_str());
  if(he == 0)throw socket_exception("Invalid hostname");
  in_addr Address;
  memcpy(&Address,he->h_addr_list[0],he->h_length);
  char Buffer[64];
  strcpy(Buffer,inet_ntoa(Address));
  return std::string(Buffer);
}

mlLib::Host::Host(const std::string& address)
    :Address_(address)
{
  
}

bool mlLib::Host::IsDotted()const
{
  unsigned long RemoteAddress = inet_addr(Address_.c_str());
  if(RemoteAddress == INADDR_NONE)return false//not a dotted address
  else return true;
}

std::string mlLib::Host::GetIPAddress()const
{
  if(!IsDotted())
  {
    hostent *he = gethostbyname(Address_.c_str());
    if(he == 0)throw socket_exception("Invalid hostname");
    in_addr Address;
    memcpy(&Address,he->h_addr_list[0],he->h_length);
    char Buffer[64];
    strcpy(Buffer,inet_ntoa(Address));
    return std::string(Buffer);
  }
  else throw socket_exception("Not a valid hostname");
}

std::string mlLib::Host::GetHostName()const
{
  if(IsDotted())
  {
    unsigned long RemoteAddress = inet_addr(Address_.c_str());
    hostent *he = gethostbyaddr((char*)&RemoteAddress,4,PF_INET);
    if(he==0)throw socket_exception("Invalid IP address");
    return he->h_name;
  }
  else throw socket_exception("Not a dotted address");
}

/**
 * Socket class implementation
 */

mlLib::Socket::Socket()
    : Initialized(false),
      Domain(AF_INET),
      SocketDescriptor(SOCKET_ERROR),
      ClientIP("")
{

}

mlLib::Socket::Socket(int domain,int type,int protocol)
    : Initialized(false),
      Domain(domain)
{
  SocketDescriptor = socket(domain,type,protocol);
  Initialized = (SocketDescriptor != INVALID_SOCKET);
}

mlLib::Socket::Socket(int fd,const std::string& clientip)
    : Initialized(fd != INVALID_SOCKET?true:false),
      SocketDescriptor(fd),
      ClientIP(clientip)
{

}

/**
 * Copy constructor and assignment operator transfer ownership
 * of the socket to the new Socket object.
 */

mlLib::Socket::Socket(Socket& rhs)
    : Initialized(rhs.Initialized),
      Domain(rhs.Domain),
      SocketDescriptor(rhs.ReleaseOwnership()),
      ClientIP(rhs.ClientIP)
{

}                    

mlLib::Socket& mlLib::Socket::operator=(mlLib::Socket& rhs)
{
  Initialized = rhs.Initialized;
  Domain = rhs.Domain;
  ClientIP = rhs.ClientIP;
  int tmp = rhs.ReleaseOwnership();
  if(SocketDescriptor != tmp)Close();
  SocketDescriptor = tmp;
  return *this;
}

mlLib::Socket::~Socket()
{
  if(Initialized)Close();
}

int mlLib::Socket::ReleaseOwnership()
{
  int tmp = SocketDescriptor;
  SocketDescriptor = INVALID_SOCKET;
  Initialized = false;
  return tmp;
}

bool mlLib::Socket::GetAddress(const std::string& host,unsigned short port,
    sockaddr_in* in)
{
  memset(in,0,sizeof(sockaddr_in));
  in->sin_family = Domain;
  in->sin_port = htons(port);
  if(isdigit(host[0]))
  {
    in->sin_addr.s_addr = inet_addr(host.c_str());
  }
  else
  {
    hostent* hostStruct;
    in_addr* hostNode;
    hostStruct = gethostbyname(host.c_str());
    if(hostStruct)
    {
      hostNode = (in_addr*)hostStruct->h_addr;
      in->sin_addr.s_addr = hostNode->s_addr;
    }
    else return false;
  }
  return true;
}

void mlLib::Socket::Connect(const std::string& hostname,u_short port)
{
  if(!Initialized)
  {
    throw socket_exception("Socket not initialized");
  }
  sockaddr_in servaddr;
  if(!GetAddress(hostname,port,&servaddr))
      throw socket_exception("Unable to resolve host name");
  if(connect(SocketDescriptor,(sockaddr*)&servaddr,
      sizeof(servaddr)) < 0)
  {
    throw socket_exception("Unable to connect to host");
  }
}

void mlLib::Socket::Bind(u_short port)
{
  if(!Initialized)
  {
    throw socket_exception("Socket not initialized");
  }
  sockaddr_in servaddr;
  memset(&servaddr,0,sizeof(servaddr));
  servaddr.sin_family = Domain;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(port);
  if(bind(SocketDescriptor,(sockaddr*)&servaddr,sizeof(servaddr)) == -1)
  {
    throw socket_exception("Unable to bind to specified port");
  }
}

void mlLib::Socket::Listen(int connections)
{
  if(!Initialized)
  {
    throw socket_exception("Socket not initialized");
  }
  listen(SocketDescriptor,connections);
}

mlLib::Socket mlLib::Socket::Accept()
{
  if(!Initialized)
  {
    throw socket_exception("Socket not initialized");
  }
  sockaddr_in clientaddr;
  int clientlen=sizeof(clientaddr);
  int fd = accept(SocketDescriptor,(sockaddr*)&clientaddr,&clientlen );
  if(fd == INVALID_SOCKET)
      throw socket_exception("Invalid socket error");
  std::string clientip = inet_ntoa(*(in_addr*)&clientaddr.sin_addr.s_addr );
  Socket client(fd,clientip);
  return client;
}

void mlLib::Socket::Close()
{
  shutdown(SocketDescriptor,SD_BOTH);
  closesocket(SocketDescriptor);
  SocketDescriptor = INVALID_SOCKET;
  Initialized=false;
}

unsigned long mlLib::Socket::BytesAvailable()
{
   if(!Initialized)
  {
    throw socket_exception("Socket not initialized");
  }
  unsigned long BytesAvailable;
  ioctlsocket(SocketDescriptor,FIONREAD,&BytesAvailable);
  return BytesAvailable;
}

bool mlLib::Socket::Readable()
{
  fd_set rset;
  FD_ZERO(&rset);
  FD_SET(SocketDescriptor,&rset);
  if(select(0,&rset,NULL,NULL,NULL) == SOCKET_ERROR)
  {
    throw socket_exception("Error reading through socket");
  }
  return FD_ISSET(SocketDescriptor,&rset);
}

/**
 * Read maximum size bytes. The function returns the number of bytes
 * read. If the returned value is 0, the connection was closed by peer.
 * But be aware that all characters must be read before the connection
 * is closed.
 * That means that the data buffer must be large enough to read all the
 * characters. The following read will yield 0, indicating a
 * connection closed.
 */
int mlLib::Socket::Read(void* data,int size)
{
  int bytesreceived;
  fd_set rset;          
  FD_ZERO(&rset);
  FD_SET(SocketDescriptor,&rset);
  if(select(0,&rset,NULL,NULL,NULL) == SOCKET_ERROR)
  {
    throw socket_exception("Error reading through socket");
  }
  bytesreceived = recv(SocketDescriptor,(char*)data,size,0);
  if(bytesreceived == SOCKET_ERROR)
      throw socket_exception("Error reading through socket");
  return bytesreceived;
}

void mlLib::Socket::Write(const void* data,int size)
{
  char* ptr = (char*)data;
  int nleft = size;
  while(nleft > 0)
  {
    int nwritten = send(SocketDescriptor,ptr,nleft,0);
    nleft -= nwritten;
    ptr += nwritten;
  }
}

std::string mlLib::Socket::ReadLine()
{
  std::string ret;
  char rec = 0;
  while(rec != '\n')
  {
    // if Read() returns 0, the connection was closed by peer.
    if(Read((void*)&rec,1) == 0)return "";
    ret += rec;
  }
  return ret;
}

void mlLib::Socket::WriteString(const std::string& data)
{
  Write((void*)data.c_str(),data.length());
}

int mlLib::Socket::RecvFrom(int desc,void* data,int size,sockaddr* source)
{
  int bytesreceived;
  fd_set rset;
  FD_ZERO(&rset);
  FD_SET(SocketDescriptor,&rset);
  if(select(0,&rset,NULL,NULL,NULL) <= 0)
  {
    throw socket_exception("Error reading through socket");
  }
  int sourcelen=sizeof(sockaddr);
  bytesreceived=recvfrom(desc,(char*)data,size,0,source,&sourcelen);
  if(bytesreceived == SOCKET_ERROR)
      throw socket_exception("Error reading through socket");
  return bytesreceived;
}

void mlLib::Socket::SendTo(int desc,const void* data,int size,
    sockaddr* target)
{
  char* ptr = (char*)data;
  int nleft = size;
  while(nleft > 0)
  {
    int nwritten = sendto(desc,ptr,nleft,0,target,sizeof(sockaddr));
    nleft -= nwritten;
    ptr += nwritten;
  }
}

/**
 * TCP Client
 */

mlLib::TCPClient::TCPClient(const std::string& host,unsigned short port)
    : Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)
{
  Connect(host,port);
}

/**
 * TCP Server
 */

mlLib::TCPServer::TCPServer(unsigned short port,int connections)
    : Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)
{
  Bind(port);
  Listen(connections);
}

// not needed for UDP protocol
mlLib::Socket mlLib::TCPServer::AcceptClient()
{
  return Accept();
}

/**
 * UDP Client
 */

mlLib::UDPClient::UDPClient(const std::string& host,unsigned short port)
    : Socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)
{
  ResolveAddress(host,port);
}

void mlLib::UDPClient::ResolveAddress(const std::string& host,
    unsigned short port)
{
  if(!GetAddress(host,port,(sockaddr_in*)&RemoteAddress))
    throw socket_exception("Unable to resolve host name");
}

int mlLib::UDPClient::ReadFrom(void* data,int size)
{
  return RecvFrom(SocketDescriptor,data,size,&RemoteAddress);
}

void mlLib::UDPClient::WriteTo(const void* data,int size)
{
  SendTo(SocketDescriptor,data,size,&RemoteAddress);
}

/**
 * UDP Server
 */

mlLib::UDPServer::UDPServer(unsigned short port)
    : Socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)
{
  Bind(port);
}

int mlLib::UDPServer::ReadFrom(void* data,int size)
{
  return RecvFrom(SocketDescriptor,data,size,&ClientAddress);
}

void mlLib::UDPServer::WriteTo(const void* data,int size)
{
  SendTo(SocketDescriptor,data,size,&ClientAddress);
}


Example 1: HTTP GET command Exemple 1: Commande HTTP GET
The first example shows you how to use the HTTP GET command to retrieve a file from the Internet.
Le premier exemple montre comment utiliser la commande HTTP GET pour récupérer un fichier depuis Internet.

#include <string>
#include <iostream>
#include <conio.h>

#pragma hdrstop
#include "socket.h"

/*
 * demonstrates the use of the socket class to get a file using
 * the http GET command and a proxy server
 */
int main()
{
  try
  {
    mlLib::TCPClient cs("yourproxy.server.be",8080);
    cs.WriteString("GET http://www.leunen.com/index.html HTTP/1.0\n\n");
    std::string line;
    do
    {
      line=cs.ReadLine();
      std::cout<<line<<std::endl;
    }
   while(line != "");
   }
  catch(mlLib::socket_exception& ex)
  {
    std::cout<<ex.what()<<std::endl;
  }

  getch();
  return 0;
}


Example 2: A proxy server
Exemple 2: Un serveur proxy
Below is a simple proxy server using the Socket class. You can use it, for instance, to spy what your mail client sends and receives to/from your mail server. To do this, change the mail server (POP) in the settings of your mail client to 'localhost', port 8888 for instance. Compile the application and launch it from the command line with:
proxy 8888 your.mail.server 110
You'll see all data exchanged with the server when you're getting your mail appearing in the console window.
Ci-dessous, vous trouverez un simple proxy serveur. Vous pouvez l'utiliser, par exemple, pour visualiser les données échangées entre votre client mail et le serveur de mail. Pour ceci, changez les settings de votre client mail en remplaçant le serveur par 'localhost' et le port POP par 8888 par exemple. Ensuite, compilez l'application et tapez en ligne de commande:
proxy 8888 votre.mail.serveur 110
Vous verrez apparaître ds la console toutes les données echangées avec le serveur quand vous retirez vos mails.

#include <windows.h>
#pragma hdrstop
#include <condefs.h>
#include <process.h>
#include <iostream>
#include <string>
#include <cstdlib>
#include "socket.h"

//---------------------------------------------------------------------------
// Global variables
std::string ServerAddress;
int ServerPort;

// Thread function
unsigned int __stdcall ProxyThread(void* param)
{
  // as is the socket which our server is listening to
  Socket* as=reinterpret_cast<Socket*>(param);
  bool connected=true;
  // Create a client socket to the remote server
  TCPClient cs(ServerAddress,ServerPort);
  while(connected)
  {
    // Wait for data to be read
    if(cs.Readable())
    {
      do
      {
        char buffer[128];
        // Read the data by chunk of 127 characters
        int nb=cs.Read(buffer,sizeof(buffer)-1);
        as->Write(buffer,nb);
        buffer[nb]=0x00;
        std::cout<<buffer;
        // recv yields 0 if the socket was closed by the peer
        if(nb==0)connected=false;
      }
      // Repeat the socket read until there is no more character to be read.
      while(cs.BytesAvailable()>0);
    }
    if(as->Readable())
    {
      do
      {
        char buffer[128];
        int nb=as->Read(buffer,sizeof(buffer)-1);
        cs.Write(buffer,nb);
        buffer[nb]='\0';
        std::cout<<buffer;
        if(nb==0)connected=false;
     }
     while(as->BytesAvailable()>0);
    }
  }
  as->Close();
  cs.Close();
  delete as;
  return 0;
}

int main(int argc, char* argv[])
{
  if (argc == 4)
  {
    int ProxyPort(std::atoi(argv[1]));
    ServerAddress=argv[2];
    ServerPort=std::atoi(argv[3]);
    try
    {
      TCPServer ss(ProxyPort,10);
      while(true)
      {
        Socket cs=ss.AcceptClient();
        Socket *ts=new Socket(cs);
        unsigned int id;
        _beginthreadex(NULL,0,ProxyThread,(void*)ts,0,&id);
      }
    }
    catch(socket_exception& ex)
    {
      std::cout<<ex.what()<<std::endl;
    }
  }
  else
  {
    std::cout<<"\nUsage:\n";
    std::cout<<"\tproxy <port proxy> <addr server> <port server>\n"<<std::endl;
    std::cout<<"\tAllows you to see the data exchanged by a client and a\n";
    std::cout<<"\tserver.\n";
    std::cout<<"\tFor instance: set your POP server to 'localhost' and the\n";
    std::cout<<"\tPOP server port to '8888' then type:\n";
    std::cout<<"\t\tproxy 8888 your.mail.server 110\n"<<std::endl;
  }
  return 0;
}


Since the application launch at least one thread, don't forget to enable multithreading when compiling and linking your application.
You can download the Socket class, the httpget sample and the proxy sample here.
N'oubliez pas lorsque vous compilez et linkez l'application qu'elle lance un ou plusieurs threads et que vous devez donc autoriser la compilation en multithread.
Vous pouvez télécharger la classe Socket, l'exemple httpget et l'application proxy ici.