お久しぶりです。季節の移り変わりとともに、もはや誰も TELNET の電子公告の話をしなくなってしまいましたが、今回は TELNET サーバ用のプログラムを作ったという内容の記事です。
非常に今更感があるのでお蔵入りにしようかとも思いましたが、最近マジで記事に書くネタがないのでやっぱり書いておきます。
実験環境など
-
OS : Windows 10, macOS 13.3.1
-
Rust 1.72.0
-
encoding_rs 0.8.23
-
tokio 1.32.0
-
clap 4.4.2
実験環境など
-
OS : Windows 10, macOS 13.3.1
-
Rust 1.72.0
-
encoding_rs 0.8.23
-
tokio 1.32.0
-
clap 4.4.2
リポジトリ
前回に引き続き、rustel にサーバモードとして実装しました。
GitHub - yotiosoft/rustel at rustel_v3
サーバの立て方
- メッセージを引数で渡す場合:
$ rustel -s -u [URL(127.0.0.1 etc.)] -p [Port Number] -e [Encode (utf8 or sjis)] -m [message]
- メッセージをファイルから読み込む場合:
$ rustel -s -u [URL(127.0.0.1 etc.)] -p [Port Number] -e [Encode (utf8 or sjis)] -f [filepath]
- 1文字ずつ送信する場合:
$ rustel -s -u [URL(127.0.0.1 etc.)] -p [Port Number] -e [Encode (utf8 or sjis)] -f [filepath] -o
実装
サーバと言っても、メッセージを平文でソケット通信で送出するだけです。
クライアントモードとは異なり、サーバ側ですので指定したポート番号を bind し、listener からの要求を待機します。
その後クライアントからのアクセスがあり次第、定型文 server_message を送信します。
今回、複数のクライアントからの同時接続に対応するため、クライアントごとにスレッドを生成しています。
async fn server(host: String, port: u16, encode: args::Encode, ipv: IPv, server_one_char: bool, server_message: Option<String>, wait_ms: u64) -> Result<(), std::io::Error> {
let host_and_port = format!("{}:{}", host, port);
let mut addresses = host_and_port.to_socket_addrs()?;
let address = match ipv {
IPv::IPv4 => addresses.find(|x| x.is_ipv4()),
IPv::IPv6 => addresses.find(|x| x.is_ipv6()),
};
let listener = TcpListener::bind(address.unwrap()).await?;
loop {
let (mut stream, _) = listener.accept().await?;
let encode_clone = encode.clone();
let server_message = server_message.clone();
tokio::spawn(async move {
let addr = stream.peer_addr().unwrap();
println!("Accepted connection from: {}", addr);
let (reader, writer) = tokio::io::split(stream);
// read
let reader = tokio::spawn(telnet::telnet_recv(reader, encode_clone.clone()));
// write
let writer = if let Some(server_message) = server_message {
if server_one_char {
tokio::spawn(telnet::telnet_send_message_per_one_char(writer, encode_clone.clone(), server_message, wait_ms))
}
else {
tokio::spawn(telnet::telnet_send_message(writer, encode_clone.clone(), server_message))
}
}
else {
tokio::spawn(telnet::telnet_send(writer, encode_clone.clone()))
};
let _ = reader.await;
writer.abort();
println!("Connection with {} closed.", addr);
});
}
Ok(())
}
工夫した点として、例の TELNET 電子公告は RPG のごとく 1 文字ずつメッセージが表示されます。
これはサーバが 1 パケットに対し 1 文字ずつ送信しているからなのですが、これを再現するために 1 文字ずつ送信するモード (–one-char) を設けました。パケット送信時の wait 時間を調整 (-wait-ms) することも可能です。
Usage: rustel [OPTIONS] --url <URL>
Options:
-s, --server set as server mode
-c, --client set as client mode (default)
-u, --url <URL> destination URL (required)
-p, --port <PORT> destination port number (default: 23) [default: 23]
-e, --encode <ENCODE> encode (utf8 or shift_jis; default: utf8) [default: utf8]
-i, --ipv <IPV> IP version (4 or 6; default: 4) [default: 4]
-o, --one-char send one character to client (server mode only)
-w, --wait-ms <WAIT_MS> wait time for sending the message (millisecond) (server mode only; default: 100) [default: 100]
-m, --message <MESSAGE> message to send to client (server mode only)
-f, --file <FILE> message file to send to client (server mode only)
-h, --help Print help
-V, --version Print version
ファイルから読み込ませて送信することもできます (–file) 。
動作確認
ファイルから読み込み、1文字ずつサーバからクライアントに送信している様子です。
今後の予定
-
サーバ側でチャット機能に対応する
-
パイプからサーバ用メッセージを受け取れるようにする
-
TELNET on SSL に対応する(できたら)