Jumat, 20 Desember 2013

SFML Network dengan Thread untuk client server [bagian 4]

mari kita lanjutkan journey kita ke bagian code Server::runAccepter() yang masih bagian  dari server.cpp, berikut ini adalah codenya:
void Server::runAccepter() {
    if(!isFull()) {
        sf::Uint16 OpenSlot = 0;
        for(sf::Uint16 i = 1; OpenSlot == 0; i++) {
            if(isSlotOpen(i)) OpenSlot = i;
        }
        {
        std::wstringstream ss; ss << L"Started listen on port {YD" << Port+OpenSlot << L"}.";
        writeToLog(ss.str());
        }
        if(Listener.listen(Port+OpenSlot) != sf::Socket::Done) {
            {
            std::wstringstream ss; ss << "{RDFailed to listen on port} {YD" << Port+OpenSlot << L"}";
            writeToLog(ss.str());
            }
        } else {
            clientAccess.lock();
            delete Clients[OpenSlot-1];
            Clients[OpenSlot-1] = new Client();
            clientAccess.unlock();

            if(Listener.accept(Clients[OpenSlot-1]->Socket) != sf::Socket::Done) {
                {
                std::wstringstream ss; ss << L"{RDFailed to accept incomming connection on slot} {YDno. " << OpenSlot << L"}.";
                writeToLog(ss.str());
                }
            } else {
                {
                std::wstringstream ss; ss << L"{GDClient connected} on port {YD" << OpenSlot+Port << L"}.";
                writeToLog(ss.str());
                }
                Clients[OpenSlot-1]->Dead = false;
                Clients[OpenSlot-1]->listenThread = new sf::Thread(&Client::listen, Clients.at(OpenSlot-1));
                Clients[OpenSlot-1]->listenThread->launch();
            }
            Listener.close();
        }
    }
    Listening = false;
}

mari kita bahas kode Server::runAccepter(), yang pertama kali dijalankan adalah if(!isFull()) dimana fungsi isFull() adalah rangkaian dari beberapa fungsi yang dipanggil secara berurutan yaitu:


sf::Uint16 Server::calcAvgPing() { //Weighted average
    sf::Uint16 temp(0), count(0);
    for(sf::Uint16 i = 0; i < Clients.size(); i++) {
        if(!Clients.at(i)->isDead()) {temp+=Clients.at(i)->getPing(); count++;}
    }
  
    if(count != 0) {
        return temp/count;
    } else {
        return 0;
    }
}


bool Server::isSlotOpen(sf::Uint16 slot) {
    if(slot>0 && slot<=config.max_connections) {
        sf::Lock lock(clientAccess);
        return Clients[slot-1]->isDead();
    } else {
        writeToLog(L"{RDInvalid slot request.}");

        return false;
    }
}

sf::Uint16 Server::getSlotsOpen() {
    sf::Uint16 open = 0;
  
    for(sf::Uint16 i = 0; i < config.max_connections; i++) {
        if(isSlotOpen(i+1)) open++;
    }

    return open;
}

bool Server::isFull() {
    return (getSlotsOpen() == 0);
}


Begini singkat ceritanya: fungsi isFull() memanggil fungsi getSlotOpen() kemudian fungsi getSlotOpen() memanggil sf::Uint16() dan fungsi isSlotOpen() untuk mendapatkan apakah status slot open atau close.


Nah kembali ke kode Server::runAccepter() jadi nilai kembalian dari fungsi isFull() jika tidak true maka deklarasikan variable openslot sebagai sf::uint16 dengan nilai awal = 0. Kemudian baris berikutnya merupakan loop dengan for(sf::Uint16 i = 1; OpenSlot == 0; i++) sejauh incremental dari nilai i masih memenuhi kondisi openSlot==0 maka perintah if dibawahnya akan dijalankan yaitu if(isSlotOpen(i)) OpenSlot = i; berarti jika nilai kembalian fungsi isSlotOpen(dengan parameter nilai incremental i) adalah true maka variable OpenSlot akan diisi dengan nilai incremental i tersebut. Selesai dari loop, dua baris perintah berikutnya adalah untuk penulisan ke layar dan file log.
 

