-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
313 additions
and
1 deletion.
There are no files selected for viewing
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
--- | ||
title: TCP/IP 소켓 프로그래밍 4 | ||
tags: ['Network', 'Computer Science', 'iOS'] | ||
--- | ||
|
||
## TCP / IP 프로토콜 스택 | ||
|
||
`TCP/IP`는 `프로토콜 스택이다.` 프로토콜이 스택 형태로 쌓여 있다는 것을 의미한다. 인터넷 기반이 데이터 송수신을 목적으로 설계된 스택을 의미한다. | ||
|
||
소켓 프로그래밍 기준으로는 데이터 송수신 과정을 네 영역으로 계층화하여 이야기 할 수도 있다. | ||
|
||
1. `LINK 계층`: 물리적인 계층 | ||
2. `IP 계층`: 라우팅 알고리즘 표준화, 경로 설정 | ||
3. `TCP 계층 or UDP 계층`: 구체적인 데이터 전송 방식의 구분 | ||
4. `APPLICATION 계층`: | ||
|
||
> OSI 7 계층 모델은 물리 계층, 데이터 링크 계층, 네트워크 계층, 전송 계층, 세션 계층, 표현 계층, 응용 계층으로 구성되어 있다. | ||
각 영역은 영역별로 전문화 되어 있으며, 동시에 표준화 되어 있다. 라우터는 IP계층에서 동작하는데 IP계층에서 따르는 라우팅 알고리즘을 라우터 내에 적용해야 하는 것이 바로 표준화가 갖는 의미이다. | ||
|
||
소켓을 생성하면 보통 `LINK`, `IP`, `TCP & UDP` 계층까지 자동화해준다. 개발자는 `APPLICATION` 계층에서 컨트롤만 잘 해주면 된다. 전송 형태를 개발자가 정의하고 소켓 생성 및 데이터 전송 타이밍, 형태 등 다양하게 직접 커스텀해주면 된다. | ||
|
||
## TCP 서버의 함수 호출 순서 | ||
|
||
1. `socket()`: 소켓 생성 | ||
2. `bind()`: 소켓 주소 할당, bind 호출 단계 까지는 소켓의 용도가 결정되지 않은 상태이다. | ||
3. `listen()`: 연결요청 대기, 해당 단계를 통해 리스닝 소켓 (서버 소켓)이라는 정체성이 확립된다. | ||
4. `accept()`: 연결 허용 | ||
5. `read() / write()`: 데이터 송수신 | ||
6. `close()`: 연결 종료 | ||
|
||
연결 요청 대기 상태로 진입하는 `listen`함수가 중요하다. | ||
|
||
```c | ||
#include <sys/type.h> | ||
|
||
// 성공 시 0, 실패 시 -1 반환 | ||
int listen(int sock, int backlog); | ||
``` | ||
1. `sock`: 연결 요청 대기상태에 두고자 하는 소켓 파일 디스크립터를 전달한다. | ||
2. `backlog`: 연결요청 대기 큐 크기정보를 전달한다. 클라이언트의 연결 요청 갯수를 제한할 수 있다. | ||
리스닝 소켓은 생성 뒤에 클라이언트로부터 날라오는 요청들을 연결 요청 대기 큐에 push하는 작업을 할 뿐이다. 연결 요청 대기큐에서 데이터를 pop하여 클라이언트 소켓과 통신 연결을 형성하는 작업은 `accept`함수 호출을 통해 이루어진다. | ||
```c | ||
#include <sys/socket.h> | ||
// 소켓 시 생성된 소켓 파일 디스크립터, 실패 시 -1 반환 | ||
int accept(int sock, struct sockaddr * addr, socklen_t * addrlen); | ||
``` | ||
|
||
1. `sock`: 서버 소켓 (리스닝 소켓)의 파일 디스크립터 전달. 파일 디스크립터가 전달되는 이유는 연결 요청 대기 큐와 서버 소켓이 1대1 대응 관계이기 때문이다. 서버 소켓이 2개 생성된다는 의미는 연결 요청 대기큐가 2개 생성된다는 의미이다. | ||
2. `addr`: 연결 요청을 한 클라이언트 소켓 주소정보를 담을 주소 값. 함수호출 완료 이후 addr 변수에 클라이언트 주소 정보가 채워진다. | ||
3. `addrlen`: `addr` 변수의 크기를 바이트 단위로 전달한다. 변수의 참조를 전달해야 하며, 함수 호출 완료 이후에는 연결 요청된 클라이언트의 주소 정보 길이가 바이트 단위로 계산되어 할당된다. | ||
|
||
## TCP 클라이언트 함수 호출 순서 | ||
|
||
1. `socket()`: 소켓 생성 | ||
2. `connect()`: 연결 요청. 함수 호출 후 **클라이언트 소켓은 BLOCK 상태가 된다.** 해당 상태에서 빠져나오기 위해서는 서버로부터 `accept`가 호출되었음을 확인 한 뒤에 이루어진다. | ||
3. `read() / write()`: 데이터 송수신 | ||
4. `close()`: 연결 종료 | ||
|
||
```c | ||
#include <sys/socket.h> | ||
|
||
int connect(int sock, const struct sockaddr * servaddr, socklen_t addrlen); | ||
``` | ||
`connect`함수 각 인자가 하는 역할은 리스닝 소켓의 `accept`함수 인자의 역할과 동일하다. | ||
:::warning accept 함수 호출로 생성된 소켓의 포트 번호는? | ||
accept로 생성된 소켓의 포트번호는 **리스닝 소켓 시 할당한 포트 번호와 동일하다.** 편의상 accept 소켓이라고 명명했을 때, 서로 다른 클라이언트 소켓으로부터 서버의 accept 소켓으로 연결이 이루어질 때 어떤 데이터를 전송해야 할지 선택해야 하는 상황을 가정해보자. | ||
 | ||
