240228_echo server

추성결·2024년 2월 28일
0
post-thumbnail

에코 서버란?

Client에서 요청한 송신한 데이터를 Server에서 수신하여 그대로 되돌려주는 기능을 하는 서버를 말한다.

소켓 프로그래밍 흐름

echo client, server를 구현하기 전, 소켓 프로그래밍의 흐름을 보면 함수의 이해가 좀 더 쉽다.

지금 구현하려는 TCP/IP 통신 기반으로 데이터 송수신을 하려면, IP와 Port가 반드시 소켓에 할당되어야한다.

먼저, getaddrinfo()함수를 호출하여 IP 및 Port의 정보를 받는다.

그 후, socket()함수를 호출해 소켓을 생성하고, 서버에서는 bind()함수를 호출해 할당하고, 클라이언트에서는 connect()함수를 호출해 할당한다.

클라이언트에서는 open_clientfd()함수를 호출하여 위 과정을 진행하고, 서버에서는 위 과정과 데이터를 수신받을 준비 상태로 만들 listen()함수 호출을 포함한 open_listenfd()함수를 호출하여 송수신할 준비를 한다.

그 후 서버에서 클라이언트에서 연결 요청을 Accept()함수를 호출하여 클라이언트와 연결하고, 데이터 송수신을 진행한다.

Client

#include "csapp.h"

int main(int argc, char **argv)
{
    int clientfd;
    char *host, *port, buf[MAXLINE];
    rio_t rio;

    if(argc != 3)
    {
        fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
        exit(0);
    }

    host = argv[1];
    port = argv[2];

    clientfd = open_clientfd(host, port);          // Getaddrinfo, socket, connect를 시켜준다. 호스트와 포트값을 설정한다.
    Rio_readinitb(&rio, clientfd);

    // 입력이 없을 때까지 Rio 패키지를 통해 입력된 문자열을 버퍼에 담아 요청한다.
    while(Fgets(buf, MAXLINE, stdin) != NULL)
    {
        Rio_writen(clientfd, buf, strlen(buf));
        Rio_readlineb(&rio, buf, MAXLINE);
        Fputs(buf, stdout);
    }

    Close(clientfd);
    exit(0);
}

리눅스에서는 특정 데이터(소켓, 파이프 등)을 파일로 인식하며 정수의 값으로 리턴하는데 이를 파일 디스크립터라 한다.

즉 clienfd에 클라이언트의 소켓 식별자 파일 디스크립터가 선언되고, Rio 패키지를 통해 데이터를 송신한다.

Server

#include "csapp.h"

void echo(int connfd)
{
    size_t n;
    char buf[MAXLINE];
    rio_t rio;

    Rio_readinitb(&rio, connfd);
    while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0)
    {
        printf("server received %d bytes\n", (int)n);
        Rio_writen(connfd, buf, n);
    }
}

int main(int argc, char **argv)
{
    int listenfd, connfd;
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;
    char client_hostname[MAXLINE], client_port[MAXLINE];

    if(argc != 2)
    {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(0);
    }

    listenfd = Open_listenfd(argv[1]);                      // Getaddrinfo, socket, bind를 하여 대기 상태로 만들어준다.
    // accept를 시켜 다른 소켓을 하나 복제하여 소통하며, echo를 실행한다.
    while(1)
    {
        clientlen = sizeof(struct sockaddr_storage);
        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
        Getnameinfo((SA *)&clientaddr, clientlen, client_hostname, MAXLINE, client_port, MAXLINE, 0);
        printf("Connected to (%s, %s)\n", client_hostname, client_port);
        echo(connfd);
        Close(connfd);
    }
    exit(0);
}

마찬가지로, listenfd에 소켓 식별자가 할당되고, 반복문 내 연결 요청이 온 client와 연결한 후, 수신한 데이터를 읽고 다시 클라이언트쪽에 송신한다.

0개의 댓글