RustではTCP/IP通信を利用するためにいくつかの組み込みオブジェクトが用意されています。この章では簡単なTCP/IPアプリケーションを実装する上での基本的な方法を説明します。
まずTCP/IPアプリケーションを作成するにはいくつかの方法があります。
- rust標準のstd::rt::io::netパッケージを利用する
- rustで利用されるlibvuをffi経由で利用する
- OS標準のライブラリwinsocketやBSD socketをffi経由で利用する
今回は標準ライブラリであるstd::rt::io::netを利用した方法を説明します。
まず接続先をしているために、std::rt::io::net::ip スペースにある SocketAddrを利用します。もしIPアドレス10.0.0.2ポート、番号8080を指定するにはSockAddrを以下のように指定します。
SocketAddr {
ip: Ipv4Addr(127, 0, 0 ,1),
port: 8080
}
IPv6アドレスの場合は、IPv4Addrではなくて、IPv6Addr()を IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1)と利用することで指定可能です。
またホスト名の名前解決を行う場合は、std::rt::io::net::get_host_addresses("ホスト名") を使いましょう。これはIpAddrを返します。
先ほど説明したSocketAddrを利用してサーバーへ接続をしましょう。接続するためにはTcpStream::connectを使うことで可能になります。
TcpStream::connect(SocketAddr {
ip: Ipv4Addr(127, 0, 0 ,1),
port: 8080
});
データ送信には、TcpStreamを使います。さきほど説明したTcpStream::connectはTcpStreamを返しますので、それを利用することで可能になります。そしてTcpStreamのwrite()メソッドを利用することでデータ送信が行えます。
let mut oStream = TcpStream::connect(
SocketAddr {
ip: Ipv4Addr(127, 0, 0 ,1),
port: 8080
});
oStream.write(bytes!("HELLO\r\n\r\n"));
またデータ受信は、read()メソッドを利用します、
let mut buf = [0];
oStream.read(buf);
以上説明したものをまとめると以下のようなソースコードになります。なお、以下のコードはio_eror::cond.trapを利用してエラー処理を追加しています。
use std::rt::io::net::tcp::TcpStream;
use std::rt::io::net::ip::{SocketAddr, Ipv4Addr};
use std::rt::io::{io_error, Reader, Writer};
fn main() {
do io_error::cond.trap(|e| {
println("Cannot connected/sent");
}).inside {
let mut oStream = TcpStream::connect(
SocketAddr {
ip: Ipv4Addr(10, 0, 0 ,2),
port: 8080});
oStream.write(bytes!("HELLO\r\n\r\n"));
let mut buf = [0];
oStream.read(buf);
}
}
さてこれまでは、クライアント側の実装方法を示しました。ここからはサーバー側の実装方法について考えましょう。
待ち受けするにはTcpListener::bindを呼び出します。この例ではlocalhost (127.0.0.1) のポート8080で待ち受けします。
TcpListener::bind(SocketAddr {
ip: Ipv4Addr(127, 0, 0 ,1),
port: 8080
}).listen();
そしてクラアインとからの接続要求にAceeptするようにacceptメソッドを呼び出します。
let mut tcpStream = tcpListener.accept()
acceptメソッドはTcpStreamを返しますので、このオブジェクトを利用してデータの送受信が可能です。
match tcpStream.read(buf) {
Some(count) => {
println(str::from_utf8(buf));
}
None => {
}
}
さて、今までの例では1つづつの接続要求しか対応することはできません。複数の接続要求を一度に対処するためには、spawnを使って複数のタスクに分割しましょう。
let tcpStream = Cell::new(tcpListener.accept());
do spawn {
let mut streamReader = tcpStream.take();
loop {
let mut buf = [0];
match streamReader.read(buf) {
Some(count) => {
println(str::from_utf8(buf));
}
None => {
}
}
}
}
これをまとめると以下のようなソースコードになります。
use std::rt::io::net::tcp::TcpListener;
use std::rt::io::{Reader, Writer, Listener, Acceptor, io_error};
use std::rt::io::net::ip::{SocketAddr, Ipv4Addr};
use std::cell::Cell;
use std::str;
fn main() {
do io_error::cond.trap(|e| {
}).inside {
let mut tcpListener = TcpListener::bind(
SocketAddr {
ip: Ipv4Addr(127, 0, 0 ,1),
port: 8080}
).listen();
loop {
let tcpStream = Cell::new(tcpListener.accept());
do spawn {
let mut streamReader = tcpStream.take();
loop {
let mut buf = [0];
match streamReader.read(buf) {
Some(count) => {
println(str::from_utf8(buf));
}
None => {
}
}
}
}
}
}
}
std::rt::io::net::udp名前空間に用意されているUdpSocketとUdpStreamを利用してください。使用方法はTcpSocketとTcpStreamとほぼ同様です。 (分量が足りなければサンプル付きにします)
標準ライブラリとして提供されていませんが、https://github.com/chris-morgan/rust-http にサーバーとクライアントの実装があります。これを利用することでHTTP通信を簡単に実装することができます。
TCP/IPアプリケーションを方書く法はRustではTaskを利用してタスク自体を分割できるため非同期プログラミング的な要素はそれほど必要としません。そのために従来のソケットを利用した方法とほぼ変更なく簡単に記述可能かと思います。
また、シンプルなAPIのみを提供しているため、ソケットで存在するようなKEEP-ALIVEオプションなどはまだ実装されていないなど、まだ不十分なところがありますが、今後もそれほど変更されることはないでしょう。
これ、libuvのtypoではないですか?