본문 바로가기
KraftonJungle2기/Today I Learned

[TIL] CSAPP 11장 공부2 - 예제 echo 서버

by SooooooooS 2023. 5. 20.
728x90

1. 상위 수준의 도움 함수

1. open_clientfd()

클라이언트는 이 함수를 호출하여 서버와 연결을 설정한다.

2. open_listenfd()

서버는 이 함수를 호출해서 연결 요청을 받을 준비가 된 듣기 식별자 생성

▶ setsockopt() : 소켓 세부 사항 설정

int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen);
  • sock : 소켓 파일 디스크립터
  • level : 프로토콜 레벨 전달
    • SOL_SOCKET : 일반
    • IPPROTO_IP : IP - IPv4
    • IPPROTO_TCP : TCP
  • optname : 변경할 옵션의 이름
    • SO_REUSEADDR : 이미 사용중인 주소나 포트에 대해서도 바인드 허용
  • optval : 확인 결과의 저장을 위한 버퍼 주소 값 전달
  • optlen : optval 로 전달된 주소 값의 버퍼 크기
  • https://jhnyang.tistory.com/entry/네트워크소켓-프로그래밍-setsockopt

▶ open_listenfd()


2. echo 서버 만들기

1. 에코 서버란?

클라이언트가 전송해주는 데이터를 그대로 되돌려 전송해 주는 기능을 가진 서버
몇 바이트 송수신할 것인지 예상할 수 있다 → 전송한만큼 바이트를 되돌려 받는다.

< 참고 > https://developer-jun.tistory.com/17

2. client

클라이언트 역할을 할 서버 파일
인자로 호스트 주소포트 번호가 주어져야 한다.
#include "../csapp.h"

int main(int argc, char **argv) {
    int clientfd; //client file descriptor
    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); //지정된 호스트와 포트로 서버에 연결하는 클라이언트 소켓 생성
    Rio_readinitb(&rio, clientfd); //rio 구조체 초기화 -> 읽기 작업을 위한 버퍼 및 상태정보 저장

    while (Fgets(buf, MAXLINE, stdin) != NULL) { //EOF를 만나면 종료
        Rio_writen(clientfd, buf, strlen(buf)); //입력받은 문자열 서버로 전송
        Rio_readlineb(&rio, buf, MAXLINE); //rio 구조체를 통해 서버로부터 응답을 읽어들이다.
        Fputs(buf, stdout); //출력
    }

    Close(clientfd);
    exit(0);
}

3. server

서버 역할을 할 서버 파일
인자로 포트 번호가 주어져야 한다.
#include "../csapp.h"

void echo(int connfd);

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]); //듣기 식별자 생성
    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);
}

4. echo

클라이언트에서 서버로 들어온 값을 그대로 클라이언트에 보내준다.
#include "../csapp.h"

//connfd : 클라이언트와의 연결을 나타내는 연결 소켓
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);
    }
}

※ 현재 폴더 구조 ※


5. server Makefile

서버를 실행하기 위한 Makefile
주의할점❗️ csapp 파일들은 같은 폴더에 존재하지 않으므로 상대 경로로 표시해주어야 한다.
CC = gcc
CFLAGS = -g -Wall
LDFLAGS = -lpthread

all: client

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

client: echoclient.o ../csapp.o
	$(CC) $(CFLAGS) echoclient.o ../csapp.o -o client $(LDFLAGS)

clean:
	rm -f *~ *.o client

6.client Makefile

클라이언트를 실행할 Makefile
위와 마찬가지로 상대경로 주의❗️
CC = gcc
CFLAGS = -g -Wall
LDFLAGS = -lpthread

all: server

echoserver.o: echoserver.c ../csapp.h
	$(CC) $(CFLAGS) -c echoserver.c

echo.o: echo.c ../csapp.h
	$(CC) $(CFLAGS) -c echo.c

server: echoserver.o echo.o ../csapp.o
	$(CC) $(CFLAGS) echoserver.o echo.o ../csapp.o -o server $(LDFLAGS)

clean:
	rm -f *~ *.o server

3. 서버 실행하기

▶ 사용할 포트 번호 찾기

./port-for-user.pl
      • bash: ./port-for-user.pl: Permission denied 와 같은 오류 발생
      • 실행 권한이 없기 때문에 발생한 문제이다. 
        • ls -l port-for-user.pl : port-for-user.pl 파일 권한 확인하기
        • -r-------- : 읽기 권한밖에 없는 상태였다.
        • chmod +x port-for-user.pl : 실행권한 추가해주기
        • -r-x--x--x : 실행권한을 추가해주었다.
      • 위와 같이 실행 권한을 추가하고 다시 파일을 실행하면 안쓰는 포트 번호를 얻어낼 수 있었다.

오류 이미지


▶ 서버 실행하기

  • server Makefile 실행하여 실행파일 생성하기 = server 생성
  • ./server 포트번호 : 서버 실행하기

▶ 클라이언트 실행하기

  • client Makefile 실행하여 실행파일 생성하기 = client 생성
  • ./client 서버주소 포트번호 : 클라이언트 실행하기
    • 서버주소
    • 현재 같은 로컬에서 실행 중이라서 로컬 호스트 주소를 사용했다.
    • 우분투 서버의 public IP 주소를 사용해도 된다.

※ ec2 포트 열어주기


4. 동작 과정 그려보기


C로 서버를 직접 실행하고 그 서버에 접속하는 작업을 하는 것이 처음이라서 감이 안잡혔었다.
특히 어떻게 실행을 해야할지 감이 안잡혔다.

같이 공부하는 분의 도움으로 파일 구조 및 실행의 과정에 대해 설명을 듣고 실행해보았다.
정말 이번에는 동료분의 설명이 매우 도움이 되었다. 같이 공부하는 사람이 있으니 정말 좋다!
728x90