컴퓨터 사이언스/Network

echo 클라이언트와 서버

kimjingyu 2023. 11. 19. 13:39
728x90

RIO(Robust I/O)

RIO 버퍼 없는 입력 및 출력 함수

메모리와 파일 간에 직접 데이터를 전송할 수 있게 한다.

 

rio_readn

현재 파일 식별자 fd에서 n만큼의 데이터를 버퍼 usrbuf로 보낸다.

#include "csapp.h"

ssize_t rio_readn(int fd, void* usrbuf, size_t n)
  • int fd : 내가 보낼 바이트가 저장된 현재 파일의 위치
  • void *usrbuf : 파일에서 전송할 대상 메모리 버퍼의 위치
  • size_t n : 전송할 바이트의 수
  • 리턴 값 : 성공하면 전송한 바이트의 수를 반환하고, EOF를 읽기 중에 만나면 0, 에러가 나면 -1을 반환한다.

 

rio_writen

현재 메모리의 버퍼 usrbuf에서 n만큼의 데이터를 파일 식별자 fd로 보낸다.

#include "csapp.h"

ssize_t rio_writen(int fd, void* usrbuf, size_t n);
  • int fd : 내가 n 바이트를 쓰고 싶은 현재 파일의 위치
  • void *usrbuf : n바이트가 있는 메모리 버퍼의 위치
  • size_t : 전송할 바이트의 수
  • 리턴 값 : 성공하면 전송한 바이트의 수를 반환하고, 에러가 나면 -1을 반환한다.

 

RIO 버퍼를 통한 입력 함수

텍스트 라인 전체를 내부 읽기 버퍼에서 복사하는 rio_readlineb와 텍스트 라인과 바이너리 데이터 모두를 읽을 수 있는 rio_readnb가 있다.

 

rio_t 구조체

#define RIO_BUFSIZE 8192
typedef struct {
    int rio_fd;                /* Descriptor for this internal buf */
    int rio_cnt;               /* Unread bytes in internal buf */
    char *rio_bufptr;          /* Next unread byte in internal buf */
    char rio_buf[RIO_BUFSIZE]; /* Internal buffer */
} rio_t;

 

rio_readinitb

읽고 싶은 파일 식별자 fd와 읽기 버퍼 rp를 연결한다.

#include "csapp.h"

void rio_readinitb(rio_t *rp, int fd) 
{
    rp->rio_fd = fd;  
    rp->rio_cnt = 0;  // unread size 라고는 하는데 사용하는 걸 보면 read size 라고 봐야 된다
    rp->rio_bufptr = rp->rio_buf; // 내부 버퍼 포인터
}

 

rio_readlineb

텍스트 라인 전체를 내부 읽기 버퍼 rp에서 읽은 후, 메모리 버퍼 usrbuf으로 복사하고, \\0(NULL)로 텍스트 라인을 종료시킨다.

최대 maxlen-1 바이트 만큼 읽고 마지막 1바이트는 \\0을 넣어준다.

ssize_t rio_readlineb(rio_t* rp, void* usrbuf, size_t maxlen)
{
    int n, rc;
    char c, *bufp = usrbuf;

    for (n = 1; n < maxlen; n++) { 
        if ((rc = rio_read(rp, &c, 1)) == 1) {
	    *bufp++ = c;
	    if (c == '\n') {
                n++;
     		break;
            }
	} else if (rc == 0) {
	    if (n == 1)
		return 0; /* EOF, no data read */
	    else
		break;    /* EOF, some data was read */
	} else
	    return -1;	  /* Error */
    }
    *bufp = 0;
    return n-1;
}

 

rio_readnb

텍스트 라인과 바이너리 데이터 모두를 읽을 수 있다. 이때, n 바이트씩 가져온다.

ssize_t rio_readnb(rio_t* rp, void* usrbuf, size_t n)
{
    size_t nleft = n;
    ssize_t nread;
    char *bufp = usrbuf;
    
    while (nleft > 0) {
	if ((nread = rio_read(rp, bufp, nleft)) < 0) 
            return -1;          /* errno set by read() */ 
	else if (nread == 0)
	    break;              /* EOF */
	nleft -= nread;
	bufp += nread;
    }
    return (n - nleft);         /* return >= 0 */
}

 

 

echoclient.c

서버와의 연결 후 클라이언트와 통신하는 역할이다.

#include "csapp.h"

