Socket Programming

I l @ ve RuBoard

Sockets are the meat of the low-level network communication programming model. Everything we've discussed so far is great when you use the standard request/response communication architecture. However, at times you'll need to get down to the nitty-gritty network infrastructure details. Members of System.Net.Sockets support a wide range of functionality and granularity. I'll focus first on the TCPClient , TCPListener , and UDPClient classes, which are built on the lower-level System.Net.Sockets.Socket class. Then I'll briefly dig into the Socket class itself and take you on a tour of an implementation of the ping functionality in Visual Basic .NET.

Getting Started with Addressing

The socket-level network classes require a different form of addressing than the one used by the WebClient and WebRequest classes. Don't let this frighten you. The scheme is not particularly daunting; it's just different. Recall from our discussion of the IP and TCP protocols that we defined the source and recipient computers ( hosts ) as endpoints. The .NET Framework continues the use of this terminology. You use an IPEndPoint class with your sockets. But before you can define an endpoint, you must get an IP address for that host. That's what this section is all about.

Creating Simple Addresses

As I've mentioned, the IPAddress class represents an IP address and is needed for creating an instance of an IPEndPoint class. To create an instance of IPAddress , you need the IP address of the target machine. The following example shows how simple it is to create an IPAddress class that you can use to define an endpoint:

 DimaddressAsIPAddress=IPAddress.Parse("207.46.228.80") 

Simple right? Not entirely. What if you don't know the physical IP address? In that case, you need to look at the DNS resolution capabilities of the .NET Framework.

DNS Host Name vs. IP Address

The Domain Name Service (DNS) is the backbone of the current Internet addressing scheme. Generally speaking, DNS is designed to provide human-friendly addresses that are mapped to physical IP addresses. In this situation, a single site ”say, Microsoft's Web site ” essentially has two valid addresses (from the user's point of view). The first is the human-readable http://www.microsoft.com. When a user enters this in a Web browser, the browser must resolve the address to a more machine-friendly IP address ( 10.10.0.123 ). DNS provides this necessary link, much like a large telephone book. A very large telephone book.

We generally know the host name (DNS name) of a machine but not the IP address. This is useful because the physical IP address can change without affecting a user's ability to access a Web site. However, you need to resolve this host name to a physical address before you can use any of the socket classes to access the site, which is where the System.Net.Dns class comes into play.

The System.Net.Dns class is a kind of catchall utility for DNS resolution issues. It provides a set of shared methods that can help you resolve host name and IP address to IPHostEntry objects. With methods such as GetHostByAddress , GetHostByName , GetHostName , and Resolve , the Dns class gives you the ability to perform all sorts of name and IP address resolutions . (See Table 6-3.)

Table 6-3. Synchronous Methods of System.Net.Dns

Method

Description

GetHostByAddress

Retrieves an IPHostEntry instance based on the specified IP address

GetHostByName

Retrieves an IPHostEntry instance based on the specified DNS host name

GetHostName

Retrieves an IPHostEntry instance for the local computer

Resolve

Retrieves an IPHostEntry instance based on the specified IP address or DNS host name

For added flexibility, the Dns class also supports asynchronous methods. You'll generally be most interested in the asynchronous methods when you expect the name/IP resolution operation to take a significant amount of time. Given that these requests are not instantaneous and are subject to transient network conditions (or slowdowns), you're likely to be interested in these methods at some point. The asynchronous methods of the Dns class are described in Table 6-4.

Table 6-4. Asynchronous Methods of System.Net.Dns

Method

Description

BeginGetHostByName

Starts an asynchronous request for resolving a host by name. Returns a reference to an IAsyncResult interface that provides a unique reference for this request. You must provide a callback that is signaled when the request is complete. Your callback must call EndGetHostByName to retrieve the result of this request.

EndGetHostByName

Retrieves an instance of the IPHostEntry class that represents the result of the original BeginGetHostByName request.

BeginResolve

Starts an asynchronous request for resolving a host name or IP address. Returns a reference to an IAsyncResult interface that provides a unique reference for this request. You must provide a callback method that is signaled when the request is complete. Your callback must call EndResolve to retrieve the result of this request.

EndResolve

