Contoh Program Client-Server dengan SDL_NET

Sebelumnya saya sudah membahas beberapa library tambahan SDL. Sekarang saya akan memberikan contoh program yang menggunakan library SDL_NET. SDL_NET diperlukan kalau kalian akan membuat program client-server sederhana yang menggunakan koneksi TCP atau UDP. Berbeda dengan library SDL lain yang harus diinstal bersama dengan library utamanya, SDL_NET bisa digunakan sendirian saja.

Sebelum bisa menggunakan SDL_NET di windows, kalian perlu mendownload library-nya di situs resminya. Kalian perlu mengatur letak header dan setting lain yang diperlukan. Untuk menjalankan programnya, kalian juga perlu meletakkan dll-nya di folder project atau folder programnya.

Kalau kalian menggunakan linux, khususnya ubuntu, kalian bisa menginstal library tersebut dari terminal dengan mengetik "sudo apt install libsdl2-net-dev". Kalau kalian belum menginstalnya, akan ada pilihan "Y/N" setelah tombol enter ditekan. Pilih "y" dan tekan enter untuk menginstalnya. SDL seharusnya sudah selesai diinstall setelah prosesnya selesai.

Kali ini, saya cuma akan memberikan contoh program untuk versi TCP-nya saja. Saya tidak akan memberikan contoh program untuk UDP-nya. Kalau kalian belum tahu apa beda TCP dan UDP, mungkin kalian bisa mencarinya di internet lewat google. OK, yang pertama adalah kode program untuk servernya.

#include <stdio.h>
#include <cstdlib>
#include <string>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_net.h>
#define Jclient 4
#define playar 640
#define tlayar 480

SDL_Renderer *trender;
const unsigned short PORT        = 1234;
const unsigned short BUFFER_SIZE = 512;
typedef char stringku[BUFFER_SIZE];

struct tulisanku{
    int lebar, tinggi;
    SDL_Texture *texture=NULL;
}teks;

int buat_tulisan( const char *tulisan, TTF_Font *huruf, SDL_Color warna){
    if(teks.texture!=NULL)SDL_DestroyTexture(teks.texture);
    SDL_Surface *textSurface = TTF_RenderText_Blended(huruf, tulisan, warna);

    if( textSurface == NULL ){
        printf( "error: %s\n", TTF_GetError() );
    }else{
        teks.texture = SDL_CreateTextureFromSurface( trender, textSurface );
        if( teks.texture == NULL ){
            printf( "////error: %s\n", SDL_GetError() );
        }else{
            teks.lebar = textSurface->w;
            teks.tinggi = textSurface->h;
        }

        SDL_FreeSurface( textSurface );
    }

    return teks.texture != NULL?1:0;//berhasil==1, gagal=0
}

