Skip to content

Instantly share code, notes, and snippets.

@makotokato
Last active December 29, 2015 23:29
Show Gist options
  • Save makotokato/7743373 to your computer and use it in GitHub Desktop.
Save makotokato/7743373 to your computer and use it in GitHub Desktop.

RustでのTCP/IPネットワークアプリケーション

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 => {
                        }
                    }
                }
            }
        }
    }
}

UDPはどうするのか?

std::rt::io::net::udp名前空間に用意されているUdpSocketとUdpStreamを利用してください。使用方法はTcpSocketとTcpStreamとほぼ同様です。 (分量が足りなければサンプル付きにします)

HTTPは標準で提供されないの?

標準ライブラリとして提供されていませんが、https://github.com/chris-morgan/rust-http にサーバーとクライアントの実装があります。これを利用することでHTTP通信を簡単に実装することができます。

まとめ

TCP/IPアプリケーションを方書く法はRustではTaskを利用してタスク自体を分割できるため非同期プログラミング的な要素はそれほど必要としません。そのために従来のソケットを利用した方法とほぼ変更なく簡単に記述可能かと思います。

また、シンプルなAPIのみを提供しているため、ソケットで存在するようなKEEP-ALIVEオプションなどはまだ実装されていないなど、まだ不十分なところがありますが、今後もそれほど変更されることはないでしょう。

@kenz-gelsoft
Copy link

釈迦に説法感はあるんですが winsocket って WinSock って呼ぶことが多かった気がします

もしIPアドレス10.0.0.2ポート、番号8080を指定するにはSockAddrを以下のように指定します。

SocketAddr

IPv6アドレスの場合は、IPv4Addrではなくて、IPv6Addr()を IPv6Addr(0, 0, 0, 0, 0, 0, 0, 1)と利用することで指定可能です。

Ipv4Addr, Ipv6Addr, Ipv6Addr

Rust では「CamelCase」でTCP/IPなど大文字の名前も先頭だけ大文字にするようです。

(分量が足りなければサンプル付きにします)

もう消してしまっていいですね

TCP/IPアプリケーションを方書く法はRust

TCP/IPアプリケーションをRustで書く場合、

くらいですかね?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment