Engee 文档

网络和流媒体

Julia提供了一个具有广泛功能的接口,用于处理执行流数据输入和输出的对象,例如终端、管道和TCP套接字。 这些对象允许您以流模式发送和接收数据,这意味着数据在到达时按顺序处理。 虽然此接口在系统级别是异步的,但它对程序员来说看起来是同步的。 这是通过密集使用Julia的协作多线程功能来实现的(协程)。

基本流式I/O

至少以下方法可用于Julia中的所有流 '阅读''write',它将流名称作为第一个参数,例如:

julia> write(stdout, "Hello World");  # подавляет возврат значения 11 за счет «;»
Hello World
julia> read(stdin, Char)

'\n': ASCII/Unicode U+000a (category Cc: Other, control)

请注意,命令 write'返回11—​输出到流的字节数(在短语`Hello World`中) 'stdout',但是,使用;'来抑制此值的返回。

之后,再次按下Enter键,以便Julia环境读取到新行的过渡。 从例子中可以看出, 'write'将正在写入的数据作为第二个参数,并且 'read'--正在读取的数据类型。

例如,要读取简单的字节数组,可以执行以下操作。

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

但是,这是一个相当繁琐的实现,因此有几种更方便的方法。 例如,上面的过程可以写成如下。

julia> read(stdin, 4)
abcd
4-element Array{UInt8,1}:
 0x61
 0x62
 0x63
 0x64

如果我们想计算整个字符串,我们需要以下内容。

julia> readline(stdin)
abcd
"abcd"

请注意,根据终端设置,您可能正在缓冲TTY中的线路("终端电传打字机"),因此您可能需要再次按Enter键将’stdin`数据发送给Julia。 在TTY中从命令行运行Julia时,将默认输出发送到控制台,并从键盘读取标准输入。

读取每一行 'stdin'可以使用 '每行'

for line in eachline(stdin)
    print("Found $line")
end

要阅读单个字符,您可以使用 '阅读'

while !eof(stdin)
    x = read(stdin, Char)
    println("Found: $x")
end

文本输入/输出

请注意,上述方法 'write'适用于二进制数据流。 特别是,这些值不会转换为任何规范文本表示形式,而是按原样输出。:

julia> write(stdout, 0x61);  # подавляет возврат значения 1 за счет «;»
a

这里是功能 write在流中输出`a' stdout,它的返回值是'1`(因为`0x61’的大小是一个字节)。

对于文本输入/输出,请酌情使用该方法 '打印''show'(有关它们之间差异的详细描述,请参阅他们的文档)。

julia> print(stdout, 0x61)
97

有关自定义类型的映射方法实现的详细信息,请参阅 程序代码的可定制结构打印输出

输出的上下文属性

有时,将上下文数据传递给输出的显示方法是很有用的。 对象用作与I/O对象通信任意元数据的平台。 'IOContext'。 例如,`:compact=>true'向I/O对象添加一个参数,并提示正在调用的显示方法应输出缩写数据(如果适用)。 在文档中 'IOContext'提供常用属性的列表。

使用文件

您可以使用`write(filename::String,content)`方法将内容写入文件:

julia> write("hello.txt", "Hello, World!")
13

('13’是写入的字节数。)

文件的内容可以使用`read(filename::String)`或`read(filename::String,String)`方法作为字符串读取:

julia> read("hello.txt", String)
"Hello, World!"

可选:文件流

使用上面的’read’和`write’方法,可以读写文件的内容。 与许多其他环境一样,Julia也有一个功能 'open',它接受文件名并返回对象 'IOStream',可用于读取和写入文件中的数据。 假设我们有一个文件’你好。txt’包含字符串’你好,世界!`.

julia> f = open("hello.txt")
IOStream(<file hello.txt>)

julia> readlines(f)
1-element Array{String,1}:
 "Hello, World!"

要将某些内容写入文件,您可以使用写入标志("w")打开它。

julia> f = open("hello.txt","w")
IOStream(<file hello.txt>)

julia> write(f,"Hello again.")
12

如果在这个阶段,我们看看’你好’中包含的内容。txt你会看到该文件是空的,因为还没有写入磁盘。 为了将记录的数据保存在磁盘上,有必要关闭"IOStream"。

julia> close(f)

如果你再复习一遍’你好。txt`,你会看到它的内容发生了变化。

