팥빵 먹으면서 코딩하는 블로그

05-1.[Linux/Ubuntu] 네트워크 프로그래밍 - UDP기반 서버/클라이언트 본문

study/NETWORK PROGRAMMING

05-1.[Linux/Ubuntu] 네트워크 프로그래밍 - UDP기반 서버/클라이언트

김팥빵_ 2025. 4. 4. 15:40

UDP 소켓의 특성과 동작원리

  • UDP 소켓 데이터 송수신
    • Flow control이 없음
      • UDP 소켓은 SEQ, ACK과 같은 메세지를 전달하지않음
      • 단, 그러므로 데이터 전송이 빠름
    • 연결 설정과 연결 해제 과정이 없음
    • 단, (안전성보다) 성능이 우선시 될 때 UDP를 사용한다.
      • 송수신하는 데이터의 양은 작으면서 빈번한 전송이 필요한 경우 UDP가 훨씬 효율적
  • TCP를 쓰든 UDP를 쓰든 IP는 반드시 거친다.
  • 패킷 전송에 있어 UDP와 IP의 역할 : 목적지를 찾기 위해서 IP를 거친다.
    • UDP 소켓에서 port 번호 참조해서 최종 응용 프로그램에 전달한다.
    • 이건 TCP도 마찬가지

UDP와 IP의 역할

  • UDP Header
    • TCP와 달리 Source port와 Destination port번호밖에 없다. 이 두 가지를 가지고 응용 프로그램에 데이터를 전달한다.

UDP Header

  • UDP의 데이터 송수신
    • UDP는 연결 설정 과정이 없음
    • TCP는 1대 1의 연결을 필요로 했다.
  • 서버 소켓과 클라이언트 소켓의 구분이 없다.
    • UDP 소켓 생성과 데이터 송수신 과정만 존재한다.
  • 하나의 소켓으로 둘 이상의 호스트와 데이터 송수신이 가능함
    • 그룹 통신: multicast, broadcast(14장)
    • 하나의 소켓에서 여러 특정한 곳에 데이터를 뿌리는 특징을 가짐 -> UDP를 쓴다.

 

UDP 데이터 통신 과정

  • UDP Server
    • 데이터 수신을 먼저 수행
    • revfrom() 함수를 먼저 호출
    • bind() 함수는 무조건 호출해야 한다.
      • 고정된 port번호를 사용해야 전송된 패킷을 수신할 수 있기 때문
  • UDP Clienet
    • 데이터 송신을 먼저 수행
    • sendto() 함수를 먼저 호출
    • bind() 함수를 호출하지 않아도 됨
      • 자동으로 자신의 ip주소와 port번호를 할당받은 상황에서, 상대방의 ip주소와 port번호를 알면 client가 송신을 먼저 했을 때 수신받은 server는 이를 받고 송수신을 수행하면 된다.

UDP 데이터 통신 과정

  • sendto() 함수 - client
    • 데이터를 전송할 때마다(매 패킷마다) 목적지에 대한 정보를 전달해야 됨 : ip주소, port 번호
    • flag는 사용하는 경우가 거의 없어 그냥 0으로 지정하면 된다.
#include <sys/socket.h>

ssize_t sendto(int sock, void *buf, size_t nbytes, int flags,
    struct sockaddr* to, socklen_t addrlen);
-> 성공 시 전송된 바이트 수, 실패 시 -1 반환
  • sock : 데이터 전송에 사용될 UDP 소켓 디스크립터
  • buf : 전송할 데이터를 저장하고 있는 버퍼의 주소
  • nbytes : 전송할 데이터의 크기

------------------여기까진 TCP의 write() 와 같음-----------------------

  • flags : 옵션 지정에 사용, 지정할 옵션이 없으면 0 입력
  • to : 목적지 주소 정보를 담고 있는 구조체 변수의 주소값
  • addrlen: to로 전달될 구조체(sockaddr)의 크기

!buf는 전송할 데이터의 주소를 담고, to는 목적지 주소 정보의 주소를 담는다!

 

  • revfrom() 함수 - server
    • UDP소켓을 통해 전송된 데이터를 수신
    • from: 발신지 주소 정보를 주소값으로 저장(보낼 사람의 주소 정보)
