Client에서 요청한 송신한 데이터를 Server에서 수신하여 그대로 되돌려주는 기능을 하는 서버를 말한다.
echo client, server를 구현하기 전, 소켓 프로그래밍의 흐름을 보면 함수의 이해가 좀 더 쉽다.
지금 구현하려는 TCP/IP 통신 기반으로 데이터 송수신을 하려면, IP와 Port가 반드시 소켓에 할당되어야한다.
먼저, getaddrinfo()
함수를 호출하여 IP 및 Port의 정보를 받는다.
그 후, socket()
함수를 호출해 소켓을 생성하고, 서버에서는 bind()
함수를 호출해 할당하고, 클라이언트에서는 connect()
함수를 호출해 할당한다.
클라이언트에서는 open_clientfd()
함수를 호출하여 위 과정을 진행하고, 서버에서는 위 과정과 데이터를 수신받을 준비 상태로 만들 listen()
함수 호출을 포함한 open_listenfd()
함수를 호출하여 송수신할 준비를 한다.
그 후 서버에서 클라이언트에서 연결 요청을 Accept()
함수를 호출하여 클라이언트와 연결하고, 데이터 송수신을 진행한다.
#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];
clientfd = open_clientfd(host, port); // Getaddrinfo, socket, connect를 시켜준다. 호스트와 포트값을 설정한다.
Rio_readinitb(&rio, clientfd);
// 입력이 없을 때까지 Rio 패키지를 통해 입력된 문자열을 버퍼에 담아 요청한다.
while(Fgets(buf, MAXLINE, stdin) != NULL)
{
Rio_writen(clientfd, buf, strlen(buf));
Rio_readlineb(&rio, buf, MAXLINE);
Fputs(buf, stdout);
}
Close(clientfd);
exit(0);
}
리눅스에서는 특정 데이터(소켓, 파이프 등)을 파일로 인식하며 정수의 값으로 리턴하는데 이를 파일 디스크립터
라 한다.
즉 clienfd에 클라이언트의 소켓 식별자 파일 디스크립터가 선언되고, Rio 패키지를 통해 데이터를 송신한다.
#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);
}
}
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]); // Getaddrinfo, socket, bind를 하여 대기 상태로 만들어준다.
// accept를 시켜 다른 소켓을 하나 복제하여 소통하며, echo를 실행한다.
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);
}
마찬가지로, listenfd에 소켓 식별자가 할당되고, 반복문 내 연결 요청이 온 client와 연결한 후, 수신한 데이터를 읽고 다시 클라이언트쪽에 송신한다.