Holy Smokes!

Thursday, November 6, 2008

"Traditional Sockets" in .NET -- Part 1

The title may be a little misleading, because when I say "traditional sockets", I don't necessarily mean I stick to any sort of convention... A couple of days ago, a co-worker of mine and I were talking; and sockets came up as an alternative to newer possibly more mainstream .NET technologies. I was asked to explain and maybe even provide a sample to pose as a starting point to become acquainted with sockets and network programming.

I did, and I felt that there may be some poor sap willing to tread through this entry and possibly learn something. Though, I'll warn you, there are at least 100 other blog entries that will explain this more properly.

Still here?... OK, read on...

I have come to realize that I can write for ages on the topic, and While I would love to show some of the more advanced features, I'm just going to loose my audience. So I'm going to start off really slow here. If you have some experience on the subject, please provide feedback. I love discussing these things. I'll progressively move into some more interesting parts of the framework I have combined with sockets to make for some really interesting applications.

Is it alright for me to assume that for the most part, readers are at least at an intermediate level with C#? I don't really want to explain standard convention, or syntax. Though I will gladly respond to anyone who has any question, or doesn't understand something.

So let's start this tutorial series, by creating a simple Hello, World! server. One that will accept a connection from a client, print the Hello, World! message and disconnect that client. For starters, let's just do a simple blocking server. Once the client has been disconnected, we will let main() return, and the server application will exit. It might not be practical in the real world, but who cares? lets have some fun! I'll get to the good stuff... but you need a foundation first.

For starters, create a Console Application in Visual Studio. I'm using 2008 Professional, but I believe this code will work for 2003 - 2008. Someone correct me if I'm wrong, I don't have the ambition to go through and run tests for each environment.

So create your Console application and start editing the Program.cs file.

Make sure your importing out of the networking namespace.


using System;
using System.Net;
using System.Net.Sockets;

Now move down to the static void Main(string[] args) method.

Let's talk a moment about what the server is going to have to do to accept a connection.

First, we have to realize that the server is going to have to lay down the law in terms of how it is going to communicate. We can explore your options in more detail down the road, or possibly in a few more tutorial lessons, but for now, lets just say that you will almost always be using something like this. Especially while you are learning.


Socket socketServer = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);

There really isn't a difference between a client and a server socket. At least when we construct it. However you will want to ensure that a client socket is created with the same rules as the server socket. This specific configuration will work over the internet, maintains a constant connection and ensures that data that is transmitted will reach it's destination, and the destination will receive the data in the same order that you sent it. It's very reliable, and very similar to most internet applications that you use, so the performance hit will not likely be something you are aware of. Again, we can get more into this later. Let's move on.

So what is the difference between a client and a server configuration?

A server "accepts" connections, and a client generally "connects" to a server. This is a great terminology, as this is the same terminology you will see using the socket framework.

We will get into how to set up a client later, probably next lesson.

If you were to look at the socketServer members (probably by typing socketServer. and looking through the list of members using intellisense) you will notice an Accept method. What do you want to bet that this is the method that is used to accept connections.

Lets look at our main method now,

static void Main(string[] args)
{
Socket socketServer = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
Socket socketClient = socketServer.Accept();
}

I hope your following along, this is all technically correct, however incomplete. We have a socket and defined how it behaves. We are also attempting to accept a client connection. But we really havent given the socket context.
Think back to almost any network program you have used. Generally when we attempt to connect to a service of any sort with a client, we must specify either a hostname or IP address, as well as a port number.
If you're new to this it's concievable to think that a server running on this machine will be available to any client that can see it, by it's IP address and / or Host name, as long as that host name will resolve into an IP address, and you're probably right. But what if your machine had more than one Network interface card. One facing the internet, and the other facing a LAN. What if you didn't want your server to be accessable by the WAN. No.. lets take this a step further, the two network cards are two separate network interfaces. Each technically participates in a separate network. And this really says nothing in the way of which port the server is serving on.
Did you know that only one server can exist on any one end point? An end point in this context is an address and a port number. Any one Address of this type can have up to 65535 ports open.

Ok enough theory, we can get into the specifics later. And yes, we do have a way of just opening the server on all interfaces at the same time.
Before we can accept connections to a socket, the socket must be bound to an end point. If you attempted to run that code right now, you would get an exception indicating something similar.
The good news is that it is very simple to both define an bind to an end point. You just need to be certain you arent attempting to bind to an end point that has already been bound to.

Try this.


IPEndPoint epLocal = new IPEndPoint(IPAddress.Any, 13001);

