网络和流
Julia提供了丰富的接口来处理流式I/O对象,如终端,管道和TCP套接字。 这些对象允许以类似流的方式发送和接收数据,这意味着数据在可用时按顺序处理。 此接口虽然在系统级别是异步的,但以同步的方式呈现给程序员。 这是通过大量使用Julia合作线程来实现的(协程)的功能。
基本流I/O
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)
例如,要读取一个简单的字节数组,我们可以这样做:
julia> x = zeros(UInt8, 4)
4-element Vector{UInt8}:
0x00
0x00
0x00
0x00
julia> read!(stdin, x)
abcd
4-element Vector{UInt8}:
0x61
0x62
0x63
0x64
但是,由于这稍微麻烦,因此提供了几种方便的方法。 例如,我们可以将上述内容写为:
julia> read(stdin, 4)
abcd
4-element Vector{UInt8}:
0x61
0x62
0x63
0x64
或者如果我们想读整行:
julia> readline(stdin)
abcd
"abcd"
请注意,根据您的终端设置,您的TTY("电传打字机终端")可能是行缓冲的,因此可能需要额外的输入之前 标准普尔 数据发送给Julia。 在TTY中从命令行运行Julia时,默认情况下将输出发送到控制台,并从键盘读取标准输入。
for line in eachline(stdin)
print("Found $line")
end
或 xref:base/io-network.adoc#Base.read[讀! 如果你想按字符来阅读:
while !eof(stdin)
x = read(stdin, Char)
println("Found: $x")
end
文本I/O
请注意, xref:base/io-network.adoc#Base.write[写 上面提到的方法对二进制流进行操作。 特别是,值不会转换为任何规范文本表示形式,而是按原样写出:
julia> write(stdout, 0x61); # suppress return value 1 with ;
a
julia> print(stdout, 0x61)
97
见 自定义漂亮-打印有关如何实现自定义类型的显示方法的更多信息。
使用文件
您可以使用 写(文件名::字符串,内容) 方法:
julia> write("hello.txt", "Hello, World!")
13
(13 是写入的字节数。)
您可以使用 读取(文件名::字符串) 方法,或 读取(文件名::字符串,字符串) 将内容作为字符串:
julia> read("hello.txt", String)
"Hello, World!"
高级:流式文件
该 讀! 和 写 上面的方法允许您读取和写入文件内容。 像许多其他环境一样,Julia也有一个 打开函数,它接受一个文件名并返回一个 [美梦]可用于从文件中读取和写入内容的对象。 例如,如果我们有一个文件, 你好!.txt的,其内容为 你好,世界!:
julia> f = open("hello.txt")
IOStream(<file hello.txt>)
julia> readlines(f)
1-element Vector{String}:
"Hello, World!"
如果你想写入一个文件,你可以用写("w")旗帜:
julia> f = open("hello.txt","w")
IOStream(<file hello.txt>)
julia> write(f,"Hello again.")
12
如果你检查 你好!.txt的 此时,您会注意到它是空的;实际上还没有写入磁盘。 这是因为 [美]梦 必须在写入实际刷新到磁盘之前关闭:
julia> close(f)
检查;检查 你好!.txt的 再次将显示其内容已更改。
打开一个文件,对其内容做一些事情,然后再次关闭它是一种非常常见的模式。 为了使这更容易,存在另一个调用 打开它将函数作为第一个参数,文件名作为第二个参数,打开文件,以文件作为参数调用函数,然后再次关闭它。 例如,给定一个函数:
function read_and_capitalize(f::IOStream)
return uppercase(read(f, String))
end
你可以打电话:
julia> open(read_and_capitalize, "hello.txt")
"HELLO AGAIN."
要打开 你好!.txt的,打电话 read_and_capitalize 就可以了,关闭 你好!.txt的 并返回大写内容。
为了避免甚至必须定义一个命名函数,您可以使用 做 语法,即时创建匿名函数:
julia> open("hello.txt") do f
uppercase(read(f, String))
end
"HELLO AGAIN."
如果要将stdout重定向到文件:
# Open file for writing
out_file = open("output.txt", "w")
# Redirect stdout to file
redirect_stdout(out_file) do
# Your code here
println("This output goes to `out_file` via the `stdout` variable.")
end
# Close file
close(out_file)
将stdout重定向到文件可以帮助保存和分析程序输出,自动化流程并确保符合法规要求。
使用TCP的简单示例
让我们直接看一个使用TCP套接字的简单示例。 此功能包含在标准库中的套接字包中。 首先,让我们创建一个简单的服务器。
julia> using Sockets
julia> errormonitor(Threads.@spawn begin
server = listen(2000)
while true
sock = accept(server)
println("Hello World\n")
end
end)
Task (runnable) @0x00007fd31dc11ae0
对于那些熟悉Unix套接字API的人来说,方法名称会感到熟悉,尽管它们的用法比原始Unix套接字API简单一些。 第一个电话 听!在这种情况下,将创建一个服务器,等待指定端口(2000)上的传入连接。 同样的功能也可以用于创建各种其他类型的服务器:
julia> listen(2000) # Listens on localhost:2000 (IPv4)
Sockets.TCPServer(active)
julia>listen(ip"127.0.0.1",2000)#相当于第一
插座。服务器(活动)
julia>listen(ip"::1",2000)#Listen on localhost:2000(IPv6)
插座。服务器(活动)
julia>listen(IPv4(0),2001)#侦听所有IPv4接口上的端口2001
插座。服务器(活动)
julia>listen(IPv6(0),2001)#侦听所有IPv6接口上的端口2001
插座。服务器(活动)
julia>listen("testsocket")#侦听UNIX域套接字
插座。PipeServer(活动)
julia>听("\\\\。\\pipe\\testsocket")#监听一个名为pipe的Windows
插座。PipeServer(活动)
请注意,上次调用的返回类型不同。 这是因为此服务器不侦听TCP,而是侦听命名管道(Windows)或UNIX域套接字。 另请注意,windows命名管道格式必须是一个特定的模式,以便名称前缀(\\.\管道\)唯一标识https://docs.microsoft.com/windows/desktop/ipc/pipe-names[文件类型]。 TCP和命名管道或UNIX域套接字之间的区别是微妙的,与 接受和 连接方法。 该 接受方法检索到在我们刚刚创建的服务器上连接的客户端的连接,而 连接函数使用指定的方法连接到服务器。 该 连接函数采用与…相同的参数 听!,所以,假设环境(即主机,cwd等。)是否相同,您应该能够将相同的参数传递给 连接就像你听建立连接一样。 所以让我们尝试一下(在创建了上面的服务器之后):
julia> connect(2000)
TCPSocket(open, 0 bytes waiting)
julia> Hello World
正如预期的那样,我们看到了"Hello World"打印。 那么,让我们实际分析一下幕后发生的事情。 当我们打电话 连接,我们连接到我们刚刚创建的服务器。 同时,accept函数返回到新创建的套接字的服务器端连接,并打印"Hello World"以指示连接成功。
Julia的一个很大优势是,由于API是同步公开的,即使I/O实际上是异步发生的,我们不必担心回调,甚至不必确保服务器运行。 当我们打电话的时候 连接当前任务等待建立连接,并在完成连接后才继续执行。 在此暂停中,服务器任务恢复执行(因为连接请求现在可用),接受连接,打印消息并等待下一个客户端。 阅读和写作的工作方式相同。 要查看这一点,请考虑以下简单的echo服务器:
julia> errormonitor(Threads.@spawn begin
server = listen(2001)
while true
sock = accept(server)
Threads.@spawn 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> errormonitor(Threads.@spawn 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
与其他流一样,使用 接近/接近断开插座:
julia> close(clientside)
异步I/O
julia> task = Threads.@spawn open("foo.txt", "w") do io
write(io, "Hello, World!")
end;
julia> wait(task)
julia> readlines("foo.txt")
1-element Vector{String}:
"Hello, World!"
很常见的情况是,您希望同时执行多个异步操作并等待它们全部完成。 您可以使用 @同步宏导致您的程序阻塞,直到它包装的所有协程都退出:
julia> using Sockets
julia> @sync for hostname in ("google.com", "github.com", "julialang.org")
Threads.@spawn begin
conn = connect(hostname, 80)
write(conn, "GET / HTTP/1.1\r\nHost:$(hostname)\r\n\r\n")
readline(conn, keep=true)
println("Finished connection to $(hostname)")
end
end
Finished connection to google.com
Finished connection to julialang.org
Finished connection to github.com
组播;组播
朱莉娅支持https://datatracker.ietf.org/doc/html/rfc1112[多播]通过IPv4和IPv6使用用户数据报协议(UDP)作为传输。
与传输控制协议不同(https://datatracker.ietf.org/doc/html/rfc793[TCP]),UDP几乎没有对应用程序的需求做出任何假设。 TCP提供流量控制(它加速和减速以最大限度地提高吞吐量)、可靠性(丢失或损坏的数据包会自动重传)、排序(数据包在被提供给应用程序之前由操作系统排序)、段大小以及会话设置和拆卸。 UDP不提供此类功能。
UDP的常见用途是在多播应用程序中。 TCP是用于两个设备之间通信的有状态协议。 UDP可以使用特殊的多播地址来允许许多设备之间同时通信。
接收IP组播报文
要通过UDP多播传输数据,只需 recv的 套接字上,并将返回收到的第一个数据包。 请注意,它可能不是您发送的第一个数据包!
using Sockets
group = ip"228.5.6.7"
socket = Sockets.UDPSocket()
bind(socket, ip"0.0.0.0", 6789)
join_multicast_group(socket, group)
println(String(recv(socket)))
leave_multicast_group(socket, group)
close(socket)
发送IP多播数据包
要通过UDP多播传输数据,只需 发送 到插座。 请注意,发送方没有必要加入多播组。
using Sockets
group = ip"228.5.6.7"
socket = Sockets.UDPSocket()
send(socket, group, 6789, "Hello over IPv4")
close(socket)
IPv6示例
此示例提供与前一程序相同的功能,但使用IPv6作为网络层协议。
听众:
using Sockets
group = Sockets.IPv6("ff05::5:6:7")
socket = Sockets.UDPSocket()
bind(socket, Sockets.IPv6("::"), 6789)
join_multicast_group(socket, group)
println(String(recv(socket)))
leave_multicast_group(socket, group)
close(socket)
寄件人:
using Sockets
group = Sockets.IPv6("ff05::5:6:7")
socket = Sockets.UDPSocket()
send(socket, group, 6789, "Hello over IPv6")
close(socket)