int main(int argc, char **argv){
    SDL_Event evt;
    SDL_Window *window;
    TTF_Font *huruf=NULL;
    SDL_Rect rct={0, 0, 0, 0};
    SDLNet_SocketSet scset;
    IPaddress ipku;
    TCPsocket socketku, tmp;
    TCPsocket clientku[Jclient]={NULL};
    bool socketkosong=true;
    stringku buffer;
    stringku data_client[Jclient]={"", "", "", ""};
    int hasil=0;
    int nclient=0;
    int terakhir=0;
    int numActiveSockets=0;
    SDL_Color hijau = { 0, 0xFF, 0 };
    SDL_Color merah = { 0xFF, 0, 0 };
    SDL_Color biru = { 0, 0, 0xFF };

    if(SDL_Init(SDL_INIT_VIDEO) < 0){
        printf( "Error: %s\n", SDL_GetError() );
    }else{
        if(SDLNet_Init()<0){
            exit(-1);
        }

        if(TTF_Init()<0){
            exit(-1);
        }else{
            huruf=TTF_OpenFont( "FreeSans.ttf", 28 );
        }

        scset=SDLNet_AllocSocketSet(Jclient+1);
        if(scset==NULL){
            exit(-1);
        }

        if(SDLNet_ResolveHost(&ipku, NULL, PORT)<0){
            exit(-1);
        }

        socketku=SDLNet_TCP_Open(&ipku);
        if(!socketku){
            exit(-1);
        }

        SDLNet_TCP_AddSocket(scset, socketku);

        window = SDL_CreateWindow( "Server", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, playar, tlayar, SDL_WINDOW_SHOWN );
        if( window == NULL || huruf==NULL){
            printf( "Error : %s", SDL_GetError() );
        }else{
            trender = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

            while(evt.type!=SDL_QUIT){
                while(SDL_PollEvent(&evt)){
                }
                SDL_SetRenderDrawColor(trender, 0xFF, 0xFF, 0xFF, 0xFF);
                SDL_RenderClear(trender);

                hasil = SDLNet_SocketReady(socketku);

                if(hasil != 0){
                    if (socketkosong== true){
                        socketkosong= false;
                    }

                    tmp= SDLNet_TCP_Accept(socketku);
                    if(nclient<Jclient){
                        SDLNet_TCP_AddSocket(scset, tmp);
                        strcpy( buffer, "OK");
                        hasil = strlen(buffer) + 1;
                        SDLNet_TCP_Send(tmp, (void *)buffer, hasil);
                        clientku[nclient]=tmp;
                        nclient++;
                    }else{
                        strcpy( buffer, "");
                        hasil = strlen(buffer) + 1;
                        SDLNet_TCP_Send(tmp, (void *)buffer, hasil);
                    }
                }

                if(socketkosong==false){
                    for(int i=0;i<nclient;i++){
                        hasil=SDLNet_SocketReady(clientku[i]);
                        if(hasil!=0){
                            hasil = strlen(buffer) + 1;
                            SDLNet_TCP_Recv(clientku[i], data_client[i], BUFFER_SIZE);
                            terakhir=i;
                        }
                    }
                }

                numActiveSockets = SDLNet_CheckSockets(scset, 0);
                if (numActiveSockets != 0){
                    printf("Ada %d socket dengan data untuk diproses.\n", numActiveSockets);

                }
                //printf("\nAda %d client terhubung\n", nclient);
                sprintf(buffer, "Ada %d client terhubung", nclient);
                if(buat_tulisan(buffer, huruf, hijau)!=0){
                    rct.x=4;
                    rct.y=4;
                    rct.w=teks.lebar;
                    rct.h=teks.tinggi;
                    SDL_RenderCopy(trender, teks.texture, 0, &rct);
                }

                for(int i=0;i<nclient;i++){
                    sprintf(buffer, "client ke-%d : %s", i+1, data_client[i]);
                    if(i==terakhir)hasil=buat_tulisan(buffer, huruf, merah);
                    else hasil=buat_tulisan(buffer, huruf, biru);
                    if(hasil!=0){
                        rct.x=4;
                        rct.y=28*i+24;
                        rct.w=teks.lebar;
                        rct.h=teks.tinggi;
                        SDL_RenderCopy(trender, teks.texture, 0, &rct);
                    }
                }

                SDL_RenderPresent(trender);
            }

            SDLNet_FreeSocketSet(scset);
            SDLNet_TCP_Close(socketku);
            SDLNet_Quit();
        }
        SDL_DestroyRenderer(trender);
        SDL_DestroyWindow(window);
        SDL_Quit();
    }

    return 0;
}

Selanjutnya adalah kode program untuk client-nya.

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <SDL2/SDL.h>
#include <SDL2/SDL_ttf.h>
#include <SDL2/SDL_net.h>

#define playar 640
#define tlayar 480

const unsigned short PORT        = 1234;
const unsigned short batas = 32;
char mshift[10]={')', '!', '@', '#', '$', '%', '^', '&', '*', '('};
struct tulisanku{
    int lebar, tinggi;
    char tulisan[batas];
    SDL_Texture *texture=NULL;
}teks;

SDL_Renderer *trender;

int dapatkan_input(char *s, char ch, char cmod){
int pj=strlen(s);
    if(ch==SDLK_BACKSPACE){
        if(pj>0){
            pj--;
        }
        s[pj]=0;
    }else if(pj<(batas-1)){
        if((ch>=SDLK_a && ch<=SDLK_z)||(ch>=SDLK_0 && ch<=SDLK_9)){
            if(ch>=SDLK_a && ch<=SDLK_z){
                ch-=SDLK_a;
                if(cmod==KMOD_RSHIFT || cmod==KMOD_LSHIFT)ch+='A';
                else ch+='a';
            }else if(ch>=SDLK_0 && ch<=SDLK_9){
                ch=ch-SDLK_0;
                if(cmod==KMOD_RSHIFT || cmod==KMOD_LSHIFT)ch=mshift[(int)ch];
                else ch+='0';
            }
            if(pj<batas){
                s[pj]=ch;
                pj++;
            }
            s[pj]=0;
        }else if(ch==SDLK_SLASH || ch==SDLK_PERIOD || ch==SDLK_COMMA || ch==SDLK_QUOTE ||
            ch==SDLK_SEMICOLON || ch==SDLK_EQUALS || ch==SDLK_MINUS || ch==SDLK_LEFTBRACKET
            || ch==SDLK_RIGHTBRACKET || ch==SDLK_BACKQUOTE || ch==SDLK_BACKSLASH || ch==SDLK_SPACE){
            if(cmod==KMOD_RSHIFT || cmod==KMOD_LSHIFT){
                switch(ch){
                    case SDLK_SLASH:
                        ch='?';
                        break;
                    case SDLK_PERIOD:
                        ch='<';
                        break;
                    case SDLK_COMMA:
                        ch='>';
                        break;
                    case SDLK_QUOTE:
                        ch='"';
                        break;
                    case SDLK_EQUALS:
                        ch='+';
                        break;
                    case SDLK_SEMICOLON:
                        ch=':';
                        break;
                    case SDLK_MINUS:
                        ch='_';
                        break;
                    case SDLK_LEFTBRACKET:
                        ch='{';
                        break;
                    case SDLK_RIGHTBRACKET:
                        ch='}';
                        break;
                    case SDLK_BACKQUOTE:
                        ch='~';
                        break;
                    case SDLK_BACKSLASH:
                        ch='|';
                        break;
                    default:
                        break;
                }
            }
            if(pj<batas){
                s[pj]=ch;
                pj++;
            }
            s[pj]=0;
        }
    }
    return 0;
}

int buat_tulisan(tulisanku *teks, const char *tulisan, TTF_Font *huruf, SDL_Color warna){
    strcpy(teks->tulisan, tulisan);
    if(teks->texture!=NULL)SDL_DestroyTexture(teks->texture);
    SDL_Surface *textSurface = TTF_RenderText_Blended(huruf, tulisan, warna);

    if( textSurface == NULL ){
        printf( "error: %s\n", TTF_GetError() );
    }else{
        teks->texture = SDL_CreateTextureFromSurface( trender, textSurface );
        if( teks->texture == NULL ){
            printf( "////error: %s\n", SDL_GetError() );
        }else{
            teks->lebar = textSurface->w;
            teks->tinggi = textSurface->h;
        }

        SDL_FreeSurface( textSurface );
    }

    return teks->texture != NULL?1:0;//berhasil==1, gagal=0
}


int main(int argc, char **argv){
    SDL_Event evt;
    SDL_Window *window;
    TTF_Font *huruf = NULL;
    SDL_Color hijau = { 0, 0xFF, 0 };
    SDL_Rect rct={0, 0, 0, 0};

    const char *host;
    IPaddress serverIP;
    TCPsocket clientSocket;
    char serverName[128]="localhost";
    char buffer[batas+1]="";
    int berhenti=0;

    if(SDL_Init(SDL_INIT_VIDEO) < 0){
        printf( "Error: %s\n", SDL_GetError() );
    }else{
        if(TTF_Init()<0){
            exit(-1);
        }else{
            huruf=TTF_OpenFont( "FreeSans.ttf", 28 );
        }

        if(SDLNet_Init()<0){
            exit(-1);
        }

        SDLNet_SocketSet socketSet = SDLNet_AllocSocketSet(1);
        if (socketSet == NULL){
            printf ("Error : %s \n", SDLNet_GetError());
            exit(-1);
        }

        int hostResolved = SDLNet_ResolveHost(&serverIP, serverName, PORT);

        if (hostResolved == -1){
            printf ("Error : %s \nLanjut...\n", SDLNet_GetError());
        }else {
            Uint8 * dotQuad = (Uint8*)&serverIP.host;
            printf("IP host : %u.%u.%u.%u\n", (unsigned short)dotQuad[0],(unsigned short)dotQuad[1], (unsigned short)dotQuad[2], (unsigned short)dotQuad[3]);
            printf(" port %d\n\n", SDLNet_Read16(&serverIP.port));
        }

        if ((host = SDLNet_ResolveIP(&serverIP)) == NULL){
            printf ("Error : %s\n", SDLNet_GetError());
        }else{
            printf ("IP Host : %s\n", host);
        }

        clientSocket = SDLNet_TCP_Open(&serverIP);

        if (!clientSocket){
            printf ("Error : %s\n", SDLNet_GetError());
            exit(-1);
        }else{
            printf("Mulai membaca status koneksi dari server...]\n");

            SDLNet_TCP_AddSocket(socketSet, clientSocket);

            int activeSockets = SDLNet_CheckSockets(socketSet, 5000);
            printf("Ada %d socket dengan data pada saat ini.\n", activeSockets);

            int gotServerResponse = SDLNet_SocketReady(clientSocket);
            if (gotServerResponse != 0){
                int serverResponseByteCount = SDLNet_TCP_Recv(clientSocket, buffer, batas);
                printf("Respon dari server: %s(%d byte)\n", buffer,serverResponseByteCount);

                if ( strcmp(buffer, "OK") == 0 ){
                    printf("Bergabung dengan server...\n\n");
                    buffer[0]=0;
                }else{
                    printf("Server penuh.\n");
                    berhenti=1;
                }
            }else{
                printf("Tidak ada tanggapan dari server...\n");
                berhenti=1;
            }
        }

        window = SDL_CreateWindow( "Client", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, playar, tlayar, SDL_WINDOW_SHOWN );
        if( window == NULL || huruf==NULL){
            printf( "Error : %s", SDL_GetError() );
        }else{
            trender = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

            while(evt.type!=SDL_QUIT && berhenti==0){
                while(SDL_PollEvent(&evt)){
                    if(evt.type==SDL_KEYDOWN){
                        if(evt.key.keysym.sym==SDLK_RETURN){
                            SDLNet_TCP_Send(clientSocket, (void*)buffer, strlen(buffer));
                        }else dapatkan_input(buffer, evt.key.keysym.sym, evt.key.keysym.mod);
                        buat_tulisan(&teks, buffer, huruf, hijau);
                    }
                }
                SDL_SetRenderDrawColor(trender, 0xFF, 0xFF, 0xFF, 0xFF);
                SDL_RenderClear(trender);
                rct.x=4;
                rct.y=4;
                rct.w=teks.lebar;
                rct.h=teks.tinggi;
                SDL_RenderCopy(trender, teks.texture, 0, &rct);
                SDL_SetRenderDrawColor(trender, 0, 0xFF, 0xFF, 0xFF);
                SDL_RenderDrawLine(trender, 4, rct.y+36, playar-8, rct.y+36);
                TTF_SizeText(huruf, teks.tulisan, &rct.x, 0);
                rct.x+=8;
                rct.y=8;
                rct.w=24;
                rct.h=24;
                SDL_RenderDrawRect(trender, &rct);

                SDL_RenderPresent(trender);
            }

            SDLNet_TCP_Close(clientSocket);
            SDLNet_Quit();
        }

        SDL_DestroyRenderer(trender);
        SDL_DestroyWindow(window);
        SDL_Quit();
    }

    return 0;
}

Seperti yang sudah saya tulis di atas, berbeda dengan library tambahan SDL lainnya, SDL_NET tidak mengharuskan keberadaan library utama SDL. SDL_NET bisa digunakan secara independen tanpa adanya library lainnya. Tapi, kali ini saya kebetulan menggunakan SDL_TTF untuk menampilkan tulisannya. Jadi, ya mau tidak mau library utama SDL akan dibutuhkan.

Sebelum melakukan build atau compile, kalian perlu mengubah pengaturan linker di menu Project=>Build Option. Berikut ini adalah "linker option" yang akan kita gunakan kali ini, untuk masing-masing OS.

  • Linux (ubuntu) => `sdl2-config --libs` -lSDL2  -lSDL2_ttf -lSDL2_net
  • Windows => -lmingw32 -lSDL2main -lSDL2 -lSDL2_net -lSDL2_ttf

Kalian seharusnya bisa melihat kalau bedanya cuma ada di awalannya saja. Kode programnya sama saja untuk tiap OS. Jika kalian cuma menggunakan SDL_NET tanpa SDL_TTF untuk membuat program lainnya, "-lSDL2" dan "-lSDL2_ttf" bisa kalian singkirkan. Kali ini kalian juga memerlukan font "FreeSans.ttf" yang mungkin kalian bisa cari sendiri atau dapatkan dari link download project komplitnya di bawah ini.

Setelah settingnya selesai, build atau compile masing-masing kode programnya supaya menjadi "program executable"! 

Jika sudah ada programnya, jalankan servernya terlebih dahulu. Selanjutnya, kalian bisa menjalankan program untuk client-nya. Kode program di atas cukup sederhana dan dibatasi hanya untuk 4 client. Kalian bisa mengubah kode programnya atau membuat sendiri program client-server yang kalian inginkan. Sebagai tambahan, berikut ini adalah function-function TCP yang bisa kalian gunakan dalam SDL_NET.

  • SDLNet_Init() : Ini diperlukan di awal program sebelum kalian bisa menggunakan function lain dalam library SDL_NET. Library bisa digunakan jika nilai yang dikembalikan function lebih besar dari atau sama dengan 0.
  • SDLNet_GetError() : Mengembalikan string yang menyimpan error terakhir dari SDL_NET.
  • SDLNet_ResolveHost(IPadress, servername, PORT) : Resolve host dan mengisi nilai IPadress. Untuk server, servername bisa bernilai null.
  • SDLNet_TCP_Open(serverIP) : Membuka koneksi TCP dengan IP adress yang ditentukan.
  • SDLNet_TCP_Close(socket) : Menutup koneksi TCP dari socket yang ada.
  • SDLNet_AllocSocketSet(n): Mengalokasikan soket sebanyak n kedalam "socket set".
  • SDLNet_TCP_AddSocket(...) : Menambahkan socket ke dalam socketset.
  • SDLNet_SocketReady(socket) : Mengecek kesiapan socket yang sudah aktif.
  • SDLNet_TCP_Send(...) : Mengirim data lewat koneksi TCP.
  • SDLNet_TCP_Receive(...) : Menerima data yang dikirim lewat koneksi TCP
  • SDLNet_Quit : Menghentikan penggunaan library SDL_NET.

Untuk lebih lengkapnya, kalian bisa lihat dokumentasinya di situs resmi SDL.

Kalau kalian malas mengetik atau bingung untuk mencari bagian tertentu dari programnya, kalian bisa mendownload programnya di sini.