TCP client server dengan winsock2

Untuk keperluan selanjutnya di pengembangan game yang sedang saya buat ( :-o ), saya memerlukan program yang bisa di tweak lewat program lain. Dengan kata lain 2 program yang saling bisa berkomunikasi. Cara yang bisa dicapai , pakai file sharing atau dengan cara yang pas : pakai networking tcp / udp . Kalau mau buat program dengan kemampuan networking, cara yang bijaksana mungkin pakai 3rd party library yang sudah cukup populer dan reliable. Di antaranya, yang saya ketahui dan sepertinya sudah terbukti bagus : boost-asio ( free ), raknet ( free untuk hobyist ), libevent ( free ), etc. ada yang lain ?. Tapi saya pinginnya ndak usah pakai 3rd party lib, lagipula program yang akan saya buat cukup sederhana. winsock2 & c/c++ . itu pilihannya. dan rasanya akan fun juga buat program nya hehe. Sebagai pemanasan awal dengan winsock2 ( saya pemula ) , saya perlu membuat program seperti ini : ada client dan server. client bisa kirim data ke server. server bisa kirim data ke client. jika client nya lebih dari satu, client bisa kirim data ke client yang lain. representasi dari program ini adalah program “chatting” :d . kemampuan server dalam keadaan running : - stand by & listening kalau ada client mau membuat koneksi. - setiap ada client tersambung, server akan membuat handler yang menangani koneksi ke client tersebut berdasarkan socket id. ( dalam test program dipost ini, setiap ada client, server membuat thread baru, untuk menanganinya ). - server bisa kirim data ke client lewat console - ada > 3 thread . 1 main thread: untuk menangkap command line dari user, 1 thread untuk “stand by” / connection acceptor, dan n thread , untuk n client yang tersambung. ya sebenarnya ndak benar ( bisa di optimize pakai thread pool mungkin nantinya ).

kemampuan client dalam keadaan running : - connect ke server - kirim data ke server - ada 2 thread, 1 main thread, 1 client handler thread ( data receiver & sender ) - port hardcoded : 22536

catatan : [1] saya membuat socket mode ke non-blocking , artinya pemanggilan fungsi2 winsock seperti connect, recv, send tidak akan mem-blok jalannya program loop. jadi non-blocking & di thread lain selain main thread. u_long iMode = 1;  // iMode=0, blocking mode, default value ioctlsocket(ConnectSocket, FIONBIO, &iMode); [2] untuk sharing data antar thread, saya menggunakan concurrent queue dari blog Anthony William http://www.justsoftwaresolutions.co.uk/threading/implementing-a-thread-safe-queue-using-condition-variables.html yang saya modifikasi menggunakan std library ( awalnya pakai boost ) [3] data yang dikirim berupa string.

Beberapa pertanyaan : - apa cara di catatan no 1 efektif ? - saat memakai non-blocking mode, kalau mengirim data byte yang sangat besaarr , apakah semuanya terkirim langsung ATAU setelah beberapa call sent?

Reference : -MSDN winsock2 ( belum semuanya saya baca :| )

berikut code program chat client & server yang saya buat :

(server code) : ~288 baris

[sourcecode language=“cpp”] // xedi xermawan < edi.ermawan@gmail.com > // May 27, 2014 // tcp client server networking test using winsock2 // server code

#define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include

#include “concurrent_queue.hpp”

#pragma comment (lib, “ws2_32.lib”)

#define DEFAULT_BUFLEN 512 #define DEFAULT_PORT “22536”

struct XDclient_info { int socket_id; struct sockaddr_in address; };

class XDclient { public: XDclient(XDclient_info& clientinfo) { m_clientinfo = clientinfo; m_recvbuflen = DEFAULT_BUFLEN; m_islive = true; m_thread = new std::thread(&XDclient::run, this); } bool isLive() { return m_islive; } XDclient_info getinfo() const { return m_clientinfo; } ~XDclient() { m_thread->join(); delete m_thread; } std::string get_data() { std::string ret_str = “”; if (!m_queue.empty()) { m_queue.try_pop(ret_str); } return ret_str; } private: XDclient_info m_clientinfo; std::thread* m_thread; char m_recvbuf[DEFAULT_BUFLEN]; int m_recvbuflen; bool m_islive; concurrent_queue std::string m_queue;

void run()
{
    while (m_islive)
    {
        unsigned int result_recv = recv( m_clientinfo.socket_id, m_recvbuf, m_recvbuflen, 0);
        int nError = WSAGetLastError();
        if (nError != WSAEWOULDBLOCK&&nError != 0)
        {
            printf("Client Error: %d\n", nError);
            printf("Client disconnected: %d\n", nError);
            m_islive = false;
        }
        else if (result_recv != INVALID_SOCKET && result_recv!= 0)
        {
            m_recvbuf[result_recv] = '\0';
            std::string str_send(m_recvbuf);
            m_queue.push(str_send);
        }
        else if (result_recv == 0)
        {
            printf("Client disconnected: %d\n", nError);
            m_islive = false;
        }
        // give a sleep, avoid high percentage cpu usage
        Sleep(50);
    }
}

};