Nah pada kode berikutnya yaitu:
if(Listener.listen(Port+OpenSlot) != sf::Socket::Done)
disinilah mode “listen to network” dijalankan oleh server ini didalam thread yang berbeda, bagaimana caranya: ingat dibagian server.init sebelumnya, pemanggilan kode AccepterThread = new sf::Thread(&Server::runAccepter); disanalah deklarasi atas Server::runAccepter yang kita bahas ini dilakukan sebagai sf::Thread baru yang independent dari thread lainnya. Listener itu sendiri adalah penurunan inherit dari sf::TcpListener(); yang dideklarasikan diawal kode server.cpp.
 

sf::TcpListener Server::Listener = sf::TcpListener();
disitu jelas terlihat bahwa Server::Listener merupakan Tcp listener jadi sekarang kita telah mengaktifkan listener pada tcp socket bukan pada udp socket. Nilai port/slot yang dibuat untuk tcp listener ini adalah udp socket + 1, dan setiap kali terjadi koneksi udp socket baru dari para client, maka nilai tcp socket +1 akan berlaku untuk seterusnya hingga max.connection terpenuhi fungsi isFull() akan true.
 

Selanjutnya marilah kita abaikan kondisi true dari if(Listener.listen(Port+OpenSlot) != sf::Socket::Done) maka baris setelah else akan dieksekusi yaitu:
else {
    clientAccess.lock();
    delete Clients[OpenSlot-1];
    Clients[OpenSlot-1] = new Client();
    clientAccess.unlock();

    if(Listener.accept(Clients[OpenSlot-1]->Socket) != sf::Socket::Done)
{
    {std::wstringstream ss; ss << L"{RDFailed to accept incomming connection on slot} {YDno. " << OpenSlot << L"}.";
    writeToLog(ss.str());}
    }
else {
    {std::wstringstream ss; ss << L"{GDClient connected} on port {YD" << OpenSlot+Port << L"}.";
    writeToLog(ss.str());}
    Clients[OpenSlot-1]->Dead = false;
    Clients[OpenSlot-1]->listenThread = new sf::Thread(&Client::listen, Clients.at(OpenSlot-1));
    Clients[OpenSlot-1]->listenThread->launch();}
    Listener.close();
    }


Bagian kode ini berfungsi untuk menyediakan, mengunci dan membuka kuncian terhadap setiap thread yang terjadi setiap kali terdapat client yang terkoneksi.
Ini berarti pada awal koneksi client kepada server akan dilakukan secara UDP dan setelah sukses server akan membuka satu port/slot baru di TCP socket sebagai thread yang berbeda dengan UDPnya untuk digunakan selanjutnya oleh para client. Masing-masing Client akan terhubung kepada tcp socket yang berbeda.
Untuk memahami kode diatas mari kita pelajari kode sumber client.h:
 

//=============client.h===========

struct ClientData {
    wchar_t * name;

    ClientData();
};



class Client {

    void disconnect();

    sf::Mutex dataMutex;
    sf::Mutex cMutex;

    sf::Uint16 ping;

    ClientData data;

public:
    bool Dead;
    sf::Uint16 getPing();
    void setPing(sf::Uint16 Ping);
    bool handleTCPPacket(sf::Packet& packet);
    sf::TcpSocket Socket;
    sf::Thread * listenThread;
    bool isDead();
    ClientData& getData();
    void freeDataAccess();
    void listen();
    Client();
    ~Client();
};

Seperti telah dijelaskan sebelumnya bahwa C++ header file adalah berisikan symbol-symbol yang bertautan terhadap fungsi-fungsi didalam file object c++. Dalam hal ini file object tersebut adalah hasil kompilasi c++ terhadap file kode sumber. File client.h tersebut adalah header atas file client.cpp sebagai berikut:
 

