Monday, September 1, 2014

Membuat Iterative Server Dengan Socket Dalam Bahasa C

Dalam konsep sistem operasi kita mengenal adanya istilah Inter Process Communication (IPC), yaitu kemampuan dari suatu sistem operasi untuk membuat process yang berjalan diatasnya mampu “berkomunikasi” dengan process yang lainnya. Tentunya yang sebenarnya dimaksud komunikasi adalah process tersebut saling melakukan pertukaran data. Untuk sistem berbasis POSIX dalam melakukan IPC ada beberapa mekanisme yang dapat digunakan diantaranya adalah File, Signal, Socket, Message Queue, Pipe, Named Pipe, Semaphore, Shared Memory, Memory-mapped File. Di artikel ini akan membahas khusus mengenai socket.
Pembuatan socket pada tutorial ini akan menggunakan protocol TCP dan IPv4. Dalam membuat aplikasi server terdapat 2 istilah yang sering ditemui yaitu iterative server dan concurrent server. Iterative server merupakan server yang hanya melayani satu client dalam satu waktu, sementara concurrent server adalah server yang dapat menangani banyak client secara parallel dalam satu waktu. Tutorial ini akan membahas pembuatan iterative server sederhana dimana client akan menerima input string dari user kemudian server akan menampilkan kembali isi string yang telah diinput oleh user.

Untuk membuat aplikasi server ada beberapa tahapan pembuatan yang harus dilakukan:
  1. Socket
  2. Bind
  3. Listen
  4. Accept
  5. Read/Write
  6. Close
Sementara untuk aplikasi client yang harus dilakukan adalah membuat:
  1. Socket
  2. Connect
  3. Read/Write
  4. Close

Pembuatan aplikasi diawali dengan membuat aplikasi servernya terlebih dahulu
  1. Socket.
int socket(int domain, int type, int protocol);
fungsi socket memiliki 3 parameter dan mengembalikan nilai integer, nilai integer inilah yang akan digunakan sebagai identifier file descriptor untuk melakukan IPC. fungsi ini juga membutuhkan tambahan library yaitu
#include <sys/types.h>
#include <sys/socket.h>
Parameter pertama kita gunakan AF_INET, ini menunjukan bahwa kita akan membuat aplikasi yang berbasis pada IPv4.
Parameter kedua kita akan menggunakan SOCK_STREAM, ini menunjukan bahwa kita akan membuat aplikasi yang menggunakan protocol TCP yaitu koneksi yang datanya ditransmisikan secara reliable atau bersifat connection oriented, koneksinya terbuat dulu baru data bisa dikirim dan diterima.
Parameter ketiga adalah kita menggunakan angka 0, kita gunakan 0 agar penerapan protocol yang digunakan dapat diserahkan pengaturannya pada sistem operasi. Contoh pemakaian:
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if(socketfd < 0)
{
perror("Error Creating Socket");
       return 1;
}
printf("Socket Created\n");

  1. Bind
    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
fungsi bind() digunakan untuk mengikat antara informasi alamat IP yang digunakan untuk tujuan pengiriman data dengan file descriptor dari socket yang telah dibuat.
Parameter pertama yang digunakan adalah integer hasil dari pembuatan fungsi socket sebelumnya.
Parameter kedua berisi informasi yang akan diikat dengan file descriptor socket, informasi tersebut dibuat dalam satu struct yaitu struct sockaddr.
Parameter ketiga berisi ukuran dari struct sockaddr yang telah dibuat. Contoh pemakaian:
struct sockaddr_in serversockaddr;
//informasi dirangkum pada struct sockaddr_in
int noport = 8888; //server akan menggunakan port 8888
memset(&serversockaddr, 0, sizeof(serversockaddr));
//sebelum di-assign value struct di set memory pada kondisi 0
serversockaddr.sin_family = AF_INET;
serversockaddr.sin_port = htons(noport);
serversockaddr.sin_addr.s_addr = htonl(INADDR_ANY); //menerima alamat IP address mana saja
if(bind(socketfd, (struct sockaddr*)&serversockaddr, sizeof(serversockaddr)) < 0)
{
      perror("Error Binding");
      return 1;
}
printf("Success Binding\n");

  1. Listen
    int listen(int sockfd, int backlog);
