TCP 소켓을 이용하여 서버의 현재 시간을 알려주는 HTTP 서버를 만들어보자.
우선 만든 결과 프로그램은 다음과 같다.
#if defined(_WIN32) // windows
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0600
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#else // linux
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#endif
#if defined(_WIN32)
#define ISVALIDSOCKET(s) ((s) != INVALID_SOCKET)
#define CLOSESOCKET(s) closesocket(s)
#define GETSOCKETERRNO() (WSAGetLastError())
#else
#define ISVALIDSOCKET(s) ((s) >= 0)
#define CLOSESOCKET(s) close(s)
#define SOCKET int
#define GETSOCKETERRNO() (errno)
#endif
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <stdio.h>
int main() {
#if defined(_WIN32)
WSADATA d;
if (WSAStartup(MAKEWORD(2, 2), &d)) {
fprintf(stderr, "Failed to initialize.\n");
return 1;
}
#endif
printf("Configuring local address...\n");
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; // use IPv4 addr
hints.ai_socktype = SOCK_STREAM; // TCP
hints.ai_flags = AI_PASSIVE;
struct addrinfo *bind_address;
getaddrinfo(0, "8080", &hints, &bind_address); // bind_address에 주소 저장
printf("Creating socket...\n");
SOCKET socket_listen;
socket_listen = socket(bind_address->ai_family, bind_address->ai_socktype, bind_address->ai_protocol);
if (!ISVALIDSOCKET(socket_listen)) {
fprintf(stderr, "socket() failed. (%d)\n", GETSOCKETERRNO());
return 1;
}
printf("Binding socket to local address...\n");
if (bind(socket_listen, bind_address->ai_addr, bind_address->ai_addrlen)) {
fprintf(stderr, "bind() failed. (%d)\n", GETSOCKETERRNO());
return 1;
}
freeaddrinfo(bind_address);
printf("Listening...\n");
if (listen(socket_listen, 10) < 0) {
fprintf(stderr, "listen() failed. (%d)\n", GETSOCKETERRNO());
return 1;
}
printf("Waiting for connection...\n");
struct sockaddr_storage client_address;
socklen_t client_len = sizeof(client_address);
while(1){
SOCKET socket_client = accept(socket_listen, (struct sockaddr*) &client_address, &client_len);
if (!ISVALIDSOCKET(socket_client)) {
fprintf(stderr, "accept() failed. (%d)\n", GETSOCKETERRNO());
return 1;
}
printf("Client is connected... ");
char address_buffer[100];
getnameinfo((struct sockaddr*)&client_address, client_len, address_buffer, sizeof(address_buffer), 0, 0, NI_NUMERICHOST);
printf("%s\n", address_buffer);
printf("Reading request...\n");
char request[1024];
int bytes_received = recv(socket_client, request, 1024, 0);
printf("Received %d bytes.\n", bytes_received);
printf("%.*s", bytes_received, request);
printf("Sending response...\n");
const char *response =
"HTTP/1.1 200 OK\r\n"
"Connection: close\r\n"
"Content-Type: text/plain\r\n\r\n"
"Local time is: ";
int bytes_sent = send(socket_client, response, strlen(response), 0);
printf("Sent %d of %d bytes.\n", bytes_sent, (int)strlen(response));
time_t timer;
time(&timer);
char *time_msg = ctime(&timer);
bytes_sent = send(socket_client, time_msg, strlen(time_msg), 0);
printf("Sent %d of %d bytes.\n", bytes_sent, (int)strlen(time_msg));
printf("Closing connection...\n");
CLOSESOCKET(socket_client);
}
printf("Closing listening socket...\n");
CLOSESOCKET(socket_listen);
#if defined(_WIN32)
WSACleanup();
#endif
return 0;
}
이 거대한 괴물을 어떻게 상대해야 할지.. 차근차근 살펴보자.
0 ~ 29
헤더를 보면, 윈도우에서는 윈도우 헤더가 적용되게, 리눅스에서는 리눅스 헤더가 적용되게 설정하였다.
37 ~ 43, 129 ~ 131
윈도우의 경우 메인 함수에서 WSAStartup과 WSACleanup을 통해 웹 소켓을 초기화해야 한다.
45 ~ 64
getaddrinfo를 통해 호스트와 서비스에 대해 사용할 수 있는 주소와 포트 번호, 프로토콜 정보를 가져온다.
그 후 해당 주소정보를 이용하여 소켓을 생성하고, 소켓을 포트에 바인드한다.
SOCKET 타입은 윈도우에서나 가능한거고, unix에선 socket descriptor의 타입이 int로 지정되어 있다.
윈도우가 아닐때 #define SOCKET int 가 지정되어있는 이유이다.
ISVALIDSOCKET 은 매크로로 지정한 함수이다. listening socket이 valid하지 않으면 프로그램을 종료시킨다.
68 ~ 73
소켓을 바인드 한 후, bind_address에 할당된 메모리를 free 해준다 (더이상 필요 없음). bind에 실패한 경우 bind 함수는 0이 아닌 값을 리턴하게 되어 if문에 걸린다.
75 ~ 83
소켓을 listen 상태로 변경하고, listen의 두번쨰 인자로 응답을 기다리는 connection의 갯수를 10개로 제한한다.
클라이언트가 많아지면, 들어오는 커넥션을 queue 하고, 응답 못받고 기다리는 커넥션이 10개 이상으로 넘어가면 reject 한다.
86 ~ 93
클라이언트의 커넥션을 accept() 를 통해 wait 한다. 이때 요청이 들어올 때 까지 프로그램이 Block 되어있다.
94 ~ 104
1024 사이즈의 버퍼로 데이터를 받는다. recv에서 입력이 오지 않으면 block 된다. 만약 client에서 connection이 제거되면 recv는 0이나 -1을 반환하기 때문에, production 레벨에서는 저걸 꼭 체크해주어야 한다.
107 ~ 113
HTTP response를 전송한다. client_socket에 response 메세지를 전송한다.
116 ~ 123
ctime 메소드를 이용하여 현재 시간에 대한 time message를 생성하고, 전송한 후 클라이언트의 커넥션을 종료한다.
Reference
'기타 > 아카이브' 카테고리의 다른 글
[Network Basic] 3. TCP Connection (0) | 2020.06.11 |
---|---|
[Network Basic] 2. Socket (0) | 2020.05.27 |
[Network exercise] 1. Local address 목록을 나열하기 (0) | 2020.05.26 |
[Network Basic] 1. Networks and Protocols (0) | 2020.05.26 |
댓글