网络和流媒体
Julia提供了一个具有广泛功能的接口,用于处理执行流数据输入和输出的对象,例如终端、管道和TCP套接字。 这些对象允许您以流模式发送和接收数据,这意味着数据在到达时按顺序处理。 虽然此接口在系统级别是异步的,但它对程序员来说看起来是同步的。 这是通过密集使用Julia的协作多线程功能来实现的(协程)。
基本流式I/O
julia> write(stdout, "Hello World"); # подавляет возврат значения 11 за счет «;»
Hello World
julia> read(stdin, Char)
'\n': ASCII/Unicode U+000a (category Cc: Other, control)
例如,要读取简单的字节数组,可以执行以下操作。
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时,将默认输出发送到控制台,并从键盘读取标准输入。
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
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)