Spec-Zone.ru › Julia 0.5

Сети и потоки

Julia предоставляет богатый интерфейс для работы со потоковыми объектами ввода-вывода, такими как терминалы, каналы и TCP-сокеты. Хотя этот интерфейс на системном уровне асинхронный, он представлен программисту в синхронном виде, и обычно нет необходимости задумываться об асинхронных операциях. Это достигается с помощью активного использования кооперативного многопоточности Julia (корутины).

Основные потоковые операции ввода-вывода

Все потоки Julia поддерживают, по меньшей мере, метод read() и метод write(), принимающие поток в качестве первого аргумента, например:

julia> write(STDOUT,"Hello World");  # suppress return value 11 with ;
Hello World
julia> read(STDIN,Char)

'\n'

Обратите внимание, что 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\n"

Обратите внимание, что в зависимости от настроек вашего терминала, ваш 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. Например, showcompact добавляет параметр подсказки в объект IO, который вызванный метод show должен вывести более короткий вывод (если применимо).

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

Как и во многих других средах, 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!\n"

Если вы хотите записать в файл, вы можете открыть его с флагом записи ("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

julia>

Для тех, кто знаком с Unix-сокет API, имена методов будут знакомы, хотя их использование несколько проще, чем использование исходного Unix-сокет API. Первый вызов 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(), поэтому, предполагая, что среда (т. е. хост, cwd и т. д.) одинакова, вы должны иметь возможность передать те же аргументы в connect(), что и для прослушивания, чтобы установить подключение. Давайте попробуем это (после создания сервера выше):

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

julia> Hello World

Как ожидалось, мы увидели «Привет, мир». Итак, давайте проанализируем, что происходило за кулисами. Когда мы вызывали connect(), мы подключались к серверу, который мы только что создали. Тем временем, функция accept возвращает подключение к серверу к вновь созданному сокету и выводит «Привет, мир», чтобы указать, что подключение было успешно.

Огромное преимущество 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(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, на порту, заданном параметром port. Это позволяет выполнять действия, такие как:

julia> connect("google.com",80)
TCPSocket(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.5/manual/networking-and-streams/

Spec-Zone.ru

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