Skip to content

소켓 프로그래밍

jeongmin edited this page Feb 2, 2024 · 1 revision

I/O의 Sync와 Blocking 그리고 Multiplexing
[네이버클라우드 기술&경험] IO Multiplexing (IO 멀티플렉싱) 기본 개념부터 심화까지 -1부-
[네이버클라우드 기술&경험] IO Multiplexing (IO 멀티플렉싱) 기본 개념부터 심화까지 -2부-

DNS 예제

#include <arpa/inet.h>  // 네트워크 주소 변환 함수를 위한 헤더
#include <netdb.h>   // 네트워크 데이터베이스 작업을 위한 헤더
#include <stdio.h>   // 표준 입출력 함수를 위한 헤더
#include <stdlib.h>  // 표준 라이브러리 함수를 위한 헤더
#include <string.h>  // 문자열 관련 함수를 위한 헤더
#include <sys/socket.h>  // 소켓 프로그래밍을 위한 헤더
#include <sys/types.h>   // 데이터 타입 정의를 위한 헤더
#include <unistd.h>      // 유닉스 표준 함수를 위한 헤더

int main(void) {
  struct addrinfo hints, *res;
  int status;

  // hints 구조체 초기화
  memset(&hints, 0, sizeof(hints));
  hints.ai_family = AF_UNSPEC;      // IPv4와 IPv6 둘 다 허용
  hints.ai_socktype = SOCK_STREAM;  // TCP 소켓

  // 주소 정보 가져오기
  if ((status = getaddrinfo("localhost", "http", &hints, &res)) != 0) {
    fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
    return 1;
  }

  // 생략된 주소 정보 처리 및 출력 부분
  for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
    void *addr;

    // IPv4와 IPv6 주소를 다루기 위한 로직
    if (p->ai_family == AF_INET) {  // IPv4
      struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
      addr = &(ipv4->sin_addr);
    } else {  // IPv6
      struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
      addr = &(ipv6->sin6_addr);
    }

    // 주소를 문자열로 변환
    char ipstr[INET6_ADDRSTRLEN];
    inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));

    printf("%s\n", ipstr);
  }

  freeaddrinfo(res);  // 동적으로 할당된 메모리 해제
  return 0;
}

기본 소켓 통신 예제

client.cpp

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <unistd.h>

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);

    connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));

    char buffer[256];

    while (1) {
      std::cin >> buffer;
      write(sock, buffer, sizeof(buffer));
    }

    close(sock);

    return 0;
}

server.cpp

#include <sys/socket.h>
#include <netinet/in.h>
#include <iostream>
#include <unistd.h>

int main() {
    int sock = socket(AF_INET, SOCK_STREAM, 0);

    sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(8080);

    bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
    listen(sock, 1);

    sockaddr_in client_addr;
    socklen_t client_len = sizeof(client_addr);
    int client_sock = accept(sock, (struct sockaddr *)&client_addr, &client_len);

    char buffer[256] = {0};

    while (1) {
      read(client_sock, buffer, 256);
      std::cout << "Message from client: " << buffer << std::endl;
    }

    close(client_sock);
    close(sock);

    return 0;
}

select 적용

#include <netinet/in.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cstring>
#include <iostream>

#define MAX_CLIENTS 30

