| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- 지도학습
- gdgm
- 함수
- 레이블인코딩
- SK쉴더스
- List
- node.js
- 꽉뚝짝 시장
- dropna
- 패키지
- echo 명령어
- 방콕
- 수업복습
- 성능평가지표
- 데이터분포
- pandas
- 생성형AI
- 루키즈33기
- git push -u
- 루키즈
- 태국
- OT후기
- sqld
- 블록(Block)
- 클래스
- tunder client
- 보안공부
- 예외처리
- Package
- Linked List
- Today
- Total
개발 블로그
서버란 무엇인가 (소켓 & 멀티 프로세스) 본문
🐢 서버란
os에 의해 동작하는 프로세스. 클라이언트의 역할을 하는 프로세스와 소켓을 통해 IPC를 수행하는 것.
🐢 IP주소와 포트번호
IP주소: 유일하게 식별이 가능한 수단으로 절대 겹치면 안되고 고유해야 한다.
❗IP주소 부족 문제 발생
사설 IP 주소를 사용하며 이를 공인 IP 주소로 바꾸는 NAT, 서브네팅, IPv6 등 어러 기술이 존재한다.
컴퓨터가 직접 네트워크에서 통신을 하는 것이 아니고 컴퓨터에서 동작하는 프로세스가 또 다른 컴퓨터의 프로세스와 통신을 하는 것이다.
❗프로세스 간의 통신을 위해 IPC를 하되, 그저 다른 시스템의 프로세스와 IPC를 한다고 생각하는 것이 중요하다.
🌠 IPC(Inter-Process Commnication)
포트 번호: IP주소를 통해 컴퓨터를 식별한 후 해당 컴퓨터에서 어떤 프로세스에게 데이터를 보낼지 식별한다.
🐢 데이터 송신 과정
- 데이터를 송신
- 서버 프로세스가 os의 write 시스템 콜을 통해 소켓에 데이터를 보냄
- 이후 TCP/IP 계층과 IP 계층 그리고 대표적으로 Ethernet을 거쳐 흐름 제어, 라우팅 등의 작업을 함
- 마지막으로 NIC(랜 카드)를 통해 외부로 데이터를 보냄
- 데이터 수신
- 송신할 때와 반대로 NIC에서 데이터를 수신 함
- 인터럽트를 통해 드라이버로 데이터를 옮김
- 이후 네트워크 스택에서 데이터가 이동하며 소켓에 데이터가 담김
- 최종적으로 수신 대상이 되는 프로세스에 데이터가 도달
🐢 소켓
- TCP 전용 소켓 (= stream socket)
- 신뢰성 있는 데이터 송수신
- TCP 소켓의 핵심: accept() 시스템 콜
- UDP 소켓 (= datagram socket)
- 비연결지향
🐢 socket() 시스템 콜
- 소켓을 만드는 시스템 콜
- return 값은 파일 디스크립터
✉️ socket(domain, type, protocol );
domain : IPv4, IPv6중 무엇을 사용할지 결정
type : stream, datagram 소켓 중 선택
protocol : 0, 6, 17 중 0을 넣으면 시스템이 프로토콜을 선택하며, 6이면 tcp, 17이면 udp
int socket_descriptor;
socket_descriptor = socket(AF_INET, SOCK_STREAM, 0);
web server 프로세스가 데이터를 전송하기 위해 write(), read() 시스템 콜을 사용할 때 대상 파일의 파일 디스크립터를 파라미터로 전송하여 os에 어떤 파일에 데이터를 작성할지, 혹은 어떤 파일의 데이터를 요청할지 결정한다.
파일 디스크립터가 소켓의 파일 디스크립터인 경우, 소켓에 데이터를 작성(데이터 송신) 혹은 소켓의 데이터를 읽어들이는(데이터 수신) 동작을 하게 되는 것
socket() 시스템 콜은 미리 Ipv4 통신을 위해 사용할지, Ipv6 통신을 위해 사용할지, TCP를 사용할지 아니면 UDP를 사용할지 틀을 만들어두는 것
🐢 bind() 시스템 콜
✉️ bind(sockfd, sockaddr, socklen_t)
sockfd: 바인딩을 할 소켓의 파일 디스크립터
sockaddr: 소켓에 바인딩 할 아이피 주소, 포트번호를 담은 구조체
socklen_t : 위 구조체의 메모리 크기
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("Socket creation failed");
return 1;
}
struct sockaddr_in server_address;
server_address.sin_family = AF_INET; // IPv4 주소 체계
server_address.sin_addr.s_addr = INADDR_ANY; // 모든 가능한 IP 주소
server_address.sin_port = htons(80); // 포트 번호 80
if (bind(sockfd, (struct sockaddr *)&server_address, sizeof(server_address)) == -1) {
perror("Bind failed");
return 1;
}
// 바인딩 성공 처리 및 작업 수행
return 0;
}- 생성한 소켓에 실제 IP주소와 포트번호를 부여하는 시스템 콜
- os에게 어떤 소켓에 IP주소와 포트번호를 부여할지 알려주기 위해 파라미터에 소켓의 파일 디스크립터를 포함
❗bind() 시스템 콜은 서버에서만 사용
클라이언트는 통신 시 포트번호가 자동으로 부여
🐢 listen() 시스템 콜
❗TCP에서만 사용
- 파라미터로 밭은 파일 디스크립터에 해당하는 소켓을 클라이언트의 연결 요청을 받아들이도록 하며 최대로 받아주는 크기를 backlog로 설정
✉️ listen(sockfd, backlog)
sockfd : 소켓의 파일 디스크립터
backlog : 연결요청을 받아줄 크기 = TCP의 백로그 큐의 크기
#include <sys/socket.h>
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("Socket creation failed");
return 1;
}
// ... 서버 소켓의 주소와 바인딩 설정 ...
int backlog = 10; // 최대 대기열 크기
if (listen(sockfd, backlog) == -1) {
perror("Listen failed");
return 1;
}
// 리스닝 성공 처리 및 연결 요청 처리
return 0;
}
서버 측의 소켓은 listen()이후 대기 상태에서 클라이언트의 연결 요청을 받아주기 위해 backlog queue를 가진 채로 기다림
실제로는 서버에 셀 수 없이 많은 클라이언트가 요청을 보내게 되고 이 요청들은 모두 backlog queue에 저장
🐢 accept() 시스템 콜
✉️ int accept(sockfd, sockaddr , socklen_t);
sockfd : 백로그 큐의 요청을 받아들이기 위한 소켓의 파일 디스크립터
sockaddr : 선입선출로 빼온 연결 요청에서 알아낸 클라이언트의 주소 정보
socklen_t : 위 구조체의 메모리 크기
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(80);
bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));
listen(server_socket, 5);
printf("Server: Waiting for client's connection...\n");
struct sockaddr_in client_address;
socklen_t client_addrlen = sizeof(client_address);
int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_addrlen);
printf("Server: Accepted connection from %s:%d\n",
inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
// 3-way handshake의 나머지 두 단계 수행
char buffer[1024];
ssize_t bytes_received = recv(client_socket, buffer, sizeof(buffer), 0); // 클라이언트의 ACK 받기
if (bytes_received > 0) {
printf("Server: Received ACK from client.\n");
}
accept 시스템 콜은 backlog queue에서 syn을 보내와 대기 중인 요청을 선입선출(큐)로 하나씩 연결에 대한 수립
파라미터를 보면 클라이언트의 IP주소, 포트번호를 받는데 이 값은 백로그 큐에서 가장 앞에 있는 연결요청 구조체에서 알아내서 가져옴
🐢 TCP 3 way handshake
- 클라이언트와 서버 간에 서로 신뢰성 통신 확인 과정
🐢 accept 시스템 콜 이후 멀티 프로세스
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(80); // 웹 서버 포트인 80
bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));
listen(server_socket, 5);
printf("Server: Listening on port 80...\n");
while (1) {
struct sockaddr_in client_address;
socklen_t client_addrlen = sizeof(client_address);
int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_addrlen);
if (fork() == 0) { // 자식 프로세스 <- 이 부분에 집중!
printf("Server: Accepted connection from %s:%d\n",
inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
// 3-way handshake의 나머지 두 단계 수행
// 여기서는 ACK를 보내는 과정만 간단히 보여줍니다.
sleep(1); // 실제로는 필요한 로직 수행
// 서버의 응답 전송
char response[] = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!";
send(client_socket, response, strlen(response), 0);
printf("Server: Sent response to client.\n");
close(client_socket);
exit(0);
}
close(client_socket);
}
close(server_socket);
return 0;
}
fork(): 자식 프로세스 생성
리턴값 = 0 자식 프로세스, 0이 아님 = 원래 본인(부모) 프로세스
fork() 이후 exec() 시스템 콜을 거쳐야 진정한 자식 프로세스가 생김
https://blog.naver.com/joyangel93/220400967632
[C-네트워크] 멀티프로세스 fork() 함수-1
안녕하세요, 쭝입니다. 오늘은 c의 fork() 함수를 이용해서 프로세스안에서 나랑 같은 프로세스를 그대로 ...
blog.naver.com
// 부모 프로세스 입장
#include <stdio.h#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(80); // 웹 서버 포트인 80
bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));
listen(server_socket, 5);
printf("Server: Listening on port 80...\n");
while (1) {
struct sockaddr_in client_address;
socklen_t client_addrlen = sizeof(client_address);
int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_addrlen);
if (fork() == 0 -> false ) {
실행안함
}
}
close(server_socket);
return 0;
}위의 코드를 보면 사실상 부모 프로세스는 연결 요청을 받아주고 자식 프로세스에게 나머지 일을 맡기고 다시 새로운 연결요청을 받아주는 형태인 것을 확인 할 수 있음
// 자식 프로세스 입장
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_address;
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = INADDR_ANY;
server_address.sin_port = htons(80); // 웹 서버 포트인 80
bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));
listen(server_socket, 5);
printf("Server: Listening on port 80...\n");
while (1) {
struct sockaddr_in client_address;
socklen_t client_addrlen = sizeof(client_address);
int client_socket = accept(server_socket, (struct sockaddr *)&client_address, &client_addrlen);
if (fork() == 0 -> true) { // 자식 프로세스
printf("Server: Accepted connection from %s:%d\n",
inet_ntoa(client_address.sin_addr), ntohs(client_address.sin_port));
// 3-way handshake의 나머지 두 단계 수행
// 여기서는 ACK를 보내는 과정만 간단히 보여줍니다.
sleep(1); // 실제로는 필요한 로직 수행
// 서버의 응답 전송
char response[] = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!";
send(client_socket, response, strlen(response), 0);
printf("Server: Sent response to client.\n");
close(client_socket);
exit(0); <-여기서 자식 프로세스가 종료됨
}
close(client_socket);
}
close(server_socket);
return 0;
}부모 프로세스가 새로 만들어준 소켓을 이어받아 이후 남은 잔여 3-way handshake를 수행 후 데이터 통신을 수행
❗자식 프로세스는 새로운 연결 요청을 받지 않고 그저 응답을 준 후 exit(0)을 통해 종료
🫥 서버는 연결을 받는 부분과 응답을 주는 부분이 병렬적으로 이루어져 있음
🐢 멀티 스레드
멀티 프로세스의 단점을 보완하기 위해 리눅스에서 등장
--> 동시성 문제
https://andjjip.tistory.com/291
[Socket 프로그래밍] 18. 멀티쓰레드 기반의 서버구현 - 1
쓰레드 등장 배경 앞에서는 멀티프로세스 기반의 서버구현에 대해 살펴보았다. 이때 보인 프로세스의 생성은 select에 비해 확실히 구분되는 장점이있다. 그러나 나름의 문제점도 있다. 프로세스
andjjip.tistory.com
🐢 HTTP 웹서버 코드
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
int main() {
const char* server_ip = "127.0.0.1";
int server_port = 8080;
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket == -1) {
perror("Socket creation failed");
return 1;
}
struct sockaddr_in server_addr, client_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
server_addr.sin_addr.s_addr = inet_addr(server_ip);
if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("Binding failed");
return 1;
}
if (listen(server_socket, 5) == -1) {
perror("Listening failed");
return 1;
}
printf("Server listening on %s:%d\n", server_ip, server_port);
while (1) {
socklen_t client_addr_len = sizeof(client_addr);
int client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_socket == -1) {
perror("Accepting client failed");
continue;
}
printf("Accepted connection from %s:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
char request[1024];
recv(client_socket, request, sizeof(request), 0);
printf("Received request:\n%s\n", request);
char response[] = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nHello, World!";
send(client_socket, response, sizeof(response), 0);
close(client_socket);
}
close(server_socket);
return 0;
}'Experience > UMC' 카테고리의 다른 글
| AWS (VPC & Internet Gateway & EC2) (0) | 2024.12.27 |
|---|---|
| Database 설계 (0) | 2024.11.19 |
| npm audit 명령어로 프로젝트 취약점 점검 및 자동 해결 방법 (npm audit fix) (0) | 2024.11.19 |