Server – Client Echo program source 분석

Server – Client Echo program source 분석

목차

  1. 프로그램의 기능과 동작 소개

  2. 원본 Source

  3. Source 분석

  4. 작동 다이어그램

  5. 오류 수정 및 개선

  1. 프로그램의 기능과 동작 소개

    LinuxNetwork Echo programtcpserv.c , tclcli.c 두개의 소스파일로 되어 있으며 Server가 되고자 하는 쪽에서는 tcpserv.c를 컴파일 하여 사용 하고 Client가 되고자 하는 쪽에서는 tclcli.c를 컴파일 하여 사용 한다.

    Server측에서 해당하는 프로그램을 실행 시키고 Client측에서 해당하는 실행 파일 뒤에 서버 주소를 적어줌으로서 서버로 접속 하고 프로그램을 정상적으로 작동 시킬 수 있다.

Example

$ ./tclcli 192.168.0.1

($ ./실행파일명 Server주소)

    Server에 접속한 Client가 원하는 문자나 문자열을 입력했을때 똑같은 데이터가 되돌아오면(화면상으로는 같은 문자열이 2번 찍힌것으로 보인다.) 프로그램이 정상적으로 작동 한 것이다.

    아래는 정상적으로 컴파일 한후 작동시키는 모습이다.

사용자 삽입 이미지서버측 tcpserv.c 의 컴파일과 실행

사용자 삽입 이미지
클라이언트측 tclcli.c 의 컴파일과 실행

  1. 원본 Source

    tcpserv.c

    tclcli.c

  1. 작동 다이어그램

