정글

에코 서버 실행하기

nkdev 2025. 5. 6. 22:02
/*
 * echo - read and echo text lines until client closes connection
 */
/* $begin echo */
#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);                             //그대로 다시 보내주기
    }
}

 

/*
 * echoclient.c - An echo client
 */

/* $begin echoclientmain */

#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]); //명령행에서 2개의 인자를 받기 ./echoclient.c [host] [port]
        exit(0);
    }
    host = argv[1];
    port = argv[2];

    clientfd = Open_clientfd(host, port);           //Open_clientfd()로 해당 host, port에 TCP 연결을 맺음 -> clientfd 소켓 생성
    Rio_readinitb(&rio, clientfd);                  //줄 단위로 안전하게 읽기 위한 작업

    while (Fgets(buf, MAXLINE, stdin) != NULL) {    //표준 입력으로 문자열을 읽음
        Rio_writen(clientfd, buf, strlen(buf));     //읽은 값을 서버로 송신
        Rio_readlineb(&rio, buf, MAXLINE);          //서버의 응답을 수신
        Fputs(buf, stdout);                         //수신받은 값을 표준 출력
    }

    Close(clientfd);                                //클라이언트 소켓을 닫음 -> 클라이언트 프로세스 종료 시 열려있던 모든 값들이 자동으로 닫히지만 명시적으로 닫아주는 것이 좋은 프로그래밍
    exit(0);
}

 

/* 
 * echoserveri.c - An iterative echo server 
 */ 
/* $begin echoserverimain */
#include "csapp.h"

void echo(int connfd);

int main(int argc, char **argv) 
{
    int listenfd, connfd;
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;  /* Enough space for any address */  //line:netp:echoserveri:sockaddrstorage
    char client_hostname[MAXLINE], client_port[MAXLINE];

    if (argc != 2) {
        fprintf(stderr, "usage: %s <port>\n", argv[0]); //명령행에서 1개의 인자를 받기 ./echoclient.c [port]
        exit(0);
    }

    listenfd = Open_listenfd(argv[1]);                                                                      //서버 소켓 생성. 지정한 포트에 대해 리스닝 소켓 생성, 클라이언트의 접속을 받을 준비 완료

    while (1) {
        clientlen = sizeof(struct sockaddr_storage);                                                        //
        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);                                           //새로운 연결 요청이 오면 연결 소켓(connfd)을 리턴함
        Getnameinfo((SA *) &clientaddr, clientlen, client_hostname, MAXLINE, client_port, MAXLINE, 0);      
        printf("Connected to (%s, %s)\n", client_hostname, client_port);
        echo(connfd);                                                                                       //연결된 소켓을 echo 함수에 넘김
        Close(connfd);                                                                                      //연결을 종료하고 다음 클라이언트를 기다림
    }
    exit(0);
}
/* $end echoserverimain */

 

위의 서버, 클라이언트, echo역할을 하는 세 파일은 하나의 프로그램으로 동작한다.

이 세 개의 파일을 gcc로 컴파일하려고 한다.

 

이 세 파일은 전처리부분에 쓰여있듯이 csapp.h 헤더 파일을 포함하고 있다.

csapp.h를 include하면 csapp.c파일에 정의된 모든 함수를 사용할 수 있다.

 

아래는 csapp.h 파일인데 이 헤더에 정의된 함수들이 모두 구현되어있는 csapp.c파일은 너무 길어서 생략했다.

/*
 * csapp.h - prototypes and definitions for the CS:APP3e book
 */
/* $begin csapp.h */
#ifndef __CSAPP_H__
#define __CSAPP_H__

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <setjmp.h>
#include <signal.h>
#include <dirent.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <math.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/* Default file permissions are DEF_MODE & ~DEF_UMASK */
/* $begin createmasks */
#define DEF_MODE   S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH
#define DEF_UMASK  S_IWGRP|S_IWOTH
/* $end createmasks */

/* Simplifies calls to bind(), connect(), and accept() */
/* $begin sockaddrdef */
typedef struct sockaddr SA;
/* $end sockaddrdef */

/* Persistent state for the robust I/O (Rio) package */
/* $begin 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;
/* $end rio_t */

/* External variables */
extern int h_errno;    /* Defined by BIND for DNS errors */ 
extern char **environ; /* Defined by libc */

/* Misc constants */
#define	MAXLINE	 8192  /* Max text line length */
#define MAXBUF   8192  /* Max I/O buffer size */
#define LISTENQ  1024  /* Second argument to listen() */

/* Our own error-handling functions */
void unix_error(char *msg);
void posix_error(int code, char *msg);
void dns_error(char *msg);
void gai_error(int code, char *msg);
void app_error(char *msg);

/* Process control wrappers */
pid_t Fork(void);
void Execve(const char *filename, char *const argv[], char *const envp[]);
pid_t Wait(int *status);
pid_t Waitpid(pid_t pid, int *iptr, int options);
void Kill(pid_t pid, int signum);
unsigned int Sleep(unsigned int secs);
void Pause(void);
unsigned int Alarm(unsigned int seconds);
void Setpgid(pid_t pid, pid_t pgid);
pid_t Getpgrp();

/* Signal wrappers */
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);
void Sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
void Sigemptyset(sigset_t *set);
void Sigfillset(sigset_t *set);
void Sigaddset(sigset_t *set, int signum);
void Sigdelset(sigset_t *set, int signum);
int Sigismember(const sigset_t *set, int signum);
int Sigsuspend(const sigset_t *set);

