데보션앱 소개페이지 바로가기
로그인 선택

신고하기

CLOSE
신고사유 (대표 사유 1개)
상세내용 (선택)
0/200
  • 신고한 게시글은 더 이상 보이지 않습니다.
  • 이용약관과 운영정책에 따라 신고사유에 해당하는지 검토 후 조치됩니다.
  • 허위 신고인 경우, 신고자의 서비스 이용이 제한될 수 있으니 유의하시어 신중하게 신고해 주세요.
(이 회원이 작성한 모든 댓글과 커뮤니티 게시물이 보이지 않고, 알림도 오지 않습니다.)

미리보기

커뮤니티

      1,234

      badge 23.06.15

      글 등록

      카테고리를 선택해주세요.

      DEVOTEE를 활성화 시키면
      지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.

      버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.

      임시저장함에 저장되었습니다. 저장일시 : 2022.5.17 14:29:08

      임시저장함

      제목을 선택하시면 이어서 작성이 가능하며,
      최대 20건까지 저장합니다.
      컨텐츠 유형, 제목, 저장일시, 삭제로 이뤄진 임시저장 목록
      컨텐츠 유형 제목 저장일 삭제

      데보션 블로그 게재 요청

      CLOSE
      • *
      • *

      본인인증

      효율적인 데보션 서비스 이용 및
      고객님의 소중한 개인정보보호를 위해
      본인인증을 진행해주세요. 본인인증 미 진행 시 로그인이 제한됩니다.
      본인인증 실패

      본인인증 로그인에 실패하였습니다.
      회원이 아니시거나 본인인증 등록이
      완료되지 않은 사용자입니다.

      회원정보 연결

      [C/PYTHON] Socket 기초 1 - 간단한 http 서버 만들기

      jangwooseok2 23.12.15
      10,950 33 0
      DEVOTEE 요약
      Socket은 운영체제에 내장된 프로그램으로 HTTP 서버를 구축할 때 사용된다. 이 글에서는 c언어와 파이썬 코드를 사용해 소켓을 통해 HTTP 서버를 생성하는 방법을 제시한다. 이를 수행하기 위해 먼저 소켓을 설정하고(IP/port 등 받는 bind()), 요청이 접수되면 클라이언트 소켓을 생성하는 accept(), 데이터를 주고 받는 read/write 또는 recv/send(운영체제에 따라 다름)와 소켓을 닫는 close() 등의 함수를 순차적으로 사용해야 한다.
      DEVOTEE 추천 블로그

      안녕하세요. SKHYNIX DRAM노광PHOTO기술팀 장우석 TL입니다.


      socket은 운영체제에 내장된, 전화를 거는 기능입니다.

      image.png

      조금 더 고급지게 말하면, socket은 응용 계층(L4)에서 그 아래 계층(L3~L1)에 접근하기 위한 인터페이스입니다.

      HTTP 서버를 구성할 때 사용하는 DJANGO, SPRING 등의 라이브러리들도 socket을 통해 네트워크에 접근합니다.


      이 글에서는 socket을 이용하여 http 서버를 만드는 코드를 c언어와 python으로 보여드리고 코드를 설명 드리겠습니다.


      1. simple http server for linux

      //server.c
      //simple http server for linux
      //gcc server.c -o server
      //./server 0.0.0.0 8080
      
      #include <stdio.h>
      #include <stdlib.h>
      #include <string.h>
      #include <unistd.h>
      #include <arpa/inet.h>
      #include <sys/socket.h>
      
      #define BUF_SIZE 1024
      
      int main(int argc, char *argv[]){
      
          int serv_sock;
          int clnt_sock;
      
          struct sockaddr_in serv_addr;
          struct sockaddr_in clnt_addr;
          socklen_t clnt_addr_size;
      
          char req_message[BUF_SIZE];
          int str_len;
      
          //예외처리 : ip와 port를 입력해주지 않으면 메시지를 출력하고 종료
          if(argc!=3){printf("Usage : %s <IP> <PORT>\n", argv[0]); return EXIT_FAILURE;}
      
      
          //소켓 생성
          serv_sock = socket(PF_INET,SOCK_STREAM,0);    //  PF_INET or PF_INET6 or PF_LOCAL or PF_PACKET ,  SOCK_STREAM or SOCK_DGRAM ,  0 or IPPROTO_TCP or IPPROTO_UDP
          if(serv_sock < 0) {printf("socket() failed"); return EXIT_FAILURE;};
      
          //소켓에 넣어줄 ip와 port 정보를 serv_addr 구조체에 맵핑
          memset(&serv_addr, 0, sizeof(serv_addr)); //serv_addr를 null값으로 채움(초기화)
          serv_addr.sin_family = AF_INET;  //AF_INET -> ADDRESS FAMILY = INTERNET (000.000.000.000 방식의 주소 표기법을 사용하겠다)
          serv_addr.sin_addr.s_addr = inet_addr(argv[1]); //ip   inet_addr : ip(char*)를 '빅 엔디안 방식으로 정의된 32비트 정수형'으로 변환
          serv_addr.sin_port=htons(atoi(argv[2]));  //port       htons : 2바이트로 적힌 port를(short) '빅 엔디안 형식으로 정의된 16비트 정수형'으로 변환
          
          //소켓에 ip와 포트 정보를 bind
          if (bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) < 0) {printf("bind() failed"); return EXIT_FAILURE;};
      
          //소켓을 listen 상태로 전환, 2번째 인자로 들어가는 정수는 대기 큐의 길이를 정의, 여러 리퀘스트가 줄지어 올 적에, 리퀘스트가 줄을 서서 대기하는 최대 길이를 정의
          if (listen(serv_sock,5) <0){printf("listen() failed"); return EXIT_FAILURE;};
      
          clnt_addr_size=sizeof(clnt_addr);
      
          for(int i=0;i<5;i++){
              //client가 serv_sock로 request를 줄 때까지 process를 멈춤, client로부터 request가 오면 client와 통신하기 위한 clnt_sock를 생성
              clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
              if(clnt_sock < 0){printf("accept() failed"); return EXIT_FAILURE;}
      
              //clnt_sock에 적힌 정보를 read (리눅스는 read, 윈도우는 recv)
              if((str_len=read(clnt_sock,req_message,BUF_SIZE))!=0) {
                  printf("this is request_message\n\n");
                  printf("%s\n",req_message);
      
                  char res_message[]="HTTP/1.1 200 OK\r\n"
                  "Server:simple web server\r\n"
                  "Content-length:2048\r\n"
                  "Content-type:text/html\r\n\r\n"
                  "<html><head><title>simple web server</title></head>\r\n"
                  "<body>hello world!</body>\r\n"
                  "</html>\r\n";
      
                  printf("this is response_message\n\n");
                  printf("%s\n",res_message);
              
                  //clnt_sock를 통해서 client에 정보를 전송 (리눅스는 write, 윈도우는 send)
                  write(clnt_sock, res_message, strlen(res_message));
      
              }
              printf("close sock\n");
              //clnt_sock를 close
              close(clnt_sock);
      
          }
          //serv_sock를 close
          close(serv_sock);
      
          return EXIT_SUCCESS;
      }


      2. simple http server for window

      //server.c
      //server for window
      //gcc server.c -o server.exe -lws2_32
      //server.exe 127.0.0.1 9119
      
      #include <stdio.h>
      #include <stdlib.h>
      #include <winsock2.h>
      
      #define BUF_SIZE 1024
      
      int main(int argc, char *argv[]){
      
          WSADATA wsaData;
      
          SOCKET hServSock;
          SOCKET hClntSock;
      
          SOCKADDR_IN servAddr; 
          SOCKADDR_IN clntAddr;
          int szClntAddr;
      
          char reqMessage[BUF_SIZE];
          int strLen;
      
          //예외처리 : ip와 port를 입력해주지 않으면 메시지를 출력하고 종료
          if(argc!=3){printf("Usage : %s <IP> <port> \n",argv[0]);return EXIT_FAILURE;}
      
          //리눅스엔 없음. 윈도우는 소켓을 열기 전에 반드시 WSAStartup을 실행해줘야 함. 윈도우 소켓 2.2 버전을 사용하겠다고 미리 신청하고 들어가야 함.
          if(WSAStartup(MAKEWORD(2,2),&wsaData) != NO_ERROR) {printf("wsastartup() failed"); return EXIT_FAILURE;}
      
          //소켓 생성
          hServSock = socket(PF_INET,SOCK_STREAM,0);
          if(hServSock == INVALID_SOCKET){printf("socket() failed"); return EXIT_FAILURE;}
      
          //소켓에 넣어줄 ip와 port 정보를 serv_addr 구조체에 맵핑
          memset(&servAddr, 0, sizeof(servAddr));
          servAddr.sin_family = AF_INET;  //protocol
          servAddr.sin_addr.s_addr = inet_addr(argv[1]); //ip
          servAddr.sin_port=htons(atoi(argv[2]));  //port
      
          //소켓에 ip와 포트 정보를 bind
          if(bind(hServSock, (SOCKADDR*) &servAddr, sizeof(servAddr))==SOCKET_ERROR){printf("socket() failed"); return EXIT_FAILURE;};
          
          //소켓을 listen 상태로 전환, 2번째 인자로 들어가는 정수는 대기 큐의 길이를 정의, 여러 리퀘스트가 줄지어 올 적에, 리퀘스트가 줄을 서서 대기하는 최대 길이를 정의
          if (listen(hServSock,5)==SOCKET_ERROR){printf("listen() failed"); return EXIT_FAILURE;};
      
          szClntAddr=sizeof(clntAddr);
      
          for (int i=0;i<5;i++){
              //client가 serv_sock로 request를 줄 때까지 process를 멈춤, client로부터 request가 오면 client와 통신하기 위한 clnt_sock를 생성
              hClntSock=accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);
              if(hClntSock == INVALID_SOCKET){printf("accept() failed"); return EXIT_FAILURE;}
      
              //clnt_sock에 적힌 정보를 read (리눅스는 read, 윈도우는 recv)
              if((strLen=recv(hClntSock,reqMessage,sizeof(reqMessage)-1,0))!=0) {
                  printf("this is request_message\n\n");
                  printf("%s\n",reqMessage);
      
                  char resMessage[]="HTTP/1.1 200 OK\r\n"
                  "Server:simple web server\r\n"
                  "Content-length:2048\r\n"
                  "Content-type:text/html\r\n\r\n"
                  "<html><head><title>simple web server</title></head>\r\n"
                  "<body>hello world!</body>\r\n"
                  "</html>\r\n";
      
                  printf("this is response_message\n\n");
                  printf("%s\n",resMessage);
      
                  //clnt_sock를 통해서 client에 정보를 전송 (리눅스는 write, 윈도우는 send)
                  send(hClntSock, resMessage, strlen(resMessage),0);
      
              }
              printf("close sock\n");
              //clnt_sock를 close
              closesocket(hClntSock);
          }
      
          //serv_sock를 close
          closesocket(hServSock);
          
          //윈도우에만. 윈도우 운영체제에 윈도우 소켓 2.2 사용 끝났다고 알려줘야 합니다
          WSACleanup();
          return EXIT_SUCCESS;
      }


      3. simple http server python

      import socket
      
      class simple_server:
          def __init__(self):
              self.bufsize = 1024
              self.counter = 0
      
          def run(self,ip,port):
              #소켓 생성
              self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
      
              #소켓에 ip와 포트 정보를 bind
              self.sock.bind((ip,port))
      
              #소켓을 listen 상태로 전환, 정수는 대기 큐의 길이를 정의
              self.sock.listen(5)
      
              while self.counter < 5:    
                  #client가 serv_sock로 request를 줄 때까지 process를 멈춤, client로부터 request가 오면 client와 통신하기 위한 clnt_sock를 생성
                  clnt_sock,req_addr = self.sock.accept()
      
                  #clnt_sock에 적힌 정보를 read
                  req_message = clnt_sock.recv(self.bufsize)
      
                  print("this is request message")
                  print(req_message)
      
                  res_message = '''HTTP/1.1 200 OK
      Server:simple web server
      Content-length:2048
      Content-type:text/html
      
      <html><head><title>simple web server</title></head>
      <body>hello world!</body>
      </html>
      
      '''.encode("utf-8")
      
                  print("this is response message")
                  print(res_message)
      
                  #clnt_sock를 통해서 client에 정보를 전송
                  clnt_sock.send(res_message)
      
                  #clnt_sock를 close
                  clnt_sock.close()
                  self.counter += 1
      
              #sock를 close
              print('close sock')
              self.sock.close()
      
      
      server = simple_server()
      server.run("127.0.0.1",10000)


      4. 설명

      image.png

      SOCKET으로 서버를 만들기 위해선 위와 같은 SOCKET API를 사용해야 합니다.

      서버는 전화가 왔을 때(REQUEST가 왔을 때) 응답을 주는(RESPONSE를 주는) 프로그램입니다.


      전화를 받으려면,

      step 1 - 우선 전화기를 셋업(서버 소켓을 설정)해야 하고(socket, bind, listen)

      step 2 - 전화가 오면 수화기를 들어야 합니다(클라이언트 소켓을 생성).(accept)

      step 3 - 수화기(클라이언트)를 통해 대화를 나눈 뒤(read / write - linux recv / send - window/python)

      step 4 - 전화를 끊어야 합니다.(close)


      4-1. socket()

      image.png

      image.png

      소켓을 생성하기 위해선, 소켓의 주소(전화번호)의 형식과 소켓이 데이터를 주고 받는 방식이 정의 되어야 합니다.

      image.png

      소켓 에는 3가지 인자를 넣어줘야 합니다. domain / type / protocol

      image.png

      domain은 주소의 형식입니다. 보통 PF_INET을 씁니다. PROTOCOL FAMILY INTERNET

      image.png

      type은 데이터를 주고 받는 방식을 정의합니다.

      신뢰도를 중시하는 TCP 방식은 SOCK_STREAM

      속도를 중시하는(주로 스트리밍에 쓰이는) UDP 방식은 SOCK_DGRAM을 사용합니다.

      image.png

      PROTOCOL은 TCP인지 UDP인지 명시적으로 적어주고 싶을 때 적습니다. 그냥 0 넣으면 됩니다.

      (파이선은 입력 불필요)


      4-2. bind()

      image.png

      bind는 socket에 ip와 port를 매핑합니다.

      	int serv_sock;
      	struct sockaddr_in serv_addr;
      
      	//소켓 생성
      	serv_sock = socket(PF_INET,SOCK_STREAM,0);
      
          //소켓에 넣어줄 ip와 port 정보를 serv_addr 구조체에 맵핑
          memset(&serv_addr, 0, sizeof(serv_addr)); //serv_addr를 null값으로 채움(초기화)
          serv_addr.sin_family = AF_INET;  //AF_INET -> ADDRESS FAMILY = INTERNET (000.000.000.000 방식의 주소 표기법을 사용하겠다)
          serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //ip   inet_addr : ip(char*)를 '빅 엔디안 방식으로 정의된 32비트 정수형'으로 변환
          serv_addr.sin_port=htons(atoi("9190"));  //port       htons : 2바이트로 적힌 port를(short) '빅 엔디안 형식으로 정의된 16비트 정수형'으로 변환
          
          //소켓에 ip와 포트 정보를 bind
          bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr));

      image.png

      소켓을 생성한 뒤, 소켓에 ip/port를 bind 해주는 코드는 위와 같습니다. sockaddr_in 구조체 안에 ip와 port 정보를 매핑하여 넣어주면 됩니다.


      4-3 listen()

      image.png

      image.png

      listen은 단순합니다. 대기 상태로 만들어줄 소켓과 정수만 넣으면 됩니다.


      4-4 accept()

      image.png

      accept는 전화기(serv_sock)에 전화가 올 때까지 프로세스를 스탑했다가

      누군가(클라이언트)가 전화를 걸면(requests하면) 수화기를 드는(clnt_sock를 만드는) 함수 입니다.

      accept에는 전화기(serv_sock)와 clnt_sock의 정보를 담을 address 구조체와 address 구조체의 크기가 들어갑니다.


      4-5 read / write (리눅스) recv / send (윈도우/파이선)

      image.png

      read, recv는 데이터를 읽는 함수 입니다.

      서버가 클라이언트가 보낸 메시지를 받을 때 씁니다.

      image.png

      write, send는 데이터를 보내는 함수 입니다.

      서버가 클라이언트에게 메시지를 보낼 때 씁니다.


      4-6 close()

      image.png

      image.png

      close는 소켓을 닫는 함수 입니다.

      수화기를 내려놔야, 다음 전화를 받을 수 있겠죠. 그게 clnt_sock을 클로즈하는 것입니다.

      serv_sock를 클로즈 하는 건 전화선을 뽑는 것이라고 보시면 됩니다.

      for(int i=0;i<5;i++){
              //client가 serv_sock로 request를 줄 때까지 process를 멈춤, client로부터 request가 오면 client와 통신하기 위한 clnt_sock를 생성
              clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
              if(clnt_sock < 0){printf("accept() failed"); return EXIT_FAILURE;}
      
              //clnt_sock에 적힌 정보를 read함 (리눅스는 read, 윈도우는 recv)
              if((str_len=read(clnt_sock,req_message,BUF_SIZE))!=0) {
                  printf("this is request_message\n\n");
                  printf("%s\n",req_message);
      
                  char res_message[]="HTTP/1.1 200 OK\r\n"
                  "Server:simple web server\r\n"
                  "Content-length:2048\r\n"
                  "Content-type:text/html\r\n\r\n"
                  "<html><head><title>simple web server</title></head>\r\n"
                  "<body>hello world!</body>\r\n"
                  "</html>\r\n";
      
                  printf("this is response_message\n\n");
                  printf("%s\n",res_message);
              
                  //clnt_sock를 통해서 client에 정보를 전송함 (리눅스는 write, 윈도우는 send)
                  write(clnt_sock, res_message, strlen(res_message));
      
              }
              printf("close sock\n");
              //clnt_sock를 close
              close(clnt_sock);
      
          }

      수화기를 들어서 전화를 받고(accept), 수화기를 내려놓고(close(clnt_sock)),

      수화기를 들어서 전화를 받고(accept), 수화기를 내려놓고(close(clnt_sock)),

      루프를 만들 수 있습니다.


      4-7 http

      http는 '웹 페이지의 정보를 어떤 식으로 구성하여야 하는가에 대한 규약'입니다.

      HTTP/1.1 200 OK
      Server:simple web server
      Content-length:2048
      Content-type:text/html
      
      <html><head><title>simple web server</title></head>
      <body>hello world!</body>
      </html>

      위 코드에서 이런 문자열이 있는데, 이게 제가 만든 서버가 접속자들에게 건넨 http 응답 메시지 입니다.

      보통은 이런 문자열을 DJANGO나 SPRING이 알아서 만들어줍니다.

      GET / HTTP/1.1
      Host: 127.0.0.1:9119
      Connection: keep-alive
      Cache-Control: max-age=0
      sec-ch-ua: "Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"
      sec-ch-ua-mobile: ?0
      sec-ch-ua-platform: "Windows"
      Upgrade-Insecure-Requests: 1
      User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36
      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
      Sec-Fetch-Site: none
      Sec-Fetch-Mode: navigate
      Sec-Fetch-User: ?1
      Sec-Fetch-Dest: document
      Accept-Encoding: gzip, deflate, br
      Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7
      Cookie: csrftoken=lYe0szu9BD6kOsiNflpH8Ntsc9sLx1fh

      이건 구글 크롬으로 위의 방식으로 만든 서버에 접속했을 때 콘솔창에 올라오는 메시지 입니다.

      구글 크롬이 이런 HTTP 요청 메시지를 제 서버에 전송해준 것입니다.


      이상입니다. 도움 되셨으면 좋겠습니다.

      댓글 0

      DEVOTEE를 활성화 시키면
      지금 작성한 댓글에 AI가 댓글을 달아줍니다.

      jangwooseok2 님의 최신 블로그

      더보기

      DEVOTEE 추천 블로그

      동영상 기고하기