std/asyncnet
SourceEditЭтот модуль реализует высокоуровневый асинхронный API сокетов, основанный на асинхронном диспетчере, определённом в модуле asyncdispatch.
Асинхронное ввод-вывод в Nim
Асинхронный ввод-вывод в Nim состоит из нескольких слоёв (с верхнего до нижнего):
-
asyncnetмодуль - Async await
-
asyncdispatchмодуль (цикл обработки событий) -
selectorsмодуль
Каждый слой строится поверх предыдущего. Модуль селекторов является абстракцией для различных системных select() механизмов, таких как epoll или kqueue. Если вы хотите, вы можете использовать его напрямую, и некоторые люди делали это успешно. Но вы должны понимать, что в Windows он поддерживает только select().
Асинхронный диспетчер реализует шаблон проактора, а также имеет реализацию IOCP. Он реализует шаблон проактора для других ОС через модуль селекторов. Здесь также реализованы фьючеры, и, на самом деле, все процедуры возвращают фьючер.
Последний слой — это преобразование async await. Это позволяет писать асинхронный код в синхронном стиле и работает аналогично await в C#. Преобразование работает путём преобразования всех асинхронных процедур в итератор.
Всё это однопоточно, полностью неблокирующее и даёт вам большой контроль. Теоретически вы должны иметь возможность работать с любыми из этих слоёв взаимозаменяемо (пока вас не интересуют платформы Windows).
Для большинства приложений использование asyncnet является лучшим вариантом, так как оно построено поверх всех слоёв, предоставляя некоторые дополнительные функции, такие как буферизация.
SSL
SSL можно включить, скомпилировав с флагом -d:ssl.
Вы должны создать новый контекст SSL с помощью функции newContext, определённой в модуле net. Затем вы можете вызвать wrapSocket для вашего сокета, используя только что созданный контекст SSL, чтобы получить SSL сокет.
Примеры
Сервер чата
Следующий пример демонстрирует простой сервер чата.
import std/[asyncnet, asyncdispatch]
var clients {.threadvar.}: seq[AsyncSocket]
proc processClient(client: AsyncSocket) {.async.} =
while true:
let line = await client.recvLine()
if line.len == 0: break
for c in clients:
await c.send(line & "\c\L")
proc serve() {.async.} =
clients = @[]
var server = newAsyncSocket()
server.setSockOpt(OptReuseAddr, true)
server.bindAddr(Port(12345))
server.listen()
while true:
let client = await server.accept()
clients.add client
asyncCheck processClient(client)
asyncCheck serve()
runForever() Импорты
- since, asyncdispatch, nativesockets, net, os, openssl
Типы
Процедуры
proc accept(socket: AsyncSocket; flags = {SafeDisconn}): owned( Future[AsyncSocket]) {....raises: [ValueError, OSError, Exception], tags: [RootEffect], forbids: [].}- Принимает новое подключение. Возвращает будущее, содержащее сокет клиента, соответствующий этому подключению. Если
inheritableравно false (по умолчанию), полученный сокет клиента не будет наследуем дочерними процессами. Будущее завершится, когда подключение будет успешно принято. Исходный код Редактировать proc acceptAddr(socket: AsyncSocket; flags = {SafeDisconn}; inheritable = defined(nimInheritHandles)): owned( Future[tuple[address: string, client: AsyncSocket]]) {. ...raises: [ValueError, OSError, Exception], tags: [RootEffect], forbids: [].}-
Принимает новое подключение. Возвращает будущее, содержащее сокет клиента, соответствующий этому подключению, и удалённый адрес клиента.
Если
inheritableравно false (по умолчанию), полученный сокет клиента не будет наследуем дочерними процессами.Будущее завершится, когда подключение будет успешно принято.
Исходный код Редактировать proc bindAddr(socket: AsyncSocket; port = Port(0); address = "") {. ...tags: [ReadIOEffect], raises: [ValueError, OSError], forbids: [].}-
Привязывает
address:portк сокету.Если
Исходный код Редактироватьaddressравно "", то будет привязано ADDR_ANY. proc bindUnix(socket: AsyncSocket; path: string) {....raises: [], tags: [], forbids: [].}- Привязывает Unix-сокет к
path. Работает только на системах Unix-подобного типа: Mac OS X, BSD и Linux Исходный код Редактировать proc close(socket: AsyncSocket) {....raises: [LibraryError, Exception, SslError], tags: [RootEffect], forbids: [].}- Закрывает сокет. Исходный код Редактировать
proc connect(socket: AsyncSocket; address: string; port: Port): owned( Future[void]) {....stackTrace: false, raises: [Exception], tags: [RootEffect], forbids: [].}-
Подключает
socketк серверу по адресуaddress:port.Возвращает
Исходный код РедактироватьFuture, который завершится, когда подключение будет выполнено успешно или произойдёт ошибка. proc connectUnix(socket: AsyncSocket; path: string): owned(Future[void]) {. ...raises: [], tags: [], forbids: [].}- Привязывает Unix-сокет к
path. Работает только на системах Unix-подобного типа: Mac OS X, BSD и Linux Исходный код Редактировать proc dial(address: string; port: Port; protocol = IPPROTO_TCP; buffered = true): owned( Future[AsyncSocket]) {....stackTrace: false, raises: [Exception, ValueError], tags: [RootEffect], forbids: [].}- Устанавливает соединение с указанной
address:portпарой через указанный протокол. Процедура перебирает возможные разрешенияaddress, пока не добьётся успеха, что означает плавную работу как с IPv4, так и с IPv6. Возвращает AsyncSocket, готовый к отправке или приёму данных. Исходный код Редактировать proc getFd(socket: AsyncSocket): SocketHandle {....raises: [], tags: [], forbids: [].}- Возвращает дескриптор файла сокета. Исходный код Редактировать
proc getLocalAddr(socket: AsyncSocket): (string, Port) {. ...raises: [OSError, Exception], tags: [], forbids: [].}-
Получить локальный адрес и номер порта сокета.
Это интерфейс высокого уровня для getsockname.
Исходный код Редактировать proc getPeerAddr(socket: AsyncSocket): (string, Port) {. ...raises: [OSError, Exception], tags: [], forbids: [].}-
Получить адрес и номер порта удалённого сокета.
Это интерфейс высокого уровня для getpeername.
Исходный код Редактировать proc getPeerCertificates(socket: AsyncSocket): seq[Certificate] {. ...raises: [Exception], tags: [], forbids: [].}- Возвращает цепочку сертификатов, полученную от удалённого узла, к которому мы подключены через указанный сокет. Рукопожатие должно быть завершено, и цепочка сертификатов должна быть успешно проверена, иначе возвращается пустая последовательность. Цепочка отсортирована от сертификата листа к корневому сертификату. Исходный код Редактировать
proc getSockOpt(socket: AsyncSocket; opt: SOBool; level = SOL_SOCKET): bool {. ...tags: [ReadIOEffect], raises: [OSError], forbids: [].}- Извлекает опцию
optв качестве булевого значения. Исходный код Редактировать proc hasDataBuffered(s: AsyncSocket): bool {....raises: [], tags: [], forbids: [].}- Определяет, содержит ли AsyncSocket буферизованные данные. Исходный код Редактировать
proc isClosed(socket: AsyncSocket): bool {....raises: [], tags: [], forbids: [].}- Определяет, был ли сокет закрыт. Исходный код Редактировать
proc isSsl(socket: AsyncSocket): bool {....raises: [], tags: [], forbids: [].}- Определяет, является ли
socketSSL-сокетом. Исходный код Редактировать proc listen(socket: AsyncSocket; backlog = SOMAXCONN) {....tags: [ReadIOEffect], raises: [OSError], forbids: [].}-
Помечает
socketкак принимающий подключения.Backlogзадаёт максимальную длину очереди ожидающих подключений.При ошибке генерирует ошибку OSError.
Исходный код Редактировать proc newAsyncSocket(domain, sockType, protocol: cint; buffered = true; inheritable = defined(nimInheritHandles)): owned(AsyncSocket) {. ...raises: [OSError], tags: [], forbids: [].}-
Создаёт новый асинхронный сокет.
Эта процедура также создаст новый дескриптор файла для этого сокета.
Если
Исходный код Редактироватьinheritableравно false (по умолчанию), новый дескриптор файла не будет наследуем дочерними процессами. proc newAsyncSocket(domain: Domain = AF_INET; sockType: SockType = SOCK_STREAM; protocol: Protocol = IPPROTO_TCP; buffered = true; inheritable = defined(nimInheritHandles)): owned(AsyncSocket) {. ...raises: [OSError], tags: [], forbids: [].}-
Создаёт новый асинхронный сокет.
Эта процедура также создаст новый дескриптор файла для этого сокета.
Если
Исходный код Редактироватьinheritableравно false (по умолчанию), новый дескриптор файла не будет наследуем дочерними процессами. proc newAsyncSocket(fd: AsyncFD; domain: Domain = AF_INET; sockType: SockType = SOCK_STREAM; protocol: Protocol = IPPROTO_TCP; buffered = true; inheritable = defined(nimInheritHandles)): owned(AsyncSocket) {. ...raises: [OSError], tags: [], forbids: [].}-
Создаёт новый
AsyncSocketна основе предоставленных параметров.Состояние неблокирующего режима предоставленного
fdбудет неявно включено.Если
inheritableравно false (по умолчанию), предоставленныйfdне будет наследуем дочерними процессами.Примечание: Эта процедура НЕ зарегистрирует
Исходный код Редактироватьfdв глобальном асинхронном диспетчере. Вам нужно сделать это вручную. Если вы использовалиnewAsyncNativeSocketдля созданияfd, то он уже зарегистрирован. proc recv(socket: AsyncSocket; size: int; flags = {SafeDisconn}): owned( Future[string]) {....stackTrace: false, raises: [Exception, ValueError], tags: [RootEffect], forbids: [].}-
Читает до
sizeбайт изsocket.Для буферизованных сокетов эта функция попытается прочитать все запрошенные данные. Она будет читать эти данные кусками по
BufferSize.Для небуферизованных сокетов эта функция не пытается прочитать все запрошенные данные. Она вернёт столько данных, сколько предоставит операционная система.
Если сокет отключается во время операции recv, будущее может завершиться с только частью запрошенных данных.
Если сокет отключен и нет данных для чтения, будущее завершится со значением
Исходный код Редактировать"".
proc recvFrom(socket: AsyncSocket; data: FutureVar[string]; size: int; address: FutureVar[string]; port: FutureVar[Port]; flags = {SafeDisconn}): owned(Future[int]) {....stackTrace: false, raises: [ValueError, Exception], tags: [RootEffect], forbids: [].}-
Принимает пакет данных от
socketвdata, размер которого должен быть не менееsize. Адрес и порт отправителя пакета будут сохранены вaddressиportсоответственно. Возвращаемое будущее завершится, как только будет получен один пакет, и вернёт размер полученного пакета.Если произойдёт ошибка, будет поднято исключение OSError.
Эта процедура обычно используется с беспроводными сокетами (UDP-сокетами).
Примечания
-
dataдолжен быть инициализирован длинойsize. -
addressдолжен быть инициализирован длиной 46.
-
proc recvFrom(socket: AsyncSocket; size: int; flags = {SafeDisconn}): owned( Future[tuple[data: string, address: string, port: Port]]) {. ...stackTrace: false, raises: [Exception, ValueError], tags: [RootEffect], forbids: [].}-
Принимает данные пакета от
socket, размер которых должен быть не менееsize. Возвращаемое будущее завершится, как только будет получен один пакет, и вернёт кортеж с данными полученного пакета и адресом и портом отправителя пакета.Если произойдёт ошибка, будет поднято исключение OSError.
Эта процедура обычно используется с беспроводными сокетами (UDP-сокетами).
Исходный код Изменить proc recvInto(socket: AsyncSocket; buf: pointer; size: int; flags = {SafeDisconn}): owned(Future[int]) {....stackTrace: false, raises: [Exception, ValueError], tags: [RootEffect], forbids: [].}-
Читает до
sizeбайт изsocketвbuf.Для буферизованных сокетов эта функция попытается прочитать все запрошенные данные. Она будет читать эти данные в
BufferSizeфрагментах.Для небуферизованных сокетов эта функция не пытается прочитать все запрошенные данные. Она вернёт столько данных, сколько предоставит операционная система.
Если соединение с сокетом прервано во время операции чтения, будущее может завершиться с лишь частью запрошенных данных.
Если соединение с сокетом прервано и доступных данных для чтения нет, будущее завершится со значением
Исходный код Изменить0. proc recvLine(socket: AsyncSocket; flags = {SafeDisconn}; maxLength = MaxLineLength): owned(Future[string]) {. ...stackTrace: false, raises: [Exception, ValueError], tags: [RootEffect], forbids: [].}-
Читает строку данных из
socket. Возвращаемое будущее завершится, как только будет прочитана полная строка или произойдёт ошибка.Если полная строка прочитана,
\r\Lне добавляется кline, однако, если прочитана только\r\L, тоlineбудет установлено в это значение.Если соединение с сокетом прервано,
lineбудет установлено в"".Если соединение с сокетом прервано посреди строки (до прочтения
\r\L), тогда строка будет установлена в"". Частичная строка будет потеряна.Параметр
maxLengthопределяет максимальное количество символов, которые могут быть прочитаны. Результат усекается после этого.Предупреждение: ФлагPeekещё не реализован.Предупреждение:Исходный код ИзменитьrecvLineдля небуферизованных сокетов предполагает, что протокол использует\r\Lдля разграничения новой строки. proc recvLineInto(socket: AsyncSocket; resString: FutureVar[string]; flags = {SafeDisconn}; maxLength = MaxLineLength): owned( Future[void]) {....stackTrace: false, raises: [ValueError, Exception], tags: [RootEffect], forbids: [].}-
Читает строку данных из
socketвresString.Если полная строка прочитана,
\r\Lне добавляется кline, однако, если прочитана только\r\L, тоlineбудет установлено в это значение.Если соединение с сокетом прервано,
lineбудет установлено в"".Если соединение с сокетом прервано посреди строки (до прочтения
\r\L), тогда строка будет установлена в"". Частичная строка будет потеряна.Параметр
maxLengthопределяет максимальное количество символов, которые могут быть прочитаны.resStringбудет усечён после этого.Предупреждение: ФлагPeekещё не реализован.Предупреждение:Исходный код ИзменитьrecvLineIntoдля небуферизованных сокетов предполагает, что протокол использует\r\Lдля разграничения новой строки. proc send(socket: AsyncSocket; buf: pointer; size: int; flags = {SafeDisconn}): owned( Future[void]) {....stackTrace: false, raises: [Exception], tags: [RootEffect], forbids: [].}- Отправляет
sizeбайт изbufвsocket. Возвращаемое будущее завершится, как только все данные будут отправлены. Исходный код Изменить proc send(socket: AsyncSocket; data: string; flags = {SafeDisconn}): owned( Future[void]) {....stackTrace: false, raises: [Exception], tags: [RootEffect], forbids: [].}- Отправляет
dataвsocket. Возвращаемое будущее завершится, как только все данные будут отправлены. Исходный код Изменить proc sendTo(socket: AsyncSocket; address: string; port: Port; data: string; flags = {SafeDisconn}): owned(Future[void]) {....stackTrace: false, raises: [Exception], tags: [RootEffect], forbids: [].}-
Эта процедура отправляет
dataна указанныйaddress, который может быть IP-адресом или именем хоста. Если указано имя хоста, эта функция попытается использовать каждый IP-адрес этого хоста. Возвращаемое будущее завершится, как только все данные будут отправлены.Если произойдёт ошибка, будет поднято исключение OSError.
Эта процедура обычно используется с беспроводными сокетами (UDP-сокетами).
Исходный код Изменить proc setSockOpt(socket: AsyncSocket; opt: SOBool; value: bool; level = SOL_SOCKET) {....tags: [WriteIOEffect], raises: [OSError], forbids: [].}- Устанавливает опцию
optв булевое значение, заданноеvalue. Исходный код Изменить proc sslHandle(self: AsyncSocket): SslPtr {....raises: [], tags: [], forbids: [].}- Получить указатель ssl для
socket. Полезно для взаимодействия сopenssl. Исходный код Изменить proc wrapConnectedSocket(ctx: SslContext; socket: AsyncSocket; handshake: SslHandshakeType; hostname: string = "") {. ...raises: [SslError], tags: [], forbids: [].}-
Оборачивает подключенный сокет в контекст SSL. Эта функция фактически преобразует
socketв SSL-сокет.hostnameдолжно быть указано, чтобы клиент знал, по какому имени хоста серверу необходимо валидировать сертификат.Это следует вызывать на подключенном сокете и немедленно выполнит SSL-рукопожатие.
Отказ от ответственности: Этот код не хорошо протестирован, может быть очень небезопасным и подвержен уязвимостям.
Исходный код Изменить proc wrapSocket(ctx: SslContext; socket: AsyncSocket) {....raises: [SslError], tags: [], forbids: [].}-
Оборачивает сокет в контекст SSL. Эта функция фактически преобразует
socketв SSL-сокет.Отказ от ответственности: Этот код не хорошо протестирован, может быть очень небезопасным и подвержен уязвимостям.
Исходный код Изменить
© 2006–2024 Andreas Rumpf
Licensed under the MIT License.
https://nim-lang.org/docs/asyncnet.html