//===========client.cpp============
#include <iostream>
#include <SFML\Network.hpp>
#include <vector>
#include <deque>
#include <sstream>
#include "config.h"
#include "client.h"
#include "server.h"
#include "packetHandling.h"
#include "ui.h"

ClientData::ClientData() {
    name = new wchar_t[MAX_CLIENTNAME_LENGTH];
}

Client::Client() : Socket(), Dead(true), listenThread(0), dataMutex(), cMutex(), ping(0), data() {
    //Socket.setBlocking(false); If I want a non-blocking socket, I will have to make a thread which sends packets in a non-blocking way (NOTREADY handling)
}

Client::~Client() {
    if(listenThread!=0) listenThread->wait();
    delete listenThread;

    Socket.disconnect();
}

ClientData& Client::getData() {
    dataMutex.lock();
    return data;
}

void Client::freeDataAccess() {
    dataMutex.unlock();
}

bool Client::isDead() {
    sf::Lock lock(cMutex);
    return Dead;
}

sf::Uint16 Client::getPing() {
    sf::Lock lock(cMutex);
    return ping;
}

void Client::setPing(sf::Uint16 Ping) {
    cMutex.lock();
    ping = Ping;
    cMutex.unlock();
}

void Client::disconnect() {
    cMutex.lock();
    Dead = true;
    cMutex.unlock();

    std::wstringstream ss; ss << "Socket {YDno. " << Socket.getLocalPort()-Server::Port << "} {RDdisconnected}.";
    writeToLog(ss.str());

    Socket.disconnect();
}

void Client::listen() {
    cMutex.lock();
    bool d = Dead;
    cMutex.unlock();
  
    while(!d) {
        sf::Packet received;
        sf::Socket::Status status = Socket.receive(received);
      
        if(status == sf::Socket::Disconnected) {
            disconnect();
        } else if(status == sf::Socket::Error) {
            std::wstringstream ss; ss << "Error on socket {YDno. " << Socket.getLocalPort()-Server::Port << "}. {RDDisconnecting it}.";
            writeToLog(ss.str());
          
            disconnect();
        } else if(status == sf::Socket::Done) {
          
            if(handleTCPPacket(received)) {
                //Success
            } else {
                std::wstringstream ss; ss << L"Packet error on slot {YDno. " << Socket.getLocalPort()-Server::Port << L"}.";
                writeToLog(ss.str());
            }
        }

        cMutex.lock();
        d = Dead;
        cMutex.unlock();
    }
}

Sekarang mari kita lihat bagaimana hubungan antara object client.cpp ini dengan server.cpp yang kita bahas sebelumnya. Perhatikan dua object berikut ini:


clientAccess.lock();
delete Clients[OpenSlot-1];
 

clientAccess.lock() merupakan turunan daripada object mutex yang didefinisikan diawal kode sumber server.cpp, yaitu: sf::Mutex Server::clientAccess = sf::Mutex(); sedangkan object Client[] merupakan bagian dari object vector yang juga didefinisikan diawal kode sumber server.cpp yaitu: std::vector<Client *> Server::Clients = std::vector<Client *>(); .
 

jadi sekarang kita telah mengetahui kemana pointer akan pergi saat kita memanggil clientAccess.lock() pointer akan pergi kepada object sf::mutex, dan saat object Client[] kita panggil maka pointer akan pergi kepada object std::vector.
 

Sekarang perhatikanlah file client.h diatas:
class Client {}
 

disanalah terjadinya hubungan antara object server.cpp dengan client.cpp, sedangkan mutex akan digunakan oleh kedua object tersebut sebagai perantara, perhatikan baris berikut:
class Client {

    void disconnect();

    sf::Mutex dataMutex;
    sf::Mutex cMutex;

    sf::Uint16 ping;

    ClientData data; …..}
 

Jadi kelas dengan nama Client ternyata juga meregistrasikan membernya kepada pointer sf::Mutex.
 

...tunggu lanjutannya...

Tidak ada komentar:

Posting Komentar