3
\$\begingroup\$

I have planned to implement a networking library for C++ which is specific to windows since I can't find a library for my needs. Here is an implementation of TCP server using this library.

First of all we need sockets so socket.h

// SWAMI KARUPPASWAMI THUNNAI
#pragma once
#ifndef SOCKET_H
#define SOCKET_H
#include<windows.h>
#include<winsock.h>
#include<string>
#include"exceptions.h"


#pragma comment(lib, "ws2_32.lib")

/**
DESCRIPTION:
    In networking there are two primary types of socket
        1. TCP
        2. UDP
    This class will give you the above specified socket of your choice
*/
class Socket
{
private:
    WSADATA winsock;
    SOCKET sock;
protected:
    const enum {
        TCP = 0,
        UDP = 1
    };
public:
    Socket();
    // Will return the socket of specified type
    // type = 0 for TCP
    // type = 1 for UDP
    // Throws: socket_error
    SOCKET get_socket(int type);

    /**
    Description:
    -------------
    This method is used to send message on the specified socket
    Throws:
    --------
    socket_error
    */
    void send_message(SOCKET s, std::string message);

    /**
    Description:
    -------------
    Will receive the message from the client with
    the specified buffer size
    */
    char* receive(SOCKET client_socket, int buffer_size);
};

#endif // SOCKET_H

and socket.cpp

// SWAMI KARUPPASWAMI THUNNAI
#include "socket.h"

Socket::Socket()
{
    // Initialize the winsock
    if ((WSAStartup(MAKEWORD(2, 2), &winsock) != 0))throw winsock_initialize_error();
}

SOCKET Socket::get_socket(int type)
{
    switch (type)
    {
    case TCP:
        if ((sock = socket(AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) throw socket_error_invalid();
        return sock;
    case UDP:
        if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) == INVALID_SOCKET) throw socket_error_invalid();
        return sock;
    default:
        break;
    }
    return SOCKET();
}

void Socket::send_message(SOCKET s, std::string message)
{
    // Send the message back
    int result = send(s, message.c_str(), message.size(), 0);
    if (result == SOCKET_ERROR) throw socket_error();
}

char * Socket::receive(SOCKET client_socket, int buffer_size)
{
    char* buffer = new char[buffer_size];
    recv(client_socket, buffer, buffer_size, 0);
    return buffer;
}

and server_socket.h

#pragma once
#include"socket.h"
#include<string>

/**
This namespace will contain classes which provides specifc sockets
for various protocols like TCP, UDP etc.,
*/
namespace server_socket
{
    /**
    RFC:
    ----
    The Transmission Control Protocol (TCP) is intended for use as a highly
    reliable host-to-host protocol between hosts in packet-switched computer
    communication networks, and in interconnected systems of such networks.
    */
    class tcp :public Socket
    {
    private:
        SOCKET socket;
        sockaddr_in server;
    public:
        /**
        Description:
        ------------
        A constructor to bind the specific ip address on a particular port number
        Throws:
        --------
        bind_error if the port is already in use
        */
        tcp(std::string ip_address, int port_no);
        /**
        Description:
        ------------
        Will listen for a specified backlog
        Throws:
        -------
        listen_error
        */
        void tcp_listen(int backlog);
        /**
        Description:
        -------------
        Will accept connections from the client
        */
        void accept_client(SOCKET& client, sockaddr_in& from);

        /**
        Description:
        -------------
        Will close the particular socket
        */
        void close(SOCKET& socket);
        /**
        Description:
        -------------
        Will close the TCP connection itself.
        */
        void close();       
    };
}

server_socket_tcp.cpp

#include "server_socket.h"
#include<iostream>

server_socket::tcp::tcp(std::string ip_address, int port_no)
{
    // Initialize the socket to tcp
    socket = get_socket(TCP);
    // Initialize the server's ip family to IP version 4
    server.sin_family = AF_INET;
    // Server's ip address
    server.sin_addr.S_un.S_addr = inet_addr(ip_address.c_str());
    // Server's port no
    server.sin_port = htons(port_no);
    // Now bind the server on the specific port
    if (bind(socket, (struct sockaddr*)&server, sizeof(server)) < 0) throw bind_error();
}

void server_socket::tcp::tcp_listen(int backlog)
{
    if (listen(socket, backlog) != 0) throw listen_error();
}

void server_socket::tcp::accept_client(SOCKET& client,  sockaddr_in& from)
{
    int size = sizeof(from);
    client = accept(socket, (sockaddr*)&from, &size);
}

void server_socket::tcp::close()
{
    closesocket(socket);
}

void server_socket::tcp::close(SOCKET& socket)
{
    closesocket(socket);
}

Finally TCP server

#include<iostream>
#include"server_socket.h"

int main()
{
    try {
        server_socket::tcp server("127.0.0.1", 90);
        server.tcp_listen(10);
        std::cout << "The server is listening\n";
        sockaddr_in from;
        SOCKET client;
        while (true) //we are looping endlessly
        {
            server.accept_client(client, from);
            std::cout << "Accepted connection\n";
            std::cout << server.receive(client, 4096);
            std::cout << "Received\n";
            server.send_message(client, "<h1>Hello World</h1>");
        }
        server.close(); // I know this wont get called :) but in real world applications 
        // you have to close the tcp connection like this
    }
    catch (std::exception &e)
    {
        std::cout << "exception: ";
        std::cout << e.what();
    }
    int a;
    std::cin >> a;
}
\$\endgroup\$
  • \$\begingroup\$ further devlopment carried out here: github.com/VISWESWARAN1998/Peacock \$\endgroup\$ – VISWESWARAN NAGASIVAM Dec 27 '17 at 15:18
  • 1
    \$\begingroup\$ Since you say that you have specific needs, it would be nice to take them in consideration in order to properly review the code. Maybe you can elaborate on why, say ASIO is not suitable for your purposes? \$\endgroup\$ – Frank Dec 27 '17 at 16:05
  • \$\begingroup\$ @Frank, Thank you for your comment, I was inspired by the socket and other network modules in python, The network modules in python have low learning curve for the users. So I have wrapped up all the toughest WinSock which is written in Hungarian Notation and provided the wrapped code for the end user which is dependency free, easy to build and deploy and looks more c++. You can see the above code offers same functionality like other network libraries offered in C++ but is more readable and will give you results in few lines of code \$\endgroup\$ – VISWESWARAN NAGASIVAM Dec 27 '17 at 16:58
  • \$\begingroup\$ @VISWESWARANNAGASIVAM And also has not been tested and used as thoroughly as Asio? :-) \$\endgroup\$ – Juho Dec 28 '17 at 10:33
  • \$\begingroup\$ @Juho That is why I came here :) \$\endgroup\$ – VISWESWARAN NAGASIVAM Dec 28 '17 at 11:44
2
+50
\$\begingroup\$

It would be nice to get an explanation of what your "needs" are. But either way, here are some recommendations for your coding style and other simple stuff.

enums

You are using an enum, but instead of using it as a parameter, you are using int? In the comments above, you are explicit defining the values. That isn't necessary, when you accept a parameter of an enum type. You should also use the more strict enum class type. This prevents users, to accidentally passing any other values than the enum class types. That means, your enum would look like this:

enum class Protocol
{
    udp,
    tcp
};

SOCKET get_socket(Protocol protoc);

get_socket(Protocol::udp);  // that's fine
get_socket(1);              // that's bad and doesn't compile

You make your code more self-descriptive.

your Socket class

Your class suggests an other meaning, than it provides. It should handle the sending and receiving without the need, to specify the WINSOCKET explicit. The user shouldn't be forced to pass the WINSOCKET themselves. That's up to the class, to provide the correct socket (at least you are holding a socket as private member). Think about that, perhaps your class has simply the wrong name for its purpose.

use std::string instead of heap array

You are using a heap char array as a read buffer. This yields exception errors, and it isn't totally clear (at least for the user of the Socket class) who is responsible for the cleanup (delete[]) of this buffer. It is never a good idea, to hand back a pointer, and let the user cleanup the stuff by themselves. Instead, you should simply return a std::string. That's clean, smart and has no hidden traps. If the user decides, he doesn't need the buffer anymore, he can simply let it go out of scope and it will be deleted automatically. The problems are even bigger, when an exception is thrown by any part of your framework, the users code or any other part of the program. If you want to pass ownership, use the provided classes. For strings it's std::string, for every other point it is std::unique_ptr or std::shared_ptr. When you think, "returning a char* has better performance than returning a std::string", that's not totally true. Most of modern compilers can optimize that. You should google for "return value optimization" (RVO). In C++17 it is guaranteed, but in c++11 or c++14 it might fail. But even in that cases, a simple move of the std::string happens, which is not as bad as it sounds.

\$\endgroup\$
  • \$\begingroup\$ Hi, Thank you for your answer much appreciated! \$\endgroup\$ – VISWESWARAN NAGASIVAM Dec 30 '17 at 14:57
0
\$\begingroup\$

I have something similar here: https://github.com/Ebenezer-group/onwards/blob/master/wrappers.hh

I work on FreeBSD and Linux more than Windows. So the Windows support is thinner than on unix. I didn't see you using getaddrinfo anywhere. I use that a lot.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.