Spec-Zone.ru › Julia 1.1

Сети и потоки

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

Если вы хотите записать в файл, вы можете открыть его с флагом write ("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–2019 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors
Licensed under the MIT License.
https://docs.julialang.org/en/v1.1.1/manual/networking-and-streams/

Spec-Zone.ru

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