Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

WatsonTcp is the easiest way to build TCP-based clients and servers in C#.

License

NotificationsYou must be signed in to change notification settings

dotnet/WatsonTcp

alt tag

WatsonTcp

NuGet VersionNuGet

WatsonTcp is the fastest, easiest, most efficient way to build TCP-based clients and servers in C# with integrated framing, reliable transmission, and fast disconnect detection.

IMPORTANT WatsonTcp provides framing to ensure message-level delivery which also dictates that you must either 1) use WatsonTcp for both the server and the client, or, 2) ensure that your client/server exchange messages with the WatsonTcp node using WatsonTcp's framing. Refer toFRAMING.md for a reference on WatsonTcp message structure.

  • If you want a library that doesn't use framing, but has a similar implementation, useSuperSimpleTcp
  • If you want a library that doesn't use framing and provides explicit control over how much data to read, useCavemanTcp

.NET Foundation

This project is part of the.NET Foundation along with other projects likethe .NET Runtime.

Contributions

Special thanks to the following people for their support and contributions to this project!

@brudo @MrMikeJJ @mikkleini @pha3z @crushedice @marek-petak @ozrecsec @developervariety@NormenSchwettmann @karstennilsen @motridox @AdamFrisby @Job79 @Dijkstra-ru @playingoDEERUX@DuAell @syntacs @zsolt777 @broms95 @Antwns @MartyIX @Jyck @Memphizzz @nirajgenius@cee-sharp @jeverz @cbarraco @DenisBalan @Markonius @Ahmed310 @markashleybell@thechosensausage @JVemon @eatyouroats @bendablegears @Laiteux @fisherman6v6 @wesoos@YorVeX @tovich37 @sancheolz @lunedis @ShayanFiroozi

If you'd like to contribute, please jump right into the source code and create a pull request, or, file an issue with your enhancement request.

New in v6.0.x

  • Remove unsupported frameworks
  • Async version ofSyncMessageReceived callback
  • Moving usings inside namespace
  • Remove obsolete methods
  • Mark non-async APIs obsolete
  • Modified test projects to use async
  • Ensured background tasks honored cancellation tokens
  • Ability to specify a client's GUID before attempting to connect
  • Remove obsolete methods

Test Applications

Test projects for both client and server are included which will help you understand and exercise the class library.

SSL

WatsonTcp supports data exchange with or without SSL. The server and client classes include constructors that allow you to include fields for the PFX certificate file and password. An example certificate can be found in the test projects, which has a password of 'password'.

To Stream or Not To Stream...

WatsonTcp allows you to receive messages using either byte arrays or streams. SetEvents.MessageReceived if you wish to consume a byte array, or, setEvents.StreamReceived if you wish to consume a stream.

It is important to note the following:

  • When usingEvents.MessageReceived
    • The message payload is read from the stream and sent to your application
    • The event is fired asynchronously and Watson can continue reading messages while your application processes
  • When usingEvents.StreamReceived
    • If the message payload is smaller thanSettings.MaxProxiedStreamSize, the data is read into aMemoryStream and sent to your application asynchronously
    • If the message payload is larger thanSettings.MaxProxiedStreamSize, the underlying data stream is sent to your application synchronously, and WatsonTcp will wait until your application responds before continuing to read
  • Only one ofEvents.MessageReceived andEvents.StreamReceived should be set;Events.MessageReceived will be used if both are set

Including Metadata with a Message

Should you with to include metadata with any message, use theSend orSendAsync method that allows you to pass in metadata (Dictionary<string, object>). Refer to theTestClient,TestServer,TestClientStream, andTestServerStream projects for a full example. Keys must be of typestring.

Note: if you use a class instance as either the value, you'll need to deserialize on the receiving end from JSON.

object myVal = args.Metadata["myKey"];MyClass instance = myVal.ToObject<MyClass>();

This is not necessary if you are using simple types (int, string, etc). Simply cast to the simple type.

IMPORTANT

Identifying the demarcation between message header and payload is CPU intensive and requires evaluation of the tail end of an internally-managed buffer. This process of evaluation is performed foreach byte read until the end of the header is reached. Thus, is it recommended that the metadata property be used sparingly and with very small amounts of data (less than 1KB). When used with large amounts of data, CPU utilization will increase dramatically and response time will be very slow.

Local vs External Connections

IMPORTANT

  • If you specify127.0.0.1 as the listener IP address in WatsonTcpServer, it will only be able to accept connections from within the local host.
  • To accept connections from other machines:
    • Use a specific interface IP address, or
    • Usenull,*,+, or0.0.0.0 for the listener IP address (requires admin privileges to listen on any IP address)
  • Make sure you create a permit rule on your firewall to allow inbound connections on that port
  • If you use a port number under 1024, admin privileges will be required

Running under Mono

.NET Core should always be the preferred option for multi-platform deployments. However, WatsonTcp works well in Mono environments with the .NET Framework to the extent that we have tested it. It is recommended that when running under Mono, you execute the containing EXE using --server and after using the Mono Ahead-of-Time Compiler (AOT). Note that TLS 1.2 is hard-coded, which may need to be downgraded to TLS in Mono environments.