accept 소켓의 경우 클라이언트와 연결이 맺어질 때 클라이언트의 소켓 주소를 보유하게 된다. 그렇기 때문에 `accept`함수에서 `addr`변수가 존재하게 되는 것이다. | ||
::: | ||
 | ||
위 그림에서 `listen`함수 호출 이후에 클라이언트로부터 `connect`요청이 언제 들어올 줄 알고 `accept`를 호출하는 걸까? 서버는 `accept`함수 호출 이후에 BLOCK 상태가 되어 대기하게 되고, 클라이언트로부터 `connect` 호출이 이루어지는 경우 `accept`를 빠져나와 데이터 송수신이 본격적으로 이루어지게 된다. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
--- | ||
title: TCP/IP 소켓 프로그래밍 5 | ||
tags: ['Network', 'Computer Science', 'iOS'] | ||
--- | ||
|
||
## 클라이언트 & 서버코드 문제점 | ||
|
||
아래는 서버 소켓 코드이다. `accept`호출 이후 `connect`로 요청이 들어온 클라이언트 소켓 파일 디스크립터를 `clnt_sock`에 저장하게 된다. 이후 read & write로 데이터 읽고 쓰기를 진행한다. | ||
|
||
```c | ||
// 서버 소켓 코드 | ||
clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz); | ||
if(clnt_sock==-1) | ||
error_handling("accept() error"); | ||
else | ||
printf("Connected client %d \n", i+1); | ||
|
||
while((str_len=read(clnt_sock, message, BUF_SIZE))!=0) | ||
write(clnt_sock, message, str_len); | ||
|
||
close(clnt_sock); | ||
``` | ||
아래는 클라이언트 소켓 코드 중 일부이다. 아래 클라이언트 코드에서는 메시지를 입력받고 서버에 전달한 뒤, `connect`이후 서버로부터 쓰여진 해당 문장을 그대로 읽어들여 출력하게 된다. | ||
```c | ||
fputs("Input message(Q to quit): ", stdout); | ||
fgets(message, BUF_SIZE, stdin); | ||
if(!strcmp(message,"q\n") || !strcmp(message,"Q\n")) | ||
break; | ||
write(sock, message, strlen(message)); | ||
str_len=read(sock, message, BUF_SIZE-1); | ||
message[str_len]=0; | ||
printf("Message from server: %s", message); | ||
``` | ||
|
||
TCP 소켓의 경우 데이터 경계가 존재하지 않기 때문에 클라이언트로부터 입력받은 문자열 데이터가 어떤 기준으로 구분되는지 알지 못한다. 클라이언트로부터 10바이트 문자열을 입력받더라도 서버가 5바이트 데이터만 읽고 쓰기가 가능하도록 코드를 작성했다면 해당 데이터만큼만 클라이언트에게 전송하게 된다. | ||
|
||
## 문제점 개선 | ||
|
||
클라이언트의 코드를 다음과 같이 수정하면 된다. | ||
|
||
```c | ||
// 문자열 길이 | ||
str_len=write(sock, message, strlen(message)); | ||
|
||
recv_len=0; | ||
while(recv_len < str_len) { | ||
recv_cnt = read(sock, &message[recv_len], BUF_SIZE-1); | ||
if(recv_cnt==-1) { | ||
error_handling("read() error!"); | ||
} | ||
recv_len+=recv_cnt; | ||
} | ||
message[recv_len]=0; | ||
printf("Message from server: %s", message); | ||
``` | ||
1. `write`가 리턴하는 입력 문자열에 대한 바이트 수를 변수에 저장해둔다. | ||
2. `read`를 통해 읽어들인 바이트 수를 0으로 초기화 해둔 카운트 변수에 더해준다. | ||
3. 카운트 변수에 저장되 바이트 수가 `write`에서 반환받은 바이트 수보다 적으면, 서버로부터 데이터를 아직 다 읽어들이지 못한 것이므로 반복문을 통해 데이터 읽어들이기를 다시 요청한다. | ||
위 코드를 통해 명심할 점은 TCP 기반의 소켓이 데이터 경계가 없기 때문에 데이터 경계 구분이 필요한 로직을 구성할 경우에 클라이언트 단에서 구분지을 로직을 구현해야 한다는 것이다! | ||
## TCP 입출력 버퍼 | ||
대부분의 입출력 과정에 버퍼가 필요한 이유는 서로 관계가 맺어진 채로 서로간의 입출력에만 집중할 수 없기 때문이다. 다른 작업을 하는 과정에서 버퍼를 살펴보았을 때 처리할 데이터가 있으면 가져가서 작업을 처리하게 되는 방식이다. | ||
TCP 프로토콜도 이러한 이유로 입출력 버퍼가 필요하다. 아래 그림을 살펴보자 | ||
 | ||