class XDserver { public: XDserver() { m_recvbuflen = DEFAULT_BUFLEN; m_isrun = true; m_thread = new std::thread(&XDserver::run, this); } ~XDserver() { m_isrun = false; m_thread->join(); delete mthread; } void run() { WSADATA wsaData; int result ;

    SOCKET listen_socket = INVALID_SOCKET;
    SOCKET client_socket = INVALID_SOCKET;

    struct addrinfo *result = NULL;
    struct addrinfo hints;

    char recvbuf[DEFAULT_BUFLEN];
    int recvbuflen = DEFAULT_BUFLEN;

    // init
    result_ = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (result_ != 0) {
        printf("WSAStartup failed with error: %d\n", result_);
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    hints.ai_flags = AI_PASSIVE;

    // get info address for host
    result_ = getaddrinfo(NULL, DEFAULT_PORT, &hints, &result);
    if (result_ != 0) {
        printf("getaddrinfo failed with error: %d\n", result_);
        WSACleanup();
    }
    // create socket
    listen_socket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
    if (listen_socket == INVALID_SOCKET) {
        printf("socket failed. error code : %ld\n", WSAGetLastError());
        freeaddrinfo(result);
        WSACleanup();
    }
    // set-up socket listening
    result_ = bind(listen_socket, result->ai_addr, (int)result->ai_addrlen);
    if (result_ == SOCKET_ERROR) {
        printf("bind failed. error code: %d\n", WSAGetLastError());
        freeaddrinfo(result);
        closesocket(listen_socket);
        WSACleanup();
        m_isrun = false;
    }
    if (!m_isrun)
        return;
    freeaddrinfo(result);

    result_ = listen(listen_socket, SOMAXCONN);
    if (result_ == SOCKET_ERROR) {
        printf("listen failed. error code: %d\n", WSAGetLastError());
        closesocket(listen_socket);
        WSACleanup();
    }
    // set to non-blocking mode
    u_long iMode = 1;
    ioctlsocket(listen_socket, FIONBIO, &iMode);
    int t_clientAddressLen = sizeof(struct sockaddr_in);
    struct sockaddr_in t_clientAddress;
    // server loop
    while (m_isrun)
    {
        // check new client connected
        client_socket = accept(listen_socket, (struct sockaddr *)&t_clientAddress, &t_clientAddressLen);
        int nError = WSAGetLastError();
        if (nError != WSAEWOULDBLOCK&&nError != 0)
        {
            // todo :
        }
        else if (client_socket != INVALID_SOCKET)
        {
            XDclient_info clientinfo;
            clientinfo.address = t_clientAddress;
            clientinfo.socket_id = client_socket;

            XDclient* clientnew = new XDclient(clientinfo);
            m_clients.push_back(clientnew);
            if (client_socket == INVALID_SOCKET) {
                printf("accept failed. error code: %d\n", WSAGetLastError());
            }
        }
        // check client live
        for (std::vector< XDclient* >::iterator it = m_clients.begin(); it != m_clients.end(); it++)
        {
            if (!(*it)->isLive())
            {
                delete *it;
                m_clients.erase(it);
                break;
            }
        }
        // check if there are data need to be sent
        if (!m_queue.empty())
        {
            std::string str = "";
            m_queue.try_pop(str );
            std::cout << " send data: " << str.c_str() <<std::endl;
            for (std::vector< XDclient* >::iterator it = m_clients.begin(); it != m_clients.end(); it++)
            {
                int m_recvbuflen;
                int sendresult = send((*it)->getinfo().socket_id, str.c_str(), str.length(), 0);
                if (sendresult == SOCKET_ERROR)
                {
                    std::cout << " send failed. error code " << sendresult << std::endl;
                }
            }
        }
        for (std::vector< XDclient* >::iterator it = m_clients.begin(); it != m_clients.end(); it++)
        {
            std::string str_data = (*it)->get_data();
            if (str_data != "")
            {
                for (std::vector< XDclient* >::iterator it2 = m_clients.begin(); it2 != m_clients.end(); it2++)
                {
                    if (*it != *it2)
                    {
                        int sendresult = send((*it2)->getinfo().socket_id, str_data.c_str(), str_data.length(), 0);
                        if (sendresult == SOCKET_ERROR)
                        {
                            std::cout << " send failed. error code " << sendresult << std::endl;
                        }
                    }
                }
            }
        }
        // give a sleep, avoid high percentage cpu usage
        Sleep(50);
    }
}
void senddata(std::string& str)
{
    m_queue.push(str);
}
bool isLive()
{
    return m_isrun;
}

private: char m_recvbuf[DEFAULT_BUFLEN]; int m_recvbuflen; bool m_isrun; std::thread* m_thread; std::vector< XDclient* > m_clients; concurrent_queue std::string m_queue; };

class Games { };

int __cdecl main(int argc, char** argv) { XDserver* server = new XDserver(); std::cout << “welcome to the server”<isLive()) { char str_in[512]=“”; std::cin >> str_in; std::string str_input(str_in); server->senddata(“server|”+str_input); if (!str_input.compare(“exit”)) { break; } } delete server; std::cout << “server shutdown” << std::endl; system(“pause”); return 0; } [/sourcecode]

