반응형
TCP (Transmission Control Protocol)
- 데이터 전송과정의 컨트롤
TCP/IP 프로토콜 스택
- 표준화 작업을 통한 ‘개방형 시스템 (Open System)’
LINK 계층 | - 물리적인 영역의 표준화 - LAN, WAN, MAN 등 같은 네트워크 표준과 관련된 프로토콜을 정의하는 영역 |
IP 계층 | - 목적지로 데이터를 전송하기 위한 중간경로 (경로탐색) - 하나의 데이터 패킷이 전송되는 과정에 중점 - 데이터를 전송할 때마다 거쳐야 할 경로를 선택, but, 경로가 읽정치 않음 → 데이터 손실, 오류 등의 문제 발생할 수 있음 → 오류발생에 대한 대비가 되어있지 않은 프로토콜 |
TCP/UDP 계층 (Transport 계층) | - IP계층에서 알려준 경로정볼르 바탕으로 데이터의 실제 송수신 수행 - TCP : 확인절차를 걸쳐서 신회성 없는 IP에 신뢰성을 부여한 프로토콜 |
Application 계층 | - 프로그램의 성격에 따라 클라이언트와 서버간의 데이터 송수신에 대한 약속 (규칙) 존재 |
TCP 서버의 기본적인 함수호출 순서
함수 | 기능 | TCP 동작 |
---|---|---|
socket() | 소켓 생성 | |
bind() | 소켓의 주소정보(IP, PORT) 할당 | |
listen() | 연결요청이 가능한 상태로 변경 | - 연결요청 대기상태 |
accept() | 연결요청에 대한 수락 | - ‘연결요청 대시 큐’에서 대기중인 클라이언트의 연결요청을 수락 |
- 연결요청 대기상태
#include <sys/socket.h> // Linux int listen(int sock, int backlog); // 성공 시 0, 실패 시 -1 반환
- sock : 서버 소켓 (리스닝 소켓) 파일 디스크립터
- backlog : 연결요청 대기 큐 (Queue)의 크기정보 전달. 클라이언트의 연결요청을 몇개까지 대기시킬 수 있는지
- 서버의 경우에는 최소 15이상을 전달
- 클라이언트의 연결요청 수락
#include <sys/socket.h> int accept(int sock, struct sockaddr * addr, socklen_t * addrlen); // 성공 시 파일 디스크립터, 실패 시 -1 반환
- 함수 호출 성공 시 내부적으로 데이터 입룰력에 사용할 소켓 생성, 그 소켓의 파일 디스크립터 반환
- 소켓이 자동 생성
- 연결요청을 한 클라이언트 소켓에 연결까지 이뤄짐
- 함수 호출 성공 시 내부적으로 데이터 입룰력에 사용할 소켓 생성, 그 소켓의 파일 디스크립터 반환
TCP 클라이언트의 기본적인 함수호출 순서
함수 | 기능 | TCP 동작 |
---|---|---|
socket() | 소켓 생성 | |
connect() | 연결요청을 진행 | - 연결요청 - 자동으로 소켓에 IP와 PORT 정보가 할당됨 → bind 함수를 명시적으로 호출할 필요 없음 |
- 연결요청
#include <sys/socket.h> int connect (int sock, struct sockaddr * servaddr, socklen_t addrlen); // 성공 시 0, 실패 시 -1 반환
- sock : 클라이언트 소켓의 파일 디스크립터
- servaddr : 연결요청 할 서버의 주서정보를 담은 변수의 주소값
- 상황
- 서버에 의해 연결 요청이 접수됨 = 클라이언트의 연결요청 정보가 서버의 연결요청 대기 큐에 등록된 상황
→ 당장 서비스가 이뤄지지 않을 수 도 있음
- 네트워크 단절 등 오류상황이 발생해서 연결요청이 중단됨
TCP 소켓에 존재하는 입출력 버퍼
- write 함수가 호출되는 순간 데이터는 출력 버퍼로 이동
- read 함수가 호출되는 순간 입력버퍼에 저장된 데이터를 읽음
- 특징
- 입출력 버퍼는 TCP 소켓 각각에 대해 별도로 존재
- 입출력 버퍼는 소켓생성시 자동으로 생성
- 소켓을 닫아도 출력버퍼에 남아있는 데이터는 계속해서 전송이 이뤄진다
- 소켓을 닫으면 입력버퍼에 남아있는 데이터는 소멸된다
- 슬라이딩 윈도우 (Sliding Window)
- 데이터의 흐름까지 컨트롤
TCP 동작원리 (흐름제어)
- 상대 소켓과의 연결
- Three-way handshaking
- A → SYN(1000, 0) → B
- A ← SYN+ACK(2000, 1001) ← B
- A → ACK(1001, 2001) → B
- Three-way handshaking
- 상대 소켓과의 데이터 송수신
- ACK 번호 = SEQ 번호 + 전송된 바이트 크기 + 1 (다음번에 전달된 SEQ 번호를 알리기 위함)
- 상대 소켓과의 연결종료
- Four-way handshaking
- A → FIN (5000, 0) → B
- A ← ACK (7500, 5001) ← B
- A ← FIN (7501, 5001) ← B
- A → ACK (5001, 7502) → B
- Four-way handshaking
우아한 연결종료
- 완전종료 : close / closesocket
- 데이터를 전송하는 것과 수신하는 것이 불가능한 상황
- 데이터의 송수신에 사용되는 스트림의 일부만 종료 (Half-close)하는 방법
- 전송은 가능하지만 수신은 불가
- 수신을 가능하지만 전송은 불가
- 하나의 스트림만 끊는 것
#include <sys/socket.h> int shutdown(int sock, int howto);
- sock : 종료할 소켓의 파일 디스크립터
- howto : 종료방법에 대한 정보 전달
SHUT_RD 입력 스트림 종료 - 입력 스트림 종료, 입력관련 함수 호출 안됨 SHUT_WR 출력 스티림 종료 - 데이터 전송 불가능 - 출력 버퍼에 아직 전송되지 못하고 남아있는 데이터가 존재하면 해당 데이터는 목적지로 전송 SHUT_RDWR 입출력 스트림 종료 - 입력 스트림과 출력 스트림이 모두 종료 - shutdown 함수를 2번 호출 하는것과 같음
- 출력 스트림을 종료하면 상대 호스트로 EOF가 전송됨
소켓의 다양한 옵션
소켓의 다양한 옵션
- SOL_SOCKET : 소켓에 대한 가장 일반적인 옵션들
- IPPROTO_IP : IP 프로토콜에 관련된 사항들
- IPPROTO_TCP : TCP 프로토콜에 관련된 사항들
Protocol Level | Option Name | Get | Set | 기능 |
---|---|---|---|---|
SOL_SOCKET | SO_SNDBUF | O | O | |
소켓 기본 | SO_RCVBUF | O | O | |
SO_REUSEADDR | O | O | ||
SO_KEEPALIVE | O | O | ||
SO_BROADCAST | O | O | ||
SO_DONTROUTE | O | O | ||
SO_OOBINLINE | O | O | ||
SO_ERROR | O | X | ||
SO_TYPE | O | X | 소켓 타입 확인 - 소켓의 타입은 소켓 생성 시 한번 결정. 변경 불가 | |
IPPROTO_IP | IP_TOS | O | O | |
IP 프로토콜 | IP_TTL | O | O | |
IP_MULTICAST_TTL | O | O | ||
IP_MULTICAST_LOOP | O | O | ||
IP_MULTICAST_IF | O | O | ||
IPPROTO_TCP | TCP_KEEPALIVE | O | O | |
TCP 프로토콜 | TCP_NODELAY | O | O | Nagle 알고리즘 사용 중단 |
TCP_MAXSEG | O | O |
getsockopt & setsockopt
- 소켓의 옵션을 확인할 때 호출하는 함수
#include <sys/socket.h> int getsockopt(int sock, int level, int optname, void *optval, socklen_t *optlen); // 성공 시 0, 실패 시 -1 반환
- sock : 옵션확인을 위한 소켓의 파일 디스크립터 전달
- level : 확인할 옵션의 프로토콜 레벨 전달
- optname : 확인할 옵션의 이름 전달
- optval : 확인결과의 저장을 위한 버퍼의 주소 값 전달
- optlen : optval로 전달된 주소 값의 버퍼크기를 담고있는 변수의 주소 값
- 함수호출이 완료되면 이 변수에 optval에서 반환된 옵션정보의 크기가 바이트 단위로 저장됨
- 소켓의 옵션을 변경할 때 호출하는 함수
#include <sys/socket.h> int setsockopt(int sock, int level, int optname, const void *optval, socklen_t optlen); // 성공 시 0, 실패 시 -1 반환
- sock : 옵션변경을 위해 소켓의 파일 디스크립터 전달
- level : 변경할 옵션의 프로토콜 레벨 전달
- optname : 변경할 옵션의 이름 전달
- optval : 변경할 옵션정보를 저장한 버퍼의 주소값 전달
- optlen : optval로 전달된 옵션정보의 바이트 단위 크기 전달
SO_REUSEADDR
- 주소할당 에러(Binding Error)
- 동일한 PORT 번호를 기준으로 서버를 재실행하면 “bind() error” 문제 발생. 일정시간 후 (약 3분) 재실행 시 정상적으로 실행
- Time-wait 상태
- 연결 해제 후 소켓이 바로 소멸되지 않고 일정시간 거침 (먼저 연결종료 요청한 server/client 인 경우)
- client 도 존재하나 client의 소켓은 port 번호가 임의로 할당되기 때문에 유동적 → 신경쓸 필요가 없음
- 해당 소켓의 PORT번호가 사용중인 상태
- 종료명령에 대한 ACK 전송을 위해 존재
- 연결 해제 후 소켓이 바로 소멸되지 않고 일정시간 거침 (먼저 연결종료 요청한 server/client 인 경우)
- 주소 재할당
- 재빨리 서버를 재시작 해야하는 경우 존재
- 네트워크 상황이 원활하지 못하는 경우 Time-wait 상태가 언제까지 지속될 지 모름
- SO_REUSEADDR 상태 변경 (Default 0 - FALSE)
- 재빨리 서버를 재시작 해야하는 경우 존재
TCP_NODELAY
- 네이글(Nagle) 알고리즘
- 네트워크상에서 돌아다니는 패킷들의 흘러 넘침을 막기 위해서 1984년에 제안된 알고리즘
앞서 전송한 데이터에 대한 ACK 메시지를 받아야만, 다음 데이터를 전송하는 알고리즘
- ACK를 기다리는 동안 나머지 데이터가 출력버퍼에 쌓이게 되며, ACK 수신시 하나의 패킷으로 전송됨
- 기본적으로 TCP 소켓에 적용됨
- Nagle 알고리즘을 적용하지 않으면 네트워크 트래필(traffic)에 좋지 않은 영향을 미침
- 네트워크의 효율적인 사용을 위해 반드시 적용
- 전송하는 데이터의 특성에 따라 효율성이 달라짐
- 용량이 큰 파일 데이터 전송 : 파일 데이터를 출력버퍼에 밀어넣는 작업이 오래 걸리지 않음 → Nagle 알고리즘을 적용하지 않아도 출력 버퍼를 다 채운 상태에서 패킷 전송 → 패킷의 수가 크게 증가하지 않음으로 ACK를 기다리지 않고 연속해서 데이터 전송하는게 더 빠름
- 데이터의 특성을 정확히 판단하지 않은 상태에서는 Nagle 알고리즘 사용해야 함
- Nagle 알고리즘 중단
// Nagle 알고리즘 사용 종료 설정 int opt_val = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, sizeof(opt_val));
// Nagle 알고리즘 사용 확인 - 설정 (0), 미설정 (1) int opt_val; socklen_t opt_len; opt_len = sizeof(opt_val); getsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (void*)&opt_val, &opt_len);
소켓 옵션 예재
소켓 옵션 예재
SO_TYPE (Windows)
- SOL_SOCKET 레벨, SO_TYPE 옵션이용 소켓 타입 정보(TCP or UDP) 확인
#include <stdio.h> #include <stdlib.h> #include <string.h> // Socket #include <WinSock2.h> #pragma comment(lib,"ws2_32") #define BUF_SIZE 1024 void ErrorHandling(char* message); // SOL_SOCKET 옵션 활용 소켓 타입 (TCP or UDP) 확인하는 예제 int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hTcpSock, hUdpSock; int state, len, sock_type; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); len = sizeof(sock_type); hTcpSock = socket(PF_INET, SOCK_STREAM, 0); hUdpSock = socket(PF_INET, SOCK_DGRAM, 0); printf("SOCK_STREAM: %d \n", SOCK_STREAM); printf("SOCK_DGRAM: %d \n", SOCK_DGRAM); state = getsockopt(hTcpSock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &len); if (state) ErrorHandling("getsockopt() error!"); printf("Socket type one: %d \n", sock_type); state = getsockopt(hUdpSock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &len); if (state) ErrorHandling("getsockopt() error!"); printf("Socket type two: %d \n", sock_type); WSACleanup(); return 0; } void ErrorHandling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
SO_SNDBUF & SO_RCVBUF (Windows)
- 소켓 생성 시 입력버퍼, 출력버처 기본 생성
- 설정을 요구한 대로 크기가 그대로 반영되지 않음, 최소한의 버퍼 크기 등을 고려하여 설정됨
#include <stdio.h> #include <stdlib.h> #include <string.h> // Socket #include <WinSock2.h> #pragma comment(lib,"ws2_32") void ErrorHandling(char* message); void ShowSocketBufSize(SOCKET sock); // SOL_SOCKET 옵션 활용 소켓 타입 (TCP or UDP) 확인하는 예제 int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET hSock; int sndBuf, rcvBuf, state; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) ErrorHandling("WSAStartup() error!"); hSock = socket(PF_INET, SOCK_STREAM, 0); ShowSocketBufSize(hSock); sndBuf = 1024 * 3, rcvBuf = 1024 * 3; state = setsockopt(hSock, SOL_SOCKET, SO_SNDBUF, (char*)&sndBuf, sizeof(sndBuf)); if (state == SOCKET_ERROR) ErrorHandling("setsockopt() error!"); state = setsockopt(hSock, SOL_SOCKET, SO_SNDBUF, (char*)&rcvBuf, sizeof(rcvBuf)); if (state == SOCKET_ERROR) ErrorHandling("setsockopt() error!"); ShowSocketBufSize(hSock); closesocket(hSock); WSACleanup(); return 0; } void ErrorHandling(char* message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } void ShowSocketBufSize(SOCKET sock) { int sndBuf, rcvBuf, state, len; len = sizeof(sndBuf); state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sndBuf, &len); if (state == SOCKET_ERROR) ErrorHandling("getsockopt() error"); len = sizeof(rcvBuf); state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&rcvBuf, &len); if (state == SOCKET_ERROR) ErrorHandling("getsockopt() error"); printf("Input buffer size: %d \n", rcvBuf); printf("Output buffer size: %d \n", sndBuf); }
반응형
'Study > 네트워크 프로그래밍' 카테고리의 다른 글
네트워크 프로그래밍 기본 지식 (0) | 2022.02.23 |
---|---|
[책 추천] 네트워크 기초 지식을 그림으로 알려주는 <모두의 네트워크> (0) | 2020.05.11 |
댓글