This defines an edpoint using a constant that specifies any interface on the machine, and port number 13001. I doubt you have any service using port 13001, but if you do, just change it to something differnet. Just keep it in the higher numbers and don't exceed 65535. (most well known and used port numbers are lower values). We can later talk about that, but if you googled well known services or something similar, you'd get a list of common port numbers.

By the way, you defined an end point, you you havent used it yet. technically, you bind with socketServer.Bind(epLocal);


static void Main(string[] args)
{
IPEndPoint epLocal = new IPEndPoint(IPAddress.Any, 13001);

Socket socketServer = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
socketServer.Bind(epLocal);
socketServer.Listen(4);
Socket socketClient = socketServer.Accept();

socketClient.Close();
socketServer.Close();
}
I also stuck in a Listen(4) in there. If you weren't aware, this allows me to specify how many clients can remain in the connect Queue to be accepted. This is NOT a maximum number of connections, I'm afraid you would have to code something like that yourself. This is useful because it would automatically turn away clients if the server was overloaded with clients attempting to connect. We will get into this later too. Don't worry about it now...

This would work now. You can try it, but you won't see much yet. If you were to run this, a console application will start, with a black screen, and appear to do nothing. You might even get a message from whatever firewall software you are using, asking if you want to block the server or not. I added the Close() statements, just to remind us that when we no longer need these resources, we should be releasing them.

If you were to open a windows console, and type something like telnet 127.0.0.1 13001, you would see your server window close, and you would probably not see anything in the telnet session, because the server hasn't sent any data.

Now even though you cannot see an awful lot happening, there is a lot more happening than meets the eye. The Accept() statement is very powerful, and must be used with consideration. Accept() is considered a blocking statement. When you call it, the application will wait until a client connects. This particular use of sockets is called "Blocking Sockets".

Blocking Sockets are a great place to start, because you can write very simple networked applications. You don't need to worry about a lot going on. Once we bring this tutorial into more real world examples, I'll introduce Asynchronous Sockets. Don't worry about this for now.

So let's complete our server. Why don't we send a Hello, World! to the client.

After the Accept() call, why not add something like this.

byte[] bytSend =
System.Text.ASCIIEncoding.ASCII.GetBytes("Hello, World!");
socketClient.Send(bytSend, 0, bytSend.Length, SocketFlags.None);

If this looks confusing, don't let it. I have simply taken the binary for the string Hello, World! represented as ASCII text, and set it into a byte array. Sockets transmit data in binary form.

The next statement simply says send data out of the bytSend array, start at index 0, and transmit the the full length of the array. The third argument is actually size, and because each byte is 1 byte in size, it naturally translates to number of indexes to transmit. Moving along..
We are not going to get into SocketFlags right now.

your program should look like this now.


static void Main(string[] args)
{
IPEndPoint epLocal = new IPEndPoint(IPAddress.Any, 13005);

Socket socketServer = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
socketServer.Bind(epLocal);
socketServer.Listen(4);
Socket socketClient = socketServer.Accept();

byte[] bytSend =
System.Text.ASCIIEncoding.ASCII.GetBytes("Hello, World!");
socketClient.Send(bytSend, 0, bytSend.Length, SocketFlags.None);

socketClient.Close();
socketServer.Close();
}

Go ahead, give it a try. Execute the code and then connect with the telnet client.

The server terminates, but the client now get's it's Greeting.

Congratulations, You've just written your first server. It's a silly server that does not do anything really useful, but a server none the less. Tomorrow I will try and find some time to elaborate on something more practical.

Next tutorial, I will focus on the following.
  • Creating a client application that connects to this server
  • And we will give the server the ability to wait for additional clients after the first, so it does not terminate after sending the message to the client.
  • I might move into two way communication as well, It really depends on how the client aspect looks on paper. I don't want to overwhelm anyone.
Now, that siad, if anyone needs any more detail on anything I've talked about, or has any requests on... well.. anything that I may know, please ask away.

I apologize for the fact that this is moving slow, I have no idea how on the ball you guys are, I just don't want to leave anyone behind.

Thanks for reading!

3 comments:

  1. Looking at this now, If I'm to actually write code tutorials, I'm going to have to update my template a little to ensure preformatted areas are fully visible. I think I got them all fixed up, but if something runs off the side, you should be able to just copy and paste the block of code into a text editor or your IDE, and it will look fine.

    ReplyDelete
  2. Great tutorial. Looking forward to the next one

    ReplyDelete

Followers