#include <sys/socket.h>

ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags,
	struct sockaddr* from, socklen_t *addrlen);
-> 성공 시 수신한 바이트 수, 실패 시 -1 반환
  • sock : 데이터 수신에 사용될 UDP 소켓 디스크립터
  • buf : 데이터 수신에 사용될 버퍼의 주소값
  • nbytes : 수신할 최대 바이트 수, buf의 크기를 넘을 수 없음

------------------여기까진 TCP의 read() 와 같음-----------------------

  • flags : 옵션 지정에 사용, 지정할 옵션이 없으면 0 입력
  • from : 발신지 주소 정보를 저장할 sockaddr 구조체 주소값
  • addrlen : from으로 전달될 주소값의 구조체(sockaddr) 변수 크기

 

  • 참고) 포인트 쓰는 이유
    • 단순하게 값을 가져와야하기 때문 / c언어는 return값이 1개이다.
    • malloc쓰면 아작난다.. malloc안쓰고 memset으로 초기화 꼭 해주기

 

UDP 기반의 에코 서버와 클라이언트

  • UDP echo server
    • 수신한 데이터의 전송지 정보(clnt_adr)를 참조하여 데이터를 전송(echo)한다.

UDP echo server

  • UDP echo client
    • UDP는 데이터의 경계가 존재하기 때문에 한번의 recvfrom() 함수 호출을 통해 하나의 메세지를 완전히 읽어들임
    • sendto() 함수 호출 시 자신의 IP주소와 Port번호가 자동으로 할당
    • UDP의 클라이언트 프로그램에서는 자신의 주소정보를 할당하는 별도의 과정이 없음

UDP echo client

  • message[str_len] = 0 // 문자열 마지막에 null 추가(쓰레기값 방지)

 

netstat 명령어

  • netstat 명령어
    • 시스템의 네트워크 연결 목록(tcp, udp)을 보여주는 유틸리티
    • 옵션
      • -a : 현재 다른 PC와 연결되어 있꺼나 대기(listening)중인 모든 포트 번호를 확인
      • -t : TCP Protocol
      • -u : UDP Protocol
      • -s : IP, ICMP, UDP 프로토콜별 상태를 보여줌
      • -nap : 열려있는 모든 포트를 보여줌 
  • 참고)
    • ex. char msg[] = "hi!" ->크기를 지정하지 않았으므로 sizeof 연산 했을 때 byte크기 = 4bytes (null값이 자동 추가)
    • 단, 구조체를 보낼 땐 sizeof 연산이 맞다. 문자열에서 null을 제외한 값을 보낼 땐 strlen이 맞다.

 

UDP 소켓

  • 데이터 경계가 존재하는 UDP 소켓
    • TCP
      • 송수신하는 데이터에는 경계가 존재하지 않음
      • 데이터 송수신 과정에서 입출력 함수의 호출 횟수는 큰 의미 없음
    • UDP
      • 데이터 경계가 존재
      • 전송 함수(sendto())의 호출 횟수수신 함수(revfrom())의 호출 횟수가 일치해야 함!
  • Connected UDP 소켓
    • 기존 UDP 소켓과는 다르게 UDP 소켓에 목적지 IP와 port 번호를 등록하고, 데이터 전송 후 UDP 소켓에 등록된 목적지 정보를 삭제하는 과정을 거치지 않음.
    • 단지 데이터를 전송한다.
    • 이걸 쓰는 목적은 read(), write() 함수 호출하려고.
    • 하나의 호스트와 장시간 데이터를 송수신할 때 사용
      • 하나의 호스트와 장시간 송수신, 이럴 때 Connected UDP가 효율적이다.
      • socket 디스크립터와 상대방 주소 정보 연결하기 위해 쓴다.
      • client부분에서 connect를 사용한다.
      • ex. connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));     // bind와 같다.
      • write(sock, message, strlen(message));    
      • str_len=read(sock, message, sizeof(message)-1);    
  • 참고)
    • sendto() 에서 void *buf -> 뭐든 다 보낼수 있단 뜻
    • -> 어떤 데이터 타입을 보낼 수도 있고 특징적으론 구조체를 보낼수도 있다.