TCP connection overview
- accept()를 호출하면 새로운 connect 요청이 들어올 때까지 프로그램 실행이 block 됨
- recv를 호출하면 읽을 수 있는 새로운 데이터가 들어올 때까지 프로그램 실행이 block 됨
blocking의 문제점
만약 여러 서버, 클라이언트가 통신하는 상황에서
- (서버 입장) recv()를 호출하여 새로운 데이터가 들어올 때까지 프로그램 실행이 block 된다면? → block 상태로 데이터 수신을 대기하는 동안 다른 client의 요청을 처리하지 못함
- (client 입장) 여러 탭에서 각각의 웹페이지를 병렬로 로드해야 하는데 block되면 동시에 여러 웹페이지, 이미지, 리소스 등을 화면에 띄우는 것이 불가능함
—> 여러 connection을 동시에 처리할 수 있는 해결책: Polling non-blocking sockets, Forking and multithreading
Polling non-blocking sockets
- active 소켓을 계속 확인하여 데이터가 있는 지 검사함
- 단점: 대부분의 시간 동안 읽을 데이터가 없음 → 자원 낭비
Forking and multithreading
- 새로운 연결이 들어올 때마다 새로운 스레드나 프로세스를 생성하여 연결을 처리
- 단점: 멀티스레드나 멀티프로세스 프로그램은 디버깅 어려움
select()
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
struct timeval *timeout);
- 입력: 소켓 집합
- 출력: 준비된(read, write) 소켓 집합, 예외 상태가 있는지 알려줌
- readfds에서 read할 준비가 된 소켓이 하나라도 나올 때까지 block
- 소켓이 write할 준비가 됐거나 에러가 발생하면 리턴
- 지정된 시간 동안 아무 일도 발생하지 않으면 리턴
- fd_set 삭제: FD_CLR()
- select 호출 전에 fd_set 복사해서 복사본을 사용 → select가 fd_set을 수정하는 것을 막기 위해서
- 모니터링 할 fd 중 가장 큰 값의 +1한 값을 select의 첫 번째 인수로 사용
- FD_ISSET(socket s, fd_set *set): 소켓 s가 set에 있으면 0 제외 값을 리턴, set에 없으면 0을 리턴
- tv_sec: 초
- tv_usec: 마이크로 초
- 위 예제 코드는 1.5초 동안 copy라는 readfds에 read할 준비가 된 소켓이 있는지 확인 후 리턴 == 1.5초 뒤에 소켓 상태를 리턴
select 호출
- 리턴값
- on Success: 준비된 fd의 개수
- timeout 발생 O: 0
- 에러 발생: -1
- readable 소켓 모니터링: fd_set에 소켓을 설정하고 소켓의 준비된 데이터를 확인
- writeable 소켓 모니터링: fd_set에 소켓을 설정하고 send() 호출 시 block 되지 않을 소켓을 확인
TCP client - program flow
- getaddrinfo() 호출하여 서버 주소 세팅
- socket() 호출하여 소켓 생성
- connect() 호출하여 서버에 연결 요청 전송
- 만약 stdin으로 입력받을 데이터가 있다면? → fgets() 호출하여 입력받고 → send() 호출하여 전송 4-1 stdin으로 입력받을 거 없으면 바로 5번으로
- select() 호출하여 소켓 모니터링 하다가
- 준비된 소켓이 발생하면 recv() 호출하여 데이터 읽음
~getaddrinfo()
- args 3개 들어왔는지 확인하고 (client입장에서, client실행 명령어, hostname, port)
- 서버 주소 세팅 - getaddrinfo()
- SOCK_STREAM: TCP 연결을 지정
- argv[1]: hostname
- argv[2]: port
- hostname, port 주소 정보를 가져오고 &hints 구조체의 소켓 유형 등 정보를 참고하여 peer_address에 결과(주소 정보)를 저장함
socket()
- 소켓 생성
- 새로운 소켓을 만들어서 서버 주소 세팅해줌
connect()
- 연결 요청
- peer_address(서버 주소)로 소켓 날려서 연결 요청 전송
select(), recv(), send()
- recv(): read한 바이트 수를 리턴
- 1보다 작으면 connection 끝나고 loop 중단
TCP Server
socket()
- AF_INET: IPv4
- AF_INET6: IPv6
- listen on port 8080
- socket()으로 소켓 생성(IP버전, TCP or UDP 프로토콜 등의 정보를 초기화)
- getaddrinfo() 호출하여 서버 주소 초기화 (IP주소, port번호, 서버의 주소 정보 구조체, 바인딩 할 구조체) 이 때 hints 구조체의 ip버전, 프로토콜(TCP, UDP) 등의 조건을 참고한다. → [getaddrinfo()를 이용해서 ip, port를 지정한 bind_address 구조체]를 따로 두는 이유는 IPv4, IPv6를 동시 지원하는 소켓으로 만들려고 == getaddrinfo()를 사용하는 이유
- 0: 모든 로컬 IP주소를 사용하겠다(AI_PASSIVE 플래그가 설정된 경우)
bind(), listen()
- bind() 호출하여 소켓에 local address 연결(IP, port) → listening이 가능한 state가 됨
- listen() 호출하여 connection 요청 기다림
active 소켓을 저장하기 위한 set
- listening 중인 소켓을 소켓 집합인 master에 추가
- max_socket을 listening 중인 소켓으로 초기화
select()
- select() 사용할 때 fd_set이 바뀌는 걸 방지하기 위해 reads라는 fd_set에 master를 복사 → 복사본(reads) 사용
accept()
- reads에 있는 소켓(i)이 listening state인 소켓(socket_listen)이면 accept() 호출하여 client의 소켓 정보 저장 → client의 연결 요청 수락
- master(fd_set)에 client 정보를 저장한 소켓을 추가하고 이 소켓을 max 소켓으로 설정
아모르겠다
TCP Server-Client Architectual
Blocking on send()
- send()를 호출하면 데이터는 OS가 제공하는 출력 버퍼에 복사됨
- 출력 버퍼가 가득 차 있다면 버퍼에 빈 공간이 생길 때까지(새로운 데이터를 수용할 수 있을 때까지) send()는 block 상태로 대기함
- 경우에 따라 send()가 block 되지 않고 호출된 데이터 중 일부만 전송할 수도 있음 → 이 경우 send()는 실제로 전송된 바이트 수를 리턴함 → 나머지 데이터는 호출자가 다시 시도해야 함
send()가 데이터를 다 전송할 때까지 반복적으로 호출하는 코드
int begin = 0;
while (begin < buffer_len) {
int sent = send(peer_socket, buffer + begin, buffer_len - begin, 0);
if (sent == -1) {
//Handle error
}
begin += sent;
}
'CS > Network' 카테고리의 다른 글
05_Thread (12) | 2024.07.24 |
---|---|
04_Multiprocess (2) | 2024.07.24 |
02_Socket_IO (2) | 2024.07.23 |
TCP flow control이란? (0) | 2024.03.18 |
다중화와 역다중화 (0) | 2024.02.01 |