Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

A simple but effective Windows socket program

2.29/5 (9 votes)
22 Jun 2006CPOL4 min read 1   545  
This Windows server socket class wraps Winsock functions and gives you the ability to write robust code, without the intricacies of modifying the code.

Introduction

The need to receive commands via the internet from a box running under a Unix platform and send responses back to it required that I create a socket server application. I needed a socket class, and every tutorial or sample code I found either did not make a robust enough socket class, or made the code hard to use (by making it too specific to their use). So, I decided to come up with my own code. It allows you to have an overlaying code control the socket server, in a sense that you have an executive class that starts your socket, tells it when to send and receive etc.

C++
class server_socket
{
    ///<summary> constructor and destructor</summary>
public:
    //constructs a well known socket structure
    server_socket(int user_port_number);
    //constructs a accepted socket structure
    server_socket();
    //destructor
    ~server_socket();
    ///<summary>Operations</summary>///
public:
    //creates the socket connection
    bool create_socket();
    //accepts a client connection
    bool socket_accept();
    //gives you the socket address
    sockaddr* socket_address();
    //socket send 
    bool socket_send(const char* data_buffer,int data_size);

    //socket receive
    bool socket_receive(char* data_buffer, int data_size);
    //bool socket_receive(char* data_buffer, int data_size);
    //shutdown the socket
    bool socket_shutdown();
protected:
    short port_number; //this is the port number
    struct sockaddr_in socket_structure; //provides the low-level TCP/IP socket structure
    SOCKET    theClient;
};

First off, we pass the port number to listen to as an argument to the constructor. We then proceed to zero out the socket structure. We assign the socket address family with its appropriate family; in our case, this is AF_INET. We have the address, which in our case is INADDR_ANY; this tells the server to listen to any type of IP address over the specified port we assign. And of course, we assign the socket port with the port sent as an argument in the constructor.

C++
server_socket::server_socket(int user_port_number)
:port_number(user_port_number)
{
    memset(&socket_structure, 0, sizeof(struct sockaddr_in));
    socket_structure.sin_family=AF_INET;
    socket_structure.sin_addr.s_addr=INADDR_ANY;
    socket_structure.sin_port=htons(port_number);
    created_from_accept=false;
}

In the usual circumstances, you want want your socket code to be able to create a connection (which will deal with the listening and binding to a specific port and IP address (although it is desirable to have your server socket code listen to any type of IP address coming through a specified port).

C++
bool server_socket::create_socket()
{
    if (created_from_accept)
    {
        return false;
    }//end if (false....
    version=MAKEWORD(2,0); //creates a word which in our case
    // is the version of winsock that we are using  winsock2 to be precise
    nret=WSAStartup(version,&wsaData);
    //this starts up the winsock

    if (nret!=0)
    {
        cout<<"\nCreate_Socket::: Error occured could not create socket\n";
        return false;
    }//end if(nret
    
    listeningSocket=socket(AF_INET,
                           SOCK_STREAM,
                           IPPROTO_TCP);
    if(INVALID_SOCKET==listeningSocket)
    {
        cout<<"\n Create_Socket:::Error: "<<WSAGetLastError()
            <<" occurred, could not create socket\n";
        WSACleanup();
        return false;
    }//end if(invalid_error.....
    nret=bind(listeningSocket,(LPSOCKADDR)&socket_structure,
              sizeof(socket_structure));
    if (SOCKET_ERROR==nret)
    {
        cout<<"\n Create_Socket:::Binding Error: "<<WSAGetLastError()
            <<" occurred, could not create socket\n";
        WSACleanup();
        return false;
    }//end if(socketerror....
    //up to 10 connections may be waiting at any one time to be accepted
    nret=listen(listeningSocket,10);
    if (SOCKET_ERROR==nret)
    {
        cout<<"\n Create_Socket:::Listening Error: "<<WSAGetLastError()
            <<" occurred, could not create socket\n";
        WSACleanup();
        return false;
    }//end if (socket_error.....
        cout<<"socket created\n";
    return true;
}

Looking at the code, the first thing we do is determine what version of Winsock we plan to use and then startup Winsock with the WSAStartup call. It is key to ensure that the startup call works as no further Winsock action should be taken if it is unsuccessful. The next thing we do is create the listening socket using the socket function. The socket operation takes in the address family specification, the type of socket (in our case, it is a socket stream), and finally the protocol as arguments. Once the socket is created, you bind it to the specified address which we specified in the constructor. We are now ready to listen for a connection.

In the code, I am able to listen to a maximum of 10 connections at a time. But the key thing to notice is that since I am utilizing a synchronous socket implementation, we can only accept one connection at a time. The listening will hold all 10 connections, but the accept function will accept only one connection at a time. Once we hear an incoming connection, we call the accept function to do its thing.

C++
int socket_struct_size=sizeof(socket_structure);
    theClient=accept(listeningSocket,
                     NULL, //new_socket->socket_address(),
                     NULL);//&socket_struct_size);
    if(INVALID_SOCKET==theClient)
    {
        cout<<"\n Accept_Socket:::Listening Error: "<<WSAGetLastError()
            <<" occurred, could not create socket\n";
        WSACleanup();
        return false;
    }//end if(INVALID_SOCKET....

    //listeningSocket=theClient;  //this is wrong when you do 
    //this you servere the connection after your first disconnect
    cout<<"socket_accepted\n";
    return true;

The accept function accepts the connection and returns the descriptor to the new connection. I called this theClient; this is what enables us to read and write to the connected client. The commented out listening Socket = theClient was left there just to ensure that people do not make the same mistake I made when I wrote this class. While looking through MSDN, they equated the listening socket with the value of the equated accepted descriptor. The reason they did this was because all they wanted was one connection and end the application. In our situation, we don't to leave the control of that to the server socket class.

C++
bool server_socket::socket_send(const char *data_buffer, int data_size)
{
    int number_of_bytes_transferred=0;
    bool return_value=true;
    number_of_bytes_transferred=send(theClient,
                                     data_buffer,
                                     data_size,
                                     0);
    if (SOCKET_ERROR==number_of_bytes_transferred)
    {
        return_value=false;
    }
    else
    {
        if(number_of_bytes_transferred==data_size)
        {
            return_value=true;
        }
        else
        {
            return_value=false;
        }//end if (number_of_bytes_ transferred
    }//end if(socket_error....
    return true;
}

Once we have accepted a connection, we can either send or receive. The code above shows you how to send data. The send command takes in the accepted connection descriptor, the data to be transferred, and the size of the data to be transferred as arguments, and returns the number of bytes transferred.

C++
bool server_socket::socket_receive(char* data_buffer,int data_size)
{
    bool return_value=true;
    int socket_return_value;
    int total_received=0;
    while((return_value !=0)&&(total_received<data_size))
    {
        socket_return_value=recv(theClient,
                                 (data_buffer)+total_received,  //const_cast<char*>
                                 (data_size-total_received),
                                 0);
        if((socket_return_value<=0)||(socket_return_value==WSAECONNRESET))
        {
            return_value = false;
            break;
        }
        else
        {
            total_received=total_received+socket_return_value;
        }//end if((socket_return_value.....
        
    }//end while((return_value......
    if(data_size==total_received)
    {
        return_value=true;
    }
    else
    {
        return_value=false;
    }
    return return_value;

The receive is just like the send, the difference is we enclose the receive command in a while loop to ensure that we receive everything sent to us. In my implementation, I have a fixed header that I receive that tells me the size of what else is coming to me. Hence my checking to make sure that the total received is equal to the data size sent in as an argument.

C++
bool server_socket::socket_shutdown()
{
    closesocket(theClient);
    
    return true;
}

Once we are done with the accepted connection, we close the socket using the closesocket function. We are still able to listen for connections because the listening socket is still alive.

C++
server_socket::~server_socket()
{
    closesocket(listeningSocket);
    WSACleanup();
}

Finally, once we are done listening, we close the listening socket and we are done. This is but a simple implementation of using Winsock. And for what I needed it to do, it did. As an example of how to use the socket server example, I create a function that deals with setting up the socket server and another function that deals with the sending and receiving.

C++
void run_server()
{
    server_socket* the_Server_socket;
    bool continue_flag = true;
    the_Server_server = new server_socket(port_number);
    if (the_Server_socket->create_socket() == false)
    {
        cout<<"executive::run():  ERROR:  Call to create_socket failed. " 
              "Constructor failed.\n"<<flush;
        return false;
    }
    // Accept new connect requests and spawn threads to process them
    while (continue_flag)
    {
        if (the_Server_socket->socket_accept() == false)
        {
            cout<<"executive::run():  ERROR:  Accept error occurred.\n"<<flush;
            //return false;
        }
        else
        {
            num_clients_so_far++;
            cout << "Accepted new connection:  client " << dec 
                 << num_clients_so_far <<"\n" << flush;    
            // Receive and process and commands. 
               receive_loop(the_Server_socket);
        }
    } // while (true)                
    itsServer_socket->socket_shutdown();
}

receive_loop(server_socket *new_Server)
{
   bool return_status =true;
   char input_buffer [10];
  while ( (return_status = new_Server->socket_receive( input_buffer, 10)!=false)) 
  {
      //put your code here to deal with the received data
  }
}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)