The basics of TCP/IP

Oddly enough, the standard methods of PC communication were devised long before the original IBM PC was even thought of. Even odder, the problem the communication system was designed to address wasn’t online shopping. Most, if not all PCs communicate with each other and the Internet using the basic Internet Protocol (IP), with various other protocols bolted onto that. The most common protocol is the Transmission Control Protocol (TCP), and the combination referred to as TCP/IP.
Initially, the whole IP communication system was dreamt up to solve a military problem: if Dr Strangelove became reality – that is, if someone really did press the nuclear button – how would a post-nuclear America communicate? The key point in this scary scenario was that the communications would be unreliable. If Chicago, say, no longer existed, then how would you replace the communication routes that used to go there? The solution was to use ‘packet switching’. Small chunks of data (packets) were routed dynamically through available telephone lines. The resulting network (ARPAnet) was secure against any one line going down. This concept proved remarkably successful, even though the main problem wasn’t an atom bomb but a rather more dangerous workman with the proverbial JCB.
It’s good to talk
Having a communication protocol suite is one thing; using it is another. The now standard interface to TCP/IP is called the ‘sockets’ interface, developed at Berkley University in the mid-1980s. Last month we had a quick look at a simple socket program using .NET. The .NET interface is nicer to work with than the original Berkley sockets, which is really a C interface into TCP/IP. This month, we’ll go a little more slowly through what’s involved in getting two computers to talk over a network. In any non-trivial computer network, you’ll find the data is transferred between ‘layers’. Each layer transfers data between its adjacent layers.
At the lowest level, you have the Network Access Layer. This is responsible for putting data packets onto the network – Ethernet, for example – and for receiving packets. The next level up is the Internet Layer. This is where the Internet Protocol (IP) lives. This layer is responsible for the assembly and disassembly of packets into suitable sizes for the Network Access Layer and for putting addressing information into the packets. Above this layer is the Host-to-Host Transport Layer. This is where the Transmission Control Protocol (TCP) is. It’s also where another useful protocol lives, the Unreliable Datagram Protocol (UDP).
For all practical purposes, TCP and UDP are the only protocols you’ll need. They both have their uses, though TCP is more widely used. The difference between them is that TCP is a streaming protocol, while UDP is an unsequenced datagram protocol. With TCP, all packets are guaranteed to be delivered in sequence, while in UDP they aren’t.
So why use UDP if you’re likely to get your messages jumbled up? UDP is mostly used when there isn’t a sequence or stream, or when the order isn’t important. For example, a general single packet broadcast to the network saying “I’m here!” might be done via UDP.
Sockets are similar to a data terminal point or an end-point. A better term might indeed be end-point since that’s what USB calls the equivalent. You can create a socket and then ‘listen’ on the socket for incoming messages or connect to an existing socket. Of course, to do this requires an IP address for the socket. Your PC normally has just one address, and if you’re on a network, this will be the address used by other PCs on the network to communicate with you.
Sometimes a PC may have more than one network card and multiple network addresses, but this is uncommon for home PCs. There’s one special address that’s always available, as well as the PC’s network address, and this is the loopback address. This is always synonymous with the local network address. It’s useful for testing and for cases when you may not know your own network address, or if your address changes, as with a dynamically allocated address. The loopback address is 127.0.0.1 in dotted notation.
In addition to a network address, a socket must be qualified by a port number. A port is owned by a specific application on a PC. That’s how several different applications can communicate over the network even though there’s only one IP address. Some ports are well known and are defined historically. For example, the FTP program uses port 21.
Listening out
Sockets come in two flavours: blocking and non-blocking. These would be better termed waiting and non-waiting since a blocking socket waits until it receives a suitable event. Using a blocking socket is in some ways easier as it makes the coding simpler. There are some disadvantages to using this method, as we’ll see later on.
Last month we looked at a fairly basic client-server socket application. This time, we’ll use an even simpler version provided by the .NET class library. This uses the TcpListener and TcpClient classes which are ‘wrappers’ around the more fundamental Socket class. There’s also another useful class, NetworkStream, which makes reading and writing from a socket look like a more normal, and more civilised, file stream. We’ll look at the server first. This listens for connection requests on the special loopback address and uses port number 10000, which as far as I know isn’t used by anything important:
Dim localAddr As IPAddress
Dim server As TcpListener
localAddr = IPAddress.Parse(“127.0.0.1”)
server = New TcpListener(localAddr, 10000)
server.Start()
It’s useful to be able to see what’s going on so I’ve included a text output box on the server program’s main form. The textbox is passed as an argument, t, to the listen routine and it’s updated like this:
t.Text += “started” + vbCrLf Application.DoEvents()
You have to call the DoEvents procedure to get the textbox to update, otherwise you won’t see anything since the listen routine doesn’t return. Even worse, once you enter the blocking socket call via the AcceptTcpClient method, no events will be processed. The listen routine then sits in a loop, waiting for a call:
While True
client = server.AcceptTcpClient()
stream = client.GetStream()
b = New Byte(256) {}
i = stream.Read(b, 0, b.Length)
The program will block on the call to AcceptTcpClient. That is, it will wait until a client sends a connection request to the server. This wait is rather different from the normal wait of a Visual Basic program. Usually, a Visual Basic program is waiting on the user to press a key. If you move the main window, it will be refreshed. Just try moving the window of the server – you won’t be able to even try dragging it while the program is blocking, let alone get any diagnostic text displayed. We’ll look at how to deal with this properly next month, but for now we’ll just have to live with it.
Final connection
Once the client has made a connection request and the server has accepted it (AcceptTcpClient), we can get a data stream from the client object and read any data from the stream into a byte buffer. The Read method takes the destination buffer as the first argument, followed by an offset into the buffer as the second and the number of bytes to attempt to read as the third. The number of bytes actually read is returned.
Now we need to convert the byte array into a string, add an indication that we received it and send it back again using the stream’s Write method:
While (i <> 0)
s = System.Text.Encoding.ASCII.GetString(b, 0, i)
s = “OK: “ + s
b = System.Text.Encoding.ASCII.GetBytes(s) stream.Write(b, 0, b.Length)
i = stream.Read(b, 0, b.Length)
End While
In times past, a byte was the same as a character – both were eight bits in size and you didn’t need to jump through hoops to convert between characters and bytes. However, with Unicode, characters are now 16 bits wide to allow for languages and scripts other than the original ANSII codes. Also, strings are often no longer terminated with a special null character because memory is managed by the .NET Common Runtime.
The last thing to note is that I’ve enclosed the whole of the code in a Try clause. The effect of the Try clause is to capture any errors that occur while communicating with the client. For example, if the client unexpectedly exits, the server Try clause should capture this event.
When it comes to defining a protocol, evolution wins over design.
There are two ways to define a protocol. The first is to define it theoretically and wait for someone to build it. The second is to just do it and deal with the problems as they come along. In the past, the Open Systems Interconnect (OSI) was a good example of the first approach. OSI defined seven layers, ranging from the physical layer at the bottom to the application layer at the top. The problem was that no one bothered to specify a reference application with code for the seven layers. I remember having to deal with this monster in the past and I could never get anyone to tell me what the presentation layer – the one below the application layer – actually did or was supposed to do. The second way is the Internet way: build it, kick the tyres and if it doesn’t immediately fall over, try it out in the real world, then look at what works and what doesn’t and repeat the process. This is an evolutionary approach to software – the survival of the software fittest, if you like. It works very well, but only up to a point. Protocols which are actually used will thrive, like TCP/IP, and those that don’t (remember gopher?) drop by the wayside. The problem is that important things like security get left out. It’s much harder to reverse engineer security into a protocol.
There’s a third way, though. Take the USB specification process. This doesn’t allow a proposal or change to the specification until someone has built a reference implementation. This has the advantage of a centralised design authority but keeps the design rooted in reality. It’s not perfect though: USB plug design is just bizarre.
Back to the OSI model. Why seven layers? You may as well ask why there are seven colours in the spectrum. Well, there aren’t. Isaac Newton just thought that there should be seven. However, Newton did have the odd good idea which survived. Unlike the defunct seven-layer OSI model. Hurrah!
Handling the client
The TcpClient class provides an easy way of connecting to a server.
The client side of this is a little easier than the server side. First, we create a TcpClient object and one for the data stream, NetworkStream, then convert the data to be sent from a string to a byte array:
Dim client As TcpClientNow we simply write the byte array to the stream and wait for the response:
Dim stream As NetworkStream
client = New TcpClient(“127.0.0.1”, 10000)
stream = client.GetStream
b = New Byte(63) {}
msg = System.Text.Encoding.ASCII.GetBytes(message)
stream.Write(msg, 0, msg.Length)Once we’ve got the response from the server, we close the client connection and reverse the byte to string conversion, returning the text as a string to the caller:
r = stream.Read(b, 0, b.Length)
client.Close()That’s all there is to it, though there are some other useful methods such as SendBufferSize. This gets or sets the size of the underlying network buffer.
Send = System.Text.Encoding.ASCII.G etString(b, 0, r)
Normally, the default value of 8192 is fine, but if you’re sending a lot of data, you may want to alter this. However, the physical network connection imposes a buffer size which may negate any change, so increasing the TCP level buffer size may not have the effect you’re expecting.
For small amounts of data, the TcpClient class waits a while to try to accumulate enough data to send in one packet. You can remove this delay by setting the NoDelay property to True.