Retrieves an instance of the IPHostEntry class that represents the result of the original BeginResolve request.

But enough of playing with addresses. Let's start digging into the real meat of our subject.

Using the TcpClient , TcpListener , and UdpClient Classes

TCPClient and TCPListener aren't hugely complicated wrappers. For example, they don't provide any asynchronous methods. They do, however, take care of setting up and managing the IP socket for TCP. UDPClient is designed differently to accommodate the different role that UDP plays in networks. Essentially, the design of UDPClient is geared toward sending and receiving data over IP multicast.

TcpClient and TcpListener

The TcpClient and TcpListener classes are built on top of the Socket class. These classes are simple to use and provide simpler access to the network ”much like WebClient does. Both classes serve a special purpose. The TcpClient class is designed for initiating a connection with another host. The TcpListener class is designed to support other hosts that connect to the local machine (receive incoming requests).

Both the TcpClient and TcpListener classes uses synchronous (blocking) methods. If you need to make asynchronous method calls or access other features not exposed by these classes, you must use the Socket class directly. One way of getting access to a raw Socket is through the TcpListener class, by calling the AcceptSocket method.

Let's get our hands dirty and look at some real code. A typical implementation of the TcpListener functionality might look like the following:

 ImportsSystem ImportsSystem.Net.Sockets ImportsSystem.Text ModuleTcpServerApp SubMain() DimserverAsNewTcpListener(65535) server.Start() 'Keeplistening WhileTrue Console.WriteLine("Waitingforconnection") DimclientAsTcpClient=server.AcceptTcpClient() Console.WriteLine("Clientconnected") DimnsAsNetworkStream=client.GetStream() Dimbuffer(1024)AsByte DimbytesReadAsInteger=ns.Read(buffer,0,buffer.Length) Console.WriteLine("Clientsays:{0}",_ Encoding.ASCII.GetString(buffer,0,bytesRead)) buffer=Encoding.ASCII.GetBytes("Hellofromserver!") ns.Write(buffer,0,buffer.Length) ns.Close() Console.WriteLine("Disconnectingfromclient...") client.Close() EndWhile server.Stop() EndSub EndModule 

This sample is a console application that waits infinitely for client connections and echoes the incoming status and received message, if any. AcceptTcpClient is the key method ”it blocks the server while waiting for clients to connect. The rest of the sample is all reading and writing to various streams.

Note

Be sure to not step on other ports in use, and use your port number correctly. Refer to http://www.iana.org/assignments/port- numbers for an official list of ports.


Here's an example of a TcpClient application that opens a connection to the above server:

 ImportsSystem ImportsSystem.Net.Sockets ImportsSystem.Text ImportsSystem.IO ModuleTcpClientApp SubMain() DimclientAsNewTcpClient("Sarath",65535) DimnsAsNetworkStream=client.GetStream() DimswAsStreamWriter=NewStreamWriter(ns) sw.Write("Hellofromclient!") sw.Flush() Dimbytes(1024)AsByte DimbytesReadAsInteger=ns.Read(bytes,0,bytes.Length) Console.WriteLine("Serversays:{0}",_ Encoding.ASCII.GetString(bytes,0,bytesRead)) EndSub EndModule 

Again, the code here is not rocket science ”just use the correct server name and port number and read/write to Stream -appropriate information.

The above scenario is relatively trivial. However, it demonstrates the potential of the TcpClient and TcpListener classes. You can, for example, convert the server to a Windows service, have multiple threads depending on the number of clients connecting, and so forth.

More Info

For an example of using the TcpClient and TcpListener classes, see Chapter 7.


UdpClient

UDP services are provided through the UdpClient class, which is also built on the Socket class. The most common use of this class is for broadcasting messages to a group of machines ”for example, sending a message to your whole group to take a break. Unlike TCP, UDP is connectionless, which means there's no guarantee if/when the datagram (packets) will arrive . The UDPClient class does make it easier to send and receive data over IP multicast. It does this by providing group management APIs such as JoinMulticastGroup and DropMulticastGroup .