(client  code) :  ~181 baris

[sourcecode language=“cpp”] // xedi xermawan < edi.ermawan@gmail.com > // May 27, 2014 // tcp client server networking test using winsock2 // client code

#define WIN32_LEAN_AND_MEAN #include #include #include #include #include #include #include #include #include #include

#include “concurrent_queue.hpp”

// link to winsock2 static lib #pragma comment (lib, “ws2_32.lib”)

#define DEFAULT_BUFLEN 512 #define DEFAULT_PORT “22536”

class XDclientapp { public: XDclientapp(std::string& clientname, std::string& serveraddresss) { m_server_address = serveraddresss; m_client_name = clientname; m_recvbuflen = DEFAULT_BUFLEN; m_isrun = true; m_thread = new std::thread(&XDclientapp::run, this); } ~XDclientapp() { m_isrun = false; m_thread->join(); delete m_thread; } void run() { WSADATA wsaData; SOCKET ConnectSocket = INVALID_SOCKET; struct addrinfo *result = NULL, *ptr = NULL, hints; char *sendbuf = “this is a test”; char recvbuf[DEFAULT_BUFLEN]; int iResult; int recvbuflen = DEFAULT_BUFLEN;

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
    }

    ZeroMemory(&hints, sizeof(hints));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo( m_server_address.c_str(), DEFAULT_PORT, &hints, &result);
    if (iResult != 0) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
    }

    // Attempt to connect to an address until one succeeds
    for (ptr = result; ptr != NULL; ptr = ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
            ptr->ai_protocol);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
        }

        // Connect to server.
        iResult = connect(ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }
    // set to non-blocking mode
    u_long iMode = 1;
    ioctlsocket(ConnectSocket, FIONBIO, &iMode);
    freeaddrinfo(result);
    int errorid;
    while (1)
    {
        unsigned int result_recv = recv(ConnectSocket, recvbuf, recvbuflen, 0);

        errorid = WSAGetLastError();
        if (errorid != WSAEWOULDBLOCK && errorid != 0)
        {
            printf("Client disconnected: %d\n", errorid);
            break;
        }
        else if (result_recv != INVALID_SOCKET && result_recv != 0)
        {
            // printf("Bytes received: %d\n", result_recv);
            recvbuf[result_recv] = '\0';
            printf("\n%s\n", recvbuf);
            std::cout << m_client_name.c_str() << "(You) : ";
        }
        else if (result_recv == 0)
        {
            printf("Client disconnected: %d\n", errorid);
            break;
        }

        // check if there are data need to be sent
        if (!m_queue.empty())
        {
            std::string str = "";
            m_queue.try_pop(str);
            int m_recvbuflen;
            int sendresult = send( ConnectSocket , str.c_str(), str.length(), 0);
            if (sendresult == SOCKET_ERROR)
            {
                std::cout << " send failed. error code " << sendresult << std::endl;
            }
        }
        // give a sleep, avoid high percentage cpu usage
        Sleep(50);
    }
}
void senddata(std::string& str)
{
    std::string pstr = m_client_name + " : " + str;
    m_queue.push(pstr);
}

private: char m_recvbuf[DEFAULT_BUFLEN]; int m_recvbuflen; bool m_isrun; std::thread* m_thread; concurrent_queue std::string m_queue; std::string m_client_name; std::string m_server_address; }; //– int __cdecl main(int argc,char** argv) { std::string clientname = “”; std::string serveraddress = “localhost”; std::cout << “Please enter your name: “; std::getline(std::cin, clientname);

if (argc > 1)
{
    serveraddress = argv[1];
}
XDclientapp* client = new XDclientapp(clientname, serveraddress);
std::cout << "welcome to the chat client" << std::endl;
std::cout << "type an input to broadcast to all client. ""exit"" to close program " << std::endl;
std::cout << "---" << std::endl;
while (1)
{
    std::string str_input = "";
    std::cout << clientname.c_str() << "(You) : ";
    std::getline(std::cin, str_input);

    client->senddata(str_input);
    if (!str_input.compare("exit"))
    {
        break;
    }
}
delete client;
return 0;

} [/sourcecode]

Screenshoot : chatprogram Download : source + vs project + binary ( hanya 39 kb ! ) .

comments powered by Disqus