열렬히.뛰기

4. TCP 기반 서버 (1)

네트워크 > 소켓 프로그래밍 > 소켓 프로그래밍 > 4. TCP 기반 서버 (1)

이론 : TCP/IP

네트워크 이론과 겹치는 내용이므로 짧게 다루고 넘어감.

c
사용자
-----------------
응용 계층 -> 응용, 표현, 세션         (html)
전송 계층 -> 전송                   (tcp / udp)
인터넷 계층 -> 네트워크               (ip)
네트워크 접속 계층 -> 데이터 링크, 물리  (link)
-----------------
전선 

서버 구현하기

서버는 다음과 같은 과정을 거친다.

mermaid
flowchart LR
	socket["socket()"] --> 
	bind["bind()"] --> 
	listen["listen()"] --> 
	accept["accept()"] -->
	data["read() / write()"] -->
	close["close()"]
  • socket()과 bind()에 관한 부분은 앞에서 이미 설명
  • listen(), accept() 에 관해 설명.

listen()

c
#include <sys/socket.h>

int listen(int sock, int backlog);
  • sock : 소켓 = 파일 디스크립터
  • backlog : “연결요청 대기 큐”의 크기.

연결요청 대기 큐

  • 클라이언트의 연결요청도 인터넷을 통해서 흘러 들어오는 일종의 데이터 전송
  • 이를 받아드리려면 당연히 소켓이 하나 있어야 함.
  • 소켓에서 연결요청이 여러 개가 있을 때를 대비한 큐가 바로 연결요청 대기 큐.

accept()

c
#include <sys/socket.h>

int accept(int sock, struct sockaddr *addr, socklen_t addrlen);
  • sock : 소켓
  • addr : 클라이언트의 주소정보를 담을 변수의 주소값
  • addrlen : addr에 전달된 주소의 변수 크기

클라이언트 구현하기

mermaid
flowchart LR
	socket["socket()"] --> 
	connect["connect()"] --> 
	data["read() / write()"] -->
	close["close()"]
  • “소켓의 생성”과 “연결의 요청”이 전부
  • 연결의 요청 = connect()

connect()

c
#include <sys/socket.h>

int accept(int sock, struct sockaddr *servaddr, socklen_t addrlen);
  • sock : 소켓 = 파일 디스크립터
  • servaddr : 연결요청할 서버의 주소정보를 담은 변수의 주소값
  • addrlen : servaddr에 전달된 주소의 변수 크기

서버와 클라이언트의 관계

mermaid
sequenceDiagram
    client->>server: 연결요청
		server-->>client: "데이터 송수신"
		server-->>client: "데이터 송수신"
    client-)server: "close()"
    server-)client: "close()"
  • 사전준비 : server의 listen()
  • 연결요청 : client의 connect() → server의 accept()
  • 데이터 송수신 : read() / write()
  • 연결종료 : close()

iterative 기반 서버

mermaid
flowchart
	socket["socket()"] --> 
	bind["bind()"] --> 
	listen["listen()"] --> 
	accept["accept()"] -->
	data["read() / write()"] -->
	close1["close() - client"] -->
	close2["close() - server"]

	close1 --> accept
  • 여러 클라이언트의 요청을 받을 수 있도록 바꾼 모습
    • 그래서 iterative 서버라는 것.
    • 계속 요청을 넣으면 계속 수락.
  • 반복문을 삽입해서 accept 함수를 반복.
github.com/InryeolChoi/socket_programming/tree/main/part1/chap4

문제점

echo_client.cecho_server.c 에 적힌 입/출력에 관한 부분이다.

c
/* echo_client.c */

// write()를 통해 sock에 message를 쓴다. (echo 서버로 보내짐.)
write(sock, message, strlen(message));
----------------------------------------------------------------
/* echo_server.c */

// read()를 통해 message를 읽고, write()를 통해 message를 작성
// 메시지가 안 오면(=클라이언트가 q를 누르면) str_len == 0으로 인식하고 종료
while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
	write(clnt_sock, message, str_len);
---------------------------------------------------------------
/* echo_client.c */

// read()를 통해 서버가 적은 sock의 문자열을 읽는다. 
str_len = read(sock, message, BUF_SIZE - 1);

// 해당 문자열 출력
message[str_len] = 0;
printf("message from server : %s", message);

문제가 생길 수 있는 상황은 다음과 같다.

  1. client의 write()에 적힌 문자열이 아주 길다고 가정해보자
  2. server에서 문자열의 일부만 read().
  3. server에서 그 부분만 다시 write() 해서 보냄.
  4. client의 read()는 처음 보낸 문자열의 일부만 출력.