├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── dub.json ├── examples ├── buffered │ ├── client │ │ ├── dub.json │ │ └── src │ │ │ ├── account.buffer │ │ │ └── app.d │ └── server │ │ ├── dub.json │ │ └── src │ │ ├── account.buffer │ │ ├── app.d │ │ └── business.d ├── echo │ ├── client │ │ ├── dub.json │ │ └── src │ │ │ └── app.d │ └── server │ │ ├── dub.json │ │ └── src │ │ └── app.d └── protocol │ ├── client │ ├── dub.json │ └── src │ │ └── app.d │ └── server │ ├── dub.json │ └── src │ └── app.d └── libasync └── src └── async ├── codec.d ├── container ├── bytebuffer.d ├── map.d └── package.d ├── event ├── epoll.d ├── iocp.d ├── kqueue.d └── selector.d ├── eventloop.d ├── net ├── tcpclient.d └── tcplistener.d ├── package.d └── thread.d /.gitignore: -------------------------------------------------------------------------------- 1 | /.project 2 | **/.dub/ 3 | **/dub.selections.json 4 | /libasync.a 5 | /async.lib 6 | client 7 | server 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: d 2 | sudo: false 3 | os: 4 | - linux 5 | - osx 6 | d: 7 | - dmd 8 | - ldc 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 shove70(shove) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/shove70/async.svg?branch=master)](https://travis-ci.org/shove70/async) 2 | [![GitHub tag](https://img.shields.io/github/tag/shove70/async.svg?maxAge=86400)](https://github.com/shove70/async/releases) 3 | [![Dub downloads](https://img.shields.io/dub/dt/async.svg)](http://code.dlang.org/packages/async) 4 | 5 | # A cross-platform event loop library of asynchroneous network sockets. 6 | 7 | This is a simple wrapper of network library, D language implementation. Its encapsulation is simple and clear, and is very suitable for reading and reference. Though very small, its performance is pretty good, It's a real full duplex mode. 8 | 9 | ### Support platform: 10 | 11 | * Platforms 12 | * FreeBSD 13 | * Windows 14 | * OSX 15 | * Linux 16 | * NetBSD 17 | * OpenBSD 18 | 19 | ### Support for kernel loop (while supporting IN and OUT event listener): 20 | 21 | * Epoll 22 | * Kqueue 23 | * IOCP 24 | 25 | ### Quick Start: 26 | 27 | ```d 28 | 29 | // Echo server: 30 | 31 | import async; 32 | import std.stdio; 33 | import std.socket; 34 | import std.exception; 35 | 36 | void main() 37 | { 38 | auto listener = new TcpListener(); 39 | listener.bind(new InternetAddress(12290)); 40 | listener.listen(10); 41 | 42 | auto loop = new EventLoop(listener, null, null, &onReceive, null, null); 43 | loop.run(); 44 | } 45 | 46 | void onReceive(TcpClient client, const scope ubyte[] data) nothrow @trusted 47 | { 48 | collectException({ 49 | writefln("Receive from %s: %d", client.remoteAddress, data.length); 50 | client.send(cast(ubyte[])data); 51 | }()); 52 | } 53 | 54 | 55 | // Echo client: 56 | 57 | import std.stdio; 58 | import std.socket; 59 | 60 | void main(string[] argv) 61 | { 62 | auto socket = new TcpSocket(); 63 | socket.connect(new InternetAddress("127.0.0.1", 12290)); 64 | 65 | auto data = "hello, server."; 66 | writeln("Client say: ", data); 67 | socket.send(data); 68 | 69 | ubyte[1024] buffer = void; 70 | size_t len = socket.receive(buffer[]); 71 | writeln("Server reply: ", cast(string)buffer[0..len]); 72 | 73 | socket.shutdown(SocketShutdown.BOTH); 74 | socket.close(); 75 | } 76 | 77 | ``` 78 | 79 | ### Todo: 80 | 81 | You can add, repair and submit after fork, and improve it together. Thank you. 82 | -------------------------------------------------------------------------------- /dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": ["Shove"], 3 | "name": "async", 4 | "description": "A cross-platform event loop library of asynchroneous network sockets.", 5 | "license": "MIT", 6 | "copyright": "Copyright © 2017-2020, Shove", 7 | "targetType": "library", 8 | "targetName": "async", 9 | "importPaths": [ 10 | "libasync/src" 11 | ], 12 | "sourcePaths": [ 13 | "libasync/src" 14 | ], 15 | "dependencies": {} 16 | } -------------------------------------------------------------------------------- /examples/buffered/client/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Shove" 4 | ], 5 | "name": "client", 6 | "targetType": "executable", 7 | "license": "MIT", 8 | "copyright": "Copyright © 2017-2020, Shove", 9 | "targetName": "client", 10 | "description": "A client example for async.", 11 | "dependencies": { 12 | "buffer": "*", 13 | "crypto": "*" 14 | }, 15 | "stringImportPaths": [ 16 | "src/" 17 | ] 18 | } -------------------------------------------------------------------------------- /examples/buffered/client/src/account.buffer: -------------------------------------------------------------------------------- 1 | message LoginRequest 2 | { 3 | string idOrMobile; 4 | string password; 5 | string UDID; 6 | } 7 | 8 | message LoginResponse 9 | { 10 | int16 result; 11 | string description; 12 | 13 | uint64 userId; 14 | string token; 15 | string name; 16 | string mobile; 17 | } 18 | -------------------------------------------------------------------------------- /examples/buffered/client/src/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.conv; 3 | import std.socket; 4 | import std.concurrency; 5 | import core.thread; 6 | import core.stdc.errno; 7 | import core.atomic; 8 | import std.bitmanip; 9 | import std.datetime; 10 | 11 | import buffer.message; 12 | import crypto.rsa; 13 | 14 | mixin(LoadBufferFile!"account.buffer"); 15 | 16 | __gshared RSAKeyInfo publicKey; 17 | 18 | void main() 19 | { 20 | publicKey = RSA.decodeKey("AAAAIH4RaeCOInmS/CcWOrurajxk3dZ4XGEZ9MsqT3LnFqP3/uk="); 21 | Message.settings(615, publicKey, true); 22 | 23 | foreach (i; 0..200) 24 | { 25 | new Thread( { go(); } ).start(); 26 | } 27 | } 28 | 29 | shared long total; 30 | 31 | private void go() 32 | { 33 | for (int i; i < 100000; i++) 34 | { 35 | i++; 36 | auto req = new LoginRequest; 37 | req.idOrMobile = "userId"; 38 | req.password = "******"; 39 | req.UDID = Clock.currTime().toUnixTime().to!string; 40 | ubyte[] buf = req.serialize("login"); 41 | 42 | auto socket = new TcpSocket; 43 | 44 | try 45 | { 46 | socket.connect(new InternetAddress("127.0.0.1", 12290)); 47 | } 48 | catch(Exception e) 49 | { 50 | writeln(e); 51 | continue; 52 | } 53 | 54 | long len; 55 | for (size_t off; off < buf.length; off += len) 56 | { 57 | len = socket.send(buf[off..$]); 58 | 59 | if (len > 0) 60 | { 61 | continue; 62 | } 63 | if (len == 0) 64 | { 65 | writefln("Server socket close at send. Local socket: %s", socket.localAddress); 66 | socket.close(); 67 | 68 | return; 69 | } 70 | if (errno == EINTR || errno == EAGAIN/* || errno == EWOULDBLOCK*/) 71 | { 72 | len = 0; 73 | continue; 74 | } 75 | 76 | writefln("Socket error at send. Local socket: %s, error: %s", socket.localAddress, formatSocketError(errno)); 77 | socket.close(); 78 | 79 | return; 80 | } 81 | 82 | ubyte[] buffer; 83 | 84 | buf = new ubyte[ushort.sizeof]; 85 | len = socket.receive(buf); 86 | 87 | if (len != ushort.sizeof) 88 | { 89 | writefln("Socket error at receive1. Local socket: %s, error: %s", socket.localAddress, formatSocketError(errno)); 90 | socket.close(); 91 | 92 | return; 93 | } 94 | 95 | if (buf.peek!ushort(0) != 615) 96 | { 97 | writefln("Head isn't 407. Local socket: %s", socket.localAddress); 98 | socket.close(); 99 | 100 | return; 101 | } 102 | 103 | buffer ~= buf; 104 | 105 | buf = new ubyte[int.sizeof]; 106 | len = socket.receive(buf); 107 | 108 | if (len != int.sizeof) 109 | { 110 | writefln("Socket error at receive2. Local socket: %s, error: %s", socket.localAddress, formatSocketError(errno)); 111 | socket.close(); 112 | 113 | return; 114 | } 115 | 116 | size_t size = buf.peek!int(0); 117 | buffer ~= buf; 118 | 119 | buf = new ubyte[size]; 120 | len = 0; 121 | for (size_t off; off < buf.length; off += len) 122 | { 123 | len = socket.receive(buf[off..$]); 124 | 125 | if (len > 0) 126 | { 127 | continue; 128 | } 129 | else if (len == 0) 130 | { 131 | writefln("Server socket close at receive. Local socket: %s", socket.localAddress); 132 | socket.close(); 133 | 134 | return; 135 | } 136 | else 137 | { 138 | if (errno == EINTR || errno == EAGAIN/* || errno == EWOULDBLOCK*/) 139 | { 140 | len = 0; 141 | continue; 142 | } 143 | 144 | writeln("Socket error at receive3. Local socket: %s, error: %s", socket.localAddress, formatSocketError(errno)); 145 | socket.close(); 146 | 147 | return; 148 | } 149 | } 150 | 151 | buffer ~= buf; 152 | 153 | core.atomic.atomicOp!"+="(total, 1); 154 | socket.shutdown(SocketShutdown.BOTH); 155 | socket.close(); 156 | 157 | LoginResponse res = Message.deserialize!LoginResponse(buffer); 158 | writefln("result: %d, description: %s, userId: %d, token: %s, name: %s, mobile: %s", res.result, res.description, res.userId, res.token, res.name, (Clock.currTime() - SysTime.fromUnixTime(res.mobile.to!long)).total!"seconds"); 159 | Thread.sleep(50.msecs); 160 | } 161 | } -------------------------------------------------------------------------------- /examples/buffered/server/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": ["Shove"], 3 | "name": "server", 4 | "targetType": "executable", 5 | "license": "MIT", 6 | "copyright": "Copyright © 2017-2020, Shove", 7 | "targetName": "server", 8 | "description": "A server example for async.", 9 | "dependencies": { 10 | "async": { 11 | "path": "../../../" 12 | }, 13 | "buffer": "*", 14 | "crypto": "*" 15 | }, 16 | "stringImportPaths": [ 17 | "src/" 18 | ] 19 | } -------------------------------------------------------------------------------- /examples/buffered/server/src/account.buffer: -------------------------------------------------------------------------------- 1 | message LoginRequest 2 | { 3 | string idOrMobile; 4 | string password; 5 | string UDID; 6 | } 7 | 8 | message LoginResponse 9 | { 10 | int16 result; 11 | string description; 12 | 13 | uint64 userId; 14 | string token; 15 | string name; 16 | string mobile; 17 | } 18 | -------------------------------------------------------------------------------- /examples/buffered/server/src/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.socket; 3 | import std.exception; 4 | import std.bitmanip; 5 | import async; 6 | import buffer; 7 | import buffer.rpc.server; 8 | import crypto.rsa; 9 | import package_business; 10 | 11 | __gshared Server!(Business) business; 12 | __gshared ThreadPool businessPool; 13 | 14 | void main() 15 | { 16 | RSAKeyInfo privateKey = RSA.decodeKey("AAAAIH4RaeCOInmS/CcWOrurajxk3dZ4XGEZ9MsqT3LnFqP3HnO6WmZVW8rflcb5nHsl9Ga9U4NdPO7cDC2WQ8Y02LE="); 17 | Message.settings(615, privateKey, true); 18 | 19 | businessPool = new ThreadPool(32); 20 | business = new Server!(Business)(); 21 | 22 | auto listener = new TcpListener; 23 | listener.bind(new InternetAddress(12290)); 24 | listener.listen(1024); 25 | 26 | Codec codec = new Codec(CodecType.SizeGuide, 615); 27 | EventLoop loop = new EventLoop(listener, &onConnected, &onDisConnected, &onReceive, &onSendCompleted, &onSocketError, codec, 8); 28 | loop.run(); 29 | 30 | //loop.stop(); 31 | } 32 | 33 | void onConnected(TcpClient client) nothrow @trusted 34 | { 35 | collectException({ 36 | writefln("New connection: %s, fd: %d", client.remoteAddress, client.fd); 37 | }()); 38 | } 39 | 40 | void onDisConnected(TcpClient client) nothrow @trusted 41 | { 42 | collectException({ 43 | writefln("\033[7mClient socket close: %s, fd: %d\033[0m", client.remoteAddress, client.fd); 44 | }()); 45 | } 46 | 47 | void onReceive(TcpClient client, const scope ubyte[] data) nothrow @trusted 48 | { 49 | collectException({ 50 | businessPool.run!businessHandle(client, data); 51 | }()); 52 | } 53 | 54 | void businessHandle(TcpClient client, const scope ubyte[] buffer) 55 | { 56 | ubyte[] ret_data = business.Handler(buffer); 57 | client.send(ret_data); 58 | } 59 | 60 | void onSocketError(TcpClient client, int err) nothrow @trusted 61 | { 62 | collectException({ 63 | writeln("Client socket error: ", client.remoteAddress, " ", formatSocketError(err)); 64 | }()); 65 | } 66 | 67 | void onSendCompleted(TcpClient client, const scope ubyte[] data, size_t sent_size) nothrow @trusted 68 | { 69 | collectException({ 70 | if (sent_size != data.length) 71 | { 72 | writefln("Send to %s Error. Original size: %d, sent: %d, fd: %d", 73 | client.remoteAddress, data.length, sent_size, client.fd); 74 | } 75 | else 76 | { 77 | writefln("Sent to %s completed, Size: %d, fd: %d", client.remoteAddress, sent_size, client.fd); 78 | } 79 | }()); 80 | } 81 | -------------------------------------------------------------------------------- /examples/buffered/server/src/business.d: -------------------------------------------------------------------------------- 1 | module package_business; 2 | 3 | import buffer; 4 | 5 | class Business 6 | { 7 | mixin(LoadBufferFile!"account.buffer"); 8 | 9 | LoginResponse login(string idOrMobile, string password, string UDID) 10 | { 11 | LoginResponse res = new LoginResponse(); 12 | 13 | res.result = 0; 14 | res.description = ""; 15 | res.userId = 1; 16 | res.token = "a token"; 17 | res.name = "userName"; 18 | res.mobile = UDID; 19 | 20 | return res; 21 | } 22 | } -------------------------------------------------------------------------------- /examples/echo/client/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Shove" 4 | ], 5 | "name": "client", 6 | "targetType": "executable", 7 | "license": "MIT", 8 | "copyright": "Copyright © 2017-2020, Shove", 9 | "targetName": "client", 10 | "description": "A client example for async.", 11 | "dependencies": {} 12 | } -------------------------------------------------------------------------------- /examples/echo/client/src/app.d: -------------------------------------------------------------------------------- 1 | import std.stdio; 2 | import std.socket; 3 | 4 | void main() 5 | { 6 | auto socket = new TcpSocket; 7 | socket.connect(new InternetAddress("127.0.0.1", 12290)); 8 | 9 | auto data = "hello, server."; 10 | writeln("Client say: ", data); 11 | socket.send(data); 12 | 13 | ubyte[1024] buffer = void; 14 | size_t len = socket.receive(buffer[]); 15 | if (len != Socket.ERROR) 16 | writeln("Server reply: ", cast(string)buffer[0..len]); 17 | 18 | socket.shutdown(SocketShutdown.BOTH); 19 | socket.close(); 20 | } 21 | -------------------------------------------------------------------------------- /examples/echo/server/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": ["Shove"], 3 | "name": "server", 4 | "targetType": "executable", 5 | "license": "MIT", 6 | "copyright": "Copyright © 2017-2020, Shove", 7 | "targetName": "server", 8 | "description": "A server example for async.", 9 | "dependencies": { 10 | "async": { 11 | "path": "../../../" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /examples/echo/server/src/app.d: -------------------------------------------------------------------------------- 1 | import 2 | async, 3 | std.stdio, 4 | std.socket, 5 | std.exception; 6 | 7 | void main() 8 | { 9 | auto listener = new TcpListener; 10 | listener.bind(new InternetAddress(12290)); 11 | listener.listen(10); 12 | 13 | auto loop = new EventLoop(listener, null, null, &onReceive, null, null); 14 | loop.run(); 15 | 16 | //loop.stop(); 17 | } 18 | 19 | void onReceive(TcpClient client, const scope ubyte[] data) nothrow @trusted 20 | { 21 | collectException({ 22 | writefln("Receive from %s: %d", client.remoteAddress, data.length); 23 | client.send(data); // echo 24 | }()); 25 | } 26 | -------------------------------------------------------------------------------- /examples/protocol/client/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": [ 3 | "Shove" 4 | ], 5 | "name": "client", 6 | "targetType": "executable", 7 | "license": "MIT", 8 | "copyright": "Copyright © 2017-2020, Shove", 9 | "targetName": "client", 10 | "description": "A client example for async.", 11 | "dependencies": {} 12 | } -------------------------------------------------------------------------------- /examples/protocol/client/src/app.d: -------------------------------------------------------------------------------- 1 | import core.thread; 2 | 3 | void main() 4 | { 5 | auto data = new ubyte[10000]; 6 | data[0] = 1; 7 | data[$ - 1] = 2; 8 | 9 | foreach (i; 0..3) 10 | { 11 | new Thread( { go(data); } ).start(); 12 | } 13 | } 14 | 15 | shared long total; 16 | 17 | private void go(ubyte[] data) 18 | { 19 | import 20 | core.stdc.errno, 21 | core.atomic, 22 | std.bitmanip, 23 | std.stdio, 24 | std.socket; 25 | 26 | foreach (i; 0..100000) 27 | { 28 | auto socket = new TcpSocket; 29 | socket.connect(new InternetAddress("127.0.0.1", 12290)); 30 | 31 | ubyte[] buffer = new ubyte[4]; 32 | buffer.write!int(cast(int)data.length, 0); 33 | buffer ~= data; 34 | 35 | long len; 36 | for (size_t off; off < buffer.length; off += len) 37 | { 38 | len = socket.send(buffer[off..$]); 39 | 40 | if (len > 0) 41 | { 42 | continue; 43 | } 44 | if (len == 0) 45 | { 46 | writefln("Server socket close at send. Local socket: %s", socket.localAddress); 47 | socket.close(); 48 | 49 | return; 50 | } 51 | if (errno == EINTR || errno == EAGAIN/* || errno == EWOULDBLOCK*/) 52 | { 53 | len = 0; 54 | continue; 55 | } 56 | 57 | writefln("Socket error at send. Local socket: %s, error: %s", socket.localAddress, formatSocketError(errno)); 58 | socket.close(); 59 | 60 | return; 61 | } 62 | 63 | for (size_t off; off < buffer.length; off += len) 64 | { 65 | len = socket.receive(buffer[off..$]); 66 | 67 | if (len > 0) 68 | { 69 | continue; 70 | } 71 | if (len == 0) 72 | { 73 | writefln("Server socket close at receive. Local socket: %s", socket.localAddress); 74 | socket.close(); 75 | 76 | return; 77 | } 78 | if (errno == EINTR || errno == EAGAIN/* || errno == EWOULDBLOCK*/) 79 | { 80 | len = 0; 81 | continue; 82 | } 83 | 84 | writefln("Socket error at receive. Local socket: %s, error: %s", socket.localAddress, formatSocketError(errno)); 85 | socket.close(); 86 | 87 | return; 88 | } 89 | 90 | atomicOp!"+="(total, 1); 91 | writeln(total, ": receive: [0]: ", buffer[4], ", [$ - 1]: ", buffer[$ - 1]); 92 | 93 | socket.shutdown(SocketShutdown.BOTH); 94 | socket.close(); 95 | Thread.sleep(50.msecs); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /examples/protocol/server/dub.json: -------------------------------------------------------------------------------- 1 | { 2 | "authors": ["Shove"], 3 | "name": "server", 4 | "targetType": "executable", 5 | "license": "MIT", 6 | "copyright": "Copyright © 2017-2020, Shove", 7 | "targetName": "server", 8 | "description": "A server example for async.", 9 | "dependencies": { 10 | "async": { 11 | "path": "../../../" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /examples/protocol/server/src/app.d: -------------------------------------------------------------------------------- 1 | import 2 | async, 3 | std.stdio, 4 | std.socket, 5 | std.exception; 6 | 7 | void main() 8 | { 9 | auto listener = new TcpListener; 10 | listener.bind(new InternetAddress(12290)); 11 | listener.listen(10); 12 | 13 | auto codec = new Codec(CodecType.SizeGuide); 14 | auto loop = new EventLoop(listener, &onConnected, &onDisconnected, &onReceive, &onSendCompleted, &onSocketError, codec); 15 | loop.run(); 16 | 17 | //loop.stop(); 18 | } 19 | 20 | void onConnected(TcpClient client) nothrow @trusted 21 | { 22 | collectException({ 23 | writefln("New connection: %s, fd: %d", client.remoteAddress, client.fd); 24 | }()); 25 | } 26 | 27 | void onDisconnected(TcpClient client) nothrow @trusted 28 | { 29 | collectException({ 30 | writefln("\033[7mClient socket close: %s, fd: %d\033[0m", client.remoteAddress, client.fd); 31 | }()); 32 | } 33 | 34 | void onReceive(TcpClient client, const scope ubyte[] data) nothrow @trusted 35 | { 36 | collectException({ 37 | writefln("Receive from %s: %d, fd: %d", client.remoteAddress, data.length, client.fd); 38 | client.send(data); // echo 39 | }()); 40 | } 41 | 42 | void onSocketError(TcpClient client, int err) nothrow @trusted 43 | { 44 | collectException({ 45 | writeln("Client socket error: ", client.remoteAddress, " ", formatSocketError(err)); 46 | }()); 47 | } 48 | 49 | void onSendCompleted(TcpClient client, const scope ubyte[] data, size_t sent_size) nothrow @trusted 50 | { 51 | collectException({ 52 | if (sent_size != data.length) 53 | { 54 | writefln("Send to %s Error. Original size: %d, sent: %d, fd: %d", 55 | client.remoteAddress, data.length, sent_size, client.fd); 56 | } 57 | else 58 | { 59 | writefln("Sent to %s completed, Size: %d, fd: %d", client.remoteAddress, sent_size, client.fd); 60 | } 61 | }()); 62 | } -------------------------------------------------------------------------------- /libasync/src/async/codec.d: -------------------------------------------------------------------------------- 1 | module async.codec; 2 | 3 | import std.typecons : Tuple; 4 | import std.bitmanip : peek, write; 5 | import async.container.bytebuffer; 6 | 7 | /// 8 | enum CodecType 9 | { 10 | TextLine, SizeGuide 11 | } 12 | 13 | /// 14 | class Codec 15 | { 16 | /// 17 | private CodecType ct; 18 | private const ubyte[] magic; 19 | 20 | /// 21 | this(const CodecType ct) 22 | { 23 | this.ct = ct; 24 | magic = null; 25 | } 26 | 27 | /// 28 | this(const CodecType ct, const ushort magic) 29 | { 30 | this.ct = ct; 31 | 32 | auto buf = new ubyte[2]; 33 | buf.write!ushort(magic, 0); 34 | this.magic = buf; 35 | } 36 | 37 | /// Returns: 38 | /// ptrdiff_t: spliter position 39 | /// size_t: spliter size 40 | Tuple!(ptrdiff_t, size_t) decode(ref ByteBuffer buffer) 41 | { 42 | if (magic.length) 43 | { 44 | if (buffer.length < magic.length) 45 | { 46 | return typeof(return)(-1, 0); 47 | } 48 | 49 | if (buffer[0 .. magic.length] != magic) 50 | { 51 | return typeof(return)(-2, 0); 52 | } 53 | } 54 | 55 | if (ct == CodecType.TextLine) 56 | { 57 | ptrdiff_t endPoint = -1; 58 | 59 | for (size_t i; i < buffer.length; i++) 60 | { 61 | const char ch = buffer[i]; 62 | if (ch == '\r' || ch == '\n') 63 | { 64 | endPoint = i; 65 | break; 66 | } 67 | } 68 | 69 | if (endPoint == -1) 70 | { 71 | return typeof(return)(-1, 0); 72 | } 73 | 74 | size_t spliterSize = 1; 75 | for (size_t i = endPoint + 1; i < buffer.length; i++) 76 | { 77 | const char ch = buffer[i]; 78 | if ((ch != '\r') && (ch != '\n')) 79 | { 80 | break; 81 | } 82 | 83 | spliterSize++; 84 | } 85 | 86 | return typeof(return)(endPoint, spliterSize); 87 | } 88 | else if (ct == CodecType.SizeGuide) 89 | { 90 | if (buffer.length < magic.length + int.sizeof) 91 | { 92 | return typeof(return)(-1, 0); 93 | } 94 | 95 | ubyte[] header = buffer[0 .. magic.length + int.sizeof]; 96 | size_t size = header[magic.length .. $].peek!int(0); 97 | if (buffer.length < magic.length + int.sizeof + size) 98 | { 99 | return typeof(return)(-1, 0); 100 | } 101 | 102 | return typeof(return)(magic.length + int.sizeof + size, 0); 103 | } 104 | 105 | assert(0); 106 | } 107 | } -------------------------------------------------------------------------------- /libasync/src/async/container/bytebuffer.d: -------------------------------------------------------------------------------- 1 | module async.container.bytebuffer; 2 | 3 | import std.container.dlist; 4 | import std.conv : to; 5 | import std.exception : enforce; 6 | 7 | struct ByteBuffer 8 | { 9 | @safe: 10 | @property pure nothrow @nogc { 11 | bool empty() const { return _size == 0; } 12 | 13 | size_t length() const { return _size; } 14 | } 15 | 16 | ref typeof(this) opBinary(string op : "~", T: const void[])(auto ref T rhs) @trusted 17 | { 18 | if (rhs is null) 19 | { 20 | return this; 21 | } 22 | 23 | _queue.insertBack(cast(ubyte[])rhs); 24 | _size += rhs.length; 25 | 26 | return this; 27 | } 28 | 29 | void opOpAssign(string op : "~", T: const void[])(auto ref T rhs) @trusted 30 | { 31 | if (rhs is null) 32 | { 33 | return; 34 | } 35 | 36 | _queue.insertBack(cast(ubyte[])rhs); 37 | _size += rhs.length; 38 | } 39 | 40 | ubyte[] opSlice(size_t low, size_t high) @trusted 41 | { 42 | enforce(low <= high && high <= _size, "ByteBuffer.opSlice: Invalid arguments low, high"); 43 | 44 | ubyte[] ret; 45 | 46 | if (low == high) 47 | { 48 | return ret; 49 | } 50 | 51 | size_t count, lack = high - low; 52 | foreach (a; _queue) 53 | { 54 | count += a.length; 55 | 56 | if (count < low) 57 | { 58 | continue; 59 | } 60 | 61 | size_t start = low + ret.length - (count - a.length); 62 | ret ~= a[start .. ($ - start) >= lack ? start + lack : $]; 63 | lack = high - low - ret.length; 64 | 65 | if (lack == 0) 66 | { 67 | break; 68 | } 69 | } 70 | 71 | return ret; 72 | } 73 | 74 | alias opDollar = length; 75 | 76 | ref ubyte opIndex(size_t index) @trusted 77 | { 78 | enforce(index < _size, "ByteBuffer.opIndex: Invalid arguments index"); 79 | 80 | size_t size, start; 81 | foreach (a; _queue) 82 | { 83 | start = size; 84 | size += a.length; 85 | 86 | if (index < size) 87 | { 88 | return a[index - start]; 89 | } 90 | } 91 | 92 | assert(0); 93 | } 94 | 95 | @property ref inout(ubyte[]) front() inout nothrow @nogc 96 | in(!_queue.empty, "ByteBuffer.front: Queue is empty") { 97 | return _queue.front; 98 | } 99 | 100 | void popFront() in(!_queue.empty, "ByteBuffer.popFront: Queue is empty") 101 | { 102 | _size -= _queue.front.length; 103 | _queue.removeFront(); 104 | } 105 | 106 | void popFront(size_t size) 107 | in(size >= 0 && size <= _size, "ByteBuffer.popFront: Invalid arguments size") { 108 | if (size == 0) 109 | { 110 | return; 111 | } 112 | 113 | if (size == _size) 114 | { 115 | _queue.clear(); 116 | _size = 0; 117 | 118 | return; 119 | } 120 | 121 | size_t removed, lack = size; 122 | 123 | size_t line, count, currline_len; 124 | foreach (a; _queue) 125 | { 126 | line++; 127 | currline_len = a.length; 128 | count += currline_len; 129 | 130 | if (count > size) 131 | { 132 | break; 133 | } 134 | } 135 | 136 | if (line > 1) 137 | { 138 | removed = count - currline_len; 139 | lack = size - removed; 140 | _queue.removeFront(line - 1); 141 | } 142 | 143 | if (lack > 0) 144 | { 145 | _queue.front = _queue.front[lack .. $]; 146 | } 147 | 148 | _size -= size; 149 | } 150 | 151 | void clear() nothrow 152 | { 153 | _queue.clear(); 154 | _size = 0; 155 | } 156 | 157 | string toString() 158 | { 159 | return this[0 .. $].to!string; 160 | } 161 | 162 | auto opCast(T)() if (isSomeString!T || is(T: const ubyte[])) 163 | { 164 | static if (isSomeString!T) 165 | { 166 | return toString(); 167 | } 168 | else 169 | { 170 | return this[0 .. $]; 171 | } 172 | } 173 | 174 | private: 175 | 176 | DList!(ubyte[]) _queue; 177 | size_t _size; 178 | } -------------------------------------------------------------------------------- /libasync/src/async/container/map.d: -------------------------------------------------------------------------------- 1 | module async.container.map; 2 | 3 | import core.sync.mutex; 4 | 5 | class Map(TKey, TValue) 6 | { 7 | nothrow: 8 | this() { mutex = new Mutex; } 9 | 10 | ref auto opIndex(TKey key) 11 | { 12 | import std.traits : isArray; 13 | 14 | lock(); 15 | scope(exit) unlock(); 16 | if (key !in data) 17 | { 18 | static if (isArray!TValue) 19 | { 20 | data[key] = []; 21 | } 22 | else 23 | { 24 | return null; 25 | } 26 | } 27 | 28 | return data[key]; 29 | } 30 | 31 | void opIndexAssign(TValue value, TKey key) 32 | { 33 | lock(); 34 | data[key] = value; 35 | unlock(); 36 | } 37 | 38 | @property bool empty() const pure @nogc { return data.length == 0; } 39 | 40 | bool remove(TKey key) 41 | { 42 | lock(); 43 | scope(exit) unlock(); 44 | return data.remove(key); 45 | } 46 | 47 | void clear() @trusted 48 | { 49 | lock(); 50 | data.clear(); 51 | unlock(); 52 | } 53 | 54 | @safe: 55 | final void lock() { mutex.lock_nothrow(); } 56 | 57 | final void unlock() { mutex.unlock_nothrow(); } 58 | 59 | TValue[TKey] data; 60 | alias data this; 61 | protected Mutex mutex; 62 | } -------------------------------------------------------------------------------- /libasync/src/async/container/package.d: -------------------------------------------------------------------------------- 1 | module async.container; 2 | 3 | public import 4 | async.container.map, 5 | async.container.bytebuffer; -------------------------------------------------------------------------------- /libasync/src/async/event/epoll.d: -------------------------------------------------------------------------------- 1 | module async.event.epoll; 2 | 3 | debug import std.stdio; 4 | 5 | version (linux): 6 | 7 | import 8 | async.codec, 9 | async.event.selector, 10 | async.net.tcplistener, 11 | core.stdc.errno, 12 | core.sys.linux.epoll, 13 | core.sys.posix.netinet.in_, 14 | core.sys.posix.netinet.tcp, 15 | core.sys.posix.signal, 16 | core.sys.posix.time, 17 | std.socket; 18 | 19 | alias LoopSelector = Epoll; 20 | 21 | class Epoll : Selector 22 | { 23 | this(TcpListener listener, OnConnected onConnected = null, OnDisconnected onDisconnected = null, 24 | OnReceive onReceive = null, OnSendCompleted onSendCompleted = null, 25 | OnSocketError onSocketError = null, Codec codec = null, uint workerThreadNum = 0) 26 | { 27 | super(listener, onConnected, onDisconnected, onReceive, onSendCompleted, onSocketError, codec, workerThreadNum); 28 | 29 | _eventHandle = epoll_create1(0); 30 | reg(_listener.fd, EventType.ACCEPT, EPOLL_CTL_ADD); 31 | } 32 | 33 | private auto reg(int fd, EventType et, int op) 34 | { 35 | epoll_event ev; 36 | ev.events = EPOLLHUP | EPOLLERR; 37 | ev.data.fd = fd; 38 | 39 | if (et != EventType.ACCEPT) 40 | { 41 | ev.events |= EPOLLET; 42 | } 43 | if (et == EventType.ACCEPT || et == EventType.READ || et == EventType.READWRITE) 44 | { 45 | ev.events |= EPOLLIN; 46 | } 47 | if (et == EventType.WRITE || et == EventType.READWRITE) 48 | { 49 | ev.events |= EPOLLOUT; 50 | } 51 | return epoll_ctl(_eventHandle, op, fd, &ev); 52 | } 53 | 54 | override bool register(int fd, EventType et) 55 | { 56 | if (fd < 0) 57 | { 58 | return false; 59 | } 60 | 61 | if (reg(fd, et, EPOLL_CTL_ADD)) 62 | { 63 | return errno == EEXIST; 64 | } 65 | 66 | return true; 67 | } 68 | 69 | override bool reregister(int fd, EventType et) 70 | { 71 | return fd >= 0 && reg(fd, et, EPOLL_CTL_MOD) == 0; 72 | } 73 | 74 | override bool unregister(int fd) 75 | { 76 | return fd >= 0 && epoll_ctl(_eventHandle, EPOLL_CTL_DEL, fd, null) == 0; 77 | } 78 | 79 | override protected void handleEvent() 80 | { 81 | epoll_event[64] events = void; 82 | const len = epoll_wait(_eventHandle, events.ptr, events.length, -1); 83 | 84 | foreach (i; 0 .. len) 85 | { 86 | int fd = events[i].data.fd; 87 | 88 | if (events[i].events & (EPOLLHUP | EPOLLERR | EPOLLRDHUP)) 89 | { 90 | if (fd == _listener.fd) 91 | { 92 | debug writeln("Listener event error.", fd); 93 | } 94 | else 95 | { 96 | int err; 97 | socklen_t errlen = err.sizeof; 98 | getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen); 99 | removeClient(fd, err); 100 | } 101 | 102 | continue; 103 | } 104 | 105 | if (fd == _listener.fd) 106 | { 107 | accept(); 108 | } 109 | else if (events[i].events & EPOLLIN) 110 | { 111 | read(fd); 112 | } 113 | else if (events[i].events & EPOLLOUT) 114 | { 115 | write(fd); 116 | } 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /libasync/src/async/event/iocp.d: -------------------------------------------------------------------------------- 1 | module async.event.iocp; 2 | 3 | version (Windows): 4 | 5 | import 6 | async.codec, 7 | async.event.selector, 8 | async.net.tcplistener, 9 | core.stdc.errno, 10 | core.sys.windows.mswsock, 11 | core.sys.windows.windows, 12 | core.sys.windows.winsock2, 13 | std.socket; 14 | 15 | alias LoopSelector = Iocp; 16 | 17 | class Iocp : Selector 18 | { 19 | this(TcpListener listener, OnConnected onConnected = null, OnDisconnected onDisconnected = null, 20 | OnReceive onReceive = null, OnSendCompleted onSendCompleted = null, 21 | OnSocketError onSocketError = null, Codec codec = null, uint workerThreadNum = 0) 22 | { 23 | super(listener, onConnected, onDisconnected, onReceive, onSendCompleted, onSocketError, codec, workerThreadNum); 24 | 25 | _eventHandle = CreateIoCompletionPort(INVALID_HANDLE_VALUE, null, 0, cast(uint)workerPool.size); 26 | } 27 | 28 | override bool register(int fd, EventType et) 29 | { 30 | return fd >= 0 && CreateIoCompletionPort(cast(HANDLE) fd, _eventHandle, fd, 0) != null; 31 | } 32 | 33 | override bool reregister(int fd, EventType et) 34 | { 35 | return fd >= 0; 36 | } 37 | 38 | override bool unregister(int fd) 39 | { 40 | return fd >= 0; 41 | } 42 | 43 | static void handleEvent(Selector selector) 44 | { 45 | OVERLAPPED* overlapped; 46 | IocpContext* context = void; 47 | WSABUF buffSend; 48 | uint dwSendNumBytes; 49 | uint dwFlags; 50 | 51 | while (selector._running) 52 | { 53 | ULONG_PTR key = void; 54 | DWORD bytes; 55 | int ret = GetQueuedCompletionStatus(selector._eventHandle, &bytes, &key, &overlapped, INFINITE); 56 | context = cast(IocpContext*) overlapped; 57 | 58 | if (ret == 0) 59 | { 60 | const err = GetLastError(); 61 | if (err != WAIT_TIMEOUT && context) 62 | { 63 | selector.removeClient(context.fd, err); 64 | } 65 | continue; 66 | } 67 | 68 | if (bytes == 0) 69 | { 70 | selector.removeClient(context.fd, 0); 71 | continue; 72 | } 73 | 74 | if (context.operation == IocpOperation.read) // A read operation complete. 75 | { 76 | selector.read(context.fd, cast(ubyte[]) context.wsabuf.buf[0..bytes]); 77 | 78 | // Read operation completed, so post Read operation for remainder (if exists). 79 | iocp_receive(selector, context.fd); 80 | } 81 | else if (context.operation == IocpOperation.write) // A write operation complete. 82 | { 83 | context.nSentBytes += bytes; 84 | dwFlags = 0; 85 | 86 | if (context.nSentBytes < context.nTotalBytes) 87 | { 88 | // A Write operation has not completed yet, so post another. 89 | // Write operation to post remaining data. 90 | context.operation = IocpOperation.write; 91 | buffSend.buf = context.buffer.ptr + context.nSentBytes; 92 | buffSend.len = context.nTotalBytes - context.nSentBytes; 93 | ret = WSASend(cast(SOCKET) context.fd, &buffSend, 1, &dwSendNumBytes, dwFlags, &(context.overlapped), null); 94 | 95 | if (ret == SOCKET_ERROR) 96 | { 97 | const err = WSAGetLastError(); 98 | 99 | if (err != ERROR_IO_PENDING) 100 | { 101 | selector.removeClient(context.fd, err); 102 | continue; 103 | } 104 | } 105 | } 106 | else 107 | { 108 | // Write operation completed, so post Read operation. 109 | iocp_receive(selector, context.fd); 110 | } 111 | } 112 | } 113 | } 114 | 115 | static void iocp_receive(Selector selector, int fd) nothrow 116 | { 117 | auto context = new IocpContext; 118 | context.operation = IocpOperation.read; 119 | context.nTotalBytes = 0; 120 | context.nSentBytes = 0; 121 | context.wsabuf.buf = context.buffer.ptr; 122 | context.wsabuf.len = context.buffer.sizeof; 123 | context.fd = fd; 124 | uint dwRecvNumBytes; 125 | uint dwFlags; 126 | int err = WSARecv(cast(HANDLE) fd, &context.wsabuf, 1, &dwRecvNumBytes, &dwFlags, &context.overlapped, null); 127 | 128 | if (err == SOCKET_ERROR) 129 | { 130 | err = WSAGetLastError(); 131 | 132 | if (err != ERROR_IO_PENDING) 133 | { 134 | selector.removeClient(fd, err); 135 | } 136 | } 137 | } 138 | 139 | static void iocp_send(Selector selector, int fd, const scope void[] data) nothrow 140 | { 141 | for (size_t pos; pos < data.length;) 142 | { 143 | size_t len = data.length - pos; 144 | len = len > BUFFERSIZE ? BUFFERSIZE : len; 145 | 146 | auto context = new IocpContext; 147 | context.operation = IocpOperation.write; 148 | context.buffer[0..len] = cast(char[]) data[pos..pos + len]; 149 | context.nTotalBytes = cast(int) len; 150 | context.nSentBytes = 0; 151 | context.wsabuf.buf = context.buffer.ptr; 152 | context.wsabuf.len = cast(int) len; 153 | context.fd = fd; 154 | uint dwSendNumBytes = void; 155 | enum dwFlags = 0; 156 | int err = WSASend(cast(HANDLE) fd, &context.wsabuf, 1, &dwSendNumBytes, dwFlags, &context.overlapped, null); 157 | 158 | if (err == SOCKET_ERROR) 159 | { 160 | err = WSAGetLastError(); 161 | 162 | if (err != ERROR_IO_PENDING) 163 | { 164 | selector.removeClient(fd, err); 165 | return; 166 | } 167 | } 168 | 169 | pos += len; 170 | } 171 | } 172 | } 173 | 174 | private: 175 | 176 | enum IocpOperation 177 | { 178 | accept, 179 | connect, 180 | read, 181 | write, 182 | event, 183 | close 184 | } 185 | 186 | enum BUFFERSIZE = 4096 * 2; 187 | 188 | struct IocpContext 189 | { 190 | OVERLAPPED overlapped; 191 | char[BUFFERSIZE] buffer; 192 | WSABUF wsabuf; 193 | int nTotalBytes; 194 | int nSentBytes; 195 | IocpOperation operation; 196 | int fd; 197 | } 198 | 199 | extern (Windows) nothrow @nogc: 200 | 201 | alias POVERLAPPED_COMPLETION_ROUTINE = void function(DWORD, DWORD, OVERLAPPED*, DWORD); 202 | int WSASend(SOCKET, WSABUF*, DWORD, LPDWORD, DWORD, OVERLAPPED*, POVERLAPPED_COMPLETION_ROUTINE); 203 | int WSARecv(SOCKET, WSABUF*, DWORD, LPDWORD, LPDWORD, OVERLAPPED*, POVERLAPPED_COMPLETION_ROUTINE); -------------------------------------------------------------------------------- /libasync/src/async/event/kqueue.d: -------------------------------------------------------------------------------- 1 | module async.event.kqueue; 2 | 3 | debug import std.stdio; 4 | 5 | version (Posix) 6 | { 7 | import core.sys.darwin.sys.event; 8 | } 9 | else version (FreeBSD) 10 | { 11 | import core.sys.freebsd.sys.event; 12 | } 13 | else version (DragonFlyBSD) 14 | { 15 | import core.sys.dragonflybsd.sys.event; 16 | } 17 | 18 | version (OSX) 19 | { 20 | version = KQUEUE; 21 | } 22 | else version (iOS) 23 | { 24 | version = KQUEUE; 25 | } 26 | else version (TVOS) 27 | { 28 | version = KQUEUE; 29 | } 30 | else version (WatchOS) 31 | { 32 | version = KQUEUE; 33 | } 34 | else version (FreeBSD) 35 | { 36 | version = KQUEUE; 37 | } 38 | else version (OpenBSD) 39 | { 40 | version = KQUEUE; 41 | } 42 | else version (DragonFlyBSD) 43 | { 44 | version = KQUEUE; 45 | } 46 | 47 | version (KQUEUE): 48 | 49 | import 50 | async.codec, 51 | async.event.selector, 52 | async.net.tcplistener, 53 | core.stdc.errno, 54 | core.sys.posix.netinet.in_, 55 | core.sys.posix.netinet.tcp, 56 | core.sys.posix.signal, 57 | core.sys.posix.time, 58 | std.socket; 59 | 60 | alias LoopSelector = Kqueue; 61 | 62 | class Kqueue : Selector 63 | { 64 | this(TcpListener listener, OnConnected onConnected = null, OnDisconnected onDisconnected = null, 65 | OnReceive onReceive = null, OnSendCompleted onSendCompleted = null, 66 | OnSocketError onSocketError = null, Codec codec = null, uint workerThreadNum = 0) 67 | { 68 | super(listener, onConnected, onDisConnected, onReceive, onSendCompleted, onSocketError, codec, workerThreadNum); 69 | 70 | _eventHandle = kqueue(); 71 | reg(_listener.fd, EventType.ACCEPT); 72 | } 73 | 74 | private auto reg(int fd, EventType et) 75 | { 76 | kevent_t[2] ev = void; 77 | short filter = void; 78 | ushort flags = void; 79 | 80 | if (et == EventType.ACCEPT) 81 | { 82 | filter = EVFILT_READ; 83 | flags = EV_ADD | EV_ENABLE; 84 | EV_SET(&ev[0], fd, filter, flags, 0, 0, null); 85 | 86 | return kevent(_eventHandle, &ev[0], 1, null, 0, null) >= 0; 87 | } 88 | else 89 | { 90 | filter = EVFILT_READ; 91 | flags = EV_ADD | EV_ENABLE | EV_CLEAR; 92 | EV_SET(&ev[0], fd, filter, flags, 0, 0, null); 93 | 94 | filter = EVFILT_WRITE; 95 | flags = EV_ADD | EV_CLEAR; 96 | flags |= (et == EventType.READ) ? EV_DISABLE : EV_ENABLE; 97 | EV_SET(&ev[1], fd, filter, flags, 0, 0, null); 98 | 99 | return kevent(_eventHandle, &ev[0], 2, null, 0, null) >= 0; 100 | } 101 | } 102 | 103 | override bool register(int fd, EventType et) 104 | { 105 | return reg(fd, et); 106 | } 107 | 108 | override bool reregister(int fd, EventType et) 109 | { 110 | return fd >= 0 && reg(fd, et); 111 | } 112 | 113 | override bool unregister(int fd) 114 | { 115 | if (fd < 0) 116 | { 117 | return false; 118 | } 119 | 120 | kevent_t[2] ev = void; 121 | EV_SET(&ev[0], fd, EVFILT_READ, EV_DELETE, 0, 0, null); 122 | EV_SET(&ev[1], fd, EVFILT_WRITE, EV_DELETE, 0, 0, null); 123 | 124 | return kevent(_eventHandle, &ev[0], fd == _listener.fd ? 1 : 2, null, 0, null) >= 0; 125 | } 126 | 127 | override protected void handleEvent() 128 | { 129 | kevent_t[64] events = void; 130 | //auto tspec = timespec(1, 1000 * 10); 131 | const len = kevent(_eventHandle, null, 0, events.ptr, events.length, null);//&tspec); 132 | 133 | foreach (i; 0 .. len) 134 | { 135 | auto fd = cast(int)events[i].ident; 136 | 137 | if ((events[i].flags & EV_EOF) || (events[i].flags & EV_ERROR)) 138 | { 139 | if (fd == _listener.fd) 140 | { 141 | debug writeln("Listener event error.", fd); 142 | } 143 | else 144 | { 145 | if (events[i].flags & EV_ERROR) 146 | { 147 | int err = void; 148 | socklen_t errlen = err.sizeof; 149 | getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &errlen); 150 | removeClient(fd, err); 151 | } 152 | else 153 | { 154 | removeClient(fd); 155 | } 156 | } 157 | 158 | continue; 159 | } 160 | 161 | if (fd == _listener.fd) 162 | { 163 | accept(); 164 | } 165 | else if (events[i].filter == EVFILT_READ) 166 | { 167 | read(fd); 168 | } 169 | else if (events[i].filter == EVFILT_WRITE) 170 | { 171 | write(fd); 172 | } 173 | } 174 | } 175 | } 176 | 177 | extern (D) void EV_SET(kevent_t* kevp, typeof(kevent_t.tupleof) args) @nogc nothrow 178 | { 179 | *kevp = kevent_t(args); 180 | } -------------------------------------------------------------------------------- /libasync/src/async/event/selector.d: -------------------------------------------------------------------------------- 1 | module async.event.selector; 2 | 3 | import 4 | async.codec, 5 | async.container.map, 6 | async.net.tcpclient, 7 | async.net.tcplistener, 8 | async.thread, 9 | core.sync.mutex, 10 | core.thread, 11 | std.socket; 12 | 13 | alias 14 | OnConnected = void function(TcpClient) nothrow @trusted, 15 | OnDisconnected = void function(TcpClient) nothrow @trusted, 16 | OnReceive = void delegate(TcpClient, const scope ubyte[]) nothrow @trusted, 17 | OnSendCompleted = void function(TcpClient, const scope ubyte[], size_t) nothrow @trusted, 18 | OnSocketError = void function(TcpClient, int) nothrow @trusted; 19 | 20 | enum EventType 21 | { 22 | ACCEPT, READ, WRITE, READWRITE 23 | } 24 | 25 | abstract class Selector 26 | { 27 | ThreadPool workerPool; 28 | 29 | OnReceive onReceive; 30 | OnSendCompleted onSendCompleted; 31 | OnConnected onConnected; 32 | OnDisconnected onDisconnected; 33 | OnSocketError onSocketError; 34 | 35 | this(TcpListener listener, OnConnected onConnected = null, OnDisconnected onDisconnected = null, 36 | OnReceive onReceive = null, OnSendCompleted onSendCompleted = null, 37 | OnSocketError onSocketError = null, Codec codec = null, uint workerThreadNum = 0) 38 | { 39 | import std.parallelism : totalCPUs; 40 | 41 | this.onConnected = onConnected; 42 | this.onDisconnected = onDisconnected; 43 | this.onReceive = onReceive; 44 | this.onSendCompleted = onSendCompleted; 45 | this.onSocketError = onSocketError; 46 | _codec = codec; 47 | _clients = new Map!(int, TcpClient); 48 | _listener = listener; 49 | 50 | version (Windows) { } else _acceptPool = new ThreadPool(1); 51 | workerPool = new ThreadPool(workerThreadNum ? workerThreadNum : totalCPUs * 2 + 2); 52 | } 53 | 54 | ~this() { dispose(); } 55 | 56 | bool register (int fd, EventType et) nothrow; 57 | bool reregister(int fd, EventType et) nothrow; 58 | bool unregister(int fd) nothrow; 59 | 60 | void initialize() 61 | { 62 | version (Windows) 63 | { 64 | import async.event.iocp; 65 | foreach (i; 0 .. workerPool.size) 66 | workerPool.run!(Iocp.handleEvent)(this); 67 | } 68 | } 69 | 70 | alias runLoop = handleEvent; 71 | 72 | void startLoop() 73 | { 74 | _running = true; 75 | 76 | initialize(); 77 | 78 | while (_running) 79 | { 80 | runLoop(); 81 | } 82 | } 83 | 84 | void stop() { _running = false; } 85 | 86 | void dispose() 87 | { 88 | if (_clients is null) 89 | { 90 | return; 91 | } 92 | 93 | _clients.lock(); 94 | foreach (c; _clients.data) 95 | { 96 | if (unregister(c.fd) && c.isAlive) 97 | { 98 | c.close(); 99 | } 100 | } 101 | _clients.unlock(); 102 | 103 | _clients.clear(); 104 | _clients = null; 105 | 106 | if (unregister(_listener.fd)) 107 | _listener.close(); 108 | 109 | version (Posix) 110 | { 111 | static import core.sys.posix.unistd; 112 | core.sys.posix.unistd.close(_eventHandle); 113 | } 114 | } 115 | 116 | void removeClient(int fd, int err = 0) nothrow 117 | { 118 | debug { 119 | import std.stdio; 120 | try writeln("Close event: ", fd); catch(Exception) {} 121 | } 122 | if (!unregister(fd)) 123 | return; 124 | 125 | if (auto client = _clients[fd]) 126 | { 127 | if (err > 0 && onSocketError) 128 | onSocketError(client, err); 129 | if (onDisconnected) 130 | { 131 | onDisconnected(client); 132 | } 133 | try 134 | if (client.isAlive) 135 | client.close(); 136 | catch(Exception) {} 137 | } 138 | _clients.remove(fd); 139 | } 140 | 141 | @property nothrow { 142 | Codec codec() @nogc @safe { return _codec; } 143 | 144 | auto clients() { return _clients; } 145 | } 146 | 147 | protected: 148 | 149 | version (Windows) { } else void accept() 150 | { 151 | _acceptPool.run!beginAccept(this); 152 | } 153 | 154 | static void beginAccept(Selector selector) 155 | { 156 | Socket socket = void; 157 | try 158 | { 159 | socket = selector._listener.accept(); 160 | } 161 | catch (Exception) 162 | { 163 | return; 164 | } 165 | 166 | auto client = new TcpClient(selector, socket); 167 | try 168 | client.setKeepAlive(600, 10); 169 | catch (Exception) { } 170 | 171 | selector._clients[client.fd] = client; 172 | 173 | if (selector.onConnected) 174 | { 175 | selector.onConnected(client); 176 | } 177 | selector.register(client.fd, EventType.READ); 178 | 179 | version (Windows) { 180 | import async.event.iocp; 181 | 182 | Iocp.iocp_receive(selector, client.fd); 183 | } 184 | } 185 | 186 | version (Windows) 187 | { 188 | void read(int fd, const scope ubyte[] data) 189 | { 190 | auto client = _clients[fd]; 191 | 192 | if (client && onReceive) 193 | { 194 | onReceive(client, data); 195 | } 196 | } 197 | } 198 | else 199 | { 200 | void read(int fd) 201 | { 202 | if (auto client = _clients[fd]) 203 | { 204 | client.weakup(EventType.READ); 205 | } 206 | } 207 | 208 | void write(int fd) 209 | { 210 | if (auto client = _clients[fd]) 211 | { 212 | client.weakup(EventType.WRITE); 213 | } 214 | } 215 | } 216 | 217 | Map!(int, TcpClient) _clients; 218 | TcpListener _listener; 219 | bool _running; 220 | 221 | version (Windows) 222 | { 223 | import core.sys.windows.basetsd : HANDLE; 224 | 225 | HANDLE _eventHandle; 226 | 227 | void handleEvent() 228 | { 229 | beginAccept(this); 230 | } 231 | } 232 | else 233 | { 234 | int _eventHandle; 235 | 236 | void handleEvent(); 237 | } 238 | 239 | private: 240 | ThreadPool _acceptPool; 241 | Codec _codec; 242 | } -------------------------------------------------------------------------------- /libasync/src/async/eventloop.d: -------------------------------------------------------------------------------- 1 | module async.eventloop; 2 | 3 | import 4 | async.codec, 5 | async.event.selector, 6 | async.net.tcpclient, 7 | async.net.tcplistener, 8 | std.socket; 9 | 10 | version (linux) 11 | { 12 | import async.event.epoll; 13 | } 14 | else 15 | { 16 | import async.event.kqueue; 17 | version(KQUEUE) { } else version (Windows) 18 | { 19 | import async.event.iocp; 20 | } 21 | else static assert(0, "Unsupported platform."); 22 | } 23 | 24 | class EventLoop : LoopSelector 25 | { 26 | this(TcpListener listener, OnConnected onConnected = null, OnDisconnected onDisconnected = null, 27 | OnReceive onReceive = null, OnSendCompleted onSendCompleted = null, 28 | OnSocketError onSocketError = null, Codec codec = null, uint workerThreadNum = 0) 29 | { 30 | version (Posix) 31 | { 32 | import core.sys.posix.signal; 33 | 34 | // For main thread. 35 | signal(SIGPIPE, SIG_IGN); 36 | 37 | // For background threads. 38 | sigset_t mask1; 39 | sigemptyset(&mask1); 40 | sigaddset(&mask1, SIGPIPE); 41 | sigaddset(&mask1, SIGILL); 42 | sigprocmask(SIG_BLOCK, &mask1, null); 43 | } 44 | 45 | super(listener, onConnected, onDisconnected, onReceive, onSendCompleted, onSocketError, codec, workerThreadNum); 46 | } 47 | 48 | this(TcpListener listener, OnConnected onConnected, OnDisconnected onDisconnected, 49 | void function(TcpClient, const scope ubyte[]) nothrow @trusted onReceive, OnSendCompleted onSendCompleted = null, 50 | OnSocketError onSocketError = null, Codec codec = null, uint workerThreadNum = 0) 51 | { 52 | import std.functional; 53 | this(listener, onConnected, onDisconnected, 54 | onReceive.toDelegate, onSendCompleted, onSocketError, codec, workerThreadNum); 55 | } 56 | 57 | void run() 58 | { 59 | import std.experimental.logger; 60 | debug infof("Start listening to %s...", _listener.localAddress); 61 | startLoop(); 62 | } 63 | } -------------------------------------------------------------------------------- /libasync/src/async/net/tcpclient.d: -------------------------------------------------------------------------------- 1 | module async.net.tcpclient; 2 | 3 | debug import std.stdio; 4 | 5 | import 6 | async.container.bytebuffer, 7 | async.event.selector, 8 | async.eventloop, 9 | async.net.tcplistener, 10 | core.stdc.errno, 11 | std.socket, 12 | std.string; 13 | version (Windows) { } else 14 | import core.sync.rwmutex; 15 | 16 | class TcpClient : TcpListener 17 | { 18 | this(Selector selector, Socket socket) 19 | { 20 | super(socket); 21 | _selector = selector; 22 | _closing = false; 23 | 24 | version (Windows) { } else 25 | { 26 | _sendLock = new ReadWriteMutex(ReadWriteMutex.Policy.PREFER_WRITERS); 27 | _currentEventType = EventType.READ; 28 | _lastWriteOffset = 0; 29 | } 30 | } 31 | 32 | version (Windows) { } else 33 | { 34 | void weakup(EventType event) 35 | { 36 | final switch (event) 37 | { 38 | case EventType.READ: 39 | _hasReadEvent = true; 40 | beginRead(); 41 | break; 42 | case EventType.WRITE: 43 | _hasWriteEvent = true; 44 | beginWrite(); 45 | break; 46 | case EventType.ACCEPT: 47 | case EventType.READWRITE: 48 | break; 49 | } 50 | } 51 | 52 | private: 53 | void beginRead() 54 | { 55 | _hasReadEvent = false; 56 | 57 | if (_reading) 58 | { 59 | return; 60 | } 61 | 62 | _reading = true; 63 | _selector.workerPool.run!read(this); 64 | } 65 | 66 | protected static void read(TcpClient client) 67 | { 68 | ubyte[] data; 69 | ubyte[4096] buffer = void; 70 | 71 | while (!client._closing && client.isAlive) 72 | { 73 | auto len = client.socket.receive(buffer); 74 | 75 | if (len > 0) 76 | { 77 | data ~= buffer[0 .. len]; 78 | continue; 79 | } 80 | if (len == 0) 81 | { 82 | client.readCallback(-1); 83 | return; 84 | } 85 | 86 | if (errno == EINTR) 87 | continue; 88 | if (errno == EAGAIN/* || errno == EWOULDBLOCK*/) 89 | break; 90 | client.readCallback(errno); 91 | return; 92 | } 93 | 94 | if (data.length && client._selector.onReceive) 95 | { 96 | if (client._selector.codec) 97 | { 98 | client._receiveBuffer ~= data; 99 | 100 | ptrdiff_t pos = void; 101 | for (;;) 102 | { 103 | const ret = client._selector.codec.decode(client._receiveBuffer); 104 | pos = ret[0]; 105 | 106 | if (pos < 0) 107 | break; 108 | const ubyte[] message = client._receiveBuffer[0 .. pos]; 109 | client._receiveBuffer.popFront(pos + ret[1]); 110 | client._selector.onReceive(client, message); 111 | } 112 | if (pos == -2) // The magic is error. 113 | { 114 | client.forceClose(); 115 | return; 116 | } 117 | } 118 | else 119 | { 120 | client._selector.onReceive(client, data); 121 | } 122 | } 123 | client.readCallback(0); 124 | } 125 | 126 | void readCallback(const int err) // err: 0: OK, -1: client disconnection, 1,2... errno 127 | { 128 | version (linux) 129 | { 130 | if (err == -1) 131 | { 132 | _selector.removeClient(fd, err); 133 | } 134 | } 135 | 136 | _reading = false; 137 | 138 | if (_hasReadEvent) 139 | { 140 | beginRead(); 141 | } 142 | } 143 | 144 | void beginWrite() 145 | { 146 | _hasWriteEvent = false; 147 | 148 | if (_writing) 149 | { 150 | return; 151 | } 152 | 153 | _writing = true; 154 | _selector.workerPool.run!write(this); 155 | } 156 | 157 | protected static void write(TcpClient client) 158 | { 159 | while (!client._closing && client.isAlive && (!client._writeQueue.empty || client._lastWriteOffset)) 160 | { 161 | if (client._writingData.length == 0) 162 | { 163 | synchronized (client._sendLock.writer) 164 | { 165 | client._writingData = client._writeQueue.front; 166 | client._writeQueue.popFront(); 167 | client._lastWriteOffset = 0; 168 | } 169 | } 170 | 171 | while (!client._closing && client.isAlive && client._lastWriteOffset < client._writingData.length) 172 | { 173 | long len = client.socket.send(client._writingData[client._lastWriteOffset .. $]); 174 | 175 | if (len > 0) 176 | { 177 | client._lastWriteOffset += len; 178 | continue; 179 | } 180 | if (len == 0) 181 | { 182 | //client._selector.removeClient(fd); 183 | 184 | if (client._lastWriteOffset < client._writingData.length) 185 | { 186 | if (client._selector.onSendCompleted) 187 | { 188 | client._selector.onSendCompleted(client, client._writingData, client._lastWriteOffset); 189 | } 190 | 191 | debug writefln("The sending is incomplete, the total length is %d, but actually sent only %d.", 192 | client._writingData.length, client._lastWriteOffset); 193 | } 194 | 195 | client._writingData.length = 0; 196 | client._lastWriteOffset = 0; 197 | 198 | client.writeCallback(-1); // sending is break and incomplete. 199 | return; 200 | } 201 | if (errno == EINTR) 202 | { 203 | continue; 204 | } 205 | if (errno == EAGAIN/* || errno == EWOULDBLOCK*/) 206 | { 207 | if (client._currentEventType != EventType.READWRITE) 208 | { 209 | client._selector.reregister(client.fd, EventType.READWRITE); 210 | client._currentEventType = EventType.READWRITE; 211 | } 212 | 213 | client.writeCallback(0); // Wait eventloop notify to continue again; 214 | return; 215 | } 216 | client._writingData.length = 0; 217 | client._lastWriteOffset = 0; 218 | 219 | client.writeCallback(errno); // Some error. 220 | return; 221 | } 222 | 223 | if (client._lastWriteOffset == client._writingData.length) 224 | { 225 | if (client._selector.onSendCompleted) 226 | { 227 | client._selector.onSendCompleted(client, client._writingData, client._lastWriteOffset); 228 | } 229 | 230 | client._writingData.length = 0; 231 | client._lastWriteOffset = 0; 232 | } 233 | } 234 | 235 | if (client._writeQueue.empty && client._writingData.length == 0 && client._currentEventType == EventType.READWRITE) 236 | { 237 | client._selector.reregister(client.fd, EventType.READ); 238 | client._currentEventType = EventType.READ; 239 | } 240 | 241 | client.writeCallback(0); 242 | return; 243 | } 244 | 245 | void writeCallback(int err) // err: 0: OK, -1: client disconnection, 1,2... errno 246 | { 247 | _writing = false; 248 | 249 | if (_hasWriteEvent) 250 | { 251 | beginWrite(); 252 | } 253 | } 254 | } 255 | 256 | public: 257 | 258 | int send(const void[] data) nothrow @trusted 259 | { 260 | if (!data.length) 261 | return -1; 262 | try { 263 | if (!isAlive) 264 | return -2; 265 | } catch(Exception) 266 | return -2; 267 | 268 | version (Windows) 269 | { 270 | import async.event.iocp; 271 | 272 | Iocp.iocp_send(_selector, fd, data); 273 | } 274 | else 275 | { 276 | import std.experimental.logger; 277 | try { 278 | synchronized (_sendLock.writer) 279 | { 280 | _writeQueue ~= data; 281 | } 282 | 283 | weakup(EventType.WRITE); // First write direct, and when it encounter EAGAIN, it will open the EVENT notification. 284 | } catch(Exception e) { 285 | try error(e); catch(Exception) {} 286 | } 287 | } 288 | return 0; 289 | } 290 | 291 | override void close() @trusted nothrow @nogc 292 | { 293 | _closing = true; 294 | super.close(); 295 | } 296 | 297 | /* 298 | Important: 299 | 300 | The method for emergency shutdown of the application layer is close the socket. 301 | When a message that does not meet the requirements is sent to the server, 302 | this method should be called to avoid the waste of resources. 303 | */ 304 | void forceClose() 305 | { 306 | if (isAlive) 307 | { 308 | _selector.removeClient(fd); 309 | } 310 | } 311 | 312 | private: 313 | Selector _selector; 314 | shared bool _closing; 315 | 316 | version (Windows) { } else 317 | { 318 | shared bool _hasReadEvent, 319 | _hasWriteEvent, 320 | _reading, 321 | _writing; 322 | 323 | ByteBuffer _writeQueue; 324 | ubyte[] _writingData; 325 | size_t _lastWriteOffset; 326 | ReadWriteMutex _sendLock; 327 | 328 | EventType _currentEventType; 329 | } 330 | 331 | ByteBuffer _receiveBuffer; 332 | } -------------------------------------------------------------------------------- /libasync/src/async/net/tcplistener.d: -------------------------------------------------------------------------------- 1 | module async.net.tcplistener; 2 | 3 | import std.socket; 4 | 5 | class TcpListener 6 | { 7 | this() 8 | { 9 | this(new TcpSocket()); 10 | socket.reusePort = true; 11 | 12 | Linger optLinger; 13 | optLinger.on = 1; 14 | optLinger.time = 0; 15 | setOption(SocketOptionLevel.SOCKET, SocketOption.LINGER, optLinger); 16 | } 17 | 18 | void close() @trusted nothrow @nogc 19 | { 20 | socket.shutdown(SocketShutdown.BOTH); 21 | socket.close(); 22 | } 23 | 24 | this(Socket socket) 25 | { 26 | import std.datetime; 27 | 28 | this.socket = socket; 29 | version (Windows) { } else socket.blocking = false; 30 | 31 | setOption(SocketOptionLevel.SOCKET, SocketOption.RCVTIMEO, 60.seconds); 32 | setOption(SocketOptionLevel.SOCKET, SocketOption.SNDTIMEO, 60.seconds); 33 | } 34 | 35 | @property final int fd() pure nothrow @nogc { return cast(int)handle; } 36 | 37 | Socket socket; 38 | alias socket this; 39 | } 40 | 41 | @property bool reusePort(Socket socket) 42 | { 43 | int result = void; 44 | socket.getOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, result); 45 | return result != 0; 46 | } 47 | 48 | @property bool reusePort(Socket socket, bool enabled) 49 | { 50 | socket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, enabled); 51 | 52 | version (Posix) 53 | { 54 | import core.sys.posix.sys.socket; 55 | socket.setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_REUSEPORT, enabled); 56 | } 57 | 58 | version (Windows) 59 | { 60 | if (!enabled) 61 | { 62 | import core.sys.windows.winsock2; 63 | socket.setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_EXCLUSIVEADDRUSE, true); 64 | } 65 | } 66 | 67 | return enabled; 68 | } -------------------------------------------------------------------------------- /libasync/src/async/package.d: -------------------------------------------------------------------------------- 1 | module async; 2 | 3 | public import 4 | async.event.selector, 5 | async.eventloop, 6 | async.net.tcpclient, 7 | async.net.tcplistener, 8 | async.thread, 9 | async.codec; -------------------------------------------------------------------------------- /libasync/src/async/thread.d: -------------------------------------------------------------------------------- 1 | module async.thread; 2 | 3 | import std.parallelism : TaskPool, task, totalCPUs; 4 | 5 | class ThreadPool 6 | { 7 | private TaskPool pool; 8 | 9 | this(uint size = 0) 10 | { 11 | pool = new TaskPool(size ? size : totalCPUs * 2 + 2); 12 | } 13 | 14 | ~this() { pool.finish(true); } 15 | 16 | @property size_t size() @safe const pure nothrow { return pool.size; } 17 | 18 | void run(alias fn, Args...)(Args args) 19 | { 20 | pool.put(task!fn(args)); 21 | } 22 | } --------------------------------------------------------------------------------