본문 바로가기
IT/Rust 기초 완전 정복

Rust 네트워킹 프로그래밍 기초 (23)

by 지식 발전소 2024. 4. 25.
728x90
반응형
반응형

1

 

소개

네트워킹은 소프트웨어 개발에서 매우 중요한 부분입니다. 오늘날 대부분의 애플리케이션들은 네트워크를 통해 데이터를 주고받습니다. 특히 웹 애플리케이션, 모바일 애플리케이션, 클라우드 서비스 등은 네트워킹 없이는 작동할 수 없죠. Rust는 시스템 프로그래밍 언어로, 네트워킹 프로그래밍에 적합한 언어입니다. 이번 글에서는 Rust를 사용하여 네트워킹 프로그래밍의 기초를 배워보겠습니다.

TCP/IP 프로토콜

네트워킹을 이해하기 위해서는 TCP/IP 프로토콜에 대한 기본 지식이 필요합니다. TCP/IP는 인터넷 프로토콜 스위트의 핵심으로, 다양한 네트워크 애플리케이션에서 사용됩니다. TCP(Transmission Control Protocol)는 연결 지향적 프로토콜로, 데이터의 순서와 전송 신뢰성을 보장합니다. 반면 UDP(User Datagram Protocol)는 비연결 지향적 프로토콜로, 빠른 전송 속도를 제공하지만 데이터 전송의 신뢰성은 보장하지 않습니다.

 

TCP/IP 프로토콜은 계층 구조로 이루어져 있습니다. 각 계층은 특정 기능을 담당하며, 상위 계층은 하위 계층의 서비스를 이용합니다. 주요 계층은 다음과 같습니다:

  1. 애플리케이션 계층: HTTP, FTP, SMTP 등의 프로토콜이 존재합니다.
  2. 전송 계층: TCP와 UDP가 있습니다.
  3. 인터넷 계층: IP 프로토콜이 존재하며, 패킷 전달을 담당합니다.
  4. 네트워크 인터페이스 계층: 물리적 네트워크 하드웨어와 연결되어 있습니다.

TCP/IP 프로토콜에 대한 이해는 네트워킹 프로그래밍에서 매우 중요합니다. 이를 바탕으로 클라이언트-서버 모델, 소켓 프로그래밍 등을 이해할 수 있습니다.

소켓 프로그래밍

소켓(Socket)은 네트워크 프로그래밍에서 가장 기본적인 개념입니다. 소켓은 네트워크 상에서 통신하는 두 프로그램 간의 통신 엔드포인트 역할을 합니다. 소켓을 통해 데이터를 주고받을 수 있습니다.

Rust에서는 std::net 모듈을 통해 소켓 프로그래밍을 할 수 있습니다. 이 모듈에는 TCP와 UDP 소켓을 생성하고 관리하는 기능이 포함되어 있습니다.

TCP 소켓 프로그래밍

TCP 소켓 프로그래밍은 클라이언트-서버 모델을 따릅니다. 서버는 특정 포트에서 연결 요청을 기다리고, 클라이언트는 서버에 연결을 시도합니다. 연결이 성공하면 양쪽은 데이터를 주고받을 수 있습니다.

간단한 TCP 에코 서버 예제를 살펴보겠습니다:

use std::net::{TcpListener, TcpStream};
use std::io::{Read, Write};
use std::thread;

fn handle_client(mut stream: TcpStream) {
    let mut buf = [0; 512];
    loop {
        let bytes_read = stream.read(&mut buf).unwrap();
        if bytes_read == 0 {
            break;
        }
        stream.write(&buf[..bytes_read]).unwrap();
    }
}

fn main() {
    let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
    println!("서버가 127.0.0.1:8080에서 실행 중입니다.");

    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                thread::spawn(|| {
                    handle_client(stream)
                });
            }
            Err(e) => {
                println!("에러 발생: {}", e);
            }
        }
    }
}

