Rust를 공부해야지 공부해야지 하다가 매우 오랜 시간 동안, 결국은 아무것도 안하고 있길래, 그냥 무작정 만들어보기로 했다.

 

그리고 아래 글을 읽은 이유도 있다.

http://egloos.zum.com/agile/v/5664879

 

프로그래밍 언어 배우기의 달인

미육군에서는 9년간 약 4천만불을 들여 개발한 지뢰 탐지 시스템(Handheld Standoff Mine Detection System, HSTAMIDS 나중에 PSS-14로 개명)을 취소하려고 하고 있었습니다. 프로토타입의 실 테스트에서 지뢰 탐

egloos.zum.com

대충 언어를 새로 배울 때 세가지 원칙을 기준으로 공부를 한다는 점이다.

1. 이 언어로 뭘 만들지를 생각하면서 튜토리얼을 본다.

2. 공부할 때, 표준 라이브러리 코드를 읽는다.

3. 다른 사람이작성한 코드에 내가 필요한 기능을 추가한다.

 

위 글을 보고 영감을 받아서, 그냥 무작정 지루한 챕터들을 읽기 보다는 바로 무언가를 만들어 보는게 낫지 않나 싶다.

 

 

뭘 만들면 좋을까 하다가, 간단한 웹 서버를 만들어 보기로 했다. 어차피 간단한 웹서버는 정말 간단하고, 기능들을 추가하고자 한다면 계속해서 추가할 수 있으니 말이다.

 

Rust 개발환경 구축은 다음 링크 글에서 확인해볼 수 있다.

https://eine.tistory.com/entry/Rust-%EA%B0%9C%EB%B0%9C-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95

 

Rust 개발 환경 구축(Windows10에서 Sublime Text 3 / VS code)

느닷없이 Rust를 배우고 싶어져서 Rust 개발환경을 한번 구축해보도록 하겠습니다. 사실 지금 글을 쓰는 이 시점에는 Rust를 공식적으로 지원하는 IDE나 그런 녀석들은 잘 없는데, 그래도 제일 많이

eine.tistory.com

 

일단 개발 환경은, 내가 주로 윈도우 PC를 사용하므로 아래와 같을 예정이다.

개발환경

 - Windows 10

 - rustc 1.44.0-nightly (f509b26a7 2020-03-18)

 - cargo 1.44.0-nightly (7019b3ed3 2020-03-17)

 

일단 github 레포지토리와 개발할 코드 디렉토리를 만들어보자.

 

 

그리고 repository를 clone할 디렉토리에서 WSL win bash를 켠 뒤, git clone을 해준다.

 

 

Rust 프로젝트 생성

이제 rust project를 카고로 만들어줘야 하는데, cargo new rust_web_server --bin 이라는 명령어로 프로젝트 디렉토리를 만들 수 있다.

 

 

근데 안에 디렉토리가 또 생겨버렸다. 이미 README.md와 .git이 있는 디렉토리므로 저 명령어 말고, 다른 명령어를 써야 하는 것 같다.

cargo init --bin이라고 치니 되는 것 같다.

 

 

디렉토리 구조를 보니 바람직하게 만들어진 듯 하다.

일단 VScode로 해당 디렉토리를 열어서 Hello world가 잘 동작하는지 확인해보자.

 

 

기본적으로 소스코드에 hello world프로그램이 작성되어 있다.

아래 터미널을 열어서 cargo build와 cargo run을 쳐 보니, Hello, world!가 잘 되었다.

 

네트워크 기능부터

일단 웹 서버이니 TCP 소캣을 열 수 있어야 한다. Rust에서 소캣 프로그래밍을 어떻게 하는지를 한번 알아보자.

 

소캣 프로그래밍

https://doc.rust-lang.org/std/net/struct.TcpListener.html

 

std::net::TcpListener - Rust

pub struct TcpListener(_); A TCP socket server, listening for connections. After creating a TcpListener by binding it to a socket address, it listens for incoming TCP connections. These can be accepted by calling accept or by iterating over the Incoming it