NOTE: Windows accepts '0.0.0.0' as an IP address representing any interface. On Mac and Linux you must be specified ('127.0.0.1' is also acceptable, but '0.0.0.0' is NOT).

mono --aot=nrgctx-trampolines=8096,nimt-trampolines=8096,ntrampolines=4048 --server myapp.exemono --server myapp.exe

Examples

The following examples show a simple client and server example using WatsonTcp without SSL and consuming messages using byte arrays instead of streams. For full examples, please refer to theTest.* projects.

Server

usingWatsonTcp;staticvoidMain(string[]args){WatsonTcpServerserver=newWatsonTcpServer("127.0.0.1",9000);server.Events.ClientConnected+=ClientConnected;server.Events.ClientDisconnected+=ClientDisconnected;server.Events.MessageReceived+=MessageReceived;server.Callbacks.SyncRequestReceivedAsync=SyncRequestReceived;server.Start();// list clientsIEnumerable<ClientMetadata>clients=server.ListClients();// send a messageawaitserver.SendAsync([guid],"Hello, client!");// send a message with metadataDictionary<string,object>md=newDictionary<string,object>();md.Add("foo","bar");awaitserver.SendAsync([guid],"Hello, client!  Here's some metadata!",md);// send and wait for a responsetry{SyncResponseresp=awaitserver.SendAndWaitAsync([guid],5000,"Hey, say hello back within 5 seconds!");Console.WriteLine("My friend says: "+Encoding.UTF8.GetString(resp.Data));}catch(TimeoutException){Console.WriteLine("Too slow...");}}staticvoidClientConnected(objectsender,ConnectionEventArgsargs){Console.WriteLine("Client connected: "+args.Client.ToString());}staticvoidClientDisconnected(objectsender,DisconnectionEventArgsargs){Console.WriteLine("Client disconnected: "+args.Client.ToString()+": "+args.Reason.ToString());}staticvoidMessageReceived(objectsender,MessageReceivedEventArgsargs){Console.WriteLine("Message from "+args.Client.ToString()+": "+Encoding.UTF8.GetString(args.Data));}staticasyncTask<SyncResponse>SyncRequestReceived(SyncRequestreq){returnnewSyncResponse(req,"Hello back at you!");}

Client

usingWatsonTcp;staticvoidMain(string[]args){WatsonTcpClientclient=newWatsonTcpClient("127.0.0.1",9000);client.Events.ServerConnected+=ServerConnected;client.Events.ServerDisconnected+=ServerDisconnected;client.Events.MessageReceived+=MessageReceived;client.Callbacks.SyncRequestReceivedAsync=SyncRequestReceived;client.Connect();// check connectivityConsole.WriteLine("Am I connected?  "+client.Connected);// send a messageclient.Send("Hello!");// send a message with metadataDictionary<string,object>md=newDictionary<string,object>();md.Add("foo","bar");awaitclient.SendAsync("Hello, client!  Here's some metadata!",md);// send and wait for a responsetry{SyncResponseresp=awaitclient.SendAndWaitAsync(5000,"Hey, say hello back within 5 seconds!");Console.WriteLine("My friend says: "+Encoding.UTF8.GetString(resp.Data));}catch(TimeoutException){Console.WriteLine("Too slow...");}}staticvoidMessageReceived(objectsender,MessageReceivedEventArgsargs){Console.WriteLine("Message from server: "+Encoding.UTF8.GetString(args.Data));}staticvoidServerConnected(objectsender,ConnectionEventArgsargs){Console.WriteLine("Server connected");}staticvoidServerDisconnected(objectsender,DisconnectionEventArgsargs){Console.WriteLine("Server disconnected");}staticasyncTask<SyncResponse>SyncRequestReceived(SyncRequestreq){returnnewSyncResponse(req,"Hello back at you!");}

Example with SSL

The examples above can be modified to use SSL as follows. No other changes are needed. Ensure that the certificate is exported as a PFX file and is resident in the directory of execution.

// serverWatsonTcpServerserver=newWatsonTcpServer("127.0.0.1",9000,"test.pfx","password");server.Settings.AcceptInvalidCertificates=true;server.Settings.MutuallyAuthenticate=true;server.Start();// clientWatsonTcpClientclient=newWatsonTcpClient("127.0.0.1",9000,"test.pfx","password");client.Settings.AcceptInvalidCertificates=true;client.Settings.MutuallyAuthenticate=true;client.Connect();

Example with Streams

Refer to theTest.ClientStream andTest.ServerStream projects for a full example.

// serverWatsonTcpServerserver=newWatsonTcpServer("127.0.0.1",9000);server.Events.ClientConnected+=ClientConnected;server.Events.ClientDisconnected+=ClientDisconnected;server.Events.StreamReceived+=StreamReceived;server.Start();staticvoidStreamReceived(objectsender,StreamReceivedEventArgsargs){longbytesRemaining=args.ContentLength;intbytesRead=0;byte[]buffer=newbyte[65536];using(MemoryStreamms=newMemoryStream()){while(bytesRemaining>0){bytesRead=args.DataStream.Read(buffer,0,buffer.Length);if(bytesRead>0){ms.Write(buffer,0,bytesRead);bytesRemaining-=bytesRead;}}}Console.WriteLine("Stream received from "+args.Client.ToString()+": "+Encoding.UTF8.GetString(ms.ToArray()));}// clientWatsonTcpClientclient=newWatsonTcpClient("127.0.0.1",9000);client.Events.ServerConnected+=ServerConnected;client.Events.ServerDisconnected+=ServerDisconnected;client.Events.StreamReceived+=StreamReceived;client.Connect();staticvoidStreamReceived(objectsender,StreamReceivedEventArgsargs){longbytesRemaining=args.ContentLength;intbytesRead=0;byte[]buffer=newbyte[65536];using(MemoryStreamms=newMemoryStream()){while(bytesRemaining>0){bytesRead=args.DataStream.Read(buffer,0,buffer.Length);if(bytesRead>0){ms.Write(buffer,0,bytesRead);bytesRemaining-=bytesRead;}}}Console.WriteLine("Stream received from server: "+Encoding.UTF8.GetString(ms.ToArray()));}

Specifying a Client GUID

If you wish to specify a client's GUID, you can modifyWatsonTcpClient.Settings.Guid prior to callingWatsonTcpClient.Connect().

WatsonTcpClientclient=newWatsonTcpClient("127.0.0.1",9000);client.Events.ServerConnected+=ServerConnected;client.Events.ServerDisconnected+=ServerDisconnected;client.Events.StreamReceived+=StreamReceived;client.Settings.Guid=Guid.Parse("12345678-1234-1234-123456781234");client.Connect();

Troubleshooting

The first step in troubleshooting is to implement a logging method and attach it toSettings.Logger, and as a general best practice while debugging, setSettings.DebugMessages totrue.

client.Settings.DebugMessages=true;client.Settings.Logger=MyLoggerMethod;privatevoidMyLoggerMethod(Severitysev,stringmsg){Console.WriteLine(sev.ToString()+": "+msg);}

Additionally it is recommended that you implement theEvents.ExceptionEncountered event.

client.Events.ExceptionEncountered+=MyExceptionEvent;privatevoidMyExceptionEvent(objectsender,ExceptionEventArgsargs){Console.WriteLine(args.Json);}

Disconnection Handling

The project TcpTest (https://github.com/jchristn/TcpTest) was built specifically to provide a reference for WatsonTcp to handle a variety of disconnection scenarios. The disconnection tests for which WatsonTcp is evaluated include:

Test caseDescriptionPass/Fail
Server-side disposeGraceful termination of all client connectionsPASS
Server-side client removalGraceful termination of a single clientPASS
Server-side terminationAbrupt termination due to process abort or CTRL-CPASS
Client-side disposeGraceful termination of a client connectionPASS
Client-side terminationAbrupt termination due to a process abort or CTRL-CPASS
Network interface downNetwork interface disabled or cable removedPartial (see below)

Additionally, as of v4.3.0, support for TCP keepalives has been added to WatsonTcp, primarily to address the issue of a network interface being shut down, the cable unplugged, or the media otherwise becoming unavailable. It is important to note that keepalives are supported in .NET Core and .NET Framework, but NOT .NET Standard. As of this release, .NET Standard provides no facilities for TCP keepalives.

TCP keepalives are NOT enabled by default. To enable and configure:

server.Keepalive.EnableTcpKeepAlives=true;server.Keepalive.TcpKeepAliveInterval=5;// seconds to wait before sending subsequent keepaliveserver.Keepalive.TcpKeepAliveTime=5;// seconds to wait before sending a keepaliveserver.Keepalive.TcpKeepAliveRetryCount=5;// number of failed keepalive probes before terminating connection

Some important notes about TCP keepalives:

  • Keepalives only work in .NET Core and .NET Framework
  • Keepalive.TcpKeepAliveRetryCount is only applicable to .NET Core; for .NET Framework, this value is forced to 10

Disconnecting Idle Clients

If you wish to have WatsonTcpServer automatically disconnect clients that have been idle for a period of time, setWatsonTcpServer.IdleClientTimeoutSeconds to a positive integer. Receiving a message from a client automatically resets their timeout. Client timeouts are evaluated every 5 seconds by Watson, so the disconnection may not be precise (for instance, if you use 7 seconds as your disconnect interval).

Donations

If you would like to financially support my efforts, first of all, thank you! Please refer to DONATIONS.md.

Version History

Please refer to CHANGELOG.md for details.

About

WatsonTcp is the easiest way to build TCP-based clients and servers in C#.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Sponsor this project

  •  

Packages

No packages published

Contributors18


[8]ページ先頭

©2009-2025 Movatter.jp