이 예제에서 TcpListener는 특정 IP 주소와 포트에서 연결 요청을 기다립니다. 클라이언트가 연결되면 handle_client 함수에서 스트림을 처리합니다. 받은 데이터를 그대로 클라이언트에게 다시 보내는 간단한 에코 서버입니다.

UDP 소켓 프로그래밍

UDP 소켓 프로그래밍은 연결 없는 통신 방식입니다. 데이터그램을 직접 보내거나 받을 수 있습니다. 예를 들어 간단한 UDP 에코 서버를 만들어보겠습니다:

use std::net::UdpSocket;

fn main() {
    let socket = UdpSocket::bind("127.0.0.1:8080").expect("couldn't bind to address");
    println!("서버가 127.0.0.1:8080에서 실행 중입니다.");

    let mut buf = [0; 1024];
    loop {
        let (amt, src) = socket.recv_from(&mut buf).expect("didn't receive data");
        let buf = &mut buf[..amt];
        socket.send_to(buf, &src).expect("couldn't send data");
    }
}

 

이 예제에서 UdpSocket을 생성하고 특정 주소와 포트에 바인딩합니다. recv_from 메서드를 사용하여 데이터를 받고, 받은 데이터를 그대로 send_to 메서드를 통해 보냅니다.

 

UDP 소켓 프로그래밍은 TCP보다 간단하지만, 데이터 전송의 신뢰성이 보장되지 않는다는 단점이 있습니다. 상황에 따라 적절한 프로토콜을 선택해야 합니다.

비동기 프로그래밍

네트워크 프로그래밍에서는 비동기 프로그래밍 기법이 매우 중요합니다. 블로킹 I/O 작업으로 인해 전체 애플리케이션이 멈추는 것을 방지하기 위해서입니다. Rust는 비동기 프로그래밍을 지원하는 여러 라이브러리를 제공합니다.

async/await 키워드

Rust 1.39부터 async/await 키워드가 안정화되었습니다. 이를 통해 비동기 코드를 더 쉽게 작성할 수 있습니다. async 키워드를 통해 비동기 함수를 정의할 수 있고, await 키워드를 사용하여 비동기 작업의 완료를 기다릴 수 있습니다.

use tokio::net::{TcpListener, TcpStream};
use tokio::io::{AsyncReadExt, AsyncWriteExt};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let listener = TcpListener::bind("127.0.0.1:8080").await?;
    println!("서버가 127.0.0.1:8080에서 실행 중입니다.");

    loop {
        let (mut socket, _) = listener.accept().await?;
        tokio::spawn(async move {
            let mut buf = [0; 1024];
            loop {
                let bytes_read = socket.read(&mut buf).await?;
                if bytes_read == 0 {
                    break;
                }
                socket.write_all(&buf[..bytes_read]).await?;
            }
        });
    }
}

 

이 예제에서는 tokio 크레이트를 사용하여 비동기 TCP 에코 서버를 구현했습니다. async 키워드를 사용하여 비동기 함수를 정의하고, await를 사용하여 비동기 작업의 완료를 기다립니다. tokio::spawn을 통해 각 클라이언트 연결을 별도의 태스크로 처리할 수 있습니다.

futures 크레이트

async/await가 도입되기 전에는 futures 크레이트를 사용하여 비동기 프로그래밍을 했습니다. futures 크레이트는 비동기 프로그래밍을 위한 다양한 도구를 제공합니다.

use futures::future::Future;
use tokio::net::{TcpListener, TcpStream};
use tokio::io::{copy, copy_bidirectional};

fn handle_client(stream: TcpStream) -> impl Future<Item=(), Error=std::io::Error> {
    let (reader, writer) = stream.split();
    let client_to_server = copy(reader, writer);
    let server_to_client = copy_bidirectional(writer, reader);

    client_to_server.join(server_to_client).map(|_| ())
}

fn main() {
    let addr = "127.0.0.1:8080".parse().unwrap();
    let listener = TcpListener::bind(&addr).unwrap();
    println!("서버가 {}에서 실행 중입니다.", addr);

    let server = listener.incoming().map_err(|e| {
        eprintln!("accept 에러 발생: {}", e);
    }).for_each(|socket| {
        tokio::spawn(handle_client(socket).map_err(|e| {
            eprintln!("클라이언트 오류: {}", e);
        }))
    });

    tokio::run(server);
}

 

이 예제에서는 futures 크레이트와 tokio 크레이트를 사용하여 TCP 프록시를 구현했습니다. handle_client 함수에서 클라이언트와 서버 간의 데이터 전송을 처리합니다. join 메서드를 통해 두 개의 비동기 작업을 연결할 수 있습니다.

futures 크레이트는 여전히 유용하지만, async/await가 도입되면서 코드 가독성이 크게 향상되었습니다.

웹소켓

웹소켓(WebSocket)은 웹 애플리케이션에서 양방향 실시간 통신을 가능하게 하는 프로토콜입니다. HTTP 프로토콜과 달리 웹소켓은 서버와 클라이언트 간에 전이중(full-duplex) 통신 채널을 제공합니다. 이를 통해 실시간 데이터 교환이 가능해집니다.

Rust에서는 websocket 크레이트를 사용하여 웹소켓 프로그래밍을 할 수 있습니다. 예를 들어 간단한 웹소켓 에코 서버를 만들어보겠습니다:

use websocket::sync::Server;
use std::thread;

fn main() {
    let server = Server::bind("127.0.0.1:8080").unwrap();
    println!("웹소켓 서버가 127.0.0.1:8080에서 실행 중입니다.");

    for request in server.filter_map(Result::ok) {
        thread::spawn(|| {
            if !request.protocols().contains(&"echo".to_string()) {
                request.reject().unwrap();
                return;
            }

            let mut response = request.accept().unwrap();
            let ip = response.remote_addr().unwrap();
            println!("새로운 웹소켓 연결: {:?}", ip);

            let message = response.read_message().unwrap();
            response.send_message(&message).unwrap();
        });
    }
}

 

이 예제에서는 websocket 크레이트를 사용하여 웹소켓 서버를 구현했습니다. 클라이언트로부터 "echo" 프로토콜 요청이 올 경우에만 연결을 수락합니다. 연결이 수립되면 클라이언트로부터 메시지를 받아 그대로 다시 보냅니다.

웹소켓은 실시간 웹 애플리케이션, 채팅 애플리케이션, 실시간 게임 등에 널리 사용됩니다. 양방향 통신이 필요한 경우 웹소켓을 고려해야 합니다.

참고 자료

[1] Stevens, W. R. (1998). UNIX Network Programming, Volume 1: The Sockets Networking API (3rd Edition). Addison-Wesley Professional.

[2] Kurose, J. F., & Ross, K. W. (2017). Computer Networking: A Top-Down Approach (7th Edition). Pearson.

[3] Rust 공식 문서: https://doc.rust-lang.org/std/net/ 

 

 

 

한 고대 문서 이야기

여기 한 고대 문서가 있습니다. 이 문서는 B.C. 1,500년 부터 A.D 100년까지 약 1,600 여 년 동안 기록되었습니다. 이 문서의 저자는 약 40 명입니다. 이 문서의 고대 사본은 25,000 개가 넘으나, 사본간 오

gospel79.tistory.com

 

유튜브 프리미엄 월 1만원 할인받고 월 4000원에 이용하는 방법

올해 5월부터 월 8000원 정도이던 유튜브 프리미엄 요금이 15000원 정도로 인상됩니다. 각종 OTT 서비스, ChatGPT 같은 서비스들이 늘어나다보니 이런 거 몇 개만 이용하더라도 월 이용요금이 5만원을

stock79.tistory.com

 

"이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다."

728x90
반응형

댓글