Page Views: {0}
" + 27 | " " + 30 | " " + 31 | ""; 32 | 33 | 34 | public static async Task HandleIncomingConnections() 35 | { 36 | bool runServer = true; 37 | 38 | // While a user hasn't visited the `shutdown` url, keep on handling requests 39 | while (runServer) 40 | { 41 | // Will wait here until we hear from a connection 42 | HttpListenerContext ctx = await listener.GetContextAsync(); 43 | 44 | // Peel out the requests and response objects 45 | HttpListenerRequest req = ctx.Request; 46 | HttpListenerResponse resp = ctx.Response; 47 | 48 | // Print out some info about the request 49 | Console.WriteLine("Request #: {0}", ++requestCount); 50 | Console.WriteLine(req.Url.ToString()); 51 | Console.WriteLine(req.HttpMethod); 52 | Console.WriteLine(req.UserHostName); 53 | Console.WriteLine(req.UserAgent); 54 | Console.WriteLine(); 55 | 56 | // If `shutdown` url requested w/ POST, then shutdown the server after serving the page 57 | if ((req.HttpMethod == "POST") && (req.Url.AbsolutePath == "/shutdown")) 58 | { 59 | Console.WriteLine("Shutdown requested"); 60 | runServer = false; 61 | } 62 | 63 | // Make sure we don't increment the page views counter if `favicon.ico` is requested 64 | if (req.Url.AbsolutePath != "/favicon.ico") 65 | pageViews += 1; 66 | 67 | // Write the response info 68 | string disableSubmit = !runServer ? "disabled" : ""; 69 | byte[] data = Encoding.UTF8.GetBytes(String.Format(pageData, pageViews, disableSubmit)); 70 | resp.ContentType = "text/html"; 71 | resp.ContentEncoding = Encoding.UTF8; 72 | resp.ContentLength64 = data.LongLength; 73 | 74 | // Write out to the response stream (asynchronously), then close it 75 | await resp.OutputStream.WriteAsync(data, 0, data.Length); 76 | resp.Close(); 77 | } 78 | } 79 | 80 | 81 | public static void Main(string[] args) 82 | { 83 | // Create a Http server and start listening for incoming connections 84 | listener = new HttpListener(); 85 | listener.Prefixes.Add(url); 86 | listener.Start(); 87 | Console.WriteLine("Listening for connections on {0}", url); 88 | 89 | // Handle requests 90 | Task listenTask = HandleIncomingConnections(); 91 | listenTask.GetAwaiter().GetResult(); 92 | 93 | // Close the listener 94 | listener.Close(); 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /02.HttpListener/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | 4 | // Information about this assembly is defined by the following attributes. 5 | // Change them to the values specific to your project. 6 | 7 | [assembly: AssemblyTitle("02.HttpListener")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("")] 11 | [assembly: AssemblyProduct("")] 12 | [assembly: AssemblyCopyright("ben")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 17 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 18 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 19 | 20 | [assembly: AssemblyVersion("1.0.*")] 21 | 22 | // The following attributes are used to specify the signing key for the assembly, 23 | // if desired. See the Mono documentation for more information about signing. 24 | 25 | //[assembly: AssemblyDelaySign(false)] 26 | //[assembly: AssemblyKeyFile("")] 27 | 28 | -------------------------------------------------------------------------------- /03.b.TcpChatServer/03.b.TcpChatServer.csproj: -------------------------------------------------------------------------------- 1 | 2 |To start things off simple, were just going to make a small utility that will download a web page for us and save it. To do this, we will use the HttpClient class.
4 | 5 |6 | 7 |
You will need to add the System.Net.Http
Assembly to your project's references.
12 | 13 |
HttpClient
works by establishing a connection over TCP to get a response from any server that serves web pages (or responds to HTTP requests). It's pretty simple to use, but you need to use asynchronous code. This is probably a slightly more complex example of HttpClient
, but it shows simple usage of async
, await
, and Task
.
16 | 17 |
Our program first sets up a page to get, waits to hear back from the server (a 200 HTTP Response
), converts the data to a UTF-8 encoded byte array, then lastly saves it to a file name we specify at the top. In the Main()
body, as soon as we call DownloadWebPage()
, it starts to execute the code inside, but as soon as it hits an await
statement execution returns back outside, waiting for the awaited statement to finish. But since the call to Thread.Sleep()
is there, it pauses execution inside the main thread for at least five seconds; this doesn't halt the downloading at all. So if you're on a good internet connection, the web page should finish downloading before the five seconds are over. If not, then the call to GetAwaiter().GetResult()
at the end will block execution until the Task
completes.
20 | 21 | 22 |
23 | 24 |
You might notice that there is a lengthier call to dlTask.GetAwaiter().GetResult()
at the end of Main()
, where we could have just called dlTask.Wait()
. We do this because if an exception is thrown by the Task
, we will get that original exception thrown to us instead of it being wrapped inside of a AggregateException
that would be produced by the Wait()
method.
27 | 28 |
Right now this program just downloads a single web page from a single resource. A fun exercise for you would be to modify the code to download many different resources from any website.
29 | 30 |I would like to thank AngularBeginner of Reddit for suggesting some code improvements.
-------------------------------------------------------------------------------- /html/03.b.tcp-chat-server.html: -------------------------------------------------------------------------------- 1 |I've seen many networking tutorials that start you with the client code. I think it's better to start off with the server code first. And there is quite a bit here.
4 | 5 |The main two classes that are used here are TcpClient
& TcpListener
. In a nutshell, we use TcpListener
to listen for incoming connections, then once someone has connected, we spin up a new TcpClient
to talk to that remote process.
Here is the code for the server:
8 | 9 |10 | 11 | 12 |
13 | 14 |
When instancing the class TcpChatServer
, the listener is created but doesn't start listening until we call Start()
on it in the Run()
method. IPAddress.Any
, means "let anyone from any network interface connect to me."
The Run()
method is the main heart of the program. Like said above, when we make the call to _listener.Start()
, the TcpListener
will start listening for new incoming TCP connections the port we fed it (6000 our case). If there is a new pending connection (checked via the Pending()
method), we break into a function see who the new client is.
_handleNewConnection()
does what the name implies. After the connection has been established we can grab it's TcpClient
instance by calling AcceptTcpClient
in the listener, the client should send a message stating either viewer
or name:MY_NAME
.
MY_NAME
hasn't been taken yet
23 | Back in Run()
, we check for disconnects, new messages, and then send queued ones in the rest of the loop. There is also a call to Thread.Sleep(10)
, so that we save on CPU resources.
_checkForDisconnects()
are two foreach
loops that go through all of the clients and use the _isDisconnected()
method to test for the FIN/ACK packets that might have been sent.
36 | 37 |
The Connected
property of TcpClient
might seem like the right thing to use here instead of _isDisconnected()
, but there's a problem with that. Connected
returns true
only if the last IO operation was a success. This means that the FIN/ACK might have been sent, but since there were no IO operations on our end, Connected
will still return true
. Be careful of this.
42 | 43 |
_checkForNewMessages()
is also a foreach
check of all the Messengers. We can use the Available
property to see how big their message is (in bytes), and then read that from the client's NetworkStream
.
_sendMessages()
empties the _messageQueue
by writing the the Viewer's streams.
There is also the function _cleanupClient()
. It's a small helper that closes both the TcpClient
's NetworkStream
and itself.
50 | 51 |
If you are wondering why we need to manually close the stream ourselves, is because the stream is created from the underlying Socket
object (which can be accessed via TcpClient
's Client
property). This means you need to clean it up manually. Read more about it here.
The Messenger is a lot smaller of a program that the server. It connects, checks if its supplied name is okay, then lets the user spit messages until they want to quit.
4 | 5 |6 | 7 | 8 |
9 | 10 |
There are many different constructors for TcpClient
. All of them, except for the one with no parameters, will try to connect to the supplied address & port as soon as its instantiated. We use the one with no parameters for ours.
Our Connect()
function is what will try to initialize the connection. If we connected successfully, it will send send the message name:MY_NAME
, where MY_NAME
is what is provided by the user at run time. If we're still connected after that, then we're good to start sending messages. Note that if the user supplies a name that is the empty string, the server will boot them.
SendMessages()
is our main loop for this program. If we are connected (checked with the Running
property), poll the user for some input.
quit
or exit
, start the process to disconnect from the chat serverAfter that, take a 10 millisecond nap (like what the server does). Once rested, verify that we're still connected by calling _isDisconnected()
on our TcpClient
.
_cleanupNetworkResources()
is called just to make sure our NetworkStream
is closed as well as the TcpClient
we're using.
This is also a much tinier one like the Messenger, in fact, most of the code is pretty similar. Once it has connected to the server, it will get a "hello," message, and then keep printing out messages until the user presses Ctrl-C.
4 | 5 |6 | 7 |
8 | 9 |
Similar to the Messenger, we're using the constructor of TcpClient
that doesn't start a connection. We do that in our Connect()
method. If we're connected, we immediately send over the text viewer
to tell it that we are, well, a Viewer client. If we are still connected, then we're safe to assume that the server recognises us as a viewer and will send us messages. As this point, the server has already sent us a message with "Welcome to the 'SERVER_NAME' Chat Server". Though, we don't find out about it until we enter ListenForMessages()
.
In that function, if we are connected, we test to see if there is a new message or not. If there is, TcpClient.Available
will report how many bytes are incoming. If so, we then try to read that many bytes from the NetworkStream
. The call to Read()
will block until that many bytes have been read in. Once the message is received, it will print it out to the user. Like the other programs, we also sleep for 10 milliseconds to conserve CPU resources. Then we do a check for that FIN/ACK packet and a check to see if the user pressed Ctrl-C.
Once we've exited the while(Running)
loop, we clean up any network resources that we have left over.
And here we have the collection of programs running on many different Linux machines. The one on the left is my laptop, in the middle is a cloud instance I spun up, and on the right is this server hosting the Server portion. If you're wondering what my order of operations was:
4 | 5 |19 | 20 | 21 | 22 |
23 | 24 |
With all that has been said, done, and typed, there are still a few things to go over. This is not a good implementation of a internet chatting application. Keep in mind my goal is to teach you the C# # APIs of System.Net
. Lets go over what's oh-so-very wrong:
bye
message should be sent by the clients/server to tell the other that it will disconnect. Sure, the FIN/ACK packets will still be sent after the bye
is sent, but this is a much more graceful method and more descriptive. There are many applications out there (like online video games) that use this to tell if some has quit the application normally (i.e. ragequit) or not.Look at the function _handleNewConnection()
in TcpChatServer.cs
, there's something really-really-bad about it. Walk through it and see if you can spot the really-really-bad problem. Hint: netStream.Read()
If a client connects, but doesn't send a message. The server is just going to hold here. Forever. We didn't set any timeouts in the code, so by default the stream will just keep trying to read for a really-really-long time.
40 |
41 | Someone could just telnet into the Chat Server, but not send a message to identify themselves. This would hold the server hostage.
NetworkStream
s are waiting for data indefinitely. Take a look at the timeout related properties of NetworkStream
.async
code, be multi-threaded, and have timeouts; there is none here. Normally, servers will spin up a new thread or process when a new client connects, so it can handle both the newbie as well as the currently connected clients. We could have also used the asynchronous read/write methods on NetworkStream
.53 |
56 | 57 |
If you have anything to say about this, send me a message. There might have been some major flaws that I've missed. I would like to hear about them and put them up here. Also, if you want to have your own fun and try to make this server multi-threade, asynchronous and time'd-out, feel free to do that and send me a link to the code when you're done. It would be nice to see a "more correct," version of this.
58 | 59 |This also was a much more lengthy example than my other stuff. I'll try to make the rest of them a bit simpler.
60 | -------------------------------------------------------------------------------- /html/04.a.tcp-games.html: -------------------------------------------------------------------------------- 1 |Alright. Since we've already done a single-threaded networked application, what is there to do next? Why a multithreaded one of course! But let's go the extra mile and use some async
code too. This one turned out to be more complex than I envisioned, but bear with me because it can do more. Hold tight, it will be fun.
Please note that this section does not have any async
Socket
programming. That's something completely different and it will be covered in a later section.
I always find video games as a great way of teaching programming concepts, languages, and libraries. But since I want to keep this tutorial series as "GUI-less," as possible we're going to do some text (console) based gaming. Get ready to party like its 1989 because George H. W. Bush has been elected, Tim Burton is directing a Batman film, Milli Vanilli is topping the charts, and Game Boys are all the rage; We're making a BBS game!! (sort-of)
8 | 9 |10 | 11 | 12 | 13 |
Pictured: 1989
14 | 15 |16 | 17 |
Just as a side note, if you want to make a real video game, it would be much, MUCH better to use UDP instead of TCP for most cases. TCP here is optimal since we're doing text based gaming only and any of the games that we have on the server should be turn-based. UDP is a much better choice if your networked game runs in real time.
19 |23 |
26 | 27 |
If you haven't read the previous section, TCP Chat, go do so so you can familiarise yourself with TcpClient
and TcpListener
. Part of the design of the server side is so that we can switch out different games. Because of time constraints, I've only implemented one game called "Guess My Number," which is single player. I also wanted to do Tic-Tac-Toe as well (multi-player), but I'll leave that an exercise for you. It shouldn't be too difficult.
We only should have two separate projects in our solution for this section. One being the TcpGamesServer
and the other the TcpGamesClient
. Our namespace for both will be TcpGames
.
We also will be using the same _isDisconnected()
method to check for ungraceful disconnects. Note that it's name is slightly different in the server code. Here it is again:
36 | -------------------------------------------------------------------------------- /html/04.b.tcp-games-application-protocol.html: -------------------------------------------------------------------------------- 1 |
Let's first talk about how our clients and server are going to pass around messages/packets. To make this more structured than last time, we will be using JSON, which will only contain plaintext. Each JSON object will only have two fields, command
and message
.
command
: this tells the client/server how the packet should be processedmessage
: this adds some extra context for the command
. It may be an empty string sometimes11 | 12 |
We have only three simple command
s for each type of packet:
bye
: this can be sent by either the server or the client. It notifies the other end that the sender is disconnecting gracefully.
16 |
17 | message
and clean up its network resourcesmessage
(if there is one). It should also clean up any resources associated with the clientmessage
: note that this is a command
. It just sends a plaintext message
24 | message
fieldmessage
packets anywaysinput
: requests some input
31 | message
field should be treated as a prompt for the userinput
request
38 | 47 | 48 |
If you're a little confused, here's a diagram of how our apps will operate:
49 | 50 | 51 | 52 |54 |
57 | 58 |
Here is our Packet
class. ne sure to add it to both projects in your solution. The main reason we have this is so we can access the data in a more C# like way. We will be using Newtonsoft's Json.NET library for parsing.
63 | 64 | 65 |
66 | 67 |
Before we go and shove the Packet
into the internet tubes there is a little pre-processing that needs to be done first:
ToJson()
method on the Packet
to get it as a string
. Then after that, encode it (in UTF-8) into a byte arrayThat final byte array that we have is what will be sent over the network. This way whoever receives a Packet
knows how long it is.
80 | 81 |
You might be wondering why we have to do this. Here's the thing, TcpClient.Available
will report how many bytes have been received but not read yet, and only that. There is the possibility that two Packet
s may have been received before our app has had a chance to read from the NetworkStream
. This poses the question of how we will pull out the data into two separate Packet
s.
Since we are using JSON, we could match first level curly brackets. Instead, stuffing the length of the "true message," right before the actual message is a common technique that is used, and thus it's best that we practice that here.
85 |88 | 89 |
I chose to use unsigned an unsigned 16 bit integer because it lets us have JSON strings that are almost 64 KB long; that's plenty of data for text based games. We could use just an 8 bit unsigned integer too but that will only give us 255 bytes max, so our messages we send would have to be quite tiny.
90 | -------------------------------------------------------------------------------- /html/04.d.tcp-games-guess-my-number.html: -------------------------------------------------------------------------------- 1 |Guess My Number is a very simple game. It's played by only one player. The server will think of a number between 1 and 100 (included) and then ask the client to guess it. It will tell the client if their guess was too high or too low. Once the user guesses correctly the game will then tell the server to disconnect the player. Keep in mind the Run()
method is run in a separate thread. While the rest of the server is asynchronous and multithreaded, we're going to run stuff synchronously and single threaded here.
6 | 7 | 8 |
9 | 10 |
Like said before, it needs to implement the IGame
interface. At the top we store a pointer to the TcpGamesServer
that is running this game. This way we can access the server's methods to transmit Packet
s. We simply call our game "Guess My Number," with the Name
property. And the RequiredPlayers
only is one. In our constructor we also initialize a random number generator.
AddPlayer()
will only accept one player for the game. If another one is provided, we ignore them and return false
.
DisconnectClient()
is a notifying method. In case we have one and it's our player, then we need to disconnect them (thus also quitting the Game too).
The Run()
method will not start the game unless we have a player. If we do, we send them a message
Packet
containing the rules. Then we generate our number. In the main game loop we send the user a request for input
. Since ReceivePacket()
might return a null
, we keep checking in a loop until we have one. Our Game has to check for disconnects since we're now handling the client. If is there is a graceful one we mark it. If we get a response to our input
request, we then check it. Based on the input
, we see if they were right or wrong and send them a message
based upon that. At the end of the main game loop we check for our disconnects (graceful or not).
Once the client has disconnected or guessed the right number, we exit the Run()
function. And then our game is over.
Now the last thing that we have to do is write the client code. Remember to add the Packet
to the client project.
6 | 7 |
Don't forget to include the Newtonsoft.Json package in this project too.
9 |12 | 13 | 14 |
15 | 16 |
This code is somewhat similar to the clients that we wrote for the TCP Chat application. Because of the packets, we have added a Dictionary
at the top the store functions and to dispatch messages that we've received. In the constructor we setup the TcpClient
(but don't connect it) and init some other resources. _cleanupNetworkResources()
will close the TcpClient
for us and it's NetworkStream
.
The Connect()
method is a bit more robust than last time. First it will try to create the connection in a try-catch block. If it wasn't successful, then nothing else will work. If successful, we yank out the underlying NetworkStream
and hook up our Packet
dispatchers. Disconnect()
will attempt a graceful disconnect with the server by sending a bye
Packet
.
The Run()
method of this class is also the most important part. If we're connected, _handleIncomingPackets()
will do what the name says. Then after that there is a short nap to save on CPU usage and a check for any type of disconnect. After the main loop, we give it one second for any other incoming packets to be handled. Lastly, we clean up the network resources.
_sendPacket()
will asynchronously send a Packet over to the server. Likewise _handleIncomingPackets()
will check for available messages and then dispatch them to their correct handlers.
Since we have three different types of command
s that can be sent in a Packet
, we've got three handlers:
_handlebye()
will shutdown the client_handleMessage()
will print out the contents of the message
field to the console_handleInput()
will request the user for some input from the command line and then send a response to the server33 | 34 |
_isDisconnected()
is used to check for ungraceful disconnects from the server (like always). And the last bits of the file are for program execution.
I didn't feel like putting this up on my server, so here's it running locally. The order of operations were:
4 | 5 |14 | 15 | 16 | 17 |
18 | 19 |
As always, there are some problems with anything we do and a few things here we can improve on:
20 | 21 |bye
Packet
and then cleanup the network resources on its end before the client can process that Packet
(even though the ACK for that Packet should have been sent to the server before resources are cleaned up). We fixed it by sleeping the calling thread for 100ms. A possible solution might be that the client needs to respond by sending its own bye
Packet
._nextGame
might lock the main server thread when attempting to add players. Say if there was only one client in the lobby, but the game kept on rejecting adding them, we'd be in the loop at lines 84 through 95 forever. Any running games would still run, but nothing new would be able to start or connect.Task
objects returned by_handleNewConnection()
(in the server) or _handleIncomingPackets()
(in the client) is bad. It's very possible that those Lists could grow to immense sizes. It would be better to create a structure that could house Task
s until they've completed and then throw them into the garbage collector.Packet
s as much as they do. In a much better designed application we would have a more complex "Player," object, handle all types of disconnects, and let the server dispatch Packet
s to the Games. The games also notify the server when they are done, placing any current player back into the waiting lobby.ReadAsync
on the NetworkStream
s return a Task
that tells us how many bytes were actually read (instead of how many were requested in the third parameter). Most of the time we will get the requested amount of bytes, but there is a chance that we could get less. We should be checking the requested count versus how many were actually read. You can read his original comment here.31 | 32 |
I think this has been a good example of a multithreaded/async TCP server. I'm a little disappointed in myself that I got lazy and didn't implement Tic-Tac-Toe too, but I'll leave that as an exercise for you to do. I'd love to see it if you get it done (send me an email)! If you have any other suggestions for the review, drop me a line. And if you address any the problems I have listed above don't' hesitate to send me your solution.
33 | -------------------------------------------------------------------------------- /html/06.a.udp-file-transfer.html: -------------------------------------------------------------------------------- 1 |UDP is very different from TCP. We have to work with datagrams instead of streams & connections. It can be very fast (and unreliable), but there are many things that we have to do ourselves. Trying to think of a good (and interesting example), I decided upon building a simple file transfer application (over UDP) would be a nice place to start.
4 | 5 |6 | 7 |
On /r/programming there was an interesting article about a new web protocol that Google is working on called QUIC. The tl;dr is that it's meant to replace TCP usage in some protocols, thus giving some speed improvements. Read the article here.
9 |13 |
16 | 17 |
This program will be separated into two parts. The Sender and the Receiver. The Sender will act as the server (of sorts) that will transfer the files to the Receivers.
20 | 21 |The code is going to cover synchronous, singled threaded usage of the UdpClient
class. Friendly reminder that this is not a tutorial about UDP, but just how to use the APIs in C#. I'd recommend making a Google search or checking YouTube for some other resources that can explain UDP better on a theory level. The code is a lot more complex than I originally anticipated; but just as always, don't get discouraged.
24 | 25 |
Seeing as we're dealing with file transfers, I think using the video Don't Copy That Floppy is the most appropriate test case here.
28 | 29 |30 | 31 |
This protocol is going to be a bit complex. Since UDP doesn't have ACKs built in, we're going to have to add them manually. Not every type of message with have an ACK, just some control ones. The Packet
class (code in next part), contains two fields, PacketType
(control) and Payload
(data). For each type, the data in Payload
will be structured differently.
6 | 7 |
File data will be transferred in Block
s. They have simply two fields, an unsigned 32 bit integer that is it's ID Number
, and a byte array that is the contained Data
. Typically the Data
in the Block
is compressed. The code for this class is in the next part.
12 | 13 |
These are used to tell our app what each datagram is supposed to mean, they are:
16 | 17 |ACK
- An acknowledgement. Mostly for control messages, sent by both sides.BYE
- An "end transfer," message. Can be sent by either the Sender or Receiver at any time.REQF
- A request for a file. Sent by the Receiver to the Sender. Needs to be ACK
'd by the Sender.INFO
- Information regarding a transferable file. Sent by the Sender to the Receiver after the REQF
. Needs to be ACK
'd by the Receiver.REQB
- A request for a Block
of data. Sent by the Receiver to the Sender after the INFO
.SEND
- A response to a Block
request, containing Block data. Sent by the Sender to the Receiver of a corresponding REQB
.Here is a helpful diagram that explains how the exchanges work:
27 | 28 |29 | 30 | 31 | 32 |
33 | 34 |
Here's the data that some exchanges should contain.
37 | 38 |REQF
- Payload
should contain a UTF8 encoded string that is the desired file.
40 |
41 | ACK
must always be sent by the Sender. If the file is present, it should contain the same Payload
that was in the REQF
, if not, then it should send an empty Payload
.INFO
- First 16 bytes of the Payload
must be a MD5 checksum of the original file (uncompressed). The next 4 bytes are a UInt32
of the file size (in bytes). The 4 bytes after that is the maximum Block
size (in bytes), The last 4 bytes are the total number of Block
s that will be transferred.
46 | ACK
must be sent by the Receiver. It should have an empty Payload
.REQB
- Payload
is an UInt32
number that is the Block.Number
that is being requested by the Receiver.SEND
- Payload
is a Block
who's Number is that of one requested in a previous REQB
.BYE
- No Payload
is needed.If you are confused, take a look at the diagram as it has some examples:
56 | 57 |58 | 59 |
This UDP example is split into two projects, but they share some common code. Feel free to just paste these in (I do recommend typing them in, but it can get lengthy), though make sure you read the summaries at the bottom of each.
4 | 5 |6 | 7 |
10 | 11 | 12 |
13 | 14 |
This is a little data container that's used to chop up a larger file into smaller chunks. We have two constructors. One for creating a new one with a set Number
, and the other for reconstructing it from a byte array. The GetBytes()
method is used to well, get the Block
as a byte array.
18 |
21 | 22 |
25 | 26 | 27 |
28 | 29 |
Packet
is what we're bouncing around the inter-networks. It has two only data fields, PacketType
and Payload
. All Packet
s should have one of the types listed at the top under the region "Message Types." Like with Block
, there is also a second constructor to create a Packet
from a byte array, and a GetBytes()
method to get a Packet
as a byte array.
At the bottom there are "specific packets," which are there to make working with Payload
data easier with some message types. You'll notice there isn't one for BYE
as it doesn't need any Payload
.
35 |
38 | 39 |
42 | 43 | 44 |
45 | 46 |
NetworkMessage
is a tiny data structure that used to pair a Packet
with a Sender
. These are only used when reading data we've received.
Here is what will wait for file requests and respond to them. This acts as a server of sorts. Note that in Main()
you can comment out the hard coded arguments and used command line args instead.
6 | 7 | 8 |
9 | 10 |
So there is quite some code here, let's break it down.
11 | 12 |In the constructor for UdpFileSender
, we tell it what directory we want to use for files that are able to be transferred and we create the UdpClient
. We create it to listen for incoming datagrams on a specific port, and only IPv4 connections.
15 | 16 |
Init()
will scan the FilesDirectory
(non-recursively) for any files and mark them as transferable. It also sets Running
to true
so we can start listening for Receivers. Shutdown()
is used to simply shut down the "server."
Run()
is the main meat of UdpFileSender
. At the top we have some variables that are used for the transfer state as well as a mini-helper function to reset the state in case we have to. In the beginning of the loop we see if we have any new Packets that were sent to us. If one was a BYE
message, it means the client has prematurely disconnected and we should reset the transfer state to wait for a new client. The switch statement is used to run through the transfer process. Each state will wait for a certain type of Packet
, do some processing (e.g. creating a response or preparing a file for transfer) then move to the next one in line. I won't go into the gory details as it's pretty easy to read (IMO). At the bottom of the while loop we take a short nap to save on CPU resources. And at the end of the function we send a BYE
message to a client if we have one currently "connected," (in case the Sender operator wanted to shut down in the middle of a transfer).
21 | 22 |
Talking about the Send
method of UdpClient
, we are using the overload that requires you list and endpoint. If you application may connect with multiple clients, I recommend using this method over the other ones. Reminder that UDP is connectionless, therefore we don't have an established connection with any client. Please ignore that Connect
method you might have seen for now...
27 | 28 |
And Close()
will clean up the underlying UdpClient
; 'nuff said.
_checkForNetworkMessages()
will see if we have any new datagrams that have ben received. Since all of our Packets are at least four bytes in length, we want to make sure that many bytes are ready. We create a dummy IPEndPoint
object. Using UdpClient.Receive
, it will grab exactly one datagram that has been received by the network. Unlike TcpClient
, we don't have to mess around with NetworkStream
s. After we have it, we shove its sender and the Packet
into a NetworkMessage
queue for later processing.
_prepareFile()
will do what the name implies and makes a file ready for transfer. It takes in a filepath, computes a checksum of the original bytes, compresses it, and chops it into Block
s. If all went well, it will return true
, and its out
parameters will be populated. If you missed my last tutorial on Compression in C# be sure to read it, find it over here.
That's really all there is to UdpFileSender
. If you glossed over the switch statement in Run()
that handles the state of the file transfer process, I recommend you read it well.
This is what requests a file from a Sender and downloads it to a local machine. Just like with the other program you can twiddle with some of the hard coded arguments and replace them with command line ones in the Main()
function at the bottom.
6 | 7 | 8 |
9 | 10 |
Like with the Sender, in our constructor we create our UdpClient
with a default remote host set (which should be that of the Sender).
13 | 14 |
I mentioned in the last part that there is a Connect
method in UdpClient
(doc link). Please note that this is a dirty filthy lie as UDP is a connectionless protocol. In reality, what it does is set a "default remote host," for you so every time that you call this overload of the Send
method you don't need to specify where you want to go, as it will only go to that one host. This also applies to some of the constructors for UdpClient
(like the one we're using).
19 | 20 |
Shutdown()
in this class will just request a cancel of an existing file transfer, you can see later in the GetFile()
method how this works.
GetFile()
is the most important function of the class and its very similarly structured to UdpFileSender.Run()
. It's what does all of the communication with the Sender. Let's break it down. Up at the top of the function's body there are some variables for the file transfer state and a helper function to reset them if needed. In the beginning of the while
loop we check for any new NetworkMessage
s. First checking if the latest received Packet
is a BYE
, it then passes that onto the switch
statement. Inside of the switch
is what manages the state of the file transfer (REQF
, wait for ACK
, wait for INFO
, ACK
, etc...). Inside of the ReceiverState.Transfering
case
it will empty the Block
request queue, check for any SEND
messages (i.e. Block
data). If the request queue is empty but we don't have all the Block
s, it will refill the request queue with the missing Block.Number
values. But if we have all of the Block
s, then we move onto the state where we say BYE
to the Sender (so it can accept new transfers) and then we reconstruct the Block
s into a byte array, uncompress them, and save the file. At the end of the function we make a check to see if the transfer was ended prematurely by user request or the Sender.
Close()
will clean up the underlying network resources, which is just the UdpClient
.
_checkForNetworkMessages()
is identical to the one seen in Sender. It sees if there are enough bytes for a Packet
message then queues it up for processing.
_saveBlocksToFile()
is where we take the Block
s, reassemble them, uncompress the data, verify it with a checksum and then save it to the disk.
Here's a screenshot of it in action using a tinier version of my test file of choice. Using SFTP took around a 14 seconds to transfer the video, whereas it took about 9 using the application we built. There are many reasons SFTP was slower in this case, but I'll leave that to you to figure out.
4 | 5 |6 | 7 | 8 | 9 |
11 |
14 | 15 |
With every other project that we've made so far, there are some deficiencies and improvements we could make. Let's analyze a few of them:
16 | 17 |OutOfMemoryException
s thrown at me. What we should be doing instead is saving the Block
s to the disk (instead of storing them in memory) as we send and receive them.BYE
message in the middle of a transfer, it would stop the transfer with the first. The first Receiver wouldn't even know what's going on, it would just keep queuing up block requests. The Sender really should ignore any datagrams from other clients when its in the middle of a transfer with one.ACK
, it should wait for a set amount of time and then resend its Packet
in case the original one was dropped. In the real world (and future applications) we need to be adding in our own retries and timeouts for the control Packet
s. (REQF
, INFO
, ACK
, etc...).
21 | Block
requeueing method is pretty bad. It should wait for a set amount of time first before making another request for a block. When I first tested this over the internet, my Receiver created many REQB
s before the Sender could even respond to the first one it got.Keep in mind that this is supposed to be a sample application showcasing UDP (and not a real world one). If you were to add in UDP based file transfer to any application you are building, you would really need to make these changes.
28 | 29 |As always, if you spot any other issues, be sure to send me an email and I'll evaluate them. Anything is appreciated.
30 | -------------------------------------------------------------------------------- /html/07.a.udp-pong.html: -------------------------------------------------------------------------------- 1 |We're going to something a little bit more fun for this example. We're going to write a clone of Pong that we can play over the Internet. There will be a central server that many players can connect to and play one on one matches. Pong really is the "Hello, World!" of video game programming, so I think it would be a good first game to try to network.
4 | 5 |6 | 7 |
Since we're doing video game programming I chose to use MonoGame as our media library. It's cross platform, uses C#, and it's pretty easy to use. I wanted to keep as much of this tutorials in this series in the command line, but I had to break that guideline here. I used the MonoGame.Framework.DesktopGL
package. If you're on an OS other than Linux, you might want to try Windows or MacOS versions.
Note: Audio playback is umm... kind of broken with MonoGame 3.5 (the current stable release at the time of writing this). As far as I know, this only applies to the DesktopGL package. This is really an un-fun problem because I've got projects that use an older version of MonoGame that still plays sounds fine; If you're on a Linux system you might want to try getting a slightly older version if you really want to play sounds.
11 | 12 |I've included a #define
that allows you to toggle audio playback on the Client app at compile time. It's disabled by default.
16 | 17 | 23 | 24 |
25 | 26 |
This tutorial is going to be broken up into two applications, the PongServer
and the PongClient
(make two separate Projects in your Solution). The Server is going to be authoritative in handling collisions (Ball with Paddles, Goals, and top/bottom bounds), notifying the client to play sound effects, and position updates. All the client will need to do connect to the Server and shout it's Paddle's Y position.
In a game instance (referred to as an Arena
in the Server code), there will be only two players maximum, though the server should be able to handle multiple games at the same time. A game instance will not start unless two Clients have connected. If a Client connects and then disconnects, the game instance is considered done, regardless if the match had started or not.
33 | 34 |
Put the below files inside of a folder called Content
in the Client's Project. Make sure that all of these files in the Project are copied to the build directory in compilation (right click on file > Quick Properties
> Copy to Output Directory
). I've also got them all in .zip and .tar.gz format if you'd like.
49 | 50 |
I'd like to thank Jason Francis (@halfspiral) for helping me test this.
53 | -------------------------------------------------------------------------------- /html/07.b.udp-pong-protocol-design.html: -------------------------------------------------------------------------------- 1 |There's a lot more we have to do here than in the UDP File Transfer app. By design UDP is connectionless, but the problem is that we need to have our clients to be able to establish a connection, maintain it, transfer data in between, and then quit when they want. We'll be implementing these things ourselves.
4 | 5 |6 | 7 |
The first four bytes of each packet will denote its PacketType
. Instead of encoding some ASCII bytes like we did with the File Transfer example we'll be using 32 bit unsigned integers (4 bytes). The next eight bytes will be a Timestamp
encoded in a signed 64 bit integer (8 bytes). After that will be the Payload
, it's just an array of bytes. It can be empty or it might have something to it. That all depends on the PacketType
. So at minimum all Packet
s will be 12 bytes long.
12 | 13 |
Here is what each PacketType
is, with a description:
RequestJoin
- Sent by the Client to the Server requesting to join a game.AcceptJoin
- Sent by the Server to the Client as a response to the above. It contains which Paddle
the Client will be.AcceptJoinAck
- Sent by the Client the Server to acknowledge the above.Heartbeat
- Sent by the Client to the Server to notify its still connected.HeartbeatAck
- Sent by the Server to the Client to acknowledge the above.GameStart
- Sent by the Server to the Client to notify the game is going to start.GameStartAck
- Sent by the Client to the Server to acknowledge the above.PaddlePosition
- Sent by the Client to the Server to tell its Paddle
's current position.GameState
- Sent by the Server to the Client to tell it the current state of the game (Paddle
positions, scores, Ball
position, etc.).PlaySoundEffect
- Sent by the Server to the Client to play a sound effect.Bye
- Sent by either the Server or the Client to notify the other it's done with the game.32 | 33 |
This is our version of the TCP Three-Way Handshake. The Client will send a RequestJoin
to the Server. The Server will respond with an AcceptJoin
message; it is the only one that contains data (an unsigned 32 bit integer that denotes the Client's Paddle
). Then the Client will need to respond with the AcceptJoinAck
. Here is a diagram of the handshake:
40 | 41 |
This is pretty simple, the client will send Heartbeat
messages and the server will send back HeartbeatAck
messages in response. The Client and Server will also record the times they got them and use it to determine if a connection has dropped or not. The heartbeat timeout value will be 20 seconds. These are sent after a connection has been established, while waiting for a GameStart
/GameStartAck
, and while the game is being played.
46 | 47 |
The Server will not start the game until two Clients are connected. Once that criteria has been met, it will send out the GameStart
messages. The Clients will both need to respond with a GameStartAck
before the Server will deliver any GameState
messages.
52 | 53 |
A few times per second both the Clients and the Server will send each other info about their current state. The Client only needs to send PaddlePosition
Packet
s; it contains one floating point number that is the Paddle
's Y position. The Server will send a GameState
Packet
to both Clients. It will contain the Paddle
& Ball
positions as well as the scores (see the diagram below for the data layout). Periodically the Server will send a PlaySoundEffect
message. This will tell the Client it should play a sound effect. The name of the sound effect is encoded as a UTF-8 string.
Check the code for Packet.cs
for details, specifically the classes GameStatePacket
, PaddlePositionPacket
, and PlaySoundEffect
. They will show you how the data is laid out.
60 | 61 |
At any time a Client or Server could send a Bye
. This simply tells the other that the connection is over. If a Client sends it to the Server while in the middle of a game, the Server then needs to notify the other client the game is over. When the Server shuts down, it should send a Bye
message to all connected Clients.
As stated before, Pong really is the "Hello, World!" of game development. I'm pretty sure that most of you have done it at some point, but I chose to handle it slightly differently, so I think it's fair to go over what needs to happen.
4 | 5 |6 | 7 |
If the Ball
hits the left side of the screen, it's a score for the right side player. And vice-versa if the Ball
touches the right side. The Ball should then reset to the center of the screen. A "score," noise should also be played.
12 | 13 |
This only applies to the Client application. If a user presses the Esc
key at any time, it will close the application and notify the Server. The Up and Down arrow keys will be used by the user to control the position of the Paddle
.
18 | 19 |
If the Ball
hits the top or the bottom of the screen it should flip the Y component of its velocity. It's speed will also slightly increase by a random amount and a "ballHit," sound should be played.
24 | 25 |
Each Paddle
has three collision areas. One on the top, one on the bottom, and one in the middle (called it's "Front,"). If the Ball
hits any of these its speed will increase slightly and its X direction will be flipped (a sound will also play too). But if the top or the bottom collision areas are hit, it will also flip the Y component of the Ball
's velocity. Below is a diagram of the hitboxes:
30 | 31 | 32 | -------------------------------------------------------------------------------- /html/07.d.udp-pong-common-files.html: -------------------------------------------------------------------------------- 1 |
Add all of these files to both the Client Project and the Server Project. If you want to copy & paste all of Packet.cs
, I don't blame you (it's very long, boring and repetitive), but be sure to pay attention to the Packet
class and it's subclasses that have Payload
data.
6 | 7 |
The proper thing to do would be to create a separate Project in the Solution that contains the common code (e.g. PongFramework
), but what I do instead is put all of the common files in one of the Projects (e.g. PongServer
), and then add those as linked files in the other Project (e.g. PongClient
). That way everything is nice and synced.
12 | 13 |
This is only a static data containment class. Instead of littering our program with hard coded values, it's better to put there here.
16 | 17 |18 | 19 | 20 |
21 | 22 |
This is almost identical to the one in the File Transfer example, but this also adds field where we mark the time when one side has received the Packet
.
27 | 28 | 29 | 30 |
31 | 32 |
This makes one variable (of a generic type) thread safe (via locking). It can only be accessed via the Value
property.
37 | 38 | 39 | 40 |
41 | 42 |
All right, this is the big one. It's quite similar to the one of the File Transfer example, except that is has an extra field denoting the time the Packet
was created. There are many subclasses that have specific implementations of the PacketType
enumeration. Like last time, you use the GetBytes()
method to get the Packet data as byte array that can be sent over the network. The only subclasses that have Payloads are AcceptJoinPacket
, PaddlePositionPacket, GameStatePacket, and PlaySoundEffectPacket. Be sure to pay attention to how those ones store data. Feel free to Ctrl-C, Ctrl-V the rest of the code if you find it too much to type.
47 | 48 | 49 | 50 |
51 | 52 |
This is the game object for the Ball that will be bounced around. LoadContent()
and Draw()
should only be called on the Client app. Where asInitialize()
and ServerSideUpdate()
will be called on the Server.
57 | 58 | 59 | 60 |
61 | 62 |
There are two enumerations at the top of this file. The first determines what side of the board the Paddle
is one (using None will cause an error). The other is to mark what kind of collision has happened with the Paddle
. LoadContent()
, Draw()
, and ClientSideUpdate()
are called only on the Client. That last function is what checks to see if the user has tried to move the Paddle (it also does a bit of bounds checking). Initialize()
and Collides()
only need to be called on the Server portion.
The function Collides()
is what checks if the Ball has collided with the Paddle
. If it has, the function will return true and the out parametertypeOfCollision
will be filled with a value from the PaddleCollision
enumeration.
69 | 70 | 71 | -------------------------------------------------------------------------------- /html/07.f.udp-pong-arena.html: -------------------------------------------------------------------------------- 1 |
This is where the Pong game logic lives on the Server.
4 | 5 |6 | 7 | 8 |
9 | 10 |
At the top of the class we have our member variables. State
needs to be encased in a ThreadSafe
since it will be accessed by multiple threads. The Paddle
objects are contained for us in the PlayerInfo
classes. We store a reference to the running Server instance. We also have our own message queue for Packet
s that have been dispatched to us.
TryAddPlayer()
is called when we get a new Client connecting to the Server. It will only add two players to this Arena
. The function will return true
if the Client was added successfully, in any other case it will return false
.
Start()
will shift the State
of the Arena
to where it can accept new players and will begin running the underlying _arenaThread
which runs the _arenaRun()
method.
_arenaRun()
is the heart of this class. It is another looping function. In the beginning of the loop it will check for new NetworkMessage
s and will make decisions based upon it's current State
. While we are waiting for two players, it will try to handle a connection setup. Once two players have connected it will send them the GameStart
Packet
s. Once the GameStart
Packet
s have been Ack'd by the Clients it will then shift the State
to InGame
, start the _gameTimer
, and begin processing/handling PaddlePosition
& GameState
Packet
s. In case a Bye
message has been sent by one of the clients it will then stop the loop and notify the other Client the match is over. At the end of the loop it will check for player time outs or see if a stop has been requested by the Server. Once the loop has been exited it will queue up a ByePacket
to any connected clients and notify the Server the game is done.
JoinThread()
will give the underlying _arenaThread
1/10 of a second to complete execution.
EnqueMessage()
is called by the PongServer
to tell the Arena
it's gotten something from one of it's Clients.
_sendTo()
sets a Packet
to be sent to a Client
. It will record the time when we wanted to send it.
_timedOut()
is used to check the last time we've received a Packet
from them versus how long a timeout should be (20 seconds in our case)
_handleConnection()
is what instantiates the connection between the Client and the Server (though it's stored in the Arena
code). It will assign a Paddle
to a Client and respond to Heartbeat
messages.
_sendAcceptJoin()
is a small helper method to tell a Client which Paddle
they are.
_notifyGameStart()
will tell a Client a game has started, and will retry every time the timeout has been reached.
_sendGameState()
will tell a Client about the current state of the match. This is the Paddle
positions, scores, and Ball
position.
_playSoundEffect()
will queue up a PlaySoundEffectPacket
to a Client.
_handlePaddleUpdate()
will look at a PaddlePosition
message from a Client and update it's Paddle
's Y position.
The next two methods are for collisions. _checkForBallCollision()
will do all the math to see what the Ball
is hitting something, and take an appropriate action (bounce, score, sound, etc.). _processBallHitWithPaddle()
is used to alter the Ball's
velocity for Ball
-Paddle
collisions.
This is the "fun," part of the application ecosystem that we will send to our users. Where they can control their very own Paddle
that goes up and down. This is also the graphical portion of the app. Before, we were just using the MonoGame/XNA libs for collision detection but here we're using them for their original intention (video games)!
6 | 7 |
Don't forget, if you want sounds working, you'll need to uncomment line number 7. Remember that audio isn't quite working properly with MonoGame 3.5 (DesktopGL version).
9 |12 | 13 |
14 | 15 | 16 |
17 | 18 |
You've probably noticed a lot of similarities with the Arena
and PongServer
code already, but there are some differences.
At the top of the class we have our time measurement members. They are exactly similar in meaning to those of PlayerInfo
, except that the Server/Client relationship is flip-flopped.
In the constructor we setup our game's asset directory, the display settings, initialise the game objects and instantiate the UdpClient
. For those of you unexperienced with MonoGame/XNA, LoadContent()
will load our assets into memory. UnloadContent()
will be used to clean up some other things.
Update()
is another one of those MonoGame/XNA methods. At the top it will check for an Esc
key press. If so, it will quit the Client and notify the Server. After that a timeout check is made. Next we check for any new NetworkMessage
s and do some stuff based on that. First we check for a Bye
. After that we will try to send a JoinRequest
if we don't have a connection. If we're waiting for a game start, we will ping-pong Heartbeat
s with the Server. Once we're in the game we will continue to send Heartbeat
s. We will periodically send our Paddle
's Y position. If we got a GameState
we'll update the rest of the game objects. If a PlaySoundEffect
was sent, we will then try to play that named effect.
Start()
will mark the Client as running and start the underlying _networkThread
(with the _networkRun()
method).
Draw()
is once again a MonoGame/XNA method. Depending upon the current state it will either display a message to the user or draw the game objects.
_drawCentered()
is a utility method to draw a Texture2D
object to the center of the screen. _updateWindowTitleWithScore()
is another "graphical method" that updates the window's title with the current score (hence the name). If you notice, it will also denote which side the Client is on.
_networkRun()
is a looping function that runs in the _networkThread
. It reads new messages from the Server and tries to send ones we've queued up. After the loop is done, if we've set _sendBye
to true
(via the user pressing Esc
) it will send a ByePacket
.
_sendPacket()
will queue up a Packet to be sent and mark the _lastPacketSentTime
variable.
I'm not going to spend my time on the last few private methods. They are all related to Packet
transfer. You can probably guess what they do based upon their names.
And at the end of the class we have our Main()
method which will start the Client and try to connect.
4 | 5 | 6 | 7 |
8 | 9 |
This was the largest tutorial to date. It took me three weeks to write and cleanup (where as it normally takes me one). As always, there are some good things, and some bad. Let's take a look at what we could fix and possible problem areas:
10 | 11 |Arena
s instead of resetting them is a bad idea. But I want to keep things as simple as possible for this tutorial series. I'll leave the ricing to you readers.Arena
code manage the connection with the clients? I don't think it should in retrospect. That should more be a responsibility of the PongServer
. This includes setting up the connection, handling Heartbeat
s, and disconnects. All the Arena
should do is manage the game data and make communication requests.HeartbeatAck
was a good idea since it requires the Server to respond to a Heartbeat
. If the Client sends one, but it drops during transmission, there will be no Ack created. And even if the Heartbeat
reaches the server, what's there to guarantee that the Ack will reach back to the Client? Nothing really.Heartbeat
s send once every second, and it would time out if for 15 seconds no Heartbeat
(or Ack) was recorded. It would usually time out after a few minutes (we were both on crappy connections). This means that 15 Heartbeat
s and their Acks were dropped in a row (not unlikely), which is pretty bad. So in this final version it now sends 5 Heartbeats per second and has a timeout of 20 seconds. This means that it would need to miss 400 consecutive Heartbeat
s to time out, which is a lot less likely.Heartbeat
s & Acks, or just Heartbeat
s only.Bye
should probably have its own Ack. In the Client code we waited one second to make sure the message was sent. This is a little bad. Maybe instead waiting a second or hearing an Ack first would be a better idea. This also better models a TCP connection termination.Packet
order. Time really is meta-data instead of an actual Packet
ordering.There's a bit of a problem with trusting data sent by the Client to the Server. Take a look in the code for PongClient
that has to deal with the Paddle
.
It's very possible (and easy) for a player to lie about their Paddle movement. As of right now, the Server doesn't have any code to verify the Paddle's movements have been under 100 pixels/sec. It might be a fun exercise for you to make a Client that will match the Ball's Y position with that of your Paddle's.
26 | 27 | 28 |In TcpClient
, TcpListener
, and UdpClient
(as well as many other classes) there is an underlying Socket
object. If you're looking for something simple to work with or want to get an application developed quickly, I'd recommend using those objects first. Though if you need more performance or want something that is much closer to the Berkeley/POSIX Socket API, then the Socket
class is what you'll want to use.
For this part of the series, I will present you with some C code (that uses the Berkeley Socket API) and then show you a C# port of that code. The C code is adapted from this Wikibooks article. I will not be showing you any usage of the asynchronous method right now. I want to start things off simple. We will cover async usage of the Socket
class in the next section.
8 | 9 |
The app is pretty simple. The server will open up a socket and wait for incoming connections. When a new client has connected, it will print some info to the terminal window and respond to the client with a short message. Then once it has been sent it will close the connection with the client. All of this will run over TCP.
12 | 13 |I'm not going to do an explanation of what's' going on like I usually do. I think the code is very straightforward; there are comments sprinkled all over that should explain what each line does.
14 | 15 |s
17 |20 | 21 |
Compiling & running the C code for this section is completely optional. But make sure to read through it and try to understand a little bit of what's going on. If you want to compile and run the C code, you'll need to grab yourself a C compiler. I used clang (on Linux) when writing the C code, but it will also compile with something like gcc (on Windows via Cygwin). If you don't know or understand C that well (or the POSIX interface), don't worry about it.
22 | -------------------------------------------------------------------------------- /html/09.b.socket-class-intro-original-c-code.html: -------------------------------------------------------------------------------- 1 |There are only two files that really matter in part client.c
and server.c
, but a Makefile
is provided for your convince and the end of this page.
6 | 7 |
11 | 12 |
16 | 17 |
If you are using a different C compiler (other than clang
), change the value of CC
to be whatever it is (e.g. gcc
).
22 | -------------------------------------------------------------------------------- /html/09.c.socket-class-intro-csharp-port.html: -------------------------------------------------------------------------------- 1 |
In my opinion, the C# API for Berkeley Sockets is much cleaner, thus nicer look and easier to work with. I'd recommend putting the Server and Client code into their own projects, but go ahead and do what you want.
4 | 5 |6 | 7 |
11 | 12 |
4 | 5 | 6 | 7 |
8 | 9 |
There wasn't too much that was covered here, but we got our feet wet with using the raw Socket
class. In the next section we will cover usage of the async methods in Socket
. Get ready for callbacks everywhere!
13 |
16 | 17 |
As I said before, classes like TcpClient
and UdpClient
have and underlying Socket
object (which can actually be accessed via the Client
property in both). "Why would we even want to use Socket
if it's more complicated?" you might ask.
If you chose to use TcpClient
or UdpClient
there is a slight amount of overhead versus working directly with a Socket
. If you truly need a high octane riced app, then go with Socket
. The performance/efficiency benefits mostly come from using the async methods of Socket
, which are all callback based. Yes, yes. TcpClient
and UdpClient
do have async methods too, but the Socket class
has a lot more of them which gives you way more flexibility than the other two do.
And now that we have covered the basics of Socket
, I would like to end this tutorial series here. I feel that I have given you enough example projects and explanations of how to use the networking APIs (and then some) in the System.Net
namespace. From here, I'm fairly confident that anyone who has gone through this course should be able to write their own networked C# appliations and delve into some more advanced topics on their own.
6 | 7 |
If you want to learn more, the best thing to do would be to pick and a project you want to make, and then build it! I'm really partial to video games; I already showed you how to network Pong earlier so you have someplace to start.
10 | 11 |There were two topics that I didn't get to cover that I really wanted to. (Sorry, life got a little busy for me in the past few months).
12 | 13 |async
keyword, it's a hole different beast & paradigm. Here are some good places to start on the MSDN documentation:SslStream
should be something to do.There are an ocean of third party (higher level) communication libraries available for C#. Some of the more popular ones include RakNet, Lidgren, and ZeroMQ. Each has its own purpose and complexity. You might find them a bit more useful and easier than the System.Net
namespace.
If you'd like to study distributed data comm. here is pretty cool article on how to implement the BitTorrent protocol in C#.
27 | 28 |29 | 30 |
If you were following these tutorials as I posted them, I only gave you the code for the projects in gists. Available now is a repo that contains everything (and the project files). You can find it here (GitHub mirror).
33 | 34 |35 | 36 |
My gratitude to Jason Francis (@halfspiral) for helping me test the Pong clone.
39 | 40 |I'd like to make note of the folks over at /r/csharp. They provided some ideas for tutorials, extra insight on things, a code correction or two, and were the majority of my views. I don't think I'd put in as much effort as I did if it wasn't for the community there. Thank you.
41 | 42 | -------------------------------------------------------------------------------- /html/dns.html: -------------------------------------------------------------------------------- 1 |The Dns
class is a static object contained in System.Net
that stands as a tool for domain name services. This is very handy if you want to let your users type in a domain like 16bpp.net
instead of 107.170.178.215
. For those of you who don't know what DNS is or what it does, it's magic that turns letters into numbers.
The Dns class contains a few methods for resolving host names and other things. You can do it both synchronously and asynchronously. The important methods of this class will either return IPAddress
objects the more detailed IPHostEntry
. The latter is nice if you want to get an aliases associated with the queried hostname. Keep in mind that one domain name might have multiple IP addresses associated with it.
8 | 9 | 10 |
11 | 12 |
As I said, there is an asynchronous version of Dns.GethostEntry()
. If you just care about the addresses, there are methods for that too.
IPAddress
is a very small class in the System.Net
namespace. It's job is to just represent, well, an IP address. It supports both IPv4 and IPv6 type addresses.
You can make new IP address either by supplying a byte array to the constructor, or using the IPAddress.Parse()
(or IPAddress.TryParse()
) method to get one from a string. Exceptions will be thrown if the IP address is malformed (either via strings or byte arrays). When using the constructor, if you supply a byte array of exactly four bytes, it will assume you want an IPv4 address.
8 | 9 | 10 |
11 | 12 |
Please also note the static fields of IPAddress
are very important when creating servers, all of them are read only.
Any
- This tells a Socket
to listen for connections on any network devices. Which may be coming in locally or via a networkLoopback
- This tells a Socket
to listen for connections that are only coming in from the local machine (127.0.0.1)Broadcast
- This provides the broadcast IP address to a socketNone
- This tells a Socket
not to listen in for any connectionsAll of the fields above are for IPv4, but IPAddress
has IPv6 versions of most of these static fields.
IPEndPoint
is a data structure that simply contains an IPAddress
, and a port number. It essentially is supposed to represent a resource that you an connect to over the internet.
6 | -------------------------------------------------------------------------------- /html/preface.html: -------------------------------------------------------------------------------- 1 |
While there are resources out there to learn about C#'s networking capabilities, I had some issues with finding the information myself in some nicely organized and well written tutorials. I've don network programming in other languages, but had no idea how to use C#'s APIs. I know that there are other people out there like me, so I thought about writing a set of tutorials about the System.Net
namespace.
This is not a tutorial on networking (explicitly), but how to use C#'s networking API.
6 | 7 |I work primarily with Linux, therefore Mono will be my runtime of choice (don't worry, it should still work with Microsoft's .NET VM). I'll make sure the code provided can be run in just a command line interface, so we won't need to worry about GUI programming at all. I'll try to keep the example simple and short, but I may include some larger and more complex applications. I will also include full source code for each example/tutorial.
8 | 9 |Through this tutorial's sections, you might see messages like the one below, it's a good idea to pay attention to them (like the one below for Linux users).
10 | 11 |12 | 13 |
If you run Ubuntu (or a variant of it like me), make sure you have Mono installed from the project's official repository. It's more up to date than the one in the Canonical repos. Be sure to install the ca-certificates-mono
package. This makes it much easier for you to use SSL with Mono.
18 | 19 |
I'll try to keep the amount of extra dependencies & packages to a minimum. For the basic stuff, we shouldn't need anything more than what's provided to us in System.Net
.
22 | 23 |
Each of the tutorial sections have thier own full source examples. If you want, you can download the entire collection (and project files) over here (GitHub mirror). All of the code examples that I have included fall under the Unlicense (a.k.a. Public Domain), unless otherwise noted. If you end up using my code somewhere, credit for authorship is appreciated.
26 | 27 |28 | 29 |
The main tutorial sequence is numbered off on the left. You don't have to follow it in order, or read all of them, but it's recommended. Everything else that isn't numbered are just one-off, "micro-tutorials."
32 | 33 |I recommend reading all of the text. Do not skimming anything. When it comes to code, try not to just copy n' paste the example. While it may take more time and effort, copying the code by writing it out in your editor is probably the best way to learn and memorize it. It's what I do with most of the tutorials I read online. I suggest you make one solution to contain this tutorial, but one project in the solution for each topic/section.
34 | 35 |Since we're also dealing with communications, I highly advise that you have a computer somewhere else in the world where you can run and test these programs. While running a server & client on your local machine is fine for development, it's not realistic of what happens in the real world. I'd recommend spinning up a VM on a service like Google Cloud Platform, Digital Ocean, or Amazon Web Services.
36 | 37 |If you have any questions, comments, or requests send me an email. If you send me a message like "Can you look at my code. I don't know what's wrong," or any other technical questions, I'm not likely to respond. If you do copy n' paste code from here, and it turns out to be wrong, please do notify me so I can fix it. All of the examples will be hosted on Github's Gist service.
42 | 43 | --------------------------------------------------------------------------------