C 언어로 Simple HTTP 서버 만들기

출처
기능은 아주 단순하다. 그만큼 분석하기 쉽다. 웹서버의 대략적인 동작을 엿볼 수 있다.

간단하게 아래의 기능만 구현했다.

  • HTTP 서버
  • HTML 출력
  • 라우팅

환경

아래 환경에서 개발, 테스트 했다.

  • OS : Windows 10
  • IDE : Visual Studio 2015

HTML을 표시하기

가장 유명한 HelloWorld를 해 본다.
HTML 파일을 만들고, 1행씩 보내도 좋지만 이번은 귀찮아서 간단하게 직접 문장을 보낸다.

strcpy(html,
      "<!DOCTYPE html>\n"
      "<html lang = \"ja\">\n"
      "<head>\n"
      "<meta charset = \"utf-8\">\n"
      "</head>\n"
      "<body>\n"
      "<h1>HelloWorld</h1>\n"
      "</body>"
      "</html>");
 
// 응답(HTML을 보낸다)
if (send(sockw, html, strlen(html), 0) < 1)
{
  printf("send : %d\n", WSAGetLastError());
  break;
}

미리 만들어 놓은 html 배열에 html를 저장하고, 그대로 보내면 브라우저에 HTML이 출력된다.

라우팅 해 본다.

요청에 따라 다른 HTML을 보내도록 라우팅을 만들어본다.
라우팅은 특정 주소에 액세스 할 때 이 목적 주소 결과를 출력한다.

실제로 작성한 코드는 이런 식이다.

//접속
recv_len = recvfrom(sockw, buf,1024, 0, (struct sockaddr *)&client, &sockaddr_in_size);
buf[recv_len - 1] = 0;
if (buf[0] == '\0')
  strcpy(buf, NULL);


// 통신 표시
printf("%s \n",buf);

// method
for (int i = 0; i < strlen(buf); i++) {
  printf("%d\n",i);
  if (buf[i] == 'G' && buf[i + 1] == 'E' && buf[i + 2] == 'T' && buf[i + 3] == ' ') {
  for (int j = 0; buf[i + 4 + j] != ' '; j++) {
    path[j] = buf[i + 4 + j];
  }
  break;
  }else if (buf[i] == 'P' && buf[i + 1] == 'O' && buf[i + 2] == 'S' && buf[i + 3] == 'T' && buf[i + 4] == ' ') {
  for (int j = 0; buf[i + 4 + j] != ' '; j++) {
    path[j] = buf[i + 4 + j];
  }
  break;
  }
}

이 코드에서 GET/POST의 값을 path 변수에 저장한다.
path 변수라고 말하면 왠지 헷가릴 수 있으므로 이름은 다시 한번 바꾸어 둡다.

// 라우팅
if (strcmp(path, "/page1") == 0) {
  strcpy(html,
  "<!DOCTYPE html>\n"
  "<html lang = \"ja\">\n"
  "<head>\n"
  "<meta charset = \"utf-8\">\n"
  "</head>\n"
  "<body>\n"
  "<h1>Page1</h1>\n"
  "<a href=\"/page2\">->page2</a>\n"
  "</body>"
  "</html>");
}else if (strcmp(path, "/page2") == 0) {
  strcpy(html,
  "<!DOCTYPE html>\n"
  "<html lang = \"ja\">\n"
  "<head>\n"
  "<meta charset = \"utf-8\">\n"
  "</head>\n"
  "<body>\n"
  "<h1>Page2</h1>\n"
  "<a href=\"/page1\">->page1</a>\n"
  "</body>"
  "</html>");
}else {
  strcpy(html,
  "<!DOCTYPE html>\n"
  "<html lang = \"ja\">\n"
  "<head>\n"
  "<meta charset = \"utf-8\">\n"
  "</head>\n"
  "<body>\n"
  "<h1>Not Found</h1>\n"
  "</body>"
  "</html>");
}

// 응답(HTML을 보낸다)
if (send(sockw, html, strlen(html), 0) < 1)
{
  printf("send : %d\n", WSAGetLastError());
  break;
}

이렇게하면 http://localhost:8000/page1 에 접속하면 페이지가 표시된다.
앵커로 http://localhost:8000/page2 로 이동할 수 있다.

또 다른 페이지를 열려고하면 “Not Found” 라고한다.

전체 코드는 아래와 같다.
main.cpp

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
 
WSADATA     wsaData;
SOCKET      sock0;
SOCKET      sockw;
struct      sockaddr_in addr;
struct      sockaddr_in client;
#define PORT_NUM 8000
 
 
// IP 주소 얻기
int getAddrHost(void) {
  int i;
  HOSTENT *lpHost;       //  호스트 정보를 저장하는 구조체
  IN_ADDR inaddr;       // IP 주소를 저장하는 구조체
  char szBuf[256], szIP[16];  // 호스트 이름/IP 주소를 저장하는 배열
 
  // 에러 처리
  if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) {
    printf("WSAStartup Error\n");
    return -1;
  }
 
  // 로컬 머신의 호스트 이름을 얻는다
  gethostname(szBuf, (int)sizeof(szBuf));
 
  // 호스트 정보 얻기
  lpHost = gethostbyname(szBuf);
  // IP 주소 얻기
  for (i = 0; lpHost->h_addr_list[i]; i++) {
    memcpy(&inaddr, lpHost->h_addr_list[i], 4);
  }
  strcpy(szIP, inet_ntoa(inaddr));
  printf("build server: http://%s:%d\n", szIP,PORT_NUM);  
 
  WSACleanup();
 
  return 0;
}
 
char *get_name(char *name) {
  
}
 
 
int main()
{
  int len;
  int n;
  int sockaddr_in_size = sizeof(struct sockaddr_in);
  int recv_len = 0;
  unsigned char buf[1024];
  unsigned char path[1024];
  unsigned char html[1024];
 
  //IP 어드레스 표시
  if (getAddrHost() != 0) {
    printf("get IP address failed");
    getch();
    return -1;
  }
 
  // winsock2 초기화
  if (WSAStartup(MAKEWORD(2, 0), &wsaData))
  {
    puts("reset winsock failed");
    getch();
    return -1;
  }
 
  // 소켓 만들기
  sock0 = socket(AF_INET, SOCK_STREAM, 0);
  if (sock0 == INVALID_SOCKET)
  {
    printf("socket : %d\n", WSAGetLastError());
    getch();
    return -1;
  }
 
  // 소켓 설정
  addr.sin_family = AF_INET;
  addr.sin_port = htons(PORT_NUM);
  addr.sin_addr.S_un.S_addr = INADDR_ANY;
 
  // 소켓 바인드
  if (bind(sock0, (struct sockaddr *)&addr, sizeof(addr)) != 0)
  {
    printf("bind : %d\n", WSAGetLastError());
    getch();
    return -1;
  }
 
  // TCP 클라이언트로부터의 접속 요구를 기다리면서 대기한다
  if (listen(sock0, 5) != 0)
  {
    printf("listen : %d\n", WSAGetLastError());
    getch();
    return -1;
  }
 
 
  // 서버 시작
  while (1)
  {
 
    len = sizeof(client);
    sockw = accept(sock0, (struct sockaddr *)&client, &len);
    if (sockw == INVALID_SOCKET)
    {
      printf("accept : %d\n", WSAGetLastError());
      break;
    }
 
    // 버퍼 초기화
      memset(path, 0, 1024);
    memset(html, 0, 1024);
 
    // 접속 
    recv_len = recvfrom(sockw, buf,1024, 0, (struct sockaddr *)&client, &sockaddr_in_size);
    buf[recv_len - 1] = 0;
    if (buf[0] == '\0')
      strcpy(buf, NULL);
 
 
    // 통신 표시
    printf("%s \n",buf);
 
    // method
    for (int i = 0; i < strlen(buf); i++) {
      printf("%d\n",i);
      if (buf[i] == 'G' && buf[i + 1] == 'E' && buf[i + 2] == 'T' && buf[i + 3] == ' ') {
        for (int j = 0; buf[i + 4 + j] != ' '; j++) {
          path[j] = buf[i + 4 + j];
        }
        break;
      }else if (buf[i] == 'P' && buf[i + 1] == 'O' && buf[i + 2] == 'S' && buf[i + 3] == 'T' && buf[i + 4] == ' ') {
        for (int j = 0; buf[i + 4 + j] != ' '; j++) {
          path[j] = buf[i + 4 + j];
        }
        break;
      }
    }
    printf("request: %s \n",path);
 
    // HTTP
    unsigned char *header =  
      "HTTP/1.0 200 OK\n"
      "Content-type: text/html\n"
      "\n";
 
    send(sockw, header, strlen(header), 0);
 
    // 라우팅 
    if (strcmp(path, "/page1") == 0) {
      strcpy(html,
        "<!DOCTYPE html>\n"
        "<html lang = \"ja\">\n"
        "<head>\n"
        "<meta charset = \"utf-8\">\n"
        "</head>\n"
        "<body>\n"
        "<h1>Page1</h1>\n"
        "<a href=\"/page2\">->page2</a>\n"
        "</body>"
        "</html>");
    }else if (strcmp(path, "/page2") == 0) {
      strcpy(html,
        "<!DOCTYPE html>\n"
        "<html lang = \"ja\">\n"
        "<head>\n"
        "<meta charset = \"utf-8\">\n"
        "</head>\n"
        "<body>\n"
        "<h1>Page2</h1>\n"
        "<a href=\"/page1\">->page1</a>\n"
        "</body>"
        "</html>");
    }else {
      strcpy(html,
        "<!DOCTYPE html>\n"
        "<html lang = \"ja\">\n"
        "<head>\n"
        "<meta charset = \"utf-8\">\n"
        "</head>\n"
        "<body>\n"
        "<h1>404- Not Found</h1>\n"
        "</body>"
        "</html>");
    }
 
    // 응답(HTML을 보낸다)
    if (send(sockw, html, strlen(html), 0) < 1)
    {
      printf("send : %d\n", WSAGetLastError());
      break;
    }
 
    // 소켓 닫기
    closesocket(sockw);
  }
 
  // winsock2 종료 처리
  closesocket(sock0);
  WSACleanup();
  return 0;
}

이 글은 2020-03-16에 작성되었습니다.