int main() {
  int sock = socket(AF_INET, SOCK_STREAM, 0);

  sockaddr_in serv_addr;
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(8080);

  bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
  listen(sock, MAX_CLIENTS);

  fd_set master_set;  // 모든 소켓을 관리하는 세트
  FD_ZERO(&master_set);
  FD_SET(sock, &master_set);  // 서버 소켓을 세트에 추가
  int max_sd = sock;  // 가장 높은 소켓 디스크립터 번호를 저장

  while (true) {
    fd_set copy_set = master_set;  // select 함수가 세트를 수정할 수 있으므로
                                   // 복사본을 만들어 사용

    // 클라이언트의 연결 요청이나 데이터 수신을 기다림
    if (select(max_sd + 1, &copy_set, nullptr, nullptr, nullptr) == -1) {
      perror("select");
      exit(EXIT_FAILURE);
    }

    // 모든 소켓을 확인하며 수신한 데이터가 있는지 확인
    for (int i = 0; i <= max_sd; ++i) {
      if (FD_ISSET(i, &copy_set)) {
        if (i == sock) {  // 새로운 클라이언트의 연결 요청이 있는 경우
          sockaddr_in client_addr;
          socklen_t client_len = sizeof(client_addr);
          int client_sock =
              accept(sock, (struct sockaddr *)&client_addr, &client_len);

          FD_SET(client_sock, &master_set);  // 새 클라이언트 소켓을 세트에 추가
          if (client_sock > max_sd) {
            max_sd = client_sock;
          }
          std::cout << "New client connected: " << client_sock << std::endl;

        } else {  // 기존 클라이언트로부터 데이터를 수신한 경우

          char buffer[256] = {0};
          int nbytes = read(i, buffer, 256);
          if (nbytes <= 0) {  // 클라이언트가 연결을 종료한 경우
            close(i);
            FD_CLR(i, &master_set);  // 클라이언트 소켓을 세트에서 제거
            std::cout << "Client disconnected: " << i << std::endl;
          } else {  // 데이터를 수신한 경우
            std::cout << "Message from client " << i << ": " << buffer
                      << std::endl;
          }
        }
      }
    }
  }

  close(sock);
  return 0;
}

kqueue 적용

#include <fcntl.h>
#include <netinet/in.h>
#include <sys/event.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cstring>
#include <iostream>

#define MAX_CLIENTS 30

