แนะแนว Chat Project Part 1: เริ่มต้นสร้างโปรแกรม Echo Client Server

จากโจทย์ CS348 Project ที่ http://vasabilab.cs.tu.ac.th/files/CS348_Project.pdf

แนวทางการทำ Project ขอให้ นศ พยายามมองปัญหาจากจุดที่ง่ายที่สุดหรือคุ้นเคยที่สุดและค่อยๆขยายไปทีละขั้น เนื่องจากโจทย์และเนือหาวิชานี้เป็นโจทย์ระดับบัณฑิตศึกษาดังนั้นผมจึงคาดหวังให้ นศ ทำเท่าที่จะทำได้และค่อยๆเรียนรู้ไปในขณะเดียวกัน ไม่จำเป็นต้องทำให้เสร็จหมดทุกโจทย์ ถ้าทำได้ก็ดีมากแต่ถ้าติดปัญหาก็ไม่เป็นไรขอให้พยายามหาปัญหาและตั้งคำถามที่บรรยายปัญหาเหล่านั้น ถ้ามีเวลาก็ส่งคำถามเหล่านั้นมาถามผมหรือเข้ามาถามก็ได้ หรือไม่เช่นนั้นก็ขอให้บรรยายปัญหาและเขียนแนวคิดในการออกแบบโปรแกรมในรายงาน

ขั้นตอนแรกสุดคือการสร้าง Echo Client และ Server ก่อนและหลังจากนั้นให้ขยายเป็นโปรแกรม Chat Client และ Server

1. Echo Client ไปสู่ Chat Client
จากตัวอย่างที่ให้ไว้ใน socket-code.tar.gz นศ อาจใช้โปรแกรม echo-client-select.c ข้างล่างนี้เป็น Echo Client

...
#define SERV_IP         "127.0.0.1"
#define SERV_PORT       18800
#define MAXLINE 100
int client_shutdown_flag = 0;
int conn_fd;
struct sockaddr_in serv_addr;

main(int argc, char *argv[]){
        int n, m;
        fd_set base_rfds, rfds;
        int fdmax = 0;
        char line[MAXLINE];
        conn_fd = socket(AF_INET, SOCK_STREAM, 0);
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        serv_addr.sin_addr.s_addr = inet_addr(SERV_IP);

        if (connect(conn_fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))<0) {
            perror("Problem in connecting to the server");
            exit(3);
        }
        FD_ZERO(&base_rfds);
        FD_ZERO(&rfds);
        FD_SET(fileno(stdin), &base_rfds);
        FD_SET(conn_fd, &base_rfds);

        fdmax = conn_fd;
        while(1){
          memcpy(&rfds, &base_rfds, sizeof(fd_set)); // copy it
          if (select(fdmax+1, &rfds, NULL, NULL, NULL) == -1) {
            perror("select");
            exit(4);
          }

          if(FD_ISSET(fileno(stdin), &rfds)){
            if(fgets(line, MAXLINE, stdin) == NULL){
              printf("Shutdown writing to TCP connection\n");
              shutdown(conn_fd, SHUT_WR);
              client_shutdown_flag = 1;
            }
            else{
              n = write(conn_fd, line, MAXLINE);
              printf("send %s with n = %d characters\n", line, n);
            }
          }

          if(FD_ISSET(conn_fd, &rfds)){
            if(read(conn_fd, line, MAXLINE) == NULL){
              if(client_shutdown_flag){
                printf("TCP connection closed after client shutdown\n");
                close(conn_fd);
                break;
              }
              else{
                printf("Error: TCP connection closed unexpectedly\n");
                exit(1);
              }
            }
            else{
              printf("receive %s with m = %d characters\n", line, m);
              fputs(line, stdout);
            }
          }
        }
}
...

แนวทางการเปลี่ยนแปลง
นศ ใช้โปรแกรมนี้เป็น Chat Client โปรแกรมไปเลยในตอนนี้ เริ่มต้อให้ส่งข้อมูลและรับข้อมูลกลับจาก server ข้างล่างอย่างถูกต้อง แต่ Chat Client ต้องมีหลาย Client ดังนั้น นศ ต้องเปิด terminal หลาย terminal เพื่อรันโปรแกรม Chat Client หลายๆโปรแกรม