사용자 삽입 이미지

 



 

  1. Source 분석

    tcpserv.c tclcli.c의 소스파일을 분석한다. 중복되는 헤더파일이 많으므로 헤더파일들을 먼저 설명한뒤 소스를 설명 하겠다. 원본 소스파일은 문서의 가장 마지막에 첨부 하며 소스 분석은프로그램이 작동하는 순서대로 설명 하겠다.

    • Header

      • stdio.h – 표준 입출력 라이브러리 (Standard input/output)

      • stdlib.h – 표준 라이브러리 (Standard library)

      • string.h – 문자열 처리 함수

      • unistd.h – 유닉스 시스템 표준 라이브러리 (UNIX Standard library)

      • sys/socket.h – 소켓 시스템 함수

      • sys/types.h – 소켓 시스템에 필요한 상수 선언

      • arpa/inet.h – 인터넷 주소 조작 함수

      • netinet/in.h – 인터넷 주소 조작 함수

    • tcpserv.c

      • 메인 함수 – int main(int argc, char **argv)

        int listenfd, connfd;

        /*listenfdconnfd 라는 정수형 변수 (디스크립터로 사용됨) 선언 */

        pid_t childpid;

        /*pid_t 라는 타입의 자식 프로세서의 프로세서 ID를 담는 변수 childpid 선언*/

        • 데이터 타입 : pid_t

          pid_t 데이터 타입은 프로세스 Id를 표현하는 부호화 정수 타입이다. 이를 사용하기 위해서는 ‘unistd.h’와 ‘sys/types.h’헤더를 포함 해야 한다.

        socklen_t clilen;

        /*연결정보의 길이를 담을 변수 clilen 선언*/

        struct sockaddr_in cliaddr, servaddr;

        /*클라이언트와 서버의 주소를 담을 구조체 */

        • struct sockaddr_in

          소켓에 연결할 호스트의 주소와 관계된 정보를 저장하는 구조체

          • sockaddr_in의 구조

          struct sockaddr_in{

          short sin_family; // 프로토콜의 주소 체계(거의 대부분 AF_INET)

          unsigned short sin_port; //호스트의 IP 포트

          struct in_addr sin_addr; //호스트의 IP 주소

          char sin_zero[8]; //사용되지 않음

          }; */

        listenfd = socket (AF_INET, SOCK_STREAM, 0);

        /*통신을 위한 소켓 생성*/

        • socket( )

          socket 함수는 소켓을 만들고 소켓 기술자 (descriptor)를 어플리케이션에 반환 하는 함수 입니다.

          socket (

              int af, //어드레스 체계를 결정

              int type, //소켓의 타입을 결정

              int protocol //소켓과 함께 어느 프로토콜이 사용될 것인지 지정

              );

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

        /*구조체 servaddr의 초기화*/

        servaddr.sin_family = AF_INET;

        /*서버의 주소 체계는 AF_INET*/

        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

        /* 서버의 주소 (실행하는 서버의 주소) */

        • htonl

          32비트 호스트 바이트 오더 → TCP/IP에서 사용되는 네트웍 바이트 오더로 변환

        • INADDR_ANY

          서버가 여러개의 IP주소를 가지고 있을때 그중 어떤 주소라도 허용 한다.

        servaddr.sin_port = htons(SERV_PORT);

        /*port 번호 지정 SERV_PORT는 앞부분에서 #define SERV_PORT 9877 */

        bind(listenfd, (struct sockaddr_in *) &servaddr, sizeof (servaddr));

        /* 소켓을 IPPort로 바인드 시키고 서버의 정보와 연결 한다, 이 과정 후에 servaddr은 통신 가능한 소켓이 된다.*/

        listen(listenfd, 10);

        /*생성된 소켓으로 부터 외부의 접속을 감지하고 최대 큐의 길이는 10*/

        • listen( )

          listen 함수는 외부로 부터 들어오는 접속을 감지하기 위해 특정 소켓이 사용될 것임을 알린다.

          int listen(

                SOCKET s,

                int backlog

                );

        for( : : ) { /*무한 루프 시작*/

          clilen = sizeof(cliaddr);

          /*clilen에 길이 정보를 줌*/

          connfd = accept(listenfd, (struct sockaddr_in *) &cliaddr, &clilen);

          /*소켓으로 들어오는 접속 요청을 허용*/

        • accept( )

          accept 함수는 소켓으로 들어오는 접속 요청을 허용하는 함수이다.

          SOCKET accept(

              SOCKET s, //listen( )에 의해 접속 대기하고 있는 소켓의 디스크립터

              struct sockaddr FAR* addr, //클라이언트의 주소

              int FAR* addrlen //addr 매개변수에 의해 포인트된 버퍼의 길이

              );

        if ( (childpid = fork() ) == 0) {

        /* 자식 프로세서를 만들고 그것으로 작동한다. */

        • fork( )

          fork( )는 프로세서를 이름만 다른 하나의 프로세서로 복사 시킨다.

          흔히 처음의 프로세스를 부모 프로세서라하고 복사된 프로세서를 자식 프로세서라 한다. 이 프로그램은 이방법으로 다중 접속을 처리 할수 있다.

        close(listenfd);

        /* listenfd 디스크립터를 닫는다. */

        str_echo(connfd);

        /*str_echo함수를 호출한다.*/

        exit(0);

        /*프로그램을 종료한다.*/

        } /*if문 종료*/

        close(connfd);

        /*connfd 디스크립터를 닫는다.*/

        } /*for문 종료*/

      • 에코 함수 – str_echo( int sockfd )

        ssize_t n;

        /* signed int 타입 n 선언 */

        char buf[MAXLINE];

        /* 버퍼 배열 생성 ( #define MAXLINE 2000 ) */

        again: /* goto */

        while((n=read(sockfd, buf, MAXLINE)) > 0)

        /*소켓으로 들어온 내용을 버퍼에 저장하고 그 길이를 n에 넣는다 그리고 그 값이 0보다 클 동안*/

        write(sockfd, buf, n);

        /*버퍼의 내용을 소켓에 쓴다(전송)*/

        if ( n < 0 && errno == EINTR)

          goto again:

        /*read-1을 반환하고 (0보다 작음), errnoEINTR일때(인터럽트 에러) goto again으로 돌아간다.*/

        else if (n < 0) {

          printf(“str_echo : read error”);

          exit(0); }

        /*read-1을 반환 했을때 (Error 상황) 오류 메시지를 출력하고 프로그램을 종료*/

    • tclcli.c

      (tcpserv와 중복되는 함수가 많고 이 부분은 간략히 다룬다)

      • 메인 함수 – int main(int argc, char **argv)

        int sockfd;

        /* 디스크립터 사용될 변수 선언 */

        struct sockaddr_in servaddr;

        /* 서버의 주소를 담을 구조체 */

        if (argc != 2)

          printf(“usage: tcpcli <IP address>”);

        /* 실행 방법이 틀렸을 경우 안내 메시지 출력 ( 이 부분은 잘못된 부분이고 뒤에서 수정 하도록 하겠다.)*/

        sockfd = socket(AF_INET, SOCK_STREAM, 0); /*소켓 생성*/

        memset(&servaddr, 0, sizeof(servaddr)); /*servaddr 초기화*/

        servaddr.sin_family = AF_INET; /*주소 체계는 AP_INET*/

        servaddr.sin_port = htons(SERV_PORT); /*port (define 9877) */

        inet_pton(AF_INET, argv[1], &servaddr.sin_addr); /*주소는 입력한 값*/

        connect(sockfd, (struct sockaddr_in*) &servaddr, sizeof(servaddr));

        /* 입력한 정보로 서버에 접속하고 통신 가능한 소켓이 된다. */

        str_cli(stdin, sockfd);

        /* str_cli 함수 호출 (입력과 디스크립터를 넘긴다)*/

        • stdin

          표준 입출력 장치의 파일 포인터로 기본적으로 키보드입력으로 되어 있다.

      • 문자열 보내기, 받기 함수 – str_cli(FILE *fp, int sockfd)

        char sendline[MAXLINE] , recvline[MAXLINE];

        /* 문자열을 보내고 받기 위한 버퍼 배열 생성*/

        while(fgets(sendline, MAXLINE, fp) != NULL) {

        /*입력한 값을 최대 MAXLINE이하로 보내기 버퍼 sendline[]에 넣고 입력한 값이 없을때 까지 반복한다.*/

        write(sockfd, sendline, strlen(sendline));

        /* 소켓에 보내기 버퍼의 내용을 쓴다(전송한다) */

        if (read(sockfd, recvline, MAXLINE) ==0){

        /* 소켓으로 부터 들어온 데이터를 받기 버퍼에 MAXLINE이하로 저장하고 0을 리턴하면 (아무것도 없을때 – 서버에서 아무 응답이 없다) if문 안의 명령동작*/

        printf(“str_cli: server terminated permaturelyn”);

        exit(0); }

        /* 서버와 연결이 끈어졌다는 메시지와 함께 프로그램 종료 (여기까지 서버에 응답이 없을때의 동작) */

        fputs(recvline, stdout);

        /*버퍼의 내용을 화면으로 출력 한다.*/

        } } /*while문 종료, str_cli함수 종료*/

  1. 오류 수정 및 개선

    • 클라이언트 프로그램 실행시 실행 명령 형식이 틀린 경우 세그먼트 오류가 발생

      → 오류를 검출한 다음 프로그램을 종료 시키지 않는데서 문제가 발생한다.

      if(argc != 2)

            printf(“usage: tcpcli <IP address>”);

      이 부분이 잘못 되었다. argc != 2를 체크하고 잘못 되었을 경우 안내메시지를 출력하는데 종료하는 부분이 빠져서 잘못된 정보로 명령이 계속 되면서 오류가 발생한다. 다음과 같이 if문을 수정 함으로써 해결 할수 있다.

      if(argc !=2){

            printf(“usage: <Program Name> <IP address>n”);

            exit(0);

            }

    • 긴문장을 입력한뒤 짧은 문장을 입력하면 긴문장의 뒷부분이 같이 출력 되는 현상

      → 클라이언트측의 버퍼( recvline[MAXLINE] )을 클리어하지 않는데서 문제가 발생한다. 데이터를 전송한뒤에는 버퍼를 비워 주어야 하는데 클리어하는 과정이 빠져 있다.

      fputs(recvline, stdout); 바로 다음줄에 다음을 추가 함 으로써 해결 할 수 있다.

      memset(&recvline ,0,sizeof(recvline)); /* 보내기버퍼를 클리어 한다 */

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes:

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url=""> 

This site uses Akismet to reduce spam. Learn how your comment data is processed.