/*
* 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 |