Iterative 서버 모델
-
우리가 흔이 아는 게임 서버와 파일 서버와 같은 서버 프로그램은 동시에 여러 클라이언트에게 서비스를 제공한다. 이러한 서버 모델을
Concurrent(동시)서버 모델이라고 한다. -
동시 서버 모델은 나중에 다루기로 하고, 여기서는
Concurrent서버 모델처럼 여러 클라이언트를 처리하지는 못하지만 순차적으로 여러 클라이언트에게 서비스를 제공할 수 있는Iterative(반복)서버 모델을 작성할 것이다.

- 그림에서 보이는 상태 다이어그램에서,
accept()~close()를 반복하여 클라이언트의 요청을 처리하는 것이다.
TCP 서버 프로그램
-
가장 기본이 되는 에코 클라이언트 / 에코 서버 프로그램을 작성해볼 예정이다.
-
에코 서버는 에코 클라이언트가 전송한 문자열을 그대로 클라이언트에게 전송하는 프로그램이다. 에코 클라이언트는 사용자에게 입력받은 문자열을 에코 서버에게 전달하는 프로그램이다.
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
void error_proc();
int main(int argc, char *argv[]) {
int server_sd, client_sd;
struct sockaddr_in server_addr, client_addr;
int client_addr_len, read_len, str_len;
char read_buff[BUFSIZ];
if (argc != 2) {
printf("Usage: %s [port] \n", argv[0]);
exit(1);
}
printf("server start...\n");
// 듣기 소켓으로 사용할 소켓을 생성한다.
server_sd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server_sd == -1) error_proc();
// 소켓에 설정할 주소 정보를 작성한다.
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(atoi(argv[1]));
// 작성한 주소 정보를 소켓에 생성한다.
if (bind(server_sd, (struct sockaddr *) &server_addr, sizeof(server_addr)) == -1) error_proc();
// 소켓을 듣기 소켓으로 만들어서, 클라이언트의 요청을 받을 준비를 한다.
if (listen(server_sd, 2) < 0) error_proc();
client_addr_len = sizeof(client_addr);
while (1) {
// 대기열에서 ESTABLISHED 상태인 TCP 연결을 가져온다.
client_sd = accept(server_sd, (struct sockaddr*) &client_addr, &client_addr_len);
if (client_sd == -1) error_proc();
printf("client %s: %d is connected...\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
while(1) {
// 클라이언트에서 전송한 문자열을 가져온다.
read_len = read(client_sd, read_buff, sizeof(read_buff) - 1);
if (read_len == 0) break;
read_buff[read_len] = '\0';
printf("client (%d): %s\n", ntohs(client_addr.sin_port), read_buff);
// 클라이언트에서 전송한 문자열을 다시 클라이언트에게 전송한다.
write(client_sd, read_buff, strlen(read_buff));
}
}
close(server_sd);
return 0;
}
void error_proc() {
fprintf(stderr, "error: %s\n", strerror(errno));
exit(1);
}
클라이언트 프로그램
- 시작 인자로 서버의 IP 주소 및 포트 번호를 입력 받는다.
- 사용자에게 입력 받은 문자열을 서버 프로그램으로 전송한다.
- 서버의 응답을 화면에 출력한다.
- 사용자에게
END문자열을 입력 받으면 그 문자열 전송을 마지막으로 소켓을 종료한다. END문자열 전송 이후 프로그램을 종료한다.
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int client_sd;
// 서버의 주소 정보를 struct sockaddr_in 구조체에 저장한다.
struct sockaddr_in client_addr;
int client_addr_len, read_len, receive_byte, max_buff;
char write_buff[BUFSIZ];
char read_buff[BUFSIZ];
if (argc != 3) {
printf("usage: %s [IP Address] [Port]\n", argv[0]);
}
// 접속을 시도할 소켓을 생성한다.
client_sd = socket(AF_INET, SOCK_STREAM, 0);
if (client_sd == -1) error_proc();
printf("==== client program ====\n");
memset(&client_addr, 0, sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_addr.s_addr = inet_addr(argv[1]);
client_addr.sin_port = htons(atoi(argv[2]));
// 서버에 연결을 시도한다.
if (connect(client_sd, (struct sockaddr *)&client_addr, sizeof(client_addr)) == -1) {
close(client_sd);
error_proc();
}
// 연결된 이후 입출력 작업을 시도한다.
while(1) {
fgets(write_buff, BUFSIZ - 1, stdin);
read_len = strlen(write_buff);
if (read_len < 2) continue;
receive_byte = 0;
max_buff = BUFSIZ - 1;
// 서버로부터 데이터를 받을 때 이미 받을 데이터의 크기를 알고 있기 때문에, >그 만큼의 데이터를 받을 때까지 루프를 실행한다.
do {
receive_byte += read(client_sd, read_buff, max_buff);
max_buff -= receive_byte;
} while (receive_byte < (read_len -1));
read_buff[receive_byte] = '\0';
printf("server: %s\n", read_buff);
write_buff[read_len -1] = '\0';
if (!strcmp(write_buff, "END")) break;
}
printf("END^^\n");
close(client_sd);
return 0;
}
void error_proc() {
fprintf(stderr, "error: %s\n", strerror(errno));
exit(errno);
}
참고 문헌
>> Home