Сеть и потоки
Julia предоставляет богатый интерфейс для работы с потоковыми объектами ввода/вывода, такими как терминалы, каналы и сокеты TCP. Хотя этот интерфейс асинхронный на уровне системы, он представлен синхронным образом программисту, и обычно нет необходимости задумываться об основополагающей асинхронной операции. Это достигается за счет интенсивного использования кооперативного многопоточия Julia (корутины).
Базовый ввод/вывод потоков
Все потоки Julia предоставляют по крайней мере метод read() и метод write(), принимающие поток в качестве первого аргумента, например:
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)
Обратите внимание, что 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 перед отправкой данных в Julia.
Чтобы прочитать каждую строку из STDIN, вы можете использовать eachline():
for line in eachline(STDIN)
print("Found $line")
end
или read(), если вы хотите читать посимвольно:
while !eof(STDIN)
x = read(STDIN, Char)
println("Found: $x")
end
Ввод/вывод текста
Обратите внимание, что метод write(), упомянутый выше, работает с двоичными потоками. В частности, значения не преобразуются в какое-либо каноническое текстовое представление, а записываются как есть:
julia> write(STDOUT,0x61); # suppress return value 1 with ; a
Обратите внимание, что a записывается в STDOUT функцией write(), и возвращаемое значение равно 1 (поскольку 0x61 - один байт).
Для ввода/вывода текста используйте методы print() или show(), в зависимости от ваших потребностей (см. справочник по стандартной библиотеке для подробного обсуждения различий между ними):
julia> print(STDOUT, 0x61) 97
Свойства контекста вывода IO
Иногда вывод IO может извлечь выгоду из возможности передавать контекстную информацию в методы отображения. Объект IOContext предоставляет этот фреймворк для ассоциирования произвольных метаданных с объектом IO. Например, showcompact добавляет параметр подсказки в объект IO, что вызываемый метод отображения должен выводить более короткий вывод (если это применимо).
Работа с файлами
Как и во многих других средах, Julia имеет функцию open(), которая принимает имя файла и возвращает объект IOStream, который вы можете использовать для чтения и записи данных из файла. Например, если у нас есть файл, hello.txt, содержимое которого Hello, World!:
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
Если вы сейчас проверите содержимое hello.txt, вы заметите, что оно пустое; на самом деле еще ничего не было записано на диск. Это потому, что IOStream должен быть закрыт, прежде чем запись будет фактически сброшена на диск:
julia> close(f)
Если снова проверить hello.txt, вы увидите, что его содержимое изменилось.
Открытие файла, выполнение каких-либо действий с его содержимым и последующее закрытие — очень распространенный шаблон. Чтобы упростить это, существует другое вызов open(), который принимает функцию в качестве первого аргумента и имя файла в качестве второго, открывает файл, вызывает функцию с файлом в качестве аргумента и затем снова закрывает его. Например, дана функция:
function read_and_capitalize(f::IOStream)
return uppercase(readstring(f))
end
Вы можете вызвать:
julia> open(read_and_capitalize, "hello.txt") "HELLO AGAIN."
чтобы открыть hello.txt, вызвать read_and_capitalize on it, закрыть hello.txt и вернуть содержимое в верхнем регистре.
Чтобы избежать даже необходимости определения именованной функции, вы можете использовать синтаксис do, который создает анонимную функцию на лету:
julia> open("hello.txt") do f
uppercase(readstring(f))
end
"HELLO AGAIN."
Простой пример TCP
Давайте сразу перейдем к простому примеру с использованием сокетов TCP. Сначала создадим простой сервер:
julia> @async begin
server = listen(2000)
while true
sock = accept(server)
println("Hello World\n")
end
end
Task (runnable) @0x00007fd31dc11ae0
Для тех, кто знаком с API сокетов Unix, имена методов будут знакомы, хотя их использование несколько проще, чем в API необработанных сокетов Unix. Первый вызов listen() создаст сервер, ожидающий входящих подключений на указанном порте (2000) в данном случае. Такая же функция также может быть использована для создания различных других типов серверов:
julia> listen(2000) # Listens on localhost:2000 (IPv4)
TCPServer(active)
julia> listen(ip"127.0.0.1",2000) # Equivalent to the first
TCPServer(active)
julia> listen(ip"::1",2000) # Listens on localhost:2000 (IPv6)
TCPServer(active)
julia> listen(IPv4(0),2001) # Listens on port 2001 on all IPv4 interfaces
TCPServer(active)
julia> listen(IPv6(0),2001) # Listens on port 2001 on all IPv6 interfaces
TCPServer(active)
julia> listen("testsocket") # Listens on a UNIX domain socket/named pipe
PipeServer(active)
Обратите внимание, что тип возвращаемого значения последнего вызова отличается. Это связано с тем, что этот сервер не слушает TCP, а слушает именованную трубу (Windows) или сокет доменного типа 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 является то, что поскольку API экспонируется синхронно, даже если ввод/вывод происходит асинхронно, нам не нужно было беспокоиться о коллбеках или даже о том, чтобы убедиться, что сервер выполняется. Когда мы вызвали connect(), текущая задача ожидала установления соединения и продолжила выполнение только после этого. В этой паузе задача сервера возобновила выполнение (поскольку теперь доступна заявка на подключение), приняла подключение, вывела сообщение и ожидала следующего клиента. Чтение и запись работают аналогично. Чтобы увидеть это, рассмотрим следующий простой сервер эха:
julia> @async begin
server = listen(2001)
while true
sock = accept(server)
@async while isopen(sock)
write(sock,readline(sock))
end
end
end
Task (runnable) @0x00007fd31dc12e60
julia> clientside = connect(2001)
TCPSocket(RawFD(28) open, 0 bytes waiting)
julia> @async while true
write(STDOUT,readline(clientside))
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), который попытается подключиться к хосту, указанному в параметре host, на порте, указанном в параметре порт. Это позволяет вам делать такие вещи, как:
julia> connect("google.com",80)
TCPSocket(RawFD(30) open, 0 bytes waiting)
В основе этой функциональности лежит getaddrinfo(), который выполнит соответствующее разрешение адресов:
julia> getaddrinfo("google.com")
ip"74.125.226.225"
© 2009–2016 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/release-0.6/manual/networking-and-streams/