[네트워크] TCP/IP 동기 통신 함수
Synchronous TCP/IP 통신 함수
TCP (Transmission Control Protocol) | UDP (User Datagram Protocol) |
연결형(connection-oriented) 프로토콜 : 연결 설정 후 통신 가능 |
비연결형(connectionless) 프로토콜 : 연결 설정 없이 통신 가능 |
신뢰성 있는 데이터 전송 : 데이터를 재전송함 |
신뢰성 없는 데이터 전송 : 데이터를 재전송하지 않음 |
일대일 통신(unicast) | 일대일 통신(unicast), 일대다 통신(broadcast, multicast) |
데이터 경계 구분 안함 : 바이트 스트림(byte-stream) 서비스 |
데이터 경계 구분함 : 데이터그램(datagram) 서비스 |
Synchronous TCP/IP 통신
Synchronous (동기) 통신이란?
프로그램이 특정 작업(예: 네트워크 요청)을 시작한 후, 그 작업이 완료될 때까지 기다리는 통신 방식.
작업이 완료되기 전까지는 프로그램의 실행 흐름이 해당 지점에서 멈춰(block) 있습니다. 다른 말로 하면, 함수를 호출했을 때, 그 함수의 결과가 나올 때까지 기다린 후에 다음 코드를 실행하는 방식입니다.
동기 통신의 특징:
- 간단한 구현: 코드를 순차적으로 작성할 수 있어 이해하기 쉽고 구현이 비교적 간단합니다.
- Blocking: 작업이 완료될 때까지 프로그램 실행이 중단(block)됩니다. 이로 인해 사용자 인터페이스(UI)가 멈추는 현상(freezing)이 발생할 수 있습니다. => 따라서 GUI보다는 CLI환경에서 유리함.
- 순차적 처리: 작업들이 순서대로 실행됩니다.
TCP/IP 프로토콜에서 Synchronous 통신은 (1)클라이언트가 서버에 연결을 요청하고, (2)데이터 송수신 등의 각 작업을 수행하고, (3)각 작업이 완료될 때 까지 기다리고, 다음 작업 진행을 반복한 뒤, (4)연결을 종료하는 순서로 이루어진다.
- Connect(): 클라이언트가 서버에 연결 요청, 연결이 성공할 때까지 대기.
- Send(): 클라이언트가 서버에 데이터를 보내고, 데이터가 완전히 전송될 때까지 대기.
- Receive(): 클라이언트가 서버로부터 데이터를 받고, 데이터가 완전히 수신될 때까지 대기.
- Close(): 클라이언트 또는 서버가 연결을 종료.
Synchronous TCP/IP 통신 함수
TCP/IP 통신을 위한 클래스들을 제공하는 네임스페이스 System.Net.Sockets
TcpListener(서버)
- TcpListener(IPAddress localaddr, int port): 서버가 사용할 IP 주소와 포트에서 클라이언트 연결을 수신할 TcpListener객체를 생성합니다.
- Start(): 클라이언트 연결 수신을 시작합니다.
- AcceptTcpClient(): 클라이언트로부터 온 리퀘스트(연결 요청)를 수락하고, 연결된 TcpClient객체(윈속에서는 소켓)를 반환합니다. 연결이 수락될 때까지 대기(blocking)합니다.
- Stop(): 클라이언트 연결 수신을 중지합니다.
TcpClient(클라이언트, 서버)
- TcpClient(string hostname, int port): TcpClient()는 TCP클라이언트 객체를 생성하는 생성자입니다. 괄호안에 서버의 호스트 이름(또는 IP 주소)과 포트 번호를 적어주면 내부적으로 Connect(hostname,port)를 호출하여 서버에 연결하는TcpClient객체를 생성합니다. 이 연결은 성공할 때까지 대기(blocking)합니다.
- Connect(string hostname, int port): 지정된 호스트 이름(또는 IP 주소)과 포트 번호를 사용하여 서버에 연결합니다. 연결될 때 까지 대기(blocking)합니다.
- GetStream(): 클라이언트와 서버 간의 데이터 송수신에 사용되는 NetworkStream객체를 가져옵니다.
- Close(): TcpClient연결을 닫습니다.
NetworkStream(클라이언트, 서버)
- Read(byte[] buffer, int offset, int size): 스트림에서 데이터를 읽어 buffer배열에 저장합니다.
→ 데이터가 완전히 도착할 때까지 대기(blocking)합니다. 읽은 바이트 수를 반환합니다. - Write(byte[] buffer, int offset, int size): buffer배열의 데이터를 스트림에 씁니다.
→ 데이터가 완전히 전송될 때까지 대기(blocking)합니다. - Close(): NetworkStream을 닫습니다.
UdpClient(클라이언트, 서버):
- UdpClient(Int32): 지정된 포트에서 수신 대기하는UdpClient객체를 생성합니다.
- UdpClient():UdpClient객체를 생성합니다.
- Send(Byte[], Int32, IPEndPoint): 지정된 엔드포인트에 데이터를 보냅니다.
- Receive(ref IPEndPoint): 데이터를 수신합니다.데이터가 도착할 때까지 대기(blocking)합니다.
💡C#의 자료형 'byte'
1바이트 크기의 데이터를 다루기 위한 자료형으로, C++의 unsigned char와 비슷하다.
기본값은 0, 크기는 8비트(1바이트)로, 범위는 0 ~ 255 (부호 없는 정수) 이다.
*주의* byte끼리의 연산은 기본적으로 int로 승격되기 때문에 결과를 다시 byte로 캐스팅해야 합니다.
(예시)
byte a = 100, b = 200;
byte sum = (byte)(a + b); // 형변환 필요: a + b는 int로 처리됨
Console.WriteLine(sum); // 출력: 44 (오버플로우 발생)
Synchronous 통신의 장단점
장점:
구현의 단순성: 코드가 순차적으로 실행되므로, 비동기 방식보다 이해하고 구현하기가 쉽습니다.
디버깅 용이성: 코드의 실행 흐름을 따라가기 쉬우므로, 디버깅이 비교적 용이합니다.
단점:
UI 멈춤 (Freezing): 네트워크 작업(연결, 데이터 송수신) 중에 메인 스레드가 차단(block)되어 UI가 멈추는 현상이 발생할 수 있습니다.
응답성 저하: 사용자의 입력에 즉각적으로 반응하지 못하고, 네트워크 작업이 완료될 때까지 기다려야 하므로, 애플리케이션의 응답성이 떨어집니다.
확장성 제한: 다수의 클라이언트를 동시에 처리하기 어렵습니다. (각 클라이언트에 대해 별도의 스레드를 생성해야 하므로, 스레드 관리 비용이 증가합니다.)
Stream
스트림(Stream)은 데이터를 연속된 바이트 흐름으로 주고받는 통로예요.
파일, 네트워크, 메모리 등과 같은 데이터 원천에서 데이터를 읽거나 쓰는 방식을 추상화한 거예요.
스트림의 공통 개념
- 순차적 처리: 데이터를 처음부터 끝까지 차례대로 읽거나 씀
- 버퍼링(Buffering): 효율적으로 데이터를 처리하기 위해 메모리 버퍼 사용
- 읽기/쓰기(Read/Write): 스트림은 보통 읽기 전용, 쓰기 전용, 또는 읽기/쓰기 겸용
- 종료(Closing): 스트림 사용이 끝나면 반드시 닫아줘야 리소스 누수 방지
- 스트림은 데이터 처리의 일관된 방법을 제공하고, 특히 네트워크나 파일 입출력에 매우 유용합니다.
- 언어마다 문법이나 클래스는 다르지만, 개념은 거의 동일해요.
스트림의 주된 목적과 스트림이 유용한 상황
순차적 처리 | 데이터를 한 번에 전부 처리하지 않고, 순서대로 처리 |
점진적 처리 | 데이터를 조금씩 읽고 쓰며 메모리 효율을 높임 |
비동기/실시간 처리 | 네트워크나 파일처럼 실시간으로 들어오는 데이터를 처리할 때 적합 |
추상화 | 다양한 데이터 소스(파일, 네트워크, 메모리 등)를 동일한 방식으로 다룸 |
1. 대용량 데이터 처리
- 전체를 메모리에 올리지 않고, 조각으로 처리할 수 있음
- 예: 10GB 로그 파일 분석
2. 실시간 처리 / 이벤트 기반 처리
- 데이터가 실시간으로 계속 들어오고, 그때그때 반응해야 할 때
- 예: 서버의 실시간 로그 스트리밍, 채팅 메시지 수신 등
3. 비동기 프로그래밍
- 일부 스트림은 이벤트 기반, 콜백 기반, 또는 async/await 기반으로 동작
- 예: Node.js의 Readable 스트림은 비동기 데이터 소스 처리에 최적
❗ 스트림이 불편하거나 적합하지 않은 경우
전체 데이터를 빠르게 무작위 접근해야 할 때 | 스트림은 순차적 접근이기 때문에 랜덤 액세스에 부적합 |
데이터 양이 작아서 굳이 스트림 쓸 필요 없을 때 | 단순하게 File.ReadAllText() 같은 고수준 API 사용이 더 효율적 |
특정 위치로 점프하거나 되감기해야 할 때 | 스트림은 기본적으로 직선적(순방향) 흐름 |