1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
//! # tcp server
//!
//! ## 処理の流れ
//!
//! TcoListener::bind()でソケットを生成し、
//! accept()でクライアントからの接続を待ち受ける。
//!
//! accept()で接続があると、TcpStreamを返す。
//! TcpStreamはRead, Writeトレイトを実装しているので、
//! read()でクライアントからの入力を待ち受け、
//! write()でクライアントにデータを送信する。
//!
//! このサーバは、クライアントからの入力をそのまま返却する。
//!
//! ## ソケットの役割
//!
//! [1]で生成されるlistenerの役割は、
//! クライアントからのコネクション確立要求を待ち受けること。
//! それが届いたら、3 way handshakeを行い、
//! コネクション確立済みのソケットがカーネル内部のキューに生成される。
//!
//! `accept()` は、コネクション確立済みのソケットを返却することであり、
//! もし、コネクション確立済みのソケットがなければ、
//! クライアントからの接続要求があるまでブロックする。
//!
//! 実際のデータのやり取りはsteamを通して行われる。
//!
//! listenerとstreamは、同じソケットだが、役割が違う
//! listenerのようなソケットをリスニングソケット
//! streamのようなソケットを接続済みソケットと呼ぶこととする
//! (サーバーソケット、クライアントソケットという呼び方もあるらしい)
//!
//! ## handler()の役割
//! `stream.read()` は、steamにデータが到着するまでブロックする。
//! `read()` は `EOF` が到着すると(今回は通信が切断されると)、0を返却する。
//! `stream` がdropされると、コネクションが切断される。
//!
//! run server with:
//!
//! ```shell
//! ❯ cargo run tcp server 127.0.0.1:33333
//! ❯ cargo run tcp server 127.0.0.1:33333
//!    Compiling socket-programming v0.1.0 (/Users/takanorifukuyama/git/github.com/takanorifukuyama/socket-programming)
//!     Finished dev [unoptimized + debuginfo] target(s) in 1.09s
//!      Running `target/debug/socket-programming tcp server '127.0.0.1:33333'`
//! [2023-09-12T06:43:34Z DEBUG socket_programming::tcp_server] Handling data from 127.0.0.1:52937
//! aaaaaaaaaaaaaa
//! ```
//!
//! connect to server with:
//! ```shell
//! ❯ telnet 127.0.0.1 33333
//! Trying 127.0.0.1...
//! Connected to localhost.
//! Escape character is '^]'.
//! aaaaaaaaaaaaaa
//! aaaaaaaaaaaaaa
//! ```

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

/// 指定のソケットアドレスで接続を待ち受ける
pub fn serve(address: &str) -> Result<(), failure::Error> {
    let listener = TcpListener::bind(address)?;
    loop {
        let (stream, _) = listener.accept()?; // [1]

        // スレッドを立ち上げて接続に対処する
        // これにより、複数のクライアントと同時に通信できる
        thread::spawn(move || {
            handler(stream).unwrap_or_else(|error| error!("{:?}", error));
        });
    }
}

/// クライアントからの入力を待ち受け、受信したら同じものを返却する
fn handler(mut stream: TcpStream) -> Result<(), failure::Error> {
    debug!("Handling data from {}", stream.peer_addr()?);
    let mut buffer = [0u8; 1024];
    loop {
        let nbytes = stream.read(&mut buffer)?;
        if nbytes == 0 {
            debug!("Connection closed.");
            return Ok(());
        }
        println!("{}", str::from_utf8(&buffer[..nbytes])?);
        stream.write_all(&buffer[..nbytes])?;
    }
}