/* Sio (Signal-safe I/O) routines */
ssize_t sio_puts(char s[]);
ssize_t sio_putl(long v);
void sio_error(char s[]);

/* Sio wrappers */
ssize_t Sio_puts(char s[]);
ssize_t Sio_putl(long v);
void Sio_error(char s[]);

/* Unix I/O wrappers */
int Open(const char *pathname, int flags, mode_t mode);
ssize_t Read(int fd, void *buf, size_t count);
ssize_t Write(int fd, const void *buf, size_t count);
off_t Lseek(int fildes, off_t offset, int whence);
void Close(int fd);
int Select(int  n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, 
	   struct timeval *timeout);
int Dup2(int fd1, int fd2);
void Stat(const char *filename, struct stat *buf);
void Fstat(int fd, struct stat *buf) ;

/* Directory wrappers */
DIR *Opendir(const char *name);
struct dirent *Readdir(DIR *dirp);
int Closedir(DIR *dirp);

/* Memory mapping wrappers */
void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
void Munmap(void *start, size_t length);

/* Standard I/O wrappers */
void Fclose(FILE *fp);
FILE *Fdopen(int fd, const char *type);
char *Fgets(char *ptr, int n, FILE *stream);
FILE *Fopen(const char *filename, const char *mode);
void Fputs(const char *ptr, FILE *stream);
size_t Fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
void Fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

/* Dynamic storage allocation wrappers */
void *Malloc(size_t size);
void *Realloc(void *ptr, size_t size);
void *Calloc(size_t nmemb, size_t size);
void Free(void *ptr);

/* Sockets interface wrappers */
int Socket(int domain, int type, int protocol);
void Setsockopt(int s, int level, int optname, const void *optval, int optlen);
void Bind(int sockfd, struct sockaddr *my_addr, int addrlen);
void Listen(int s, int backlog);
int Accept(int s, struct sockaddr *addr, socklen_t *addrlen);
void Connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

/* Protocol independent wrappers */
void Getaddrinfo(const char *node, const char *service, 
                 const struct addrinfo *hints, struct addrinfo **res);
void Getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, 
                 size_t hostlen, char *serv, size_t servlen, int flags);
void Freeaddrinfo(struct addrinfo *res);
void Inet_ntop(int af, const void *src, char *dst, socklen_t size);
void Inet_pton(int af, const char *src, void *dst); 

/* DNS wrappers */
struct hostent *Gethostbyname(const char *name);
struct hostent *Gethostbyaddr(const char *addr, int len, int type);

/* Pthreads thread control wrappers */
void Pthread_create(pthread_t *tidp, pthread_attr_t *attrp, 
		    void * (*routine)(void *), void *argp);
void Pthread_join(pthread_t tid, void **thread_return);
void Pthread_cancel(pthread_t tid);
void Pthread_detach(pthread_t tid);
void Pthread_exit(void *retval);
pthread_t Pthread_self(void);
void Pthread_once(pthread_once_t *once_control, void (*init_function)());

/* POSIX semaphore wrappers */
void Sem_init(sem_t *sem, int pshared, unsigned int value);
void P(sem_t *sem);
void V(sem_t *sem);

/* Rio (Robust I/O) package */
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
void rio_readinitb(rio_t *rp, int fd); 
ssize_t	rio_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t	rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);

/* Wrappers for Rio package */
ssize_t Rio_readn(int fd, void *usrbuf, size_t n);
void Rio_writen(int fd, void *usrbuf, size_t n);
void Rio_readinitb(rio_t *rp, int fd); 
ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n);
ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);

/* Reentrant protocol-independent client/server helpers */
int open_clientfd(char *hostname, char *port);
int open_listenfd(char *port);

/* Wrappers for reentrant protocol-independent client/server helpers */
int Open_clientfd(char *hostname, char *port);
int Open_listenfd(char *port);


#endif /* __CSAPP_H__ */
/* $end csapp.h */

 

gcc -c echo.c echoclient.c echoserveri.c

위 명령어를 통해 각각의 .c파일을 .o파일로 컴파일했다.

 

그리고 echoclient.o를 링크 과정을 거쳐 실행파일로 만들기 위해 아래와 같이 gcc-o echoclient echoclient.o를 실행했는데 링크 에러가 발생했다.

 

 

csapp.c 파일이 아직 컴파일되지 않아서 발생한 문제라고 한다.

gcc -c csapp csapp.c

csapp.o 파일을 만들어주고

gcc -o echoserveri echoserveri.o csapp.o echo.o

echoserveri 실행 파일을 만들 때 링크가 필요한 .o파일을 모두 명시해준다.

gcc -o echoclient echoclient.c csapp.o echo.o

마찬가지로 echoclient 실행 파일을 만들 때 링크가 필요한 .o파일을 모두 명시해준다.

 

전체 과정 :

 

 

클라이언트, 서버 실행하기

'정글' 카테고리의 다른 글

웹 서버와 CGI  (0) 2025.05.08
에코 서버(2) - 소켓 인터페이스를 위한 도움함수들  (4) 2025.05.07
gcc 컴파일 하는 법  (0) 2025.05.06
OSI 7 Layers/TCP/UDP/HTTP  (4) 2025.05.06
에코 서버(1) - 호스트와 서비스 변환  (0) 2025.05.05