fungsi listen digunakan untuk membuat socket menjadi passive dan bersiap menerima connection dengan fungsi accept nantinya.
Parameter pertama yang digunakan adalah integer hasil dari pembuatan fungsi socket sebelumnya.
Parameter kedua diisi jumlah antrian yang bisa socket terima. Dalam tutorial ini yang digunakan adalah 1, karena kita hanya akan membuat iterative server. Contoh code:
if(listen(socketfd, 1) < 0)
{
 perror("Error listen");
 return 1;
}
printf("Success create listen\n");
  1. Accept
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
fungsi accept ini menerima dari antrian koneksi yang masuk pada server, membuat file descriptor baru sebagai media komunikasi yang akan digunakan pada pertukaran data nantinya dan merujuk pada socket yang telah dibuat sebelumnya. Socket yang baru dibuat ini tidak berada pada listening state, dan tidak mempengaruhi file descriptor socket sebelumnya.
Parameter pertama yang digunakan adalah integer hasil dari pembuatan fungsi socket sebelumnya.
Parameter kedua adalah adalah pointer ke struct sockaddr. Pointer ini berisi informasi mengenai peer yang terkoneksi dengan server.
Parameter ketiga berisi ukuran dari pointer sockaddr yang menampung informasi client, nilai ini harus berbentuk pointer structure dari ukuran struct addr. Dengan demikian kita sebelumnya harus membuat penampung hasilnya. Contoh:
struct sockaddr_in clientsockaddr;
int clientsockaddrsize = sizeof(clientsockaddr);
int acceptfd = accept(socketfd, (struct sockaddr*)&clientsockaddr, (socklen_t *)&clientsockaddrsize);
      if(acceptfd < 0)
      {
      perror("Error Accept");
      }
      printf("Accept from %s\n", inet_ntoa(clientsockaddr.sin_addr));
      //memunculkan IP client di server
  1. Read
    ssize_t read(int fd, void *buf, size_t count);
Setelah memiliki koneksi dengan client dan memiliki file descriptor yang digunakan sebagai medium komunikasi data oleh socket yang telah kita buat, kita bisa menggunakan fungsi read untuk mengambil isi data pada file descriptor tersebut.
Parameter pertama kita gunakan integer hasil dari fungsi accept() sebagai file descriptornya.
Parameter kedua kita gunakan variable array of char sebagai penerima data dikirim oleh client. Selain menggunakan char kita juga bisa menggunakan tipe data lain seperti integer dan struct.
    Parameter ketiga adalah ukuran dari variabel penerima di parameter kedua. Contoh:
           char message[100];
memset(message, 0, sizeof(message)); //isi variabel dikosongkan terlebih dahulu sebelum dipakai
      if(read(acceptfd, message, sizeof(message)) < 0)
      {
      perror("Error read from client");
      }   
      printf("Message from client %s\n", message);

    Sementara untuk fungsi write juga penggunaanya hampir sama dengan read
memset(message, 0, sizeof(message)); //isi variabel dikosongkan terlebih dahulu sebelum dipakai
        strcpy(message, "Thank You"); //mengisi variabel dengan string
      if(write(acceptfd, message, sizeof(message)) < 0)
      {
      perror("Error Write");
      }
  1. Close
    int close(int fd);
Fungsi close berfungsi untuk melepas lock terhadap file descriptor yang telah selesai dipakai agar bisa digunakan oleh proses lain.

Setelah membuat server saatnya kita membuat client. Untuk penjelasan pembuatan socket, read/write, dan close kurang lebih sama dengan yang pembuatan di server. Di tutorial ini akan dijelaskan mengenai connect.

  1. connect
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Fungsi connect bertugas untuk memanggil socket yang dibuat pada sockfd dengan informasi yang terdapat pada addr.
Parameter pertama menggunakan integer yang telah dibuat sebelumnya oleh fungsi socket.
Parameter kedua adalah structure yang berisi informasi server yang akan dituju. contoh:
struct sockaddr_in serversockaddr;
int noport = 8888;
memset(&serversockaddr, 0, sizeof(serversockaddr)); //dikosongkan dulu sebelum digunakan
serversockaddr.sin_family = AF_INET;
serversockaddr.sin_port = htons(noport);
if(inet_pton(AF_INET, "127.0.0.1", &serversockaddr.sin_addr) <= 0)
{
  perror("Error inet_pton()");
  return 1;
}
Untuk assign pada structure sin_addr saya menggunakan fungsi inet_pton, sedikit penjelasan mengenai inet_pton:
    int inet_pton(int af, const char *src, void *dst);
