STUDY/정글

[웹서버] echo 서버 구현

Nobb 2025. 11. 5. 00:33

# echo 서버

: 클라가 보낸 데이터를 그대로 돌려주는 서버

    > 구현 목적

       : 소켓 I/O 흐름, 에러처리 익히기 (TCP 잡기)

 

 

# echo_server.c

//파일위치- tiny,proxy는 과제코드, echo_server.c는 연습용 네트워크 프로그램이라 별도 폴더 X
/* 
# 파일 설명
	에코 서버- 클라가 보낸 데이터를 그대로 돌려주는 서버

# 컴파일
	: 따로 makefile 안 건드려도 되고 터미널에서
	
		cd webproxy-lab
		gcc -Wall -O2 echo_server.c -o echo_server
		./echo_server
 */
#include <stdio.h>          // printf, perror 등 기본 입출력 함수
#include <stdlib.h>         // exit() 등
#include <string.h>         // memset(), strlen() 등 문자열 처리
#include <unistd.h>         // close(), read(), write() 등 시스템콜
#include <arpa/inet.h>      // inet_ntoa(), htons(), htonl() 등 네트워크 변환 함수
#include <netinet/in.h>     // sockaddr_in 구조체 정의

#define PORT 8080			// 서버가 열 포트 번호
#define BUF_SIZE 1024		// 버퍼 크기 (한번에 읽을 최대 Byte수)

int main(){
	int listenfd, connfd;
	struct sockaddr_in servaddr, cliaddr;
	socklen_t clilen;
	char buf[BUF_SIZE];	//데이터 읽고 쓸 버퍼
	ssize_t n;	// read/write 반환값 저장용

	// 1- socket() : 소켓 생성 (AF_INET: IPv4 , SOCK_STREAM: TCP)
	listenfd = socket(AF_INET, SOCK_STREAM, 0);
	if (listenfd < 0){
		perror("socket error!");
		exit(1);
	}

	// 2- 주소 재사용 옵션 설정 (서버 재시작 시 "Address already in use" 방지)
	int optval = 1;
	setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

	// 3- 서버 주소 구조체 초기화
	memset(&servaddr, 0, sizeof(servaddr)); //일단 구조체메모리 0으로 초기화
	servaddr.sin_family = AF_INET; //IPv4 사용
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //모든 네트워크 인터페이스에서 수신 _ex: 127.0.0.1
	servaddr.sin_port = htons(PORT); //포트번호를 네트워크 바이트 순서로 변환 _ex: 8080
    /*“내(서버)가 8080번 포트를 열고,
	모든 IP(INADDR_ANY) 로 들어오는 요청을 받을 준비를 하겠다.”*/

	// 4- bind() : 소켓을 해당 주소-포트에 바인딩
	if (bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
		perror("bind error!");
		exit(1);
	}

	// 5- listen() : 연결 요청 대기 상태로 전환 (동시 처리 가능 연결큐 크기 = 10)
	if (listen(listenfd,10) < 0){
		perror("listen error!");
		exit(1);
	}

	printf("Echo server running on port %d...\n", PORT);

	// 6- 무한루프 : 클라 연결 계속 수락 (accept() -> write/read() -> close())
	while (1){
		clilen = sizeof(cliaddr);
		// accept()
		connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &clilen); //accept()->커널이 cliaddr채워줌
		if (connfd < 0){
			perror("accept error!");
			continue; // 에러 나도 서버 종료 안하고 다음 요청 대기
		}
		// 연결됐다!
		printf("Connected from %s: %d\n",
				inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port)); //클라포트번호: os가 자동 랜덤배정
		
		// 클라한테 받은 데이터 읽고 그대로 돌려줌
		while ((n = read(connfd, buf, BUF_SIZE)) > 0){ //n:읽은 바이트수, connfd: 주고받을 통로(연결소켓)
			//읽은 데이터 그대로 전송 (echo)
			write(connfd, buf, n); //보낼통로:connfd소켓, 아까보낸내용담은버퍼:buf,보낼 양:n
		}
		// 클라 연결 종료
		printf("client disconnected!\n");
		close(connfd);
	}
	// 7- 서버소켓 닫기 (이 코드엔 거의 도달안함)
	close(listenfd);
	return 0;
}

 