To get a broadcast, a host must listen to a specific IP multicast address. The UdpClient class supports the JoinMulticastGroup method, and once you're done, it is possible to disjoin by calling the DropMulticastGroup method. Alternatively, you can connect to a remote host using the Connect method. After you join a multicast group or connect to a machine, you use one of the Send overloaded methods to send the datagram. For listening, you must rely on the Receive method of UdpClient or directly use the Socket class.

Let's look at some sample code. The following sample sends messages to a particular server over UDP:

 ImportsSystem ImportsSystem.Net.Sockets ImportsSystem.Text PublicClassUDPSender PublicSharedSubMain() DimupdSenderAsNewSystem.Net.Sockets.UdpClient() Try updSender.Connect("Sarath1",8080) DimsendBytesAsByte()=_ Encoding.ASCII.GetBytes("Timetogohome?") updSender.Send(sendBytes,sendBytes.Length) updSender.Close() CatcheAsException Console.WriteLine(e.ToString()) EndTry EndSub EndClass 

Similarly, we can use the Receive method to receive the datagram (packet):

 ImportsSystem ImportsSystem.Net ImportsSystem.Net.Sockets ImportsSystem.Text PublicClassUDPReceiver PublicSharedSubMain() DimupdReceiverAsNewUdpClient() 'readdatagramssentfromanysource DimipSenderAsNewIPEndPoint(IPAddress.Any,8080) Try 'Thiscallblocks DimreceiveBytesAsByte()=updReceiver.Receive(ipSender) DimreturnDataAsString=Encoding.ASCII.GetString(receiveBytes) Console.WriteLine("From{0}{1}",_ ipSender.Address.ToString(),_ returnData.ToString()) CatcheAsException Console.WriteLine(e.ToString()) EndTry EndSub EndClass 

That's the simple overview of using TCP and UDP. Now let's get even more detailed. I've already said several times that most, if not all, network communication in the .NET Framework can be traced all the way down to a socket implementation. Now we'll see what this looks like.

Down to the Wire: Socket-Level Network Programming

The System.Net.Sockets namespace is intended to give programmers who are familiar with sockets programming an interface they can use from managed code. The Socket class provides the foundation for most of the classes in the System.Net namespace.

Deciding where to begin with this subject is more than a little challenging. This material is definitely not for the faint-hearted. Your best bet for gaining a good understanding of packet-level network programming is probably to pick up a book on the subject. Many of the details that are far beyond the scope of this book. However, I'll show you how to do packet-level network communication using a simple example. With additional background, you should be able to implement anything you need.

The Socket Class

The Socket class exposes methods such as Send , SendTo , Receive , and ReceiveFrom (and their asynchronous variants) for sending and receiving data. There are three basic types of send and receive methods: synchronous (blocking), select, and asynchronous. The following example demonstrates a synchronous network operation using the Socket class:

 ImportsSystem ImportsSystem.Net ImportsSystem.Net.Sockets ImportsSystem.Text.Encoding ModuleSocketBlocking SubMain() Try DimserverIPHostInfoAsIPHostEntry=Dns.Resolve("BobsMachine") DimserverIPAddressAsIPAddress=serverIPHostInfo.AddressList(0) DimserverIPEndPointAsNewIPEndPoint(serverIPAddress,8080) DimserverSocketAsNewSocket(AddressFamily.InterNetwork,_ SocketType.Stream,ProtocolType.Tcp) serverSocket.Connect(serverIPEndPoint) DimbufferAsByte()=ASCII.GetBytes("TestingSocketBlocking") DimbytesSendAsInteger=serverSocket.Send(buffer) DimbytesReceivedAsInteger=serverSocket.Receive(buffer) Console.WriteLine("Receivedfromserver= " +_ ASCII.GetString(buffer,0,bytesReceived)) serverSocket.Shutdown(SocketShutdown.Both) serverSocket.Close() CatchseAsSocketException Console.WriteLine("SocketException:{0}",se.ToString()) CatcheAsException Console.WriteLine("Unexpectedexception:{0}",e.ToString()) EndTry EndSub EndModule 

Note

In this example, a listener is expected to be running on the server at the specified port; otherwise , the exception "No connection could be made because the target machine actively refused it" will occur.