打开一个文件,与其内容交互,然后关闭它是一种非常常见的工作模式。 为了简化它,还有另一个看涨期权。 'open',它有两个参数:函数和文件名。 它打开文件,调用一个以文件为参数的函数,然后文件再次关闭。 例如,如果有这样的功能:

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’将被调用,然后’hello。txt该文件将被关闭,其内容,写在大写字母,将被返回。

你甚至不能定义一个命名函数,而是使用`do`语法,它在运行时创建一个匿名函数。

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(@async begin
           server = listen(2000)
           while true
               sock = accept(server)
               println("Hello World\n")
           end
       end)
Task (runnable) @0x00007fd31dc11ae0

那些使用Unix套接字API的人将熟悉这些方法的名称,尽管使用这些方法稍微简单一些。 第一个挑战 `listen'在本例中指定的端口(2000)上创建一个等待传入连接的服务器。 相同的功能可用于创建其他不同类型的服务器。

julia> listen(2000) # Прослушивает localhost:2000 (IPv4)
Sockets.TCPServer(active)

julia> listen(ip"127.0.0.1",2000) # Эквивалентен первому
Sockets.TCPServer(active)

julia> listen(ip"::1",2000) # Прослушивает localhost:2000 (IPv6)
Sockets.TCPServer(active)

julia> listen(IPv4(0),2001) # Прослушивает порт 2001 на всех интерфейсах IPv4
Sockets.TCPServer(active)

julia> listen(IPv6(0),2001) # Прослушивает порт 2001 на всех интерфейсах IPv6
Sockets.TCPServer(active)

julia> listen("testsocket") # Прослушивает сокет домена UNIX
Sockets.PipeServer(active)

julia> listen("\\\\.\\pipe\\testsocket") # Прослушивает именованный канал Windows
Sockets.PipeServer(active)

请注意,最后一次调用具有不同的返回类型。 这是由于服务器不通过TCP侦听,而是通过命名管道(Windows)或UNIX域套接字侦听。 另请注意,命名的Windows管道具有特殊格式,其中名称前缀('\\。\pipe\')包含唯一标识符。 https://docs.microsoft.com/windows/desktop/ipc/pipe-names [文件类型]。 UNIX域中的TCP和命名管道和套接字之间的区别并不明显,并且与方法有关 '接受''连接'。 方法 'accept'从创建的服务器获取到客户端的连接,而函数 'connect'使用指定的方法连接到服务器。 功能 `connect'接受与 'listen',所以如果环境(即节点,cwd等。)是一样的,那么你可以通过 `connect'与监听建立连接的参数相同。 让我们尝试这样做(在创建上面的服务器之后)。

julia> connect(2000)
TCPSocket(open, 0 bytes waiting)

julia> Hello World

正如预期的那样,显示Hello World。 让我们分析一下幕后发生了什么。 打电话时 'connect'我们正在连接到新创建的服务器。 Accept函数从服务器端返回到新创建的套接字的连接,并输出Hello World,表示连接已经建立。

Julia的一个重要优点是API是同步提供的,尽管输入和输出实际上是异步发生的,所以我们不必担心回调甚至检查服务器启动。 打电话时 `connect'当前任务等待连接建立,然后才继续执行。 在此暂停期间,服务器任务将恢复(因为连接请求现在可用)。 它接受连接,输出消息,并等待下一个客户端。 阅读和写作以类似的方式工作。 让我们把这个作为一个简单的回声服务器的例子。

julia> errormonitor(@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> errormonitor(@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地址解析

其中一种方法是 'connect',它不遵循方法 'listen',是一个`connect(host::String,port)`,试图在`port`参数指定的端口上连接到`host`参数指定的节点。 它允许您执行以下操作。

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

此功能的基础是方法 'getaddrinfo',执行必要的地址解析。

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

异步I/O

所有I/O操作可通过 '基地。阅读''基地。write',可以使用异步执行 协程。 您可以使用宏创建用于读取或写入流中的数据的协程 '@async'

julia> task = @async open("foo.txt", "w") do io
           write(io, "Hello, World!")
       end;

julia> wait(task)

julia> readlines("foo.txt")
1-element Array{String,1}:
 "Hello, World!"

通常情况下,需要同时执行许多异步操作并等待所有操作完成。 要在退出所有包装在其中的协程之前阻止程序,可以使用宏 '@sync'

julia> using Sockets

julia> @sync for hostname in ("google.com", "github.com", "julialang.org")
           @async 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和IPv6https://datatracker.ietf.org/doc/html/rfc768 [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多播传输数据,只需为套接字使用`send’即可。 请注意,您不需要将发送方附加到多播组。

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)