23.06.15
DEVOTEE를 활성화 시키면
지금 작성한 커뮤니티 글에 대해 1개의 댓글을 달아줍니다.
버튼을 누르면 글 수정 시 ChatGPT가 작성한 댓글이 수정됩니다.
| 컨텐츠 유형 | 제목 | 저장일 | 삭제 |
|---|
본인인증 로그인에 실패하였습니다.
회원이 아니시거나 본인인증 등록이
완료되지 않은 사용자입니다.
안녕하세요. SKHYNIX DRAM노광PHOTO기술팀 장우석 TL입니다.
socket은 운영체제에 내장된, 전화를 거는 기능입니다.
조금 더 고급지게 말하면, socket은 응용 계층(L4)에서 그 아래 계층(L3~L1)에 접근하기 위한 인터페이스입니다.
HTTP 서버를 구성할 때 사용하는 DJANGO, SPRING 등의 라이브러리들도 socket을 통해 네트워크에 접근합니다.
이 글에서는 socket을 이용하여 http 서버를 만드는 코드를 c언어와 python으로 보여드리고 코드를 설명 드리겠습니다.
//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;
}//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;
}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)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)
소켓을 생성하기 위해선, 소켓의 주소(전화번호)의 형식과 소켓이 데이터를 주고 받는 방식이 정의 되어야 합니다.
소켓 에는 3가지 인자를 넣어줘야 합니다. domain / type / protocol
domain은 주소의 형식입니다. 보통 PF_INET을 씁니다. PROTOCOL FAMILY INTERNET
type은 데이터를 주고 받는 방식을 정의합니다.
신뢰도를 중시하는 TCP 방식은 SOCK_STREAM
속도를 중시하는(주로 스트리밍에 쓰이는) UDP 방식은 SOCK_DGRAM을 사용합니다.
PROTOCOL은 TCP인지 UDP인지 명시적으로 적어주고 싶을 때 적습니다. 그냥 0 넣으면 됩니다.
(파이선은 입력 불필요)
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));소켓을 생성한 뒤, 소켓에 ip/port를 bind 해주는 코드는 위와 같습니다. sockaddr_in 구조체 안에 ip와 port 정보를 매핑하여 넣어주면 됩니다.
listen은 단순합니다. 대기 상태로 만들어줄 소켓과 정수만 넣으면 됩니다.
accept는 전화기(serv_sock)에 전화가 올 때까지 프로세스를 스탑했다가
누군가(클라이언트)가 전화를 걸면(requests하면) 수화기를 드는(clnt_sock를 만드는) 함수 입니다.
accept에는 전화기(serv_sock)와 clnt_sock의 정보를 담을 address 구조체와 address 구조체의 크기가 들어갑니다.
read, recv는 데이터를 읽는 함수 입니다.
서버가 클라이언트가 보낸 메시지를 받을 때 씁니다.
write, send는 데이터를 보내는 함수 입니다.
서버가 클라이언트에게 메시지를 보낼 때 씁니다.
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)),
루프를 만들 수 있습니다.
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 요청 메시지를 제 서버에 전송해준 것입니다.
이상입니다. 도움 되셨으면 좋겠습니다.
DEVOTEE를 활성화 시키면
지금 작성한 댓글에 AI가 댓글을 달아줍니다.