2. Echo Server ไปสู่ Chat Server
นศ อาจใช้โปรแกรม echo-server-select.c ข้างล่างเป็นตัวอย่างของโปรแกรม Echo server

...
#define SERV_IP         "127.0.0.1"
#define SERV_PORT       18800
#define MAXLINE 100
#include 
int lis_fd;
int conn_fd;
struct sockaddr_in serv_addr;

main(int argc, char *argv[]){
        int m, n, i;
        char line[MAXLINE];
        fd_set base_rfds; // base read fd set
        fd_set rfds; // read fd set to be passed as a parameter of select()
        int fdmax;

        lis_fd = socket(AF_INET, SOCK_STREAM, 0);
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_port = htons(SERV_PORT);
        serv_addr.sin_addr.s_addr = INADDR_ANY;
        bind(lis_fd, (struct sockaddr *) &serv_addr, sizeof(serv_addr));
        listen(lis_fd, 5);
        FD_ZERO(&base_rfds);
        FD_ZERO(&rfds);
        FD_SET(lis_fd, &base_rfds);
        fdmax = lis_fd;

        for(;;){
          memcpy(&rfds, &base_rfds, sizeof(fd_set));
          if(select(fdmax+1, &rfds, NULL, NULL, NULL) < 0){
            printf("select error!\n");
            fflush(stdout);
            exit(1);
          }
          for(i = 0; i <= fdmax; i++){
            if(FD_ISSET(i, &rfds)){
                if(i == lis_fd){
                  if((conn_fd = accept(lis_fd, NULL, NULL)) < 0){
                    printf("Accept: Error occured\n");
                    exit(1);
                  }
                  printf("a new connection %d is made!\n", conn_fd);
                  FD_SET(conn_fd, &base_rfds);
                  if(conn_fd > fdmax){
                    fdmax = conn_fd;
                  }
                }
                else{
                  n = read(i, line, MAXLINE);
                  if (n <= 0){
                    if(n == 0){
                        printf("read: close connection %d\n", i);
                        FD_CLR(i, &base_rfds);
                        close(i);
                    }
                    else{
                        printf("read: Error occured\n");
                        exit(1);
                    }
                  }
                  else{
                    printf("line = %s with n = %d characters\n", line, n);
                    fflush(stdout);
                    m = write(i, line, n);
                    printf("write line = %s for m = %d characters\n", line, m);
                    fflush(stdout);
                  }
                }
            }
          }
        }
        close(lis_fd);
}
...

แนวทางการเปลี่ยนแปลง

  • ถ้าจะทำให้โปรแกรมนี้เป็น Chat server อย่างแรก นศ ต้องสร้าง data structure สมมุติว่าชื่อ client[] เป็น array ของ integer เพื่อเก็บค่าของ conn_fd socket descriptor ที่ accept() function return ค่าออกมาสำหรับ Chat Client แต่ละตัว และต้อง modify code เพื่อเก็บค่า conn_fd เหล่านั้นใน array client[] โดยให้เก็บ conn_fd ของ Chat Client แรกใน client[0] และอันถัดไปใน client[1], client[2],... client[i]
  • หลังจากนั้นที่ code ส่วนของ read() system call ที่อ่านค่าเข้ามาจาก Chat client เมื่ออ่านค่าเข้ามาแล้วให้ นศ วนลูปเขียนค่าที่ได้รับเข้ามาไปให้กับ Chat Client ทุกโปรแกรม โดยใช้คำสั่ง write() เขียนค่าออกไปยัง socket descriptor ของทุก Client ที่เก็บอยู่ใน array client[i]

เมื่อถึงจุดนี้ นศ ก็จะได้โปรแกรม Chat client และ Chat server ที่จัดการ Client ได้หลายๆ Client

หาก นศ ไม่เข้าใจบรรทัดไหนใน code ขอให้ถามผมได้ทาง email หรือมาพบที่ห้อง lab

Leave a Reply