Blocking calls are great for simple scenarios. However, if you need multiple reading and writing, you might have to launch multiple threads, and having blocking calls on each of them can increase complexity of your program. A better approach is to use Select , which allows you to manage multiple sockets for sending and receiving from a single thread. What happens is that you give a list of sockets that you test for readability and writability. The following code sample demonstrates how you might test the sockets for readability:

 DimSocket1AsSocket DimSocket2AsSocket 'codethatcreatesSocket1andSocket2. DimcheckReadSocketsAsSocket()={Socket1,Socket2} Socket.Select(checkReadSockets,Nothing,Nothing,2000) 'NowcheckReadcontainsonlythosesocketsthatneedtoberead 'OthershavetimeoutorarenotreadytoRead DimcounterAsInteger Dimbuffer(1024)AsByte Forcounter=0To(checkReadSockets.Length-1)-1 checkReadSockets(counter).Receive(buffer) Console.WriteLine("Socket " +counter.ToString()+_  " hasthemessage" +ASCII.GetString(buffer)) Nextcounter 

Even better than Select is the Asynchronous model. It works in a similar way to asynchronous methods of WebRequest / WebResponse . The main methods of interest here are listed in Table 6-5.

Table 6-5. Asynchronous Methods of the Socket Class

Method

Description

BeginAccept

Begins an asynchronous request to accept an incoming connection request

EndAccept

Completes or terminates an asynchronous request to accept an incoming connection request

BeginConnect

Initiates an asynchronous request to connect to a host

EndConnect

Completes or terminates an asynchronous request to connect to a host

BeginReceive

Initiates an asynchronous receive operation from an already-connected socket

EndReceive

Completes or terminates an asynchronous receive operation from an already-connected socket

BeginSend

Initiates a send operation to a connected socket

EndSend

Completes or terminates a send operation to a connected socket

BeginSentTo

Initiates an asynchronous send to a specific remote host

EndSendTo

Completes or terminates an asynchronous send to a specific remote host

System.Net.Sockets also exposes some other cool classes. NetworkStream gives developers a consistent, stream-based mechanism to read and write data over the network. Its value goes beyond the utility of a Stream object. This is a base type supported throughout our framework classes, so you can do all sorts of cool things with a NetworkStream , such as pass it into an XmlTextReader and read XML data. Or you can use it in conjunction with the encoding classes.

Example: Creating Ping

To see the Socket class in action, I developed a sample that demonstrates how you can implement a custom protocol on top of the Socket class. For this example, I chose to implement a bare-bones ping client. This system has two components . The first is the Internet Control Message Protocol (ICMP) packet that contains the information needed to perform a ping. This class, called IcmpPacket , is designed to do three things. First, it contains all of the information necessary to describe the ping packet. Second, it contains the logic to serialize the contents of the packet into a Byte array. Third, it computes a checksum for the packet; otherwise, the receiver will assume that the packet has become corrupted in transport and the packet will be rejected. The following is my implementation of the IcmpPacket class:

 'InternalClassrepresentingtheICMPPacketsentoverthewire PublicClassIcmpPacket 'Ourpublicmembersrepresentingtheactualdatacontained 'bythepacket PublicTypeAsByte'packettype PublicSubCodeAsByte'subcode PublicCheckSumAsUInt16'binarycomplementchecksum 'ofthestructure PublicIdentifierAsUInt16'packetidentifier PublicSequenceNumberAsUInt16'packetsequencenumber PublicData()AsByte 'InternalMembers/Constants Privatebuffer()AsByte PrivateConstICMP_ECHOAsInteger=8 PrivateConstPingDataAsInteger=32'SizeOfIcmpPacket-8 PublicSubNew() 'Constructthepackettosend Me.Type=ICMP_ECHO Me.SubCode=0 Me.CheckSum=Convert.ToUInt16(0) Me.Identifier=Convert.ToUInt16(45) Me.SequenceNumber=Convert.ToUInt16(0) Me.Data=NewByte(PingData-1){} DimiAsInteger 'InitilizetheDataArray Fori=0ToPingData-1 Me.Data(i)=CByte(Asc("#")) Next EndSub PrivateSubComputeCheckSum() DimiAsInteger DimcksumAsInteger=0 'Addupthevaluesforallofthebytesinthearray Fori=0To39Step2 cksum+=Convert.ToInt16(BitConverter.ToUInt16(buffer,i)) Next 'Addthehighandlowwords cksum=(cksum/(2^16))+(cksumAnd&HFFFF) 'Invertandclearthehighbits cksum=(Notcksum)And&HFFFF Me.CheckSum=Convert.ToUInt16(cksum) EndSub PublicFunctionGetBytes()AsByte() IfbufferIsNothingThenbuffer=NewByte(39){} DimindexAsInteger=0 DimbType()AsByte=NewByte(0){Me.Type} DimbCode()AsByte=NewByte(0){Me.SubCode} DimbCksum()AsByte=NewByte(1){0,0} DimbId()AsByte=BitConverter.GetBytes(Me.Identifier) DimbSeq()AsByte=BitConverter.GetBytes(Me.SequenceNumber) 'Copythedataintothebuffer bType.CopyTo(buffer,0) bCode.CopyTo(buffer,1) bCksum.CopyTo(buffer,2) bId.CopyTo(buffer,4) bSeq.CopyTo(buffer,6) Me.Data.CopyTo(buffer,8) 'ComputetheChecksum Me.ComputeCheckSum() 'Updatethebytearraywiththechecksum bCksum=BitConverter.GetBytes(Me.CheckSum) bCksum.CopyTo(buffer,2) Returnbuffer EndFunction EndClass 

Now that we have a packet defined, we need to send it. The following code demonstrates how to send the packet over the network. Notice how we first define our IPEndPoint classes and then worry about all of the other socket setup issues.

 ImportsSystem.Net ImportsSystem.Net.Sockets 'ThePingClass PublicNotInheritableClassPing 'DeclaresomeConstantVariables PrivateConstSOCKET_ERRORAsInteger=-1 'Thismethodtakesthe "hostname" ofamachine 'andthenping'sthathost. PublicSharedFunctionPingHost(ByValhostNameAsString)AsBoolean DimdestEndPointAsEndPoint DimsrcEndPointAsEndPoint 'GetthesourceanddestinationIPendpoints Try destEndPoint=GetEndPoint(hostName) srcEndPoint=GetEndPoint(Dns.GetHostName()) Catch 'ReturnFalseifanyexceptionoccurs. ReturnFalse EndTry 'InitilizeanewICMPSocket DimpingSocketAsNewSocket(AddressFamily.InterNetwork,_ SocketType.Raw,_ ProtocolType.Icmp) 'Setthesocket'sTimeoutvalues pingSocket.SetSocketOption(SocketOptionLevel.Socket,_ SocketOptionName.SendTimeout,_ 1000) pingSocket.SetSocketOption(SocketOptionLevel.Socket,_ SocketOptionName.ReceiveTimeout,_ 1000) Try 'sendthepacketoverthesocket DimnBytesAsInteger=0 DimpacketAsNewIcmpPacket() If(nBytes=pingSocket.SendTo(packet.GetBytes,_ packet.GetBytes.Length,_ 0,_ destEndPoint))=SOCKET_ERRORThen ReturnFalse EndIf DimReceiveBuffer()AsByte=NewByte(255){} nBytes=pingSocket.ReceiveFrom(ReceiveBuffer,_ 256,_ 0,_ srcEndPoint) If(nBytes=SOCKET_ERROR)Or(nBytes=0)Then ReturnFalse EndIf Catch ReturnFalse Finally pingSocket.Close() EndTry ReturnTrue EndFunction PrivateSharedFunctionGetEndPoint(ByValhostNameAsString)_ AsIPEndPoint 'Getthehostentryofthespecifiedhost DimhEntryAsIPHostEntry=Dns.GetHostByName(hostName) 'CreateanIPEndPointforthedefaultaddressfortheHostEntry DimePointAsEndPoint=NewIPEndPoint(hEntry.AddressList(0),0) ReturnePoint EndFunction EndClass 
I l @ ve RuBoard


Designing Enterprise Applications with Microsoft Visual Basic .NET
Designing Enterprise Applications with Microsoft Visual Basic .NET (Pro-Developer)
ISBN: 073561721X
EAN: 2147483647
Year: 2002
Pages: 103

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net