doc.rust-lang.org

TcpListener라는 녀석이 있다. 일단 소캣을 하나 열어서 Listening하는 예제 코드가 있으니 한번 테스트 해보자.

 

use std::net::{TcpListener, TcpStream};

fn handle_client(stream: TcpStream) {
    println!("Connection!")
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:80")?;

    // accept connections and process them serially
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

접속이 되면 stdout에 Connection!이라고 출력만 된다.

일단 여기까지 하고 initial commit을 한번 해 놓겠다.

 

이제 Tcp로 데이터를 전송을 해보고싶은데, Docs랑 예시 코드를 좀 보겠다.

https://riptutorial.com/rust/example/4404/a-simple-tcp-client-and-server-application--echo

 

Rust - A simple TCP client and server application: echo | rust Tutorial

rust documentation: A simple TCP client and server application: echo

riptutorial.com

https://doc.rust-lang.org/std/net/struct.TcpStream.html

 

std::net::TcpStream - Rust

A TCP stream between a local and a remote socket. After creating a TcpStream by either connecting to a remote host or accepting a connection on a TcpListener, data can be transmitted by reading and writing to it. The connection will be closed when the valu

doc.rust-lang.org

stream.write라고 하면 될 것 같은데, 사이에 &는 뭐고 ?는 또 뭔지 모르겠다.

 

찾아보니, &는 burrow라고 해서 인자를 넘겨줄 때, C++의 Reference variable 처럼 Call by reference로 넘기도록 하는 것이고, ?는 Result<T>를 리턴하는 값을 unwrap을 자동적으로 해주도록 하는 매크로이다.

 

 

웹 브라우저로 요청을 보내보니, Response는 안오긴 하는데 Connection이라고 찍히긴 한다.

 

 

그래서 대충 static한 HTTP Response를 찍도록 조금 변경해 보았다.

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

fn handle_client(mut stream: TcpStream) {
    println!("Connection!");
    let data = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 14\r\n\r\nHello world!\r\n";
    stream.write(data.as_bytes());
    
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:80")?;

    // accept connections and process them serially
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

그래서 브라우저로 접속을 해볼려고 했는데, 여러번 하면 한번정도 성공하는듯?? 왜 되었다 안되었다 하는지 모르겠다. 

성공하는 확률은 10%정도로 뭔가 Tcp 연결을 잘 못 짠거같긴 하다.

 

그리고 빌드할때마다 다음 warning이 뜬다.

 

 

에러 핸들링을 제대로 안해서 그런 것 같다.

아래와 같이 코드를 바꾸니 warning은 안뜨긴 한다만, 여전히 응답은 제대로 안된다.

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

fn handle_client(mut stream: TcpStream) {
    println!("Connection!");
    let data = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 14\r\n\r\nHello world!\r\n";
    stream.write(data.as_bytes()).expect("Response error");
    
}

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:80")?;

    // accept connections and process them serially
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

근데 약간 이상한것은, Ubuntu 16.04에서 Firefox로 접속하니, hello world!가 잘 찍힌다.

왜 Windows 10에서 크롬으로 접속을 하면 제대로 안되는 것일까..?

 

나중에 확인해보니, tcpstream 마지막에 flush를 해 주니, chrome이든 firefox든 둘 다 잘 돌아간다.

대충 아래와 같은 코드로 고쳐보았다. /로 요청할때와 그 외의 경로로 요청할때의 응답이 다르다.

from_utf8_lossy는 u8 byte stream 값을 readable한 문자열로 변경해주는 함수이다. lossy는 유효하지 않은 UTF-8 배열을 만났을때 어떻게 처리할 것인가에 대한 내용이다. 유효하지 않은 배열 값은 U+FFFD Replacement character라는 값으로 바꾸게 된다.

use std::net::{TcpListener, TcpStream};
use std::io::{Write};
use std::io::prelude::*;

fn handle_client(mut stream: TcpStream) {
    //get request and dump
    let mut request = [0; 1024];
    stream.read(&mut request).unwrap();
    println!("Received HTTP Request...\n{}", String::from_utf8_lossy(&request[..]));
    let response;
    if request.starts_with(b"GET / HTTP/1.1\r\n") {
        //send hello world response
        response = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 14\r\n\r\nHello world!\r\n";
    } else {
        response = "HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=iso-8859-1\r\nConnection: close\r\nContent-Length: 4\r\n\r\nNope\r\n";
    }
    stream.write(response.as_bytes()).unwrap();
    stream.flush().unwrap();

}

fn main() -> std::io::Result<()> {
    let server = "127.0.0.1:1024";
    let listener = TcpListener::bind(server)?;

    // accept connections and process them serially
    println!("Server is running on {}...", server);
    for stream in listener.incoming() {
        handle_client(stream?);
    }
    Ok(())
}

 

https://www.acmicpc.net/problem/2748

 

2748번: 피보나치 수 2

문제 피보나치 수는 0과 1로 시작한다. 0번째 피보나치 수는 0이고, 1번째 피보나치 수는 1이다. 그 다음 2번째 부터는 바로 앞 두 피보나치 수의 합이 된다. 이를 식으로 써보면 Fn = Fn-1 + Fn-2 (n>=2)가 된다. n=17일때 까지 피보나치 수를 써보면 다음과 같다. 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597 n이 주어졌을 때, n번째 피보나치 수를

www.acmicpc.net

Rust로 PS하기 두번째 문제이다.

 

러스트 깃북을 보다가 피보나치 수 구하기를 만들어 보라길래, 한번 간단하게 짜 보았다.

 

Rust는 역시 input 받는 것부터 난관이다.

 

책 예제대로 String으로 받은 뒤 int로 parse해서 사용했다.

 

 

use std::io;

fn fib(n : i32) {
	let mut a: i64 = 1;
	let mut b: i64 = 1;
	let mut c: i64 = 0;
	for _ in 2..n {
		c = a + b;
		a = b;
		b = c;
	}
	println!("{}", c);

}
fn main() {
	let mut input = String::new();
	io::stdin().read_line(&mut input)
		.expect("read line error");
	let n : i32 = input.trim().parse()
		.expect("Parse error");

	match n {
		1 => println!("1"),
		2 => println!("1"),
		_ => fib(n),
	}
}

코드는 간단했다. _라는 값이 예약어라는 것도 좀 신기했다. (Anything을 뜻하는 듯)

그리고 Match 구문이 switch 구문보다 더 강력하다고 한다.

 

그리고 unnecessary value, syntax에 대하여 강력한 warning으로 내 정신을 쏙 빼놓는다.

 

책만 보면 재미가 떨어지니 이렇게 간혹 코딩을 하면서 진행을 해야겠다.

https://www.acmicpc.net/problem/1620

 

1620번: 나는야 포켓몬 마스터 이다솜

첫째 줄에는 도감에 수록되어 있는 포켓몬의 개수 N이랑 내가 맞춰야 하는 문제의 개수 M이 주어져. N과 M은 1보다 크거나 같고, 100,000보다 작거나 같은 자연수인데, 자연수가 뭔지는 알지? 모르면 물어봐도 괜찮아. 나는 언제든지 질문에 답해줄 준비가 되어있어. 둘째 줄부터 N개의 줄에 포켓몬의 번호가 1번인 포켓몬부터 N번에 해당하는 포켓몬까지 한 줄에 하나씩 입력으로 들어와. 포켓몬의 이름은 모두 영어로만 이루어져있고, 또, 음... 첫 글자만

www.acmicpc.net

Rust언어를 공부하면서 책을 보고 예제를 보고는 있는데 그래도 코드를 좀 짜봐야지 익숙해질 것 같은 느낌이 든다. 그래서 백준 문제중에서 만만한 것들을 Rust로 좀 풀어보기로 하였다.

 

일단 만만한 문제의 기준은 https://solved.ac/class/에 있는 녀석 중 내가 아직 안 푼 문제들이다. 

일단근데 Rust는 Input 받는것 부터 잘 몰라서, 다음 깃허브에 있는 녀석들을 한번 참고하면 좋을 듯 하다.

https://github.com/EbTech/rust-algorithms

 

EbTech/rust-algorithms

Common data structures and algorithms in Rust. Contribute to EbTech/rust-algorithms development by creating an account on GitHub.

github.com

깃허브 자체가 Rust로 Contest나가는거니... scanner도 보인다.

 

문제 자체는 간단하다, String 배열로 받아서 저장해놓고 그때그때 맞는 녀석 출력하면 될 듯 하다. 한번 Rust로 짜보자..!

 

#![allow(unused_imports)]
#![allow(dead_code)]
use std::cmp::*;
use std::collections::*;
 
struct Scanner {
   buffer : std::collections::VecDeque<String>
}
 
impl Scanner {
 
   fn new() -> Scanner {
      Scanner {
         buffer: std::collections::VecDeque::new()
      }
   }
 
   fn next<T : std::str::FromStr >(&mut self) -> T {
 
      if self.buffer.len() == 0 {
         let mut input = String::new();
         std::io::stdin().read_line(&mut input).ok();
         for word in input.split_whitespace() {
            self.buffer.push_back(word.to_string())
         }
      }
 
      let front = self.buffer.pop_front().unwrap();
      front.parse::<T>().ok().unwrap()
   }

   fn next_str(&mut self) -> String {
    if self.buffer.len() == 0 {
       let mut input = String::new();
       std::io::stdin().read_line(&mut input).ok();
       for word in input.split_whitespace() {
          self.buffer.push_back(word.to_string())
       }
    }
    self.buffer.pop_front().unwrap()
    
 }
}
fn main() {
    let mut cin = Scanner::new();
    let n:usize = cin.next();
    let m:usize = cin.next();
    let mut arr = vec![]; // index to string
    let mut hash = HashMap::new(); // string to index
    
    for i in 0..n {
        let name = cin.next_str();
        arr.insert(i, name.clone());
        hash.insert(name.clone(), i);
    }
    for _ in 0..m {
        let query = cin.next_str();
        if query.parse::<i32>().is_ok() {
            let index: usize = query.parse::<usize>().unwrap();
            println!("{}", arr[index-1]);
        } else {
            match hash.get(&query) {
                Some(index) => println!("{}", index+1),
                None => println!("Error case!")
            }
        }
    }

}

어째 저째 대충 짜본 코드이다. 

스캐너로 input 받는것은 아래 레퍼런스를 참고했다.

http://codeforces.com/contest/702/submission/19589375

 

Submission #19589375 - Codeforces

 

codeforces.com

정답은 나올거같은데, TLE가 난다. 왜그런진 모르겟음

 

그래서 답답한 마음에 C++로 후다닥 다시 짜서 보았다.

#include <bits/stdc++.h>
using namespace std;
#define endl '\n'
#define MAXN 100005
string arr[MAXN];
map<string, int> mm;
int main() {
    int n, m;
    cin >> n >> m;
    ios::sync_with_stdio(false);
    cin.tie(NULL);
    
    for(int i=1 ;i <= n; i++) {
        string s;
        cin >> s;
        arr[i] = s;
        mm[s] = i;
    }
    for (int i=0; i < m; i++) {
        string v;
        cin >> v;
        if (v[0] >= 'A') {
            cout << mm[v] << endl;
        } else {
            int idx = atoi(v.c_str());
            cout << arr[idx] << endl;
        }
    }
}

하 C++로 하면 이렇게 간단하게 코드를 짤 수 있는데... 근데 보니깐 sync_with_stdio(false)를 넣지 않으면 TLE가 나는걸로 봐서는 아마 러스트 코드에서 TLE가 나는것은 Scanner가 느리거나, 인풋 받는데 vector를 선언해서 받고 하는데 이런게 느려서 그런거지 않을까 싶다.

 

고로... PS는 C++이 참 편하긴 한듯.

+ Recent posts