Spec-Zone.ru › Julia 1.4

Сеть и потоки

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 может извлечь выгоду из возможности передавать контекстную информацию в методы show. Объект IOContext предоставляет эту структуру для ассоциации произвольных метаданных с объектом IO. Например, :compact => true добавляет параметр подсказки в объект IO, который вызванный метод show должен вывести более короткий вывод (если это применимо). Список общих свойств см. в документации IOContext.

Работа с файлами

Как и во многих других средах, 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(read(f, String))
end

Вы можете вызвать:

julia> open(read_and_capitalize, "hello.txt")
"HELLO AGAIN."

чтобы открыть hello.txt, применить к нему read_and_capitalize, закрыть hello.txt и вернуть содержимое в верхнем регистре.

Чтобы даже не нужно было определять именованную функцию, можно использовать синтаксис do, который создает анонимную функцию на лету:

julia> open("hello.txt") do f
           uppercase(read(f, String))
       end
"HELLO AGAIN."

Простой пример TCP

Давайте сразу перейдем к простому примеру с TCP-сокетами. Эта функциональность находится в стандартной библиотеке пакета Sockets. Сначала создадим простой сервер:

julia> using Sockets

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)
Sockets.TCPServer(active)

julia> listen(ip"127.0.0.1",2000) # Equivalent to the first
Sockets.TCPServer(active)

julia> listen(ip"::1",2000) # Listens on localhost:2000 (IPv6)
Sockets.TCPServer(active)

julia> listen(IPv4(0),2001) # Listens on port 2001 on all IPv4 interfaces
Sockets.TCPServer(active)

julia> listen(IPv6(0),2001) # Listens on port 2001 on all IPv6 interfaces
Sockets.TCPServer(active)

julia> listen("testsocket") # Listens on a UNIX domain socket
Sockets.PipeServer(active)

julia> listen("\\\\.\\pipe\\testsocket") # Listens on a Windows named pipe
Sockets.PipeServer(active)

Обратите внимание, что возвращаемый тип последнего вызова отличается. Это связано с тем, что этот сервер не слушает TCP, а слушает именованную трубу (Windows) или сокет доменной области Unix. Также обратите внимание, что формат именованной трубы Windows должен соответствовать определенному шаблону, таким образом, префикс имени (\\.\pipe\) уникально идентифицирует тип файла. Разница между TCP и именованными трубами или сокетами доменной области 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, keep=true))
               end
           end
       end
Task (runnable) @0x00007fd31dc12e60

julia> clientside = connect(2001)
TCPSocket(RawFD(28) open, 0 bytes waiting)

julia> @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), который попытается подключиться к хосту, заданному параметром host, на порту, заданном параметром port. Он позволяет вам делать такие вещи, как:

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

В основе этой функциональности лежит getaddrinfo, который выполнит соответствующее разрешение адреса:

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

© 2009–2020 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.4.2/manual/networking-and-streams/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API