Fungsi ini bertugas untuk mengkonversi alamat IPv4 yang berbentuk string di pointer src menjadi network address structure pada pointer dst. Sementara pada parameter pertama akan digunakan AF_INET dikarenakan kita menggunakan IPv4.
Contoh penggunaan fungsi connect:
if(connect(socketfd, (struct sockaddr*)&serversockaddr, sizeof(serversockaddr)) < 0)
{
  perror("Error Connect\n");
  return 1;
}
Sisanya mengenai penggunaan read/write kurang lebih sama dengan yang ada di server.

Berikut ini code lengkap untuk server (server.c)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>


int main()
{
   //socket
   //bind
   //listen
   //accept
   int socketfd = socket(AF_INET, SOCK_STREAM, 0);
   if(socketfd < 0)
   {
      perror("Error Creating Socketn\n");
      return 1;
   }
   printf("Socket Created\n");
   
   struct sockaddr_in serversockaddr, clientsockaddr;
   int noport = 8888;
   memset(&serversockaddr, 0, sizeof(serversockaddr));
   serversockaddr.sin_family = AF_INET;
   serversockaddr.sin_port = htons(noport);
   serversockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
   if(bind(socketfd, (struct sockaddr*)&serversockaddr, sizeof(serversockaddr)) < 0)
   {
      perror("Error Binding\n");
      return 1;
   }
   printf("Success Binding\n");
   
   if(listen(socketfd, 1) < 0)
   {
      perror("Error listen\n");
      return 1;
   }
   printf("Success create listen\n");
   
   int acceptfd = 0;
   char message[100];
   while(1)
   {
      int clientsockaddrsize = sizeof(clientsockaddr);
      acceptfd = accept(socketfd, (struct sockaddr*)&clientsockaddr, (socklen_t *)&clientsockaddrsize);
      if(acceptfd < 0)
      {
          perror("Error Accept\n");
      }
      printf("Accept from %s\n", inet_ntoa(clientsockaddr.sin_addr));
     
      do
      {
          memset(message, 0, sizeof(message));
          if(read(acceptfd, message, sizeof(message)) < 0)
          {
              perror("Error read from client\n");
          }
         
          printf("Message from client %s\n", message);
         
      }while(strcmp(message, "exit")!=0);
     
      if(strcmp(message, "exit") == 0)
      {
          printf("Server exit: sending message to client\n");
          memset(message, 0, sizeof(message));
          strcpy(message, "Thank You");
          if(write(acceptfd, message, sizeof(message)) < 0)
          {
              perror("Error Write\n");
          }
          close(acceptfd);
          close(socketfd);
          break;
      }
   }
   
   return 0;
}

Dan ini untuk client (client.c)
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>


int main(){
   int socketfd = 0;
   char message[100];
   struct sockaddr_in serversockaddr;
   struct hostent *host_serv;
   
   int noport = 8888;
   host_serv = gethostbyname("127.0.0.1");
   
   socketfd = socket(AF_INET, SOCK_STREAM, 0);
   if(socketfd < 0)
   {
      perror("Error Socket\n");
      return 1;
   }
   
   memset(&serversockaddr, 0, sizeof(serversockaddr));
   serversockaddr.sin_family = AF_INET;
   serversockaddr.sin_port = htons(noport);
   //serversockaddr.sin_addr.s_addr
   if(inet_pton(AF_INET, "127.0.0.1", &serversockaddr.sin_addr) <= 0)
   {
      perror("Error inet_pton()\n");
      return 1;
   }
   
   if(connect(socketfd, (struct sockaddr*)&serversockaddr, sizeof(serversockaddr)) < 0)
   {
      perror("Error Connect\n");
      return 1;
   }
   
   char c;
   do{
      memset(message, 0, sizeof(message));
      printf("Pesan: ");
      scanf("%[^\n]", message);
      scanf("%c", &c);
      if(write(socketfd, message, sizeof(message)) < 0)
      {
          perror("error write");
      }
   }while(strcmp(message, "exit") !=0);
   
   if(read(socketfd, message, sizeof(message)) < 0)
   {
      perror("Error read\n");
   }
   printf("Pesan dari server: %s\n", message);
   close(socketfd);
   return 0;
}


Cara compile menggunakan gcc
compile-client.png 
compile-server.png

Contoh screent shot

No comments:

Post a Comment