/* 0번째 인자로 실행 파일, 1번째로 호스트네임, 2번째로 포트 넘버를 받는다.*/
int main(int argc, char **argv)
{
    int clientfd;
    char *host, *port, buf[MAXLINE];
    rio_t rio;

    host = argv[1];
    port = argv[2];
    clientfd = Open_clientfd(host, port);  // 서버와의 연결 성공(connect까지)
    
     /* 1. 클라이언트 소켓 파일 식별자와 읽기 버퍼 rio를 연결한다.*/
    Rio_readinitb(&rio, clientfd);
    
    /* 표준 입력에서 텍스트 줄을 반복적으로 읽는다. */
    /* 2. 표준 입력sdtin에서 MAXLINE만큼 바이트를 가져와 buf에 저장한다. */
    while (Fgets(buf, MAXLINE, stdin) != NULL) {  // 6. EOF 표준 입력을 만나면 종료한다.
        // 3. buf 메모리 안의 strlen(buf) 바이트 만큼의(사실상 모두)를 clientfd로 보낸다.
        Rio_writen(clientfd, buf, strlen(buf));  
        // 4. 서버가 rio에 echo줄을 쓰면 그 rio를 읽어서 읽기 버퍼 buf에 쓴다.
        Rio_readlineb(&rio, buf, MAXLINE);
        // 5. buf에 받아온 값을 표준 출력으로 인쇄한다.
        Fputs(buf, stdout);   
    }
    
    Close(clientfd); // 루프가 종료되면 클라이언트 식별자를 닫는다. 서버에 EOF 통지가 전송된다.
    exit(0); // 클라이언트가 종료된다.
}

 

echoserver.c

서버가 클라이언트의 연결 요청을 받아 연결 소켓 식별자를 만든다. 그리고 echo 함수를 호출한다.

#include "csapp.h"

void echo(int connfd);

/* 서버의 포트 번호를 1번째 인자로 받는다. */
int main(int argc, char **argv)
{
    int listenfd, connfd;
    socklen_t clientlen;
    /* Accept로 보내지는 client 소켓 구조체. */
    struct sockaddr_storage clientaddr; /* sockaddr_storage 구조체: 모든 프로토콜의 주소에 대해 Enough room for any addr */                                                                                                               
    char client_hostname[MAXLINE], client_port[MAXLINE];

    // 인자 2개 다 받아야 함.
    if (argc != 2){
        fprintf(stderr, "usage: %s <port> \n", argv[0]);
        exit(0);
    }


    /* 해당 포트 번호에 적합한 듣기 식별자를 만들어 준다. */
    listenfd = Open_listenfd(argv[1]);
    
    while (1) {
    /* 클라이언트의 연결 요청을 계속 받아서 연결 식별자를 만든다. */
    clientlen = sizeof(struct sockaddr_storage); /* Important! 길이가 충분히 커서 프로토콜-독립적!*/
    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);
}

 

echo.c

클라이언트와 연결된 읽기 버퍼에 서버 연결 소켓도 연결해준다. 클라이언트가 보낸 데이터를 그대로 읽기 버퍼에 다시 써 준다.

#include "csapp.h"

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

    /* 읽기 버퍼 rio와 서버의 연결 소켓 식별자를 연결해준다. clientfd도 연결되어 있다. */
    Rio_readinitb(&rio, connfd);

    /* 읽기 버퍼 rio에서 클라이언트가 보낸 데이터를 읽고, rio에 그 데이터를 고대로 쓴다.*/
    /* 읽기 버퍼 rio에서 MAXLINE만큼의 데이터를 읽어 와 buf에 넣는다. */
    while((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) { // 0이면 클라이언트 식별자가 닫혔다는 소리다.
        printf("server received %d bytes\n", (int)n);

        /* buf 안에는 클라이언트가 보낸 데이터 그대로 있다. */
        /* buf 메모리 안의 클라이언트가 보낸 바이트 만큼의(사실상 모두)를 clientfd로 보낸다. */
    	Rio_writen(connfd, buf, n);
    }

    /* 클라이언트 식별자가 닫히면 루프 종료 및 함수도 종료. */
}

 

Makefile

CC = gcc
CFLAGS = -O2 -Wall -I .

# This flag includes the Pthreads library on a Linux box.
# Others systems will probably require something different.
LIB = -lpthread

all: echoclient echoserver

echoclient: echoclient.c csapp.o
	$(CC) $(CFLAGS) -o echoclient echoclient.c csapp.o $(LIB)

echoserver: echoserver.c csapp.o echo.o
	$(CC) $(CFLAGS) -o echoserver echoserver.c csapp.o echo.o $(LIB)

csapp.o: csapp.c
	$(CC) $(CFLAGS) -c csapp.c

echo.o: echo.c
	$(CC) $(CFLAGS) -c echo.c
728x90