AnyMath 文档

网络和流

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)

请注意 返回11,字节数(in "你好世界")写给 标准输出,但是这个返回值被抑制与 ;.

这里再次按下Enter键,以便Julia阅读换行符。 现在,正如你可以从这个例子中看到的, 将要写入的数据作为其第二个参数,而 讀!将要读取的数据的类型作为第二个参数。

例如,要读取一个简单的字节数组,我们可以这样做:

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

请注意 a 被写入 标准输出函数,返回值为 1 (自 0x61 是一个字节)。

对于文本I/O,请使用 印刷业展览方法,具体取决于您的需求(请参阅这两种方法的文档,详细讨论它们之间的区别):

julia> print(stdout, 0x61)
97

自定义漂亮-打印有关如何实现自定义类型的显示方法的更多信息。

IO输出上下文属性

有时IO输出可以受益于将上下文信息传递到show方法的能力。 该 IOContextobject提供了这个框架,用于将任意元数据与IO对象相关联。 例如, :紧凑=>真 向IO对象添加提示参数,调用的show方法应打印较短的输出(如果适用)。 查看 IOContext常用属性列表的文档。

使用文件

您可以使用 写(文件名::字符串,内容) 方法:

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)

解析IP地址

其中一个 连接不遵循的方法 听!方法是 连接(主机::字符串,端口),它将尝试连接到由 主机 端口上的参数由 港口 参数。 它允许你做类似的事情:

julia> connect("google.com", 80)
TCPSocket(RawFD(30) open, 0 bytes waiting)

此功能的基础是 获取资讯,这将做适当的地址解析:

julia> getaddrinfo("google.com")
ip"74.125.226.225"

异步I/O

暴露的所有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)