int main() {
  int sock = socket(AF_INET, SOCK_STREAM, 0);
  sockaddr_in serv_addr;
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(8080);

  bind(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
  listen(sock, MAX_CLIENTS);

  int kq = kqueue();
  struct kevent evSet;
  EV_SET(&evSet, sock, EVFILT_READ, EV_ADD, 0, 0, NULL);
  kevent(kq, &evSet, 1, NULL, 0, NULL);

  while (true) {
    struct kevent evList[MAX_CLIENTS];
    int nev = kevent(kq, NULL, 0, evList, MAX_CLIENTS, NULL);
    if (nev < 1) {
      perror("kevent");
      exit(EXIT_FAILURE);
    }

    for (int i = 0; i < nev; ++i) {
      if (evList[i].ident == sock) {
        sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        int client_sock =
            accept(sock, (struct sockaddr *)&client_addr, &client_len);

        // 소켓을 non-blocking 모드로 설정
        int flags = fcntl(client_sock, F_GETFL, 0);
        fcntl(client_sock, F_SETFL, flags | O_NONBLOCK);

        EV_SET(&evSet, client_sock, EVFILT_READ, EV_ADD, 0, 0, NULL);
        kevent(kq, &evSet, 1, NULL, 0, NULL);

        std::cout << "New client connected: " << client_sock << std::endl;
      } else {
        char buffer[256] = {0};
        int nbytes = read(evList[i].ident, buffer, 256);
        if (nbytes <= 0) {
          close(evList[i].ident);

          EV_SET(&evSet, evList[i].ident, EVFILT_READ, EV_DELETE, 0, 0, NULL);
          kevent(kq, &evSet, 1, NULL, 0, NULL);

          std::cout << "Client disconnected: " << evList[i].ident << std::endl;
        } else {
          std::cout << "Message from client " << evList[i].ident << ": "
                    << buffer << std::endl;
        }
      }
    }
  }

  close(sock);
  return 0;
}

http 통신 적용 중..

#include <fcntl.h>
#include <netinet/in.h>
#include <sys/event.h>
#include <sys/socket.h>
#include <unistd.h>

#include <cstring>
#include <iostream>
#include <unordered_map>

#define MAX_CLIENTS 30

#include <fstream>

std::string read_file(const std::string& path) {
  std::ifstream file(path, std::ios::binary | std::ios::ate);
  std::streamsize size = file.tellg();
  file.seekg(0, std::ios::beg);

  std::string buffer(size, '\0');
  if (!file.read(&buffer[0], size)) {
    return "";
  }

  return buffer;
}

std::string extract_url(const std::string& request) {
  std::size_t start = request.find(' ') + 1;
  std::size_t end = request.find(' ', start);
  return request.substr(start, end - start);
}

int main() {
  int sock = socket(AF_INET, SOCK_STREAM, 0);
  sockaddr_in serv_addr;
  serv_addr.sin_family = AF_INET;
  serv_addr.sin_addr.s_addr = INADDR_ANY;
  serv_addr.sin_port = htons(8080);

  bind(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
  listen(sock, MAX_CLIENTS);

  int kq = kqueue();
  struct kevent evSet;
  EV_SET(&evSet, sock, EVFILT_READ, EV_ADD, 0, 0, NULL);
  kevent(kq, &evSet, 1, NULL, 0, NULL);

  // 각 클라이언트에서 수신한 데이터를 저장하는 버퍼
  std::unordered_map<int, std::string> buffers;

  while (true) {
    struct kevent evList[MAX_CLIENTS];
    int nev = kevent(kq, NULL, 0, evList, MAX_CLIENTS, NULL);
    if (nev < 1) {
      perror("kevent");
      exit(EXIT_FAILURE);
    }

    for (int i = 0; i < nev; ++i) {
      if (evList[i].ident == sock) {
        sockaddr_in client_addr;
        socklen_t client_len = sizeof(client_addr);
        int client_sock =
            accept(sock, (struct sockaddr*)&client_addr, &client_len);

        // 소켓을 non-blocking 모드로 설정
        int flags = fcntl(client_sock, F_GETFL, 0);
        fcntl(client_sock, F_SETFL, flags | O_NONBLOCK);

        EV_SET(&evSet, client_sock, EVFILT_READ, EV_ADD, 0, 0, NULL);
        kevent(kq, &evSet, 1, NULL, 0, NULL);

        std::cout << "New client connected: " << client_sock << std::endl;

      } else {
        char buffer[256] = {0};
        int nbytes = read(evList[i].ident, buffer, 256);
        if (nbytes <= 0) {
          close(evList[i].ident);

          EV_SET(&evSet, evList[i].ident, EVFILT_READ, EV_DELETE, 0, 0, NULL);
          kevent(kq, &evSet, 1, NULL, 0, NULL);

          std::cout << "Client disconnected: " << evList[i].ident << std::endl;
        } else {
          buffers[evList[i].ident] += buffer;

          // HTTP 헤더의 끝 확인
          if (buffers[evList[i].ident].find("\r\n\r\n") != std::string::npos) {
            std::string url = extract_url(buffers[evList[i].ident]);

            if (url == "/favicon.ico") {
              std::string body = read_file("favicon.ico");
              std::string response =
                  "HTTP/1.1 200 OK\r\n" + "Content-Type: image/x-icon\r\n" +
                  "Content-Length: " + std::to_string(body.size()) +
                  "\r\n\r\n" + body;
              write(evList[i].ident, response.c_str(), response.size());
            } else {
              // HTTP 응답을 보내는 부분
              std::string body =
                  "<html><body><h1>Hello, World!</h1></body></html>";
              std::string response =
                  "HTTP/1.1 200 OK\r\nContent-Type: "
                  "text/html\r\nContent-Length: " +
                  std::to_string(body.size()) + "\r\n\r\n" + body;

              write(evList[i].ident, response.c_str(), response.size());
            }

            // 버퍼를 비웁니다.
            buffers[evList[i].ident].clear();
          }
        }
      }
    }
  }

  close(sock);
  return 0;
}
Clone this wiki locally