
Overview of network applications based on the sockets interface.

에코 서버는 클라이언트가 보낸 메시지를 그대로 다시 돌려주는 단순한 서버
클라이언트는 사용자의 입력을 서버에 전송하고, 서버는 그 입력을 다시 돌려주며, 클라이언트는 이를 화면에 출력
./echoserveri 8000
./echoclient localhost 8000
accept)Fgets)Rio_writen)Rio_readlineb)Rio_writen)Rio_readlineb, Fputs)[클라이언트]
입력 "Hi" → "Hi\n"
↓ Rio_writen
서버로 전송
[서버]
Rio_readlineb → "Hi\n" 수신
↓ Rio_writen
클라이언트로 재전송
[클라이언트]
Rio_readlineb → "Hi\n" 수신
Fputs → 화면 출력
[서버]
Open_listenfd(port)
↓
listenfd 소켓 생성 및 리슨 상태
↓
Accept(listenfd)
↓
[BLOCKED] (클라이언트 연결 대기)
[클라이언트]
Open_clientfd("localhost", port)
↓
connect() → 커널에 연결 요청
↓
[커널]
TCP 3-way handshake 진행
연결 완료 이벤트 → 서버 & 클라이언트 깨움
[서버]
Accept() 반환 → connfd 생성
echo(connfd) 호출
↓
Rio_readinitb → 읽기 준비
Rio_readlineb → [BLOCKED] (클라이언트 데이터 대기)
[클라이언트]
Rio_readinitb → 읽기 준비
Fgets(stdin) → [BLOCKED] (사용자 입력 대기)
[사용자]
입력: "Hi" + Enter
[클라이언트]
Fgets → "Hi\n" 획득
Rio_writen(clientfd, "Hi\n") 전송
Rio_readlineb → [BLOCKED] (서버 응답 대기)
[커널]
데이터 → 서버 connfd로 전달
[서버]
Rio_readlineb → "Hi\n" 수신
printf("received 3 bytes")
Rio_writen(connfd, "Hi\n")
[커널]
데이터 → 클라이언트로 전달
[클라이언트]
Rio_readlineb → "Hi\n" 수신
Fputs → 화면 출력
Fgets(stdin) → 다음 입력 대기
[사용자]
입력: Ctrl+D (EOF)
[클라이언트]
Fgets → NULL 반환
Close(clientfd) → FIN 전송
exit(0)
[커널]
FIN → 서버로 전달
[서버]
Rio_readlineb → 0 반환 (EOF)
echo() 종료
Close(connfd)
다시 Accept(listenfd)
| 개념 | 설명 |
|---|---|
listenfd | 서버가 클라이언트 연결을 기다리는 소켓 |
connfd | 연결이 수락된 후, 데이터를 주고받는 전용 소켓 |
Rio_readlineb | 줄 단위로 읽고, 도중에 입력이 없으면 BLOCKED |
Rio_writen | 전체 버퍼를 정확히 다 보냄 (부분 쓰기 해결) |
| Blocked 상태 | 데이터가 오거나 입력이 들어올 때까지 멈춰있는 상태 |
| EOF | 클라이언트가 Ctrl+D로 종료 의사 표시할 때 발생 |
#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]);
exit(0);
}
host=argv[1];
port=argv[2];
// open_clientfd : client (getaddrinfo -> socket -> connect)
clientfd = Open_clientfd(host, port);
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);
}
#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];
char client_port[MAXLINE];
if (argc != 2){
fprintf(stderr, "usage: %s <port>\n", argv[0]);
exit(0);
}
// Open_listenfd : server (getaddrinfo -> socket -> bind -> listen)
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);
}
#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);
}
}
rio)를 사용해 부분 읽기/쓰기 문제 방지listenfd는 기다리는 전화기
유용해요!