>> 실행시키고 터미널에서 밑에처럼하면 프로그램 실행됨

 

> 여기서

nc 127.0.0.1 8080

이거 쳤을 때 내부 상황:

socket();
connect("127.0.0.1", 8080);
write("hello\n");
read(reply);

 

>> 클라쪽 소켓 생성  ->  연결요청(서버 ip주소, port번호 가지고)  ->  작업 (W/R)

 

----> 저 모든 작업들을 한번에 해주는게 nc인데,

 그걸 코드로 직접 구현하면 아래와 같음

 

# echo_client.c

#include <stdio.h>          // printf, fgets 등 표준 입출력
#include <stdlib.h>         // exit() 등
#include <string.h>         // strlen(), memset()
#include <unistd.h>         // read(), write(), close()
#include <arpa/inet.h>      // inet_pton(), htons(), sockaddr_in 등

#define PORT 8080           // 서버 포트 번호
#define BUF_SIZE 1024       // 송수신 버퍼 크기

int main(){
	int sockfd;  // client-socket
	struct sockaddr_in servaddr;  // 서버 주소 구조체
	char sendbuf[BUF_SIZE];  // 전송 버퍼
	char recvbuf[BUF_SIZE];  // 수신 버퍼
	ssize_t n;  //read() 결과 저장용

	// 1- socket() :소켓 생성 (IPv4, TCP)
	sockfd = socket(AF_INET, SOCK_STREAM, 0); //아직은 틀만 잡힌 빈 전화기 (-> )
	if (sockfd < 0){
		perror("socket error!");
		exit(1);
	}

	// 2- 타깃 서버 주소 구조체 초기화 (목적: 커널에 내가 연결하고싶은 서버의 주소 알려주기)
	memset(&servaddr, 0, sizeof(servaddr));  // 구조체 0으로 초기화
	servaddr.sin_family = AF_INET;  // IPv4 사용
	servaddr.sin_port = htons(PORT);  // 포트번호 -> 네트워크 바이트 순서로 변환

	// 3- 타깃 서버 ip주소 설정 (127.0.0.1 = 로컬호스트)
	if (inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr) <= 0){ //inet_pton(): internet p to n (사람읽는주소->컴 주소)
		// -> &servaddr.sin_addr에 변환된 결과(IPv4)가 저장됨
		perror("inet_pton error");
		exit(1);
	}

	// 4- connect() :서버에 연결 시도
	if (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
		perror("connect error!");
		exit(1);
	}
	// 연결 성공!
	printf("Connected to echo server at 127.0.0.1: %d\n", PORT);
	printf("입력한 문장을 서버에 전송합니다. (ctrl+C로 종료)\n\n");

	// 5- write() :표준 입력(stdin)으로부터 한 줄씩 읽어서 서버로 "전송" / read()
	while (fgets(sendbuf, BUF_SIZE, stdin) != NULL){
		// 서버로 전송
		write(sockfd, sendbuf, strlen(sendbuf));  //서버와 연결된 소켓, 보낼 데이터 든 버퍼, -
		// 서버로부터 응답 수신
		n = read(sockfd, recvbuf, BUF_SIZE - 1); //연결된 소켓, 서버가 보낸 데이터 저장 버퍼, -
		
		// 서버가 응답 닫은 경우
		if (n == 0){
			printf("server disconnected..!\n");
			break;
		}
		
		// 받은 데이터를 문자열로 출력
		recvbuf[n] = '\0';  //문자열 끝에 NULL 추가
		printf("서버 응답: %s", recvbuf);
	}
	// 6 - 종료 시 소켓 닫기
	close(sockfd);
	printf("클라 종료! 안녕~~!\n");
	return 0;
}
/*
# 실행 순서
	1) 서버 실행
		cd webproxy-lab
		gcc -Wall -O2 echo_server.c -o echo_server
		./echo_server

	2) 클라 실행
		cd webproxy-lab
		gcc -Wall -O2 echo_client.c -o echo_client
		./echo_client
	
	>> 테스트
*/