ネットワークとストリーム
Julia は、端末、パイプ、TCP ソケットなどのストリーミング I/O オブジェクトを扱う豊富なインターフェイスを提供します。このインターフェイスは、システム レベルでは非同期ですが、プログラマに同期的に提示され、通常は背後にある非同期処理を考える必要はありません。これは、Julia 協調スレッド(coroutine)機能を多用することによって達成されます。
Basic Stream I/O
All Julia streams expose at least a read
and a write
method, taking the stream as their first argument, e.g.:
julia> write(stdout, "Hello World"); # suppress return value 11 with ;
Hello World
julia> read(stdin, Char)
'\n': ASCII/Unicode U+000a (category Cc: Other, control)
Note that write
returns 11, the number of bytes (in "Hello World"
) written to stdout
, but this return value is suppressed with the ;
.
Here Enter was pressed again so that Julia would read the newline. Now, as you can see from this example, write
takes the data to write as its second argument, while read
takes the type of the data to be read as the second argument.
For example, to read a simple byte array, we could do:
julia> x = zeros(UInt8, 4)
4-element Array{UInt8,1}:
0x00
0x00
0x00
0x00
julia> read!(stdin, x)
abcd
4-element Array{UInt8,1}:
0x61
0x62
0x63
0x64
However, since this is slightly cumbersome, there are several convenience methods provided. For example, we could have written the above as:
julia> read(stdin, 4)
abcd
4-element Array{UInt8,1}:
0x61
0x62
0x63
0x64
or if we had wanted to read the entire line instead:
julia> readline(stdin)
abcd
"abcd"
Note that depending on your terminal settings, your TTY may be line buffered and might thus require an additional enter before the data is sent to Julia.
To read every line from stdin
you can use eachline
:
for line in eachline(stdin)
print("Found $line")
end
or read
if you wanted to read by character instead:
while !eof(stdin)
x = read(stdin, Char)
println("Found: $x")
end
Text I/O
Note that the write
method mentioned above operates on binary streams. In particular, values do not get converted to any canonical text representation but are written out as is:
julia> write(stdout, 0x61); # suppress return value 1 with ;
a
Note that a
is written to stdout
by the write
function and that the returned value is 1
(since 0x61
is one byte).
For text I/O, use the print
or show
methods, depending on your needs (see the documentation for these two methods for a detailed discussion of the difference between them):
julia> print(stdout, 0x61)
97
See Custom pretty-printing for more information on how to implement display methods for custom types.
IO Output Contextual Properties
Sometimes IO output can benefit from the ability to pass contextual information into show methods. The IOContext
object provides this framework for associating arbitrary metadata with an IO object. For example, :compact => true
adds a hinting parameter to the IO object that the invoked show method should print a shorter output (if applicable). See the IOContext
documentation for a list of common properties.
Working with Files
Like many other environments, Julia has an open
function, which takes a filename and returns an IOStream
object that you can use to read and write things from the file. For example, if we have a file, hello.txt
, whose contents are Hello, World!
:
julia> f = open("hello.txt")
IOStream(<file hello.txt>)
julia> readlines(f)
1-element Array{String,1}:
"Hello, World!"
If you want to write to a file, you can open it with the write ("w"
) flag:
julia> f = open("hello.txt","w")
IOStream(<file hello.txt>)
julia> write(f,"Hello again.")
12
If you examine the contents of hello.txt
at this point, you will notice that it is empty; nothing has actually been written to disk yet. This is because the IOStream
must be closed before the write is actually flushed to disk:
julia> close(f)
Examining hello.txt
again will show its contents have been changed.
Opening a file, doing something to its contents, and closing it again is a very common pattern. To make this easier, there exists another invocation of open
which takes a function as its first argument and filename as its second, opens the file, calls the function with the file as an argument, and then closes it again. For example, given a function:
function read_and_capitalize(f::IOStream)
return uppercase(read(f, String))
end
You can call:
julia> open(read_and_capitalize, "hello.txt")
"HELLO AGAIN."
to open hello.txt
, call read_and_capitalize
on it, close hello.txt
and return the capitalized contents.
To avoid even having to define a named function, you can use the do
syntax, which creates an anonymous function on the fly:
julia> open("hello.txt") do f
uppercase(read(f, String))
end
"HELLO AGAIN."
簡単な TCP の例
ここで、TCP ソケットを含む簡単な例に飛び込んで見ましょう。この機能は、Sockets
と呼ばれる標準ライブラリ パッケージにあります。 最初に単純なサーバーを作成してみましょう:
julia> using Sockets
julia> @async begin
server = listen(2000)
while true
sock = accept(server)
println("Hello World\n")
end
end
Task (runnable) @0x00007fd31dc11ae0
Unix ソケット API に精通している人にとって親しみやすいメソッド名ですが、その使用法は生の Unix ソケット API よりもいくらかシンプルです。listen
への最初の呼び出しは、この場合、指定されたポート (2000) 上の着信接続を待機するサーバーを作成します。同じ機能を使用して、他のさまざまな種類のサーバーを作成することもできます:
julia> listen(2000) # Listens on localhost:2000 (IPv4)
Sockets.TCPServer(active)
julia> listen(ip"127.0.0.1",2000) # Equivalent to the first
Sockets.TCPServer(active)
julia> listen(ip"::1",2000) # Listens on localhost:2000 (IPv6)
Sockets.TCPServer(active)
julia> listen(IPv4(0),2001) # Listens on port 2001 on all IPv4 interfaces
Sockets.TCPServer(active)
julia> listen(IPv6(0),2001) # Listens on port 2001 on all IPv6 interfaces
Sockets.TCPServer(active)
julia> listen("testsocket") # Listens on a UNIX domain socket
Sockets.PipeServer(active)
julia> listen("\\\\.\\pipe\\testsocket") # Listens on a Windows named pipe
Sockets.PipeServer(active)
最後の呼び出しの戻り値の型が異なることに注意してください。これは、このサーバーが TCP ではなく、名前付きパイプ (Windows) または UNIX ドメイン ソケットでリッスンするためです。また、Windows の名前付きパイプ形式は、名前プレフィックス (\\.\pipe\
) が ファイルの種類 を一意に識別するように特定のパターンである必要があります。TCP と名前付きパイプまたは UNIX ドメイン ソケットの違いはわずかで、accept
メソッドと connect
メソッドに関係しています。accept
メソッドは、作成したサーバーに接続をしようとしているクライアントとの接続を受け入れます。connect
関数は指定されたメソッドを使ってサーバーに接続します。connect
関数はlisten
と同じ引数を受け取るので、環境(ホスト、現在のカレントディレクトリなど)が同じであると仮定すると、接続を確立するために行ったのと同じ引数をconnect
に渡すことができるはずです。(上記のサーバーを作成した後)それを試してみましょう:
julia> connect(2000)
TCPSocket(open, 0 bytes waiting)
julia> Hello World
予想通り"Hello World" が(サーバー側に)プリントされました。それでは、舞台裏で何が起こったのかを実際に分析してみましょう。connect
を呼ぶと、作成したサーバーに接続します。一方、accept 関数は、新しく作成されたソケットへのサーバー側からの接続を返し、接続が成功したことを示す "Hello World" を出力します。
Julia の大きな強みは、I/O が実際に非同期的に発生しているにもかかわらず、API が同期的に公開されるので、コールバックを心配したり、サーバーが確実に実行されることを確認したりする必要がなかったことです。connect
を呼び出すと、現在のタスクは接続が確立されるのを待ち、その後も実行を続行しました。この一時停止では、サーバー タスクが実行を再開し (接続要求が利用可能になったため)、接続を受け入れ、メッセージを印刷し、次のクライアントを待機しました。ReadとWriteは同じように機能します。 これを確認するために次の単純なエコー サーバーを考えてみましょう:
julia> @async begin
server = listen(2001)
while true
sock = accept(server)
@async while isopen(sock)
write(sock, readline(sock, keep=true))
end
end
end
Task (runnable) @0x00007fd31dc12e60
julia> clientside = connect(2001)
TCPSocket(RawFD(28) open, 0 bytes waiting)
julia> @async while isopen(clientside)
write(stdout, readline(clientside, keep=true))
end
Task (runnable) @0x00007fd31dc11870
julia> println(clientside,"Hello World from the Echo Server")
Hello World from the Echo Server
他のストリームと同様に、close
を使用してソケットを切断します:
julia> close(clientside)
IP アドレスの解決
listen
メソッドと組み合わせて使われるconnect
メソッドの他にconnect(host::String,port)
の形式のものがあります。これは、host
パラメータで指定されたホストに対して、port
で指定されたポートを使って接続します。次のように使い方です:
julia> connect("google.com", 80)
TCPSocket(RawFD(30) open, 0 bytes waiting)
この機能のベースになっているのはgetaddrinfo
で、適切なアドレス解決を行います:
julia> getaddrinfo("google.com")
ip"74.125.226.225"