TCP
연결형
연결형이란 IP와 PORT를 메모리에 기억하고 있는 상태
클라이언트가 연결을 정상적이지 않은 방법으로 끊었을 경우 (PC가 꺼지거나 네트워크선이 뽑히는 등) 서버에서는 언제 연결이 끊겼는지 알 수 있나?
서버에서 클라이언트에 패킷을 보냈을 때의 응답 유무로 연결이 끊겼는지 확인
하트비트 - 연결이 끊겼는지 L7차원에서 확인 (클라 -> 서버)
신뢰성
일대일
데이터 경계 구분 없음
UDP
비연결형
비신뢰성
일대다 (브로드캐스트, 멀티캐스트)
데이터 경계 구분 있음
TCP가 커널에서 하는 일이 더 많다
상대방의 버퍼 확인
양쪽 (클라/서버)
송신버퍼
수신버퍼
100바이트를 두번 송신
send(100)
send(100)
수신 버퍼에 처리되지 않은 60바이트의 데이터가 있을 경우 송신측에서는 데이터를 쪼개서 100바이트 그리고 40바이트의 데이터를 전송한다
수신버퍼에서 데이터를 처리하고 버퍼가 비워지면 나머지 60바이트를 송신한다
TCP 헤더의 윈도우 사이즈 정보로 수신버퍼의 나머지 사이즈를 알 수 있다
디도스(DDoS) 공격
대량의 UDP 패킷을 만들어 보내 상대방이 정상적인 서비스를 하지 못하도록 한다
홀펀칭도 UDP로밖에 할 수 없다
랑데뷰 서버 (ip 고정)
p2p 게임이 UDP를 사용했던 이유
홀펀칭을 사용하기 위해
UDP패킷 헤더 구조가 간단해서 가볍고 빠르다
릴레이서버
데이터 경유의 개념
릴라이어블 UDP (RUDP)
신뢰성 있는 UDP
직접 TCP처럼 구현해야한다
모바일에선 IP와 PORT가 바뀌기 때문에 RUDP 적용을 고려해야 한다
~ 1023 잘 알려진 포트
1024 ~ 49151 등록된 포트
49152 ~ 65535 동적 포트
ws2_32.lib 사용
#pragma comment(lib, "Ws2_32.lib")
WSAGetLastError()
WSAStartup()
int WSAStartup(
WORD wVersionRequired,
LPWSADATA lpWSAData
);
성공 : 0
실패 : 에러코드
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0)
{
printf_s("error WSAStartup()\n");
return 1;
}
FormatMessage()
사용하지 않을 예정
에러코드로 직접 에러처리한다
socket()
closesocket()
멀티스레드 환경에서 주의
closesocket을 실행해서 특정세션의 소켓을 끊는다. 그런데 멀티스레드에선 내가 끊는 순간 다른곳에서 에러가나서 또 끊는 상황이 올 수 있다. closesocket을 두번 하게되는 경우이다. 먼저 소켓이 끊어지는 순간 소켓이 재사용 되어서 다른곳에선 새로운걸로 받아들이고 바로 끊어지게 된다.
라이브서비스에선 문제삼지 않을 수 도 있지만 더미 클라이언트를 사용한 테스트에선 문제가 되기 때문에 처리작업(동기화)을 꼭 해줘야 한다
윈도우 전용으로 나온 WSASocket은 사용하지 않을 예정
설정만 복잡, 쓰기도 복잡
socket API를 사용할 때에는 리턴값을 주의하자
리턴값 0이 성공인 경우도 있고, 실패인 경우도 있다
서버에서 최대로 연결 받을 수 있는 클라이언트가 몇명일까?
혹시 포트 개수만큼? 이 떠올랐다면 틀렸다
서버는 포트를 지정해서 하나만 사용하기 때문에 포트개수 제한이 없다고 봐야한다
소켓을 만들고 TCP 연결이 될 때 마다 논페이지드 메모리를 사용한다. 그 메모리의 한계치까지 접속을 허용한다
socket() 이 실패하는 경우중 WSAENOBUFS 에러는 메모리 부족에 의한 에러이고 로그를 남겨야 할 상황이다
사실 당장 해결할 수 있는 에러는 아니고, 메모리를 늘리거나 동시접속자 수를 낮추는 방법으로 해결해야 한다
보기 드문 에러이긴 하다
보통 closesocket() 은 예외처리를 잘 안 한다고 한다
소켓 주소 구조체
기본형
sockaddr
typedef struct sockaddr {
u_short sa_family;
char sa_data[14];
} SOCKADDR, *PSOCKADDR, *LPSOCKADDR;
IPv4
sockaddr_in
typedef struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN, *LPSOCKADDR_IN;
sin_addr
struct in_addr {
union {
struct {
u_char s_b1;
u_char s_b2;
u_char s_b3;
u_char s_b4;
} S_un_b;
struct {
u_short s_w1;
u_short s_w2;
} S_un_w;
u_long S_addr;
} S_un;
};
sin_zero 는 사용하지 않으며 sockaddr_in 구조체의 크기를 sockaddr 과 맞추기 위한 패딩값이다
일관성 유지를 위해 값을 0으로 모두 채우자
네트워크 장비들은 빅엔디안이다
장비들이 접근해야 할 아이피와 포트는 빅엔디안 방식으로 저장해서 사용해야 한다
나머지(데이터)는 그냥 사용(전송)해도 된다
htons(), ntohs()
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "Ws2_32.lib")
int main()
{
u_short port = 10;
printf_s("%04x\n", port);
port = htons(port);
printf_s("%04x\n", port);
port = ntohs(port);
printf_s("%04x\n", port);
return 0;
}
htonl(), ntohl()
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "Ws2_32.lib")
int main()
{
u_long ip = 2864434397;
printf_s("%08x\n", ip);
ip = htonl(ip);
printf_s("%08x\n", ip);
ip = ntohl(ip);
printf_s("%08x\n", ip);
return 0;
}
InetPton(), InetNtop()
#include <stdio.h>
#include <WinSock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "Ws2_32.lib")
int main()
{
WCHAR strIp[] = L"127.0.0.1";
SOCKADDR_IN sockAddr;
InetPtonW(AF_INET, strIp, &sockAddr.sin_addr);
printf_s("%08x\n", sockAddr.sin_addr.s_addr);
WCHAR strTemp[10];
InetNtopW(AF_INET, &sockAddr.sin_addr.s_addr, strTemp, 10);
wprintf_s(L"%s\n", strTemp);
return 0;
}
'공부' 카테고리의 다른 글
내 PC의 backlog 큐 개수 구하기 (2) | 2020.10.08 |
---|---|
도메인으로 IP 구하기 (0) | 2020.10.08 |
IPv4 서브넷 연습 (0) | 2020.09.26 |
20200918 테스트 오답노트 (0) | 2020.09.18 |
20200917 공부 (0) | 2020.09.18 |