0. PreRequisite


ํ•ด๋‹น ๋ฌธ์„œ์˜ ์ฐธ๊ณ ์ž…๋‹ˆ๋‹ค! https://app.codecrafters.io/concepts/rust-tcp-server

  • std::net ๋ชจ๋“ˆ์€ TCP ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•œ ๋ชจ๋“ˆ์ด๋‹ค.
  • ๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์˜ ๋‹ค์„ฏ๊ฐ€์ง€ ๋ฉ”์…”๋“œ๋ฅผ ์ฃผ์š”ํ•˜๊ฒŒ ์ด์šฉํ•œ๋‹ค.
  1. TcpListener::bind : pub fn bind<A: ToSocketAddrs>(addr: A) -> Result<TcpListener> - ์ฃผ์–ด์ง„ ์ฃผ์†Œ์— ๋ฐ”์ธ๋”ฉ๋œ ์ƒˆ๋กœ์šด TcpListener ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค
  2. TcpListener::incoming : pub fn incoming(&self) -> Incoming - ์ด ๋ฆฌ์Šค๋„ˆ๋กœ ๋“ค์–ด์˜ค๋Š” coneection์— ๋Œ€ํ•œ iterator๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  3. TcpStream::connect : pub fn connect<A: ToSocketAddrs>(addr: A) -> Result<TcpStream> - ์ฃผ์–ด์ง„ ์ฃผ์†Œ๋กœ ์—ฐ๊ฒฐ๋œ ์ƒˆ๋กœ์šด TcpStream ์ธ์Šคํ„ด์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
  4. TcpStream::read : pub fn read(&mut self, buf: &mut [u8]) -> Result<usize> - ์ŠคํŠธ๋ฆผ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์„œ ์ฃผ์–ด์ง„ ๋ฒ„ํผ์— ์ €์žฅํ•œ๋‹ค.
  5. TcpStream::write_all : pub fn write_all(&mut self, buf: &[u8]) -> Result<()> - ์ŠคํŠธ๋ฆผ์— ์ฃผ์–ด์ง„ ๋ฒ„ํผ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์“ด๋‹ค.

TcpLister Struct๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค.

impl TcpListener {
    // accept๋Š” ๋Œ€๊ธฐ ์ค‘์ธ ์—ฐ๊ฒฐ์„ ์ˆ˜๋ฝํ•˜๊ณ , connection์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    pub fn accept(&self) -> Result<(TcpStream, SocketAddr)>;
    // incoming์€ ์ด listener๋กœ ๋“ค์–ด์˜ค๋Š” connection์— ๋Œ€ํ•œ iterator๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    pub fn incoming(&self) -> Incoming<TcpStream>;
    // local_addr๋Š” ์ด listener๊ฐ€ ๋ฐ”์ธ๋”ฉ๋œ ์ฃผ์†Œ๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
    pub fn local_addr(&self) -> Result<SocketAddr>;
}

TcpStream Struct๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊ตฌ์„ฑ๋˜์–ด ์žˆ๋‹ค.

impl TcpStream {
    pub fn read(&mut self, buf: &mut [u8]) -> Result<usize>;
    pub fn write(&mut self, buf: &[u8]) -> Result<usize>;
    pub fn write_all(&mut self, buf: &[u8]) -> Result<()>;
}

์ฐธ๊ณ ํ•˜์ž๋ฉด, write_all์€ ์•„๋ž˜์ฒ˜๋Ÿผ ๋ฒ„ํผ์˜ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์“ฐ๋Š”๊ฑธ ๋ณด์žฅํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์—, ๊ฐ„๋‹จํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

fn write_all(&mut self, mut buf: &[u8]) -> io::Result<()> {
    while !buf.is_empty() {
        match self.write(buf) {
            Ok(0) => {
                return Err(io::Error::new(
                    io::ErrorKind::WriteZero,
                    "failed to write whole buffer",
                ));
            }
            Ok(n) => buf = &buf[n..],
            Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
            Err(e) => return Err(e),
        }
    }
    Ok(())
}
fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    loop {
        let byte_read = stream.read(&mut buffer).expect("Failed to read");
        if byte_read == 0 {
            return;
        }
        stream.write_all(&buffer[0..byte_read]).expect("Feil to write");
    }
}

fn main() {
    let listener = TcpListener::bind("localhost:8080").expect("Could not bind");
    for stream in listener.incoming() {
        match stream {
            Ok(stream) => {
                handle_client(stream);
            }
            Err(e) => {
                eprintln!("Failed: {}", e);
            }
        }
    } 
}

์ฐธ๊ณ  : ํ•œ๋ฒˆ์— ํ•˜๋‚˜์˜ ์ปค๋„ฅ์…˜๋งŒ ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ณ , ๋‹ค๋ฅธ ์ปค๋„ฅ์…˜์€ blocked๋œ๋‹ค.

1. Day 1 Code


Day 1์—์„œ๋Š” ๊ธฐ๋ณธ์ ์ธ TcbServer ๋ฐ”์ธ๋”ฉ๊ณผ, ๋ฉ”์„ธ์ง€๋ฅผ ์ฝ์–ด์„œ ๋‹ค์‹œ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ณด๋‚ด๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•œ๋‹ค.


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

enum Command {
    PING,
    ECHO(String),
}

#[tokio::main]
async fn main() {
    let listener = TcpListener::bind("127.0.0.1:6379").await.unwrap();
    loop {
        match listener.accept().await {
            Ok((stream, _)) => {
                task::spawn(async move {
                    handle_client(stream).await;
                });
            }
            Err(e) => {
                println!("Error accepting connection : {}", e);
            }
        }
    }
}

async fn handle_client(mut stream: TcpStream) {
    let mut buffer = [0; 512];
    loop {
        buffer.fill(0);
        match stream.read(&mut buffer).await {
            Ok(0) => break,
            Ok(n) => {
                let message = match std::str::from_utf8(&buffer[..n]) {
                    Ok(msg) => msg,
                    Err(_) => {
                        println!("Failed to parse message as UTF-8");
                        continue;
                    }
                };

                println!("Received message: {:?}", message);
                match parse_message(message) {
                    Ok(command) => {
                        if let Err(e) = handle_command(command, &mut stream).await {
                            println!("Failed to send response: {}", e);
                        }
                    }
                    Err(e) => {
                        println!("Failed to parse command: {}", e);
                    }
                }
            }
            Err(e) => {
                println!("Error reading from stream: {}", e);
                break;
            }
        }
    }
}

fn parse_message(message: &str) -> Result<Command, String> {
    let re_ping = Regex::new(r"^\*1\r\n\$4\r\nPING\r\n$").unwrap();
    let re_echo = Regex::new(r"^\*2\r\n\$4\r\nECHO\r\n\$(\d+)\r\n(.+)\r\n$").unwrap();

    if re_ping.is_match(message) {
        Ok(Command::PING)
    } else if let Some(captures) = re_echo.captures(message) {
        let length: usize = captures[1].parse().unwrap_or(0);
        let echo_message = &captures[2];

        if echo_message.len() == length {
            Ok(Command::ECHO(echo_message.to_string()))
        } else {
            Err("Invalid ECHO command format: length mismatch".to_string())
        }
    } else {
        Err("Unknown command".to_string())
    }
}

async fn handle_command(command: Command, stream: &mut TcpStream) -> std::io::Result<()> {
    match command {
        Command::PING => {
            stream.write_all(b"+PONG\r\n").await?;
        }
        Command::ECHO(echo_message) => {
            let response_message = format!("${}\r\n{}\r\n", echo_message.len(), echo_message);
            stream.write_all(response_message.as_bytes()).await?;
        }
    }
    Ok(())
}

2. Day 1 Review


Trouble Shooting

  1. ๋จผ์ € tokio๋ฅผ ์จ๋„ ๋˜๋Š”์ง€ ๋ชจ๋ฅด๊ณ , std::net์œผ๋กœ ๋น„๋™๊ธฐ์™€ ์ด๋ฒคํŠธ๋ฃจํ”„๋ฅผ ๊ตฌํ˜„ํ•˜๋ ค๊ณ  ํ–ˆ๋‹ค. ์ค‘๊ฐ„์— ์ด๊ฒŒ ๋งž๋‚˜ ์‹ถ์–ด์„œ ์ •๋‹ต์ง€๋ฅผ ๋ดค๋Š”๋ฐ, ๋‹ค๋ฅธ์‚ฌ๋žŒ๋“ค์€ ๊ทธ๋ƒฅ tokio ์“ฐ๋”๋ผ..
  2. ์ •๊ทœ์‹์€ ์–ด๋ ต๊ณ  ๊ท€์ฐฎ๋‹ค + ๋ ˆ๋””์Šค ํ”„๋กœํ† ์ฝœ์„ ๋ชจ๋ฅด๊ณ  ๋น„๋น„๋ ค๋‹ค๊ฐ€ ๋” ๊ณ ์ƒํ–ˆ๊ณ , ์ •๊ทœ์‹ ๋ถ€๋ถ„์€ ๊ทธ๋ƒฅ ๊ฒ€์ƒ‰ํ•ด์„œ ๊ธ์–ด์™”๋‹ค.