0. PreRequisite
ํด๋น ๋ฌธ์์ ์ฐธ๊ณ ์ ๋๋ค! https://app.codecrafters.io/concepts/rust-tcp-server
std::net
๋ชจ๋์ TCP ์๋ฒ๋ฅผ ๋ง๋ค๊ธฐ ์ํ ๋ชจ๋์ด๋ค.- ๊ทธ๋ฆฌ๊ณ ์๋์ ๋ค์ฏ๊ฐ์ง ๋ฉ์ ๋๋ฅผ ์ฃผ์ํ๊ฒ ์ด์ฉํ๋ค.
TcpListener::bind
:pub fn bind<A: ToSocketAddrs>(addr: A) -> Result<TcpListener>
- ์ฃผ์ด์ง ์ฃผ์์ ๋ฐ์ธ๋ฉ๋ ์๋ก์ดTcpListener
์ธ์คํด์ค๋ฅผ ๋ฐํํ๋คTcpListener::incoming
:pub fn incoming(&self) -> Incoming
- ์ด ๋ฆฌ์ค๋๋ก ๋ค์ด์ค๋ coneection์ ๋ํ iterator๋ฅผ ๋ฐํํ๋ค.TcpStream::connect
:pub fn connect<A: ToSocketAddrs>(addr: A) -> Result<TcpStream>
- ์ฃผ์ด์ง ์ฃผ์๋ก ์ฐ๊ฒฐ๋ ์๋ก์ดTcpStream
์ธ์คํด์ค๋ฅผ ๋ฐํํ๋ค.TcpStream::read
:pub fn read(&mut self, buf: &mut [u8]) -> Result<usize>
- ์คํธ๋ฆผ์์ ๋ฐ์ดํฐ๋ฅผ ์ฝ์ด์ ์ฃผ์ด์ง ๋ฒํผ์ ์ ์ฅํ๋ค.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
- ๋จผ์ tokio๋ฅผ ์จ๋ ๋๋์ง ๋ชจ๋ฅด๊ณ , std::net์ผ๋ก ๋น๋๊ธฐ์ ์ด๋ฒคํธ๋ฃจํ๋ฅผ ๊ตฌํํ๋ ค๊ณ ํ๋ค. ์ค๊ฐ์ ์ด๊ฒ ๋ง๋ ์ถ์ด์ ์ ๋ต์ง๋ฅผ ๋ดค๋๋ฐ, ๋ค๋ฅธ์ฌ๋๋ค์ ๊ทธ๋ฅ tokio ์ฐ๋๋ผ..
- ์ ๊ท์์ ์ด๋ ต๊ณ ๊ท์ฐฎ๋ค + ๋ ๋์ค ํ๋กํ ์ฝ์ ๋ชจ๋ฅด๊ณ ๋น๋น๋ ค๋ค๊ฐ ๋ ๊ณ ์ํ๊ณ , ์ ๊ท์ ๋ถ๋ถ์ ๊ทธ๋ฅ ๊ฒ์ํด์ ๊ธ์ด์๋ค.