Socket Programming in C/C++ (Unix/Linux)


  Network: Socket Programming in C/C++ (Unix/Linux)
  by Rhoenix

---[ Indeks:

    -> Pendahuluan
    -> Socket
    -> Concept
    -> Source Code
    -> Algorithm
    -> Lets Play
    -> Penutup
    -> Reference


-----[ Pendahuluan

Oke semuanya para pembaca tulisan ini, perkenalkan saya adalah seorang
mahasiswa jurusan komputer yang terdapat di salah satu perguruan tinggi
swasta di jakarta. Pada tulisan kali ini saya mau sharing apa yang sudah
saya oprek oprek selama satu bulan ini mengenai socket programming dengan
menggunakan bahasa C/C++, tentunya di sistem operasi unix. Oke langsung
saja, check this out !

-----[ Socket

What is a socket?
Sebelum dimulai kita kenalan dulu sama socket. Socket adalah penghubung
(bisa disebut juga dengan media) untuk berkomunikasi antara 2 program
atau lebih untuk bertukar data atau informasi. Dengan socket inilah,
sebuah program dapat menerima dan mengirim data dari dan kepada aplikasi
lain. Baik dalam komputer yang sama maupun berbeda di dalam suatu jaringan.

Terdapat beberapa macam socket. Diantaranya DARPA Internet addresses
(Internet Sockets), path names on a local node (Unix Sockets), CCIT X.25
addresses, dan masih banyak lagi yang lainnya. Pada tulisan ini saya
fokuskan pada Internet Sockets.

Two Types of Internet Sockets?
Internet Sockets ternyata memiliki dua tipe (yang saya tahu). Yang pertama
adalah "Stream Sockets", dan yang kedua adalah "Datagram Sockets".

What uses stream sockets?
Stream Sockets menggunakan protokol yang dinamakan "The Transmission
Control Protocol" atau biasa disingkat menjadi "TCP". TCP ini yang membuat
data sampai secara berurutan (jika kita mengirim data dengan urutan 1,2
maka data akan tiba dengan urutan yang sama yaitu 1,2) dan memiliki sifat
error free.

Telnet, pasti tahu kan? Iya, telnet ini adalah salah satu aplikasi yang
menggunakan stream sockets dalam cara kerjanya. Semua karakter yang Anda
ketik akan tiba dengan urutan yang sama bukan? Begitu juga dengan web
browsers yang menggunakan protokol HTTP untuk mendapatkan suatu halaman
(web) dengan menggunakan stream sockets.

What about datagram sockets?
Berbeda dengan stream sockets, datagram sockets ini sering disebut dengan
"connectionless". Mengapa disebut connectionless? artinya socket ini
selalu terbuka dan siap menerima informasi tanpa harus ada mekanisme
membuka koneksi, dengan sifat ini informasi bisa lebih cepat dibanding
Stream Sockets.

Jika Anda mengirim data datagram, mungkin saja tiba, atau mungkin saja
tiba di luar dari urutan pengiriman. Tapi jika data tersebut tiba, data
tersebut akan bebas dari error (error-free). Datagram Sockets tidak
menggunakan TCP, tetapi menggunakan "User Datagram Protocol" atau UDP.
Contoh aplikasi yang menggunakan UDP adalah tftp (trivial file transfer
protocol), dhcpd (DCHP client), multiplayer games, streaming audio, video
conferencing, dan lain sebagainya.

Yang jadi pertanyaannya adalah, kenapa menggunakan unreliable protocol
(UDP) tadi? Alasannya adalah kecepatan. Sebagai contoh, jika Anda membuat
program chat untuk berkirim pesan, gunakanlah TCP. Namun, jika Anda
membuat program untuk meng-update posisi entah itu dalam game yang ada di
seluruh dunia, maka UDP adalah pilihan yang terbaik.

-----[ Concept

Aplikasi sederhana yang menggunakan konsep dari socket programming ini
adalah client-server. Yang saya fokuskan adalah dengan menggunakan TCP.

TCP-based sockets

  server           client
+--------+ +--------+
| socket | | socket |
+--------+ +--------+
    |             |
+--------+ +--------+
|  bind  | | connect|
+--------+ +--------+
    |             |
+--------+     |
| listen |     |
+--------+     |
    |  \     |
    |    \     |
    |      \     |
    |        \    |
+--------+ +-------+   |
| accept | | close |   |
+--------+ +-------+   |
    |             |
+---------+ +---------+
|send/recv| |send/recv|
+---------+ +---------+
    |             |
+---------+ +---------+
| shutdown| | shutdown|
+---------+ +---------+
    |             |
+---------+ +---------+
|  close  | |  close  |
+---------+ +---------+

Server
1.socket: create the socket
2.bind: give the address of the socket on the server
3.listen: specifies the maximum number of connection
   requests that can be pending for this process
4.accept: establish the connection with a specific client
5.send,recv: stream-based equivalents of read and write (repeated)
6.shutdown: end reading or writing
7.close: release kernel data structures

Client
1.socket: create the socket
2.connect: connect to a server
3.send,recv: (repeated)
4.shutdown
5.close

Penjelasan :
Pertama, server membuat socket terlebih dahulu dimana fungsi socket ini
adalah sebagai media untuk berkomunikasi antara server dengan client.
Kemudian server akan mem-bind suatu alamat (alamat IP dan nomor port) ke
socket, langkah ini mengidentifikasikan server sehingga client tahu kemana
dia harus pergi. Setelah itu server me-listen (mendengarkan) apakah ada
request atau tidak, jika tidak ada request maka server akan menutup
koneksi. Dari sisi client kemudian mencoba untuk merequest atau
mengkoneksikan ke server. Karena terdapat request, maka server akan
langsung menangani request tersebut selanjutnya antara server dan client
akan saling berkomunikasi satu sama lain. Setelah selesai berkomunikasi
kemudian server menutup socket tersebut.


-----[ Source Code

Ini ada contoh program webserver dimana program ini akan dijalankan di
komputer server, dan sebagai client-nya gunakan browser kesayangan Anda
masing masing =)

Here is the code of webserver (webserver.c)

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<signal.h>
#include<fcntl.h>

#define CONNMAX 1000
#define BYTES 1024

char *ROOT;
int listenfd, clients[CONNMAX]; // listen on listenfd , new connection on clients
void error(char *);
void startServer(char *);
void respond(int);

int main(int argc, char* argv[])
{
    struct sockaddr_in clientaddr; // connector's address information
    socklen_t addrlen;
    char c;  
 
    //Default Values PATH = ~/ and PORT=10000
    char PORT[6];
    ROOT = getenv("PWD");
    strcpy(PORT,"10000");

    int slot=0;

    //Parsing the command line arguments
    while ((c = getopt (argc, argv, "p:r:")) != -1)
        switch (c)
        {
            case 'r':
                ROOT = malloc(strlen(optarg));
                strcpy(ROOT,optarg);
                break;
            case 'p':
                strcpy(PORT,optarg);
                break;
            case '?':
                fprintf(stderr,"Wrong arguments given!!!\n");
                exit(1);
            default:
                exit(1);
        }
 
    printf("Server started at port no. %s%s%s with root directory as %s%s%s\n","\033[92m",PORT,"\033[0m","\033[92m",ROOT,"\033[0m");
    // Setting all elements to -1: signifies there is no client connected
    int i;
    for (i=0; i<CONNMAX; i++)
        clients[i]=-1;
    startServer(PORT);

    // ACCEPT connections
    while (1)
    {
        addrlen = sizeof(clientaddr);
        clients[slot] = accept (listenfd, (struct sockaddr *) &clientaddr, &addrlen);

        if (clients[slot]<0)
            error ("accept() error");
        else
        {
            if ( fork()==0 )
            {
                respond(slot);
                exit(0);
            }
        }

        while (clients[slot]!=-1) slot = (slot+1)%CONNMAX;
    }

    return 0;
}

//start server
void startServer(char *port)
{
    struct addrinfo hints, *res, *p; // the code calls getaddrinfo() on whatever you pass on the comand line, that fills out the linked list pointed to by res

    // getaddrinfo for host
    memset (&hints, 0, sizeof(hints)); // make sure the struct is empty
    hints.ai_family = AF_INET; // use IPV4
    hints.ai_socktype = SOCK_STREAM; // TCP stream sockets
    hints.ai_flags = AI_PASSIVE; // fill in my IP for me
    if (getaddrinfo( NULL, port, &hints, &res) != 0)
    {
        perror ("getaddrinfo() error");
        exit(1);
    }

    // socket and bind
    for (p = res; p!=NULL; p=p->ai_next)
    {
        listenfd = socket (p->ai_family, p->ai_socktype, 0); // make a socket
        if (listenfd == -1) continue; // if error then continue
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) break; // bind it to the port we passed in to getaddrinfo()
    }
    if (p==NULL)
    {
        perror ("socket() or bind()");
        exit(1);
    }

    freeaddrinfo(res); // free the linked list

    // listen for incoming connections
    if ( listen (listenfd, 1000000) != 0 ) // 1000000 connections queue will hold
    {
        perror("listen() error");
        exit(1);
    }
}

//client connection
void respond(int n)
{
    char mesg[99999], *reqline[3], data_to_send[BYTES], path[99999];
    int rcvd, fd, bytes_read;

    memset( (void*)mesg, (int)'\0', 99999 );

    rcvd=recv(clients[n], mesg, 99999, 0);

    if (rcvd<0)    // receive error
        fprintf(stderr,("recv() error\n"));
    else if (rcvd==0)    // receive socket closed
        fprintf(stderr,"Client disconnected upexpectedly.\n");
    else    // message received
    {
        printf("%s", mesg);
        reqline[0] = strtok (mesg, " \t\n");
        if ( strncmp(reqline[0], "GET\0", 4)==0 )
        {
            reqline[1] = strtok (NULL, " \t");
            reqline[2] = strtok (NULL, " \t\n");
            if ( strncmp( reqline[2], "HTTP/1.0", 8)!=0 && strncmp( reqline[2], "HTTP/1.1", 8)!=0 )
            {
                write(clients[n], "HTTP/1.0 400 Bad Request\n", 25);
            }
            else
            {
                if ( strncmp(reqline[1], "/\0", 2)==0 )
                    reqline[1] = "/index.html";        //Because if no file is specified, index.html will be opened by default (like it happens in APACHE...

                strcpy(path, ROOT);
                strcpy(&path[strlen(ROOT)], reqline[1]);
                printf("file: %s\n", path);

                if ( (fd=open(path, O_RDONLY))!=-1 )    //FILE FOUND
                {
                    send(clients[n], "HTTP/1.0 200 OK\n\n", 17, 0);
                    while ( (bytes_read=read(fd, data_to_send, BYTES))>0 )
                        write (clients[n], data_to_send, bytes_read);
                }
                else    write(clients[n], "HTTP/1.0 404 Not Found\n", 23); //FILE NOT FOUND
            }
        }
    }

    //Closing SOCKET
    shutdown (clients[n], SHUT_RDWR);         //All further send and recieve operations are DISABLED...
    close(clients[n]); // parents doesn't need this
    clients[n]=-1;
}

-----[ Algorithm

Berikut penjelasan mengenai kode program.

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<signal.h>
#include<fcntl.h>

File header yaitu file yang berisi deklarasi fungsi dan definisi
konstanta. Beberapa file-judul sudah disediakan di C. File-file ini
mempunyai ciri bereksistensi .h. file-file header ini biasanya dipanggil
menggunakan fungsi include. Fungsi include sendiri merupakan salah satu
jenis pengarah preprocessor yang dipakai untuk membaca file-file header
itu sendiri.

#define CONNMAX 1000
#define BYTES 1024

Sedangkan dalam preprocessor define digunakan untuk mereplace atau
mengganti setiap keberadaan suatu kata pada source code dengan kata lain.

char *ROOT;
int listenfd, clients[CONNMAX]; //listen on listenfd,new connection on clients

Deklarasikan tipe data. Tipe data merupakan pengenal dari sebuah variabel.
Dengan menggunakan tipe data, compiler akan mengetahui jenis apakah
variabel yang dibuat atau di deklarasikan. Jadi dengan kata lain, tipe
data akan memberitahukan kepada compiler bahwa variabel yang
dideklarasikan setelah tipe data adalah termasuk golongannya/ jenisnya.
Men-set fungsi listen pada variabel listenfd, dan new connection pada
variabel clients.

void error(char *);
void startServer(char *);
void respond(int);

Prototype Fungsi. Dengan menyebutkan prototype fungsi, maka kompiler C
akan dapat memeriksa tipe-tipe data melalui parameter-parameter yang
dikirimkan dari program yang menggunakannya. Jika tipe-tipe data parameter
nyata yang dikirimkan ke fungsi tidak sesuai dengan tipe-tipe parameter
formalnya, maka kesalahan kompilasi dapat ditunjukkan oleh kompiler untuk
tipe data yang tidak cocok. Penggunaan prototype ini diletakkan sebelum
fungsi main(). Prototipe ini perlu dideklarasikan terlebih dahulu  agar
fungsi main() bisa memanggil fungsi fungsi lainnya.

int main(int argc, char* argv[])
{
.....
}

Dalam fungsi main ini menggunakan parameter. Parameter pertama bertipe
data int (argc) dan yang kedua bertipe data char (argv[]). Parameter argc
(argument for count) merupakan parameter bertipe int dan berfungsi untuk
menunjukkan banyaknya parameter yang digunakan dalam eksekusi program,
sedangkan parameter argv(argument for vector) merupakan pointer ke string
yang akan menyimpan parameter-parameter apa saja yang digunakan dalam
eksekusi program.

struct sockaddr_in clientaddr; // connector's address information
socklen_t addrlen;
char c;

Informasi penghubung alamat port.

char PORT[6];
ROOT = getenv("PWD");
strcpy(PORT,"10000");

int slot=0;

Mengatur Default Values Path = ~/ dan default port = 10000

while ((c = getopt (argc, argv, "p:r:")) != -1)
switch (c)
{
case 'r':
ROOT = malloc(strlen(optarg));
strcpy(ROOT,optarg);
                break;
case 'p':
                strcpy(PORT,optarg);
                break;
            case '?':
fprintf(stderr,"Wrong arguments given!!!\n");
                exit(1);
            default:
                exit(1);
        }

Memparsing argumen pada baris perintah. Pada kondisi ini digunakan library
getopt. Library getopt ini berfungsi untuk memparse parameter dari argumen
program sesuai dengan standar di Linux.
Terdapat 2 argumen option, yaitu
-p : untuk menentukan PORT
-r : untuk menentukan Direktori

printf("Server started at port no. %s%s%s with root directory as %s%s%s\n","\033[92m",PORT,"\033[0m","\033[92m",ROOT,"\033[0m");

Menampilkan teks ? Server started at port no. %s%s%s with root directory as %s%s%s\n?.
%s diatas menunjukkan argumen yang digunakan untuk menampilkan suatu kata
dengan tipe data string(kumpulan karakter). PORT menunjukkan port yang
dibuka, dan ROOT menunjukkan direktorinya.

int i;
for (i=0; i<CONNMAX; i++)
clients[i]=-1;
startServer(PORT);

Selanjutnya mengatur semua elemen menjadi -1, itu berarti menandakan tidak
ada client yang terhubung. Kemudian menjalankan fungsi startServer(PORT);
yang akan dijelaskan nanti di bawah.

while (1)
{
addrlen = sizeof(clientaddr);
        clients[slot] = accept (listenfd, (struct sockaddr *) &clientaddr, &addrlen);

        if (clients[slot]<0)
          error ("accept() error");
        else
        {
            if ( fork()==0 )
            {
                respond(slot);
                exit(0);
            }
        }
        while (clients[slot]!=-1) slot = (slot+1)%CONNMAX;
}

Setelah menerima koneksi jalankan perintah di atas. Jika clients kurang
dari 0 maka tampilkan tulisan ?accept() error?;

return 0;

Return 0 ini digunakan sebagai nilai kembalian, karena pada fungsi ini
(baca: main()) menggunakan tipe data integer. Lain halnya dengan tipe data
void yang tidak menghasilkan nilai kembalian.


void startServer(char *port)
{
.....
}

Ini merupakan fungsi kedua setelah fungsi main() tadi dan berada di
dalamnya.

struct addrinfo hints, *res, *p;

Memanggil perintah getaddrinfo() pada apa saja yang lewat pada baris
perintah, kemudian mengisi linked list yang ditunjuk oleh pointer res.

memset (&hints, 0, sizeof(hints));

Memastikan struct dalam keadaan empty.

hints.ai_family = AF_INET;

Baris ini menunjukkan pengalamatan dengan menggunakan IPV4. AF_INET adalah
nomor IP yang terdiri dari 4 byte yang dipisah dengan titik,
contoh: 192.168.78.100

hints.ai_socktype = SOCK_STREAM;

Menggunakan TCP sebagai protokol.

hints.ai_flags = AI_PASSIVE;

Memberitahu getaddrinfo() untuk menetapkan alamat dari localhost.

if (getaddrinfo( NULL, port, &hints, &res) != 0)
{
perror ("getaddrinfo() error");
        exit(1);
}

Getaddrinfo for host. Jika mengalami masalah atau error maka menampilkan
tulisan ?getaddrinfo() error?.

for (p = res; p!=NULL; p=p->ai_next)
{
listenfd = socket (p->ai_family, p->ai_socktype, 0); // buat socket
        if (listenfd == -1) continue;
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) break;
}

if (p==NULL)
{
perror ("socket() or bind()");
        exit(1);
}

Disinilah proses pembuatan socket dan pengalamatan (bind) dilakukan.

freeaddrinfo(res);

Membebaskan linked list.

if ( listen (listenfd, 1000000) != 0 )
{
perror("listen() error");
        exit(1);
}

Menunggu koneksi masuk dan sanggup menangani sampai 1000000 koneksi. Jika
fungsi listen bermasalah, maka tampilkan tulisan ?listen() error?.


//client connection
void respond(int n)
{
?..
}

Ini merupakan fungsi ketiga setelah fungsi main() dan
startServer(char*port) tadi dan berada di dalamnya.

char mesg[99999], *reqline[3], data_to_send[BYTES], path[99999];
int rcvd, fd, bytes_read;

memset( (void*)mesg, (int)'\0', 99999 );
rcvd=recv(clients[n], mesg, 99999, 0);

if (rcvd<0)    // receive error
fprintf(stderr,("recv() error\n"));
else if (rcvd==0)    // receive socket closed
        fprintf(stderr,"Client disconnected upexpectedly.\n");
else    // message received
{
        printf("%s", mesg);
        reqline[0] = strtok (mesg, " \t\n");
        if ( strncmp(reqline[0], "GET\0", 4)==0 )
        {
            reqline[1] = strtok (NULL, " \t");
            reqline[2] = strtok (NULL, " \t\n");
            if ( strncmp( reqline[2], "HTTP/1.0", 8)!=0 && strncmp( reqline[2], "HTTP/1.1", 8)!=0 )
            {
                write(clients[n], "HTTP/1.0 400 Bad Request\n", 25);
            }
            else
            {
                if ( strncmp(reqline[1], "/\0", 2)==0 )
                    reqline[1] = "/index.html"; //Because if no file is specified, index.html will be opened by default (like it happens in APACHE...

                strcpy(path, ROOT);
                strcpy(&path[strlen(ROOT)], reqline[1]);
                printf("file: %s\n", path);

                if ( (fd=open(path, O_RDONLY))!=-1 )    //FILE FOUND
                {
                    send(clients[n], "HTTP/1.0 200 OK\n\n", 17, 0);
                    while ( (bytes_read=read(fd, data_to_send, BYTES))>0 )
                        write (clients[n], data_to_send, bytes_read);
                }
                else    write(clients[n], "HTTP/1.0 404 Not Found\n", 23); //FILE NOT FOUND
            }
        }
}

Jika receive < 0 maka terjadi error kemudian menampilkan tulisan "recv()
error\n", kemudian jika receive = 0 kemudian menampilkan tulisan ,"Client
disconnected upexpectedly.\n". Jika tidak memenuhi keduanya maka pesan
diterima. Kemudian ketika client membuka address dari server, secara
default membuka file /index.html.


//Closing SOCKET
shutdown (clients[n], SHUT_RDWR); //All further send and recieve operations are DISABLED...
close(clients[n]); // parents doesn't need this

clients[n]=-1;

Mengakhiri panggilan dengan fungsi shutdown(). Menutup socket dan semua
operasi send dan receive dinon aktifkan. Setelah transaksi socket selesai,
tutup file deskriptor dengan fungsi close().

-----[ Lets Play

Sebelum menjalankan program webserver, kita kompilasi dulu dengan cara
root@mind:~# gcc webserver.c -o webserver

Kemudian jalankan programnya
root@mind:~# ./webserver -p [Port] -r [Directory]

Misalnya
root@mind:~# ./webserver -p 80 -r /var/www

Saat ini program server sudah dijalankan, kemudian untuk mengaksesnya
dibutuhkan suatu program client. Nah program client ini adalah web
browser. Selanjutnya tinggal jalankan saja web browser favorit Anda, untuk
alamatnya ketikkan alamat IP dari komputer server, misalnya ip server :
192.168.7.100 (untuk mengecek IP menggunakan perintah ifconfig). Berarti
alamatnya 192.168.7.100 . Kemudian enter, setelah itu web server sudah
bisa diakses. Tinggal dibuat saja file file html kemudian taruh di
direktori server anda. =)

-----[ Penutup

Untuk membuat socket programming dibutuhkan juga pengetahuan dasar
mengenai networking yang baik. Mengapa? Agar kita nantinya tidak mengalami
kesulitan dalam pembuatannya. Dan saya akui tulisan ini masih jauh dari
kata sempurna, semoga bisa menambah wawasan para pembaca. Terimakasih.

-----[ Reference

[1]. http://linux-junky.blogspot.com/2010/04/very-simple-http-server-writen-in-c.html
[2]. Beej, J., 2009, Beej's Guide to Network Programming Using Internet Socket, [pdf], (http://beej.us/guide/bgnet/)
[3]. Radhakrishnan, Mani & Jon Solworth., 2004, Socket Programming in C/C++, [pdf]
[4]. Nursyamsu, Ardi., Masuki Dunia Hacker dengan C++

Download source code: webserver.c

Comments

  1. ini kalo gak salah ezine ente ya jy?
    pernah baca ane soalnya gan :D

    ReplyDelete
  2. iya ndra. baru belajar bikin ezine hehe. wah terimakasih karena sudah membacanya =)

    ReplyDelete
  3. sangat bermanfaat gan...

    ReplyDelete
  4. gan hari ini demo gak gan ?

    ReplyDelete
  5. Terima kasih, Kang. Saya mencari tutorial socket programming khusus Linux dan sangat jarang saya temukan dalam Bahasa Indonesia. Mohon tulisan seperti ini diperbanyak karena itu sangat membantu pengguna baru. Terima kasih, Kang.

    ReplyDelete
    Replies
    1. Sama-sama, mas. Senang bisa membantu masalah Anda. Doakan saja mas supaya terlaksana.

      Delete
  6. btw.. pas di compiler kok masih ada error ya ?

    ReplyDelete
  7. kok header mingwnya error ya? gw compile lewat codeblocks. ini yang error apanya ya?

    ReplyDelete
    Replies
    1. cara kompilasinya
      1. copy kode program ke text editor di linux, bisa text editor (apa saja )
      2. simpan dengan ekstensi .c (contoh socket.c) simpan di directory yang anda suka dan harus dengan ekstensi .c karna program memakai bahasa pemrograman c.
      3. buka terminal dan masuk sebagai root.
      4. arahkan ke file program yang sudah di simpan di directory tdi (contoh perintah mengarahkan ke directory yang di simpan : [alif@localhost]# cd Downloads
      (directory Downloads adalah tempat file program yang di simpan sebelumnya).
      5. kompilasi program dengan perintah :
      [alif@localhost Downloads]# gcc socket.c -o socket (TEKAN TOMBOL ENTER)
      [alif@localhost Downloads]# ./socket (TEKAN TOMBOL ENTER)
      6. program siap kompilasi dan di jalankan melalui terminal anda :)

      Delete

Post a Comment

Popular posts from this blog

Jenis - Jenis Tanggung Jawab

Apa itu 'softskill' dan 'hardskill' ???

Keadilan dan Kejujuran