`write()` & `read()` 함수 호출은 그 즉시 데이터의 송수신이 이루어진다는 것이 아니라, 데이터를 입출력 버퍼에 실어 나르는 행위를 의미한다. 입출력 버퍼에 데이터가 적재되고 난 이후에 OS가 알아서 데이터를 가져다가 사용하는 방식이다. | ||
`write` 호출 이후 출력버퍼에 데이터를 보유한 채로 TCP 프로토콜에 따라 OS가 데이터 수신자에게 데이터를 보내도 되는지 물어본다. 수신자는 송신자에게 50바이트까지 데이터를 가져와도 된다고 알람을 보내고, 이에 따라 송신자는 데이터를 수신자 입력 버퍼에 전달한다. | ||
데이터 수신자의 입력버퍼 상황에 따라 데이터가 미닫이 창문이 차근히 옆으로 밀려나는 듯한 모습을 보이기 때문에 이를 슬라이딩 윈도우 프로토콜이라고 부른다. | ||
슬라이딩 프로토콜로 인해 버퍼가 흘러 넘쳐서 데이터가 소실되는 일은 없게 된다. | ||
## TCP 동작 1. 상대 소켓과의 연결 | ||
다음은 소켓 통신 과정에서의 메시지 종류를 나타낸다. | ||
 | ||
1. `SYN`: `SEQ`는 보내는 데이터 패킷의 번호를 의미한다. | ||
2. `SYN+ACK`: `ACK: 1001`은 1000번 데이터 수신이 정상적으로 이루어졌고 1001번 부터 다시 송신하라는 의미이다. 호스트 B에서 보내는 응답 메시지를 `SEQ 2000`번에 할당해서 호스트 A에게 송신한다. | ||
3. `ACK`: 호스트 A가 다시 보내는 메시지는 `SEQ 1001`번이고, 호스트 B에게 다음 메시지부터는 2001부터 패킷 번호를 할당해서 보내면 된다고 응답한다. | ||
전달하고자 하는 실제 데이터가 정상적으로 송수신이 이루어졌음 외에도, 잘 받았음을 나타내는 알람 목적의 메시지도 `SEQ`넘버가 할당되어 송수신 된다. | ||
## TCP 동작 2. 상대 소켓과의 데이터 송수신 | ||
데이터 송수신 과정에서 `ACK`번호는 전송된 바이트 크기만큼 증가시킨다. 즉 `ACK = SEQ + 전송된 바이트 크기 + 1`에 해당한다. | ||
 | ||
데이터 송신자의 SEQ를 확인하고 수신자는 `ACK번호를 수신한 데이터 크기 + increment`를 해줌으로써 데이터를 손실없이 잘 받았음을 묵시적으로 알리게 된다. | ||
또한 SEQ 전송 이후 타이머가 작동하게 되고 SEQ에 대한 ACK가 전송되지 않은 경우 동일한 SEQ를 기준으로 데이터를 재전송하게 된다. | ||
## TCP 동작 3. 상데 소켓과의 연결 종료 | ||
종료에 대한 알람까지 송수신자가 서로 주고받음으로써 여전히 데이터를 전달할 의사가 있는지 서로 알 수 있다. | ||
 | ||
1. 데이터 송신자가 `FIN`메시지를 SEQ 번호와 함께 보낸다. | ||
2. 수신자가 `FIN` 메시지를 받고 `SEQ + 1` ACK를 보낸다. | ||
3. 수신자도 송신자에게 `FIN`을 보낸다. | ||
4. 송신자도 수신자에게 `ACK`를 보냄으로써 최종적인 소켓 통신이 마무리된다. | ||
FIN 메시지까지 서로 주고받음으로써 이제 더 이상 전달할 데이터가 없음을 확신하고 통신을 종료한다. |