|
| 1 | +# Tempest |
| 2 | +Tempest is a simple library for sending and receiving messages across |
| 3 | +any number of transports and dealing with them in a uniform manner. For |
| 4 | +example, you could receive the same message from a HTTP POST and from a |
| 5 | +TCP socket and from your perspective there would be no difference in |
| 6 | +handling it. |
| 7 | + |
| 8 | +**Features:** |
| 9 | + |
| 10 | + - Simple protocol definition |
| 11 | + - Encryption |
| 12 | + - Message signing |
| 13 | + - Multiple message transports (raw tcp, http, etc.) feeding to the same |
| 14 | + message handlers |
| 15 | + - Supports multiple Tempest protocols over a single connection |
| 16 | + |
| 17 | +## Getting Started |
| 18 | + |
| 19 | +### Protocol |
| 20 | + |
| 21 | +The first thing we'll want to do is to define our protocol. We'll define |
| 22 | +a simple protocol that will simply broadcast a message to everyone else |
| 23 | +that's connected. We're only going to want reliable messages here, |
| 24 | +which we'll keep in mind for later. |
| 25 | + |
| 26 | +As Tempest supports multiple protocols over a single connection, we'll |
| 27 | +give our protocol a unique identifier. `1` is reserved for Tempest, so |
| 28 | +we'll use `2`. |
| 29 | + |
| 30 | +```csharp |
| 31 | +static class SimpleChatProtocol |
| 32 | +{ |
| 33 | + public static Protocol Instance = new Protocol (2); |
| 34 | + |
| 35 | + static SimpleChatProtocol() |
| 36 | + { |
| 37 | + // We need to tell our protocol about all the message |
| 38 | + // types belonging to it. Discover() does this automatically. |
| 39 | + Instance.Discover(); |
| 40 | + } |
| 41 | +} |
| 42 | +``` |
| 43 | + |
| 44 | +Each message type needs a unique identifier. Any `ushort` value will do, but |
| 45 | +personally I like to put the values in an enum to make things easier: |
| 46 | + |
| 47 | +```csharp |
| 48 | +public enum SimpleChatMessageType |
| 49 | + : ushort |
| 50 | +{ |
| 51 | + ChatMessage = 1 |
| 52 | +} |
| 53 | +``` |
| 54 | + |
| 55 | +We define messages by subclassing from `Message` and we'll need to tell it |
| 56 | +which `Protocol` it belongs to and its message identifier. To take care of |
| 57 | +this, I like to provide a small subclass of `Message` for the protocol: |
| 58 | + |
| 59 | +```csharp |
| 60 | +public abstract class SimpleChatMessage |
| 61 | + : Message |
| 62 | +{ |
| 63 | + protected SimpleChatMessage (SimpleChatMessageType type) |
| 64 | + : base (SimpleChatProtocol.Instance, (ushort)type) |
| 65 | + { |
| 66 | + } |
| 67 | +} |
| 68 | +``` |
| 69 | + |
| 70 | +Now to define our actual chat message. To define a message type, you'll |
| 71 | +override `WritePayload` and `ReadPayload`. These methods pass in a |
| 72 | +`IValueWriter` and `IValueReader` respectively, which you'll use to |
| 73 | +serialize and deserialize your message. |
| 74 | + |
| 75 | +```csharp |
| 76 | +public sealed class ChatMessage |
| 77 | + : SimpleChatMessage |
| 78 | +{ |
| 79 | + public string Message |
| 80 | + { |
| 81 | + get; |
| 82 | + set; |
| 83 | + } |
| 84 | + |
| 85 | + public override void WritePayload (ISerializationContext context, IValueWriter writer) |
| 86 | + { |
| 87 | + writer.WriteString (Message); |
| 88 | + } |
| 89 | + |
| 90 | + public override void ReadPayload (ISerializationContext context, IValueReader reader) |
| 91 | + { |
| 92 | + Message = reader.ReadString(); |
| 93 | + } |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +And that's it for defining the protocol. **Note:** When using a platform where |
| 98 | +`System.Reflection.Emit` is unavailable, such as iOS or Windows Phone, you |
| 99 | +will need to use `Protocol.Register` and pass in your messages manually |
| 100 | +as `Protocol.Discover` is unavailable. |
| 101 | + |
| 102 | +### Client |
| 103 | + |
| 104 | +For most applications, Tempest has a built in `TempestClient` class you can use |
| 105 | +to handle the fundamentals. We'll subclass to provide an easy method to send |
| 106 | +a chat message and add a listener for other's chat messages: |
| 107 | + |
| 108 | +```csharp |
| 109 | +public sealed class ChatClient |
| 110 | + : TempestClient |
| 111 | +{ |
| 112 | + // We need to tell TempestClient what kinds of messages we're dealing |
| 113 | + // with. Earlier we decided that we only need Reliable messages here. |
| 114 | + public ChatClient (IClientConnection connection) |
| 115 | + : base (connection, MessageTypes.Reliable) |
| 116 | + { |
| 117 | + // Here we setup a handler for any `ChatMessage`s that come through. |
| 118 | + this.RegisterMessageHandler<ChatMessage> (OnChatMessage); |
| 119 | + } |
| 120 | + |
| 121 | + // A simple event for brevity |
| 122 | + public event Action<string> ChatReceived; |
| 123 | + |
| 124 | + public Task SendChatAsync (string message) |
| 125 | + { |
| 126 | + var msg = new ChatMessage { Message = message }; |
| 127 | + return Connection.SendAsync (msg); |
| 128 | + } |
| 129 | + |
| 130 | + private void OnChatMessage (MessageEventArgs<ChatMessage> e) |
| 131 | + { |
| 132 | + ChatMessage msg = e.Message; |
| 133 | + |
| 134 | + var received = ChatReceived; |
| 135 | + if (received != null) |
| 136 | + received (msg.Message); |
| 137 | + } |
| 138 | +} |
| 139 | +``` |
| 140 | + |
| 141 | +To connect to a server, we'll need to pick a transport. We'll use the built in network |
| 142 | +transport. |
| 143 | + |
| 144 | +```csharp |
| 145 | +var connection = new Tempest.Providers.Network.NetworkClientConnection (SimpleChatProtocol.Instance); |
| 146 | + |
| 147 | +var client = new ChatClient (connection); |
| 148 | +await client.ConnectAsync (new Target ("hostname", port)); |
| 149 | +``` |
| 150 | + |
| 151 | +### Server |
| 152 | + |
| 153 | +For the server side of things, we also have a `TempestServer` class you can use |
| 154 | +to handle the fundamentals. We'll need to keep track of connections ourselves, |
| 155 | +where later on we may want to associate them with user data. So we'll subclass |
| 156 | +and add our connection list and a handler for `ChatMessage`: |
| 157 | + |
| 158 | +```csharp |
| 159 | +public sealed class ChatServer |
| 160 | + : TempestServer |
| 161 | +{ |
| 162 | + public ChatServer (IConnectionProvider provider) |
| 163 | + : base (provider, MessageTypes.Reliable) |
| 164 | + { |
| 165 | + this.RegisterMessageHandler<ChatMessage> (OnChatMessage); |
| 166 | + } |
| 167 | + |
| 168 | + private readonly List<IConnection> connections = new List<IConnection>(); |
| 169 | + private void OnChatMessage (MessageEventArgs<ChatMessage> e) |
| 170 | + { |
| 171 | + ChatMessage msg = e.Message; |
| 172 | + |
| 173 | + // Messages come in on various threads, we'll need to make |
| 174 | + // sure we stay thread safe. |
| 175 | + lock (this.connections) { |
| 176 | + foreach (IConnection connection in this.connections) |
| 177 | + connection.SendAsync (e.Message); |
| 178 | + } |
| 179 | + } |
| 180 | + |
| 181 | + protected override void OnConnectionMade (object sender, ConnectionMadeEventArgs e) |
| 182 | + { |
| 183 | + lock (this.connections) |
| 184 | + this.connections.Add (e.Connection); |
| 185 | + |
| 186 | + base.OnConnectionMade (sender, e); |
| 187 | + } |
| 188 | + |
| 189 | + protected override void OnConnectionDisconnected (object sender, DisconnectedEventArgs e) |
| 190 | + { |
| 191 | + lock (this.connections) |
| 192 | + this.connections.Remove (e.Connection); |
| 193 | + |
| 194 | + base.OnConnectionDisconnected (sender, e); |
| 195 | + } |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +To start the server, we'll need to provide a transport mechanism. You can add multiple |
| 200 | +transports to listen to, but for now we'll just add a network connection provider as we've |
| 201 | +already told the client to connect with it: |
| 202 | + |
| 203 | +``` |
| 204 | +// NetworkConnectionProvider requires that you tell it what local target to listen |
| 205 | +// to and the maximum number of connections you'll allow. |
| 206 | +var provider = new Tempest.Providers.Network.NetworkConnectionProvider (SimpleChatProtocol.Instance, Target.AnyIP, 100); |
| 207 | +
|
| 208 | +var server = new ChatServer (provider); |
| 209 | +server.Start(); |
| 210 | +``` |
| 211 | + |
| 212 | +### Transports |
| 213 | +There are currently two available transports: |
| 214 | + |
| 215 | + - TCP |
| 216 | + - Supports reliable messaging |
| 217 | + - Supports encryption and signing |
| 218 | + - `NetworkClientConnection` for client connections |
| 219 | + - `NetworkConnectionProvider` for connection listeners |
| 220 | + - UDP |
| 221 | + - _Experimental_ |
| 222 | + - Supports reliable and unreliable messaging |
| 223 | + - Supports encryption and signing |
| 224 | + - `UdpClientConnection` for client connections |
| 225 | + - `UdpConnectionProvider` for connection listeners |
| 226 | + |
0 commit comments