.NET Socket

The lowest level of network interface is the socket. If you are familiar with standard Berkley sockets, most of these interfaces and APIs should be familiar. The .Net socket class (hereafter referred to as just socket) is a thin layer around the socket interface. The socket gives the user maximum control over the interface. Although it is possible to create many different kinds of sockets, the most prevalent are the sockets that deal with the Defense Advanced Research Projects Agency (DARPA) Internet addresses; hence, they are called Internet sockets.

Internet sockets come in two types: Datagram sockets and Stream sockets. From this point forward, Datagram sockets will be referred to as Universal Datagram Protocol (UDP) sockets, and Stream sockets will be referred to as Transmission Control Protocol (TCP) sockets.

You can create a third type of socket, called a “raw” socket. A “raw” socket can only be used under special circumstances to debug or profile a network connection. Generally, this type of socket requires a special permission to create (usually Administrator), and its functionality can only be exploited from Windows 2000 or NT. From here, this chapter will briefly cover one type of application using a “raw” socket and discuss at length two specific socket types: UDP sockets and TCP sockets.

UDP Socket

A UDP Socket is also known as a “connectionless” Socket because unlike the “connection oriented” TCP Socket, it does not have to maintain an open connection for communication to occur. Every packet or block of data that is sent or received using a UDP Socket is given header information regarding its destination or source, so no connection is maintained. Each packet is completely autonomous. You can send a packet of information using a UDP Socket to “Server A” and “Server B” without having to worry about maintaining a connection to either machine. Why would you want to use any other kind of Socket?

UDP Sockets also have the distinction of being unreliable. Because there is no connection maintained, you can send two packets of data. After the first packet is sent, something could happen causing the second packet to be lost. The receiving end will never know that the second packet is lost because no notification indicated that it was sent. In addition, it is entirely possible and permissible for the second packet to arrive before the first. The UDP protocol does not specify that any time-dependent order must be maintained. One more drawback of the UDP Socket is its size. If you need to send large blocks of data, it is best to use TCP. Certainly for Windows, any packet that is larger than 8192 bytes will certainly cause trouble. In general, the Berkley socket implementation for UDP has similar size limitations. On the positive side, if a packet has been received with no errors, you can rely on the integrity of the data within that packet.

Given these restrictions, why would you choose UDP? In some applications, the reliability of the packet delivery is second to the overhead associated with maintaining a connection. Often, such applications build protocols on top of UDP to detect packet loss. One possible scheme would be to require that an acknowledgement packet be sent for each packet received. The sender waits for a certain period, and if this “ACK” packet is not received within the timeout period, the packet is resent.

UDP Server

Given that brief overview of the characteristics of a UDP Socket, it's time to look at an application using UDP Sockets to communicate. The UDP client/server application beginning with Listing 12.2 starts a server “listening” for information from a client. When the message is received from the client, the server prints the message on the Console and then listens for another message. The server exits if the message quits. The code for the server (dubbed the “listener”) starts with Listing 12.2. The source associated with this listing (Listings 12.212.8) is in UdpP2PUdpP2PSocketUdpListenersocketlistener.cs.

Listing 12.2. UDP Socket Server
using System;
using System.Net;
using System.Text;
using System.Net.Sockets;

class Listener
{
        public static void Main()
        {
        Console.WriteLine("Ready to process requests...");
        ProcessRequests();
    }

This is not surprising so far—just the required namespace declarations and the Main entry point. The next sections discuss the ProcessRequests method shown in Listing 12.2.

Creating a Socket

Listing 12.3 illustrates the beginning of the ProcessRequests() method. It is a continuation of the code listing for SocketListener.cs that was begun in Listing 12.2.

Listing 12.3. Creating a UDP Socket
public static void ProcessRequests()
{
    Socket listener = new Socket(AddressFamily.InterNetwork,
                                 SocketType.Dgram,
                                 ProtocolType.Udp);

The Socket is created. Notice that because there are three arguments to the constructor of the Socket (and each has more than one value), it is certainly possible to create more than two or three types of Sockets. For the first argument, Table 12.1 (from the documentation AddressFamily Enumeration ms-help://MS.VSCC/MS.MSDNVS/cpref/html/frlrfSystemNetSocketsAddressFamilyClassTopic.htm) lists the possibilities.

Table 12.1. AddressFamily Enumeration Values
Member Name Description
AppleTalk AppleTalk address
Atm Native ATM services address
Banyan Banyan address
Ccitt Addresses for CCITT protocols, such as X.25
Chaos Address for MIT CHAOS protocols
Cluster Address for Microsoft cluster products
DataKit Address for Datakit protocols
DataLink Direct data-link interface address
DecNet DECnet address
Ecma European Computer Manufacturers Association (ECMA) address
FireFox FireFox address
HyperChannel NSC Hyperchannel address
Ieee12844 IEEE 1284.4 workgroup address
ImpLink ARPANET IMP address
InterNetwork Address for IP version 4
InterNetworkV6 Address for IP version 6
Ipx IPX or SPX address
Irda IrDA address
Iso Address for ISO protocols
Lat LAT address
Max MAX address
NetBios NetBios address
NetworkDesigners Address for Network Designers OSI gateway-enabled protocols
NS Address for Xerox NS protocols
Osi Address for ISO protocols
Pup Address for PUP protocols
Sna IBM SNA address
Unix Unix local to host address
Unknown Unknown address family
Unspecified Unspecified address family
VoiceView VoiceView address

This chapter will only cover the InterNetwork type. This is the address family that is used for Internet Protocol (IP) and is the most prevalent type of address. It is possible that InterNetworkV6 will become increasingly important as the world runs out of IP addresses and moves on to the next-generation Internet. You should realize that explicitly specifying InterNetwork in this sample limits this code to just IPv4. A much better approach would be to get the address that you want to listen on and then get the address family from the address. This approach is used in the TCP Socket samples.

The next argument (the SocketType) has six possible enumeration values. This chapter will cover primarily the Dgram type and the Stream type, although the Raw type is covered briefly. These three types of Sockets make up the vast majority of Socket types.

The last argument (the ProtocolType) has 15 possible values. The protocol type Udp goes with the SocketType of Dgram. Similarly, the protocol type Tcp goes with the SocketType of Stream. This chapter will show an application that makes use of the protocol type of IP and SocketType.Raw in the NetSniff sample. This sample is discussed in the upcoming section, “IOControl.”

Note

Of the permutations and combinations available (36 AddressFamily, 6 SocketType, and 15 ProtocolType translate into 3,240 possibilities), this chapter will focus on two, with minor treatment of a third type of Socket. I am not sure what other combinations of these arguments do and have not been able to test them. If you have interest in any of these, probe the RFC associated with the particular address family or protocol. Reading about the Berkley Socket implementation in a good Socket programming book (Unix Network Programming, W. Richard Stevens, Volumes 1 and 2, Prentice Hall, Inc., 1990) would be a good start.


Creating an IPEndPoint and Binding to it

The server needs to be made aware of the address that it will be serving. Listing 12.4 is a continuation from Listing 12.3. Listing 12.4 illustrates the construction of an endpoint that encapsulates an address and port number pair. (Technically, a third member is the type of address, but that is virtually fixed.)

Listing 12.4. UDP Socket Server
    IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 5001);
listener.Bind(localEP);

Listing 12.4 shows your first introduction to the IPEndPoint class. The IPEndPoint class contains the host IP address and port information needed by an application to connect to a service on a host.

This class encapsulates the struct sockaddr_in. This encapsulation is helpful. Regardless of the time I spent working with sockets, I always got mixed up with struct in_addr, struct sockaddr_in, and the sin_port, sin_family, and sin_addr members. In addition, macros “helped” the user get to the right members of the struct in_addr, appropriately named s_addr, s_host, s_net, and so on. Confused? In addition, it was necessary to remember the host-to-network and network-to-host conversion utilities (htonl, htons, ntohl, and ntohs) so that the port number and address were properly converted to network-byte-order. I always had to look at a printed version of each of these structures before I got it right.

With the IPEndPoint class, you simply need to remember that it contains an address family, a host address, and a port number. The address family is the same address family that is passed to the constructor for the Socket class. For most cases, this will be AddressFamily.InterNetwork. In fact, this is a read-only property of the IPEndPoint class, so you don't have the option to set it to any other value. The host address is encapsulated with an IPAddress class. Typically, the user will have a string formatted as dotted-decimal (dotted-quad), such as 192.45.67.10. To convert it to an IPAddress class, you call the static IPAddress.Parse (address), which returns an instance of the IPAddress class appropriate to the string or throws an exception indicating that the address was improperly formatted.

If you really must deal with network-byte-order, then this is the class where you will find the .NET equivalent for the series of functions htonl, htons, ntohl, and ntohs. These are static methods called (more descriptively) HostToNetworkOrder and NetworkToHostOrder.

In this example, the address is not obtained by any of the methods mentioned previously; the address is specifically given as IPAddress.Any. This is essentially a network address of 0.0.0.0, indicating that a client can connect to this server at any address. My computer has only a loopback adapter and the address that is associated with the NIC in the computer. By specifying IPAddress.Any, a client can connect to this server from localhost (which is essentially the loopback adapter address) or from the server's machine address. If this machine supported several addresses (had several NIC cards), then IPAddress.Any would allow a client to connect from any one of the interface addresses that it supported. Other special addresses are IPAddress.Broadcast, IPAddress.Loopback, and IPAddress.None.

The final argument to build an IPEndPoint is the port number. Ports from 1–5,000 are reserved (1–1,023 are reserved, and 1,024–5,000 are reserved for the system). Static methods to IPEndPoint specify a minimum and maximum port number that can be assigned to a given IPEndPoint (properties Min and Max respectively). If the port number is assigned dynamically in your application, then you might want to limit the range of ports that can be assigned in this manner.

After an instance of the IPEndPoint is constructed, it can be passed to Bind. The Socket's class method of Bind is functionally equivalent to the traditional bind done with Berkley Sockets. It tells the system “This is my address, and any messages received for this address are to be given to me.”

Listening for Incoming Messages and Conncections

Continuing the code listing, you are now to the point that you can announce to the world your intentions to listen on this Socket. Listing 12.5 continues the code from Listing 12.4.

Listing 12.5. Start the UDP Socket Server Listening
bool continueProcessing = true;
while(continueProcessing)
{
    // negative timeout implies blocking operation
    // basically block until data is available and then read it.
    listener.Poll(-1, SelectMode.SelectRead);

You enter a loop. The only exit point for the loop is if the server receives the string “quit.”

The first function that is called is listener.Poll(). The first argument passed to this function is the timeout value. If no data is ready by the specified timeout (in microseconds), then the function will return false. Specifying –1 as the first argument to Poll implies (as the comment indicates) that a timeout does not exist. The second argument indicates that you are interested in read events. Table 12.2 indicates the possible values for SelectMode and the return values.

Table 12.2. SelectMode Enumeration Values
Mode Return Value
SelectRead true if Listen has been called and a connection is pending, Accept will succeed

-or-

true if data is available for reading

-or-

true if connection has been closed, reset, or terminated;

otherwise, returns false.
SelectWrite true if processing a non-blocking Connect, and connection has succeeded

-or-

true if data can be sent;

otherwise, returns false.
SelectError true if processing a non-blocking Connect, and connection has failed

-or-

true if OutOfBandInline is not set and out-of-band data is available;

otherwise, returns false.

Receiving Data from a Client

Continuing from Listing 12.5, Listing 12.6 shows how the property Available is filled in after the Poll returns true.

Listing 12.6. UDP Socket Server
// receive as much data as is available
// to read from the client socket
int available = listener.Available;
byte[] buffer = new byte[available];

After the Poll successfully completes, the number of bytes read from the network and available to be read is retrieved from the Available property. (You might argue that it is better to just shut down the server here because the only possible false return is an error.)

Caution

If the number of bytes expected is more than 8192, you might expect trouble with a UDP arrangement. In one failure mode, the Available property returns 8192. The buffer is allocated for 8192 and then in the ReceiveFrom method, an exception is thrown indicating that the buffer is too small. If the number of bytes returned from Available is ignored and a “large enough” buffer is constructed, then ReceiveFrom succeeds.


Continuing the code listing in Listing 12.7, you can see how the server reads data from a client.

Listing 12.7. UDP Socket Server Reading Data From a Client
IPEndPoint tempRemoteEP = new IPEndPoint(IPAddress.Any, 5002);
EndPoint trEP = (EndPoint)tempRemoteEP;

int bytesRead = listener.ReceiveFrom(buffer, ref trEP);

The server constructs an address from which to receive data. You have already seen the IPEndPoint class. Other than the fact that this endpoint uses a different port, the construction is the same.

Note

The ReceiveFrom method requires an EndPoint reference. EndPoint is an abstract base class for IPEndPoint so that it is easy to convert. I cannot understand why ReceiveFrom should be passed by reference. A plausible explanation would be that passing an IPEndPoint or EndPoint by value is unacceptably slow, and passing by reference speeds up the overall throughput.


Listing 12.8 concludes the listing of this code and continues where Listing 12.7 left off.

Listing 12.8. UDP Socket Server Decoding the Message from a Client
            string message = Encoding.ASCII.GetString(buffer, 0, bytesRead);
            if(message == "quit")
            {
                continueProcessing = false;
                break;
            }
            else
            {
                Console.WriteLine("The message received was " + message);
            }
        }
    }
    listener.Close();
  }
}

Now you have the data in a buffer. Because you know that this data is text, you convert it to an appropriate format using the Encoding class. This is the Encoding class, not the Encoder class. The client and server both have to agree on the format of the data. If the client assumes ASCII and the server assumes Unicode, a crash would be imminent. The formats that the Encoding class supports are ASCII, Unicode, UTF7, and UTF8.

After the message is received and encoded into the proper form, all that is left to do on the server end is to echo the received string and print the message to the Console.

UDP Client

Listing 12.9 starts the listing for the client code that will form the peer to the UDP server in the previous section. The full source for the client shown in Listings 12.9 and 12.10 is available at UdpP2PUdpP2PSocketUdpSendersocketsender.cs.

Listing 12.9. UDP Socket Client
using System;
using System.Net;
using System.Text;
using System.Net.Sockets;

class Sender
{

    public static void Main(string[] args)
    {
        string address = "localhost";
        if(args.Length == 1)
            address = args[0];

        bool continueSending = true;
        while(continueSending)
        {
            Console.WriteLine("Enter the message to send. Type 'quit' to
exit.");
            string message = Console.ReadLine();
            SendMessage(message, address);
            if(message == "quit")
            {
                SendMessage(message, address);
                continueSending = false;
            }
        }
    }

This is the main driver for the client program. A user would start the program as follows:

udpsender your-host

If the host name is not entered, it is assumed that the server is running on localhost.

As you can see from the code, the program accepts input from the user and sends that input to the server. When the word “quit” is entered, then the program sends the string to the server and exits successfully.

Listing 12.10 continues the listing begun in Listing 12.9. It completes the listing for the client portion of the application.

Listing 12.10. Sending a Message from a UDP Socket Client
    public static void SendMessage(string message, string server)
    {
        try
        {
                        Socket client = new Socket(AddressFamily.InterNetwork,
                                                 SocketType.Dgram,
                                                 ProtocolType.Udp);
            IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 5002);
            client.Bind(localEP);
            byte[] buffer = Encoding.ASCII.GetBytes(message);
            IPAddress address = Dns.Resolve(server).AddressList[0];
            IPEndPoint remoteEP = new IPEndPoint(address, 5001);
            client.SendTo(buffer, 0, buffer.Length, SocketFlags.None, remoteEP);
            client.Close();
        }
        catch(Exception ex)
        {
            Console.WriteLine(ex.ToString());
        }
    }
}

Compare the code shown in Listing 12.10 with the server code that is shown in Listings 12.312.7. Many similarities are worth noting. A Socket is created and bound to a local port, and the message is converted to a string. Next, the server name is Resolved using the Dns class.

The Resolve method is a static method in the Dns class. This method takes a string, which can be a dotted-quad format (such as 192.168.1.2) or a name (such as www.microsoft.com). The result of this operation is an instance of IPHostEntry. IPHostEntry is a wrapper around the struct hostent structure that is returned by Win32 APIs gethostbyname or WSAAsyncGetHostByName. This is another class where the encapsulation is welcome. This class contains a host name, an array of aliases, and an array of addresses. The code shown in Listing 12.10 assumes that the first address in the list is the address with which to communicate, as is evidenced by the following line:

IPAddress address = Dns.Resolve(server).AddressList[0];

It would be better to loop through each address on the address list and either just handle the first address that you can without error or handle all addresses. In the code for TCP Sockets, the first address in the list that can be handled without error is the address that is used, rather than strictly the first address, as is done here.

In addition to the Resolve method, static methods are available for getting the host name of the computer, getting host information for a specific computer, getting host information for a specific address, and turning a long value representing the address into a dotted-quad string.

After the IPEndPoint is created, the client sends the data to the server and closes. The client then reads more input from the Console until quit is entered.

Figure 12.1 shows a typical client server arrangement.

Figure 12.1. UDP client/server session on the same machine.


TCP Socket

TCP allows you to build a connection-oriented application. The code difference between a TCP Socket client/server application and the UDP client/server application presented in the previous section is minimal. However, the two applications are quite different in the way they function. Using UDP is inherently unreliable because a persistent connection is not maintained. It is impossible to know with certainty that a UDP message has been received on the other end. Hence, UDP is called a connectionless protocol. TCP, on the other hand, maintains a constant connection for all traffic between the client and the server. The sender of a message on a TCP connection can be assured that the message sent was received. UDP is good for communication of small lightweight packets. TCP has more overhead, but it can successfully and reliably transfer large blocks of information. The following sections illustrate how to construct a simple TCP client/server application.

TCP Server

Listing 12.11 introduces the code for the server. The full source for this sample shown in Listings 12.1112.12 can be obtained from TcpP2PTcpP2PSocketTcpListenerTcpListener.cs.

Listing 12.11. TCP Socket Server
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;

class Listener
{
    public static void Main()
    {
        Console.WriteLine("Ready to process requests...");
        ProcessRequests();
    }

    public static Socket GetListenerSocket(string host, int port)
    {
        Socket s = null;
        IPHostEntry iphe = Dns.Resolve(host);
        foreach(IPAddress ipa in iphe.AddressList)
        {
            IPEndPoint lep = new IPEndPoint(ipa, port);
            Socket ts = new Socket(lep.AddressFamily,
                                   SocketType.Stream,
                                   ProtocolType.Tcp);
            try
            {
                ts.Bind(lep);
                ts.Listen(2);
                s = ts;
                break;
            }
            catch (ArgumentNullException ae)
            {
                Console.WriteLine("ArgumentNullException: " + ae.ToString());
            }
            catch (SocketException se)
            {
                Console.WriteLine("SocketException: " + se.ToString());
            }
            catch (Exception e)
            {
                Console.WriteLine("Connection failed: " + e.ToString());
            }
        }
        return s;
    }
    public static void ProcessRequests()
    {
        Socket server = GetListenerSocket(Dns.GetHostName(), 5000);
        Socket s = server.Accept();

The server is setting up to receive data. A TCP Socket is created by specifying SocketType.Stream and ProtocolType.Tcp for the arguments to the Socket constructor. An IPEndPoint is created, which designates the address(es) in which this server is interested. Unlike the UDP server, however, the TCP server calls Socket.Listen.

Two points must be emphasized on the code snippet of Listing 12.11. The first point is that, unlike the UDP server code, this listing is correctly looping through the available addresses in the list of addresses returned from Dns.Resolve, to determine which addresses can be handled without error. Doing this ensures that in the near future when IPv6 is more prevalent, your code will not break. It would be entirely possible for the list of addresses returned from Dns.Resolve to contain IPv4 and IPv6 addresses. If you simply went with the first address that was an IPv6 address, and your code did not support IPv6 addresses, your code would break because it would not move on to an address that was supported. The second point is that the Socket is created using the AddressFamily from the resolved address rather than explicitly specifying the enumerated type of AddressFamily.InterNetwork. Again, this is a defensive measure in your code that will not lock your code into a particular AddressFamily.

Listen is a thin wrapper around the traditional Socket listen function. Like listen(), Listen takes a single argument that is documented as “Maximum length of the queue of pending connections.” This argument specifies how many connection requests can be queued by the system while it waits for the server to execute the Accept call. For example, if the backlog is 1, one client application can connect to the server and exchange data. While this is going on, another client application that tries to connect will think that it has actually connected to the server, but its first attempt to transmit data will block until the server calls Accept. If more clients try to connect, then each client above the queue limit will receive a SocketException indicating that “No connection could be made because the target machine actively refused it.” There does not seem to be a limit imposed on this argument, but traditionally, listen quietly scales the argument to be within the range of 1 to 5. The listen function doesn't fail. Listen does the same thing.

Note that the last line of Listing 12.11 shows a single call to Accept. By calling Accept, the server blocks until a client connection is received. When a client connection is received and accepted, a new Socket is constructed that is a connection to its peer. Again, if a client that is within the backlog limit imposed by Listen connects, it is connected. If the server terminates, the client receives a SocketException such as the following: “An existing connection was forcibly closed by the remote host.”

Continuing from Listing 12.11, Listing 12.12 illustrates the processing that occurs after the Accept call returns.

Listing 12.12. TCP Socket Server Receiving and Decoding a Client Message
byte[] responseData = new byte[128];
bool continueProcessing = true;

while(continueProcessing)
{
    try
    {
        int bytesRead = s.Receive(responseData);
        // string message = Encoding.ASCII.GetString(responseData, 0,
bytesRead);
        string message = Encoding.Unicode.GetString(responseData, 0, bytesRead);
        if(message == "quit")
        {
            continueProcessing = false;
            break;
        }
        else
        {
            Console.WriteLine("The message received was " + message);
        }
    }
    catch(Exception ex)
    {
        Console.WriteLine(ex.ToString());
    }
}
s.Close();
server.Close();

The rest of the code is much the same as the UDP server. With a TCP server, the data is received by a call to Receive rather than ReceiveFrom. Like ReceiveFrom, Receive blocks until the client has sent “all of the data.” What “all of the data” means is dependent on the buffering scheme in place, but in this case, it means that a line (delimited by a CR/LF) has been received from the client.

The next section discusses the code for the TCP client that is associated with this server.

TCP Client

This listing for the client code begins with Listing 12.13. The full source (Listings 12.1312.14) for this code can be found at TcpP2PTcpP2PSocketTcpSenderTcpSender.cs.

Listing 12.13. TCP Socket Client
public static void Main(string[] args)
{
    string address = "localhost";
    if(args.Length == 1)
        address = args[0];

    SendMessage(address);
}

public static Socket GetSocket(string host, int port)
{
    Socket s = null;
    IPHostEntry iphe = Dns.Resolve(host);
    // Loop through all addresses, not just
    // the first address.
    foreach(IPAddress ipa in iphe.AddressList)
    {
        IPEndPoint ipe = new IPEndPoint(ipa, port);

        // Use the AddressFamily that
        // is part of the endpoint.
        // AddressFamily.InterNetwork limits you to
        // just IPv4
        Socket ts = new Socket(ipe.AddressFamily,
                               SocketType.Stream,
                               ProtocolType.Tcp);
        try
        {
            // Return the first socket that
            // we can connect to.
            ts.Connect(ipe);
            s = ts;
            break;
        }
        catch (ArgumentNullException ae)
        {
            Console.WriteLine("ArgumentNullException: " + ae.ToString());
        }
        catch (SocketException se)
        {
            Console.WriteLine("SocketException: " + se.ToString());
        }
        catch (Exception e)
        {
            Console.WriteLine("Connection failed: " + e.ToString());
        }
    }
    return s;
}
public static void SendMessage(string server)
{
    Socket s = GetSocket(server, 5000);
    if(s == null)
        return;

Like the TCP server code, this code does not assume anything about the connection. The first address to which you can connect is returned. The AddressFamily is based on the AddressFamily that is associated with the address rather than a hard-coded AddressFamily.InterNetwork. The first address that successfully connects to the specified server and port is used to create the Socket that is returned from GetSocket. You cannot specify the first address in the address list and hard-code the AddressFamily in the server. This code shows you how to prevent these same mistakes in the client.

Because this is a connection-oriented link, the client calls Connect, which specifies to which server endpoint to connect. The endpoint that is an argument to Connect is actually an EndPoint. Unlike the EndPoint required for the ReceiveFrom call in the UDP case, the EndPoint required by Connect is passed by value so that it can be implicitly converted.

Note

The documentation indicates that a Bind is required before Connect can be called. Traditionally, the bind is optional for the client. In fact, if a Bind is called for the client, the call will fail when the client and server are running on the same machine. You can then leave the call to Bind out.


Continuing from Listing 12.13, Listing 12.14 illustrates the main loop for the client.

Listing 12.14. TCP Socket Client Sending a Message to the Server
        bool continueSending = true;
        while(continueSending)
        {
            Console.WriteLine("Enter the message to send. Type 'quit' to exit.");
            string message = Console.ReadLine();
            // byte[] buffer = Encoding.ASCII.GetBytes(message);
            byte[] buffer = Encoding.Unicode.GetBytes(message);
            s.Send(buffer);
            if(message == "quit")
            {
                continueSending = false;
            }
        }
        s.Close();
    }
}

The rest of the client code is virtually identical to the UDP client code. The exception is that with TCP, the connection remains for the entire session; a TCP client calls Send to transmit data to the server rather than SendTo, which requires an EndPoint with every call.

Figure 12.2 shows a typical client/server arrangement. It has been set up to be functionally the same as the UDP example. Except for the name changes, the output should look the same as the UDP example shown previously.

Figure 12.2. TCP client/server session on the same machine.


Socket Options

At this level, the interface again shows how thin it is. There is almost a one-to-one correspondence between the Socket options available on an unmanaged Socket and those available on the .NET managed Socket. The methods to get and set Socket options are GetSocketOption and SetSocketOption respectively. Compare these member functions with the WinSock getsockopt and setsockopt respectively.

int getsockopt(SOCKET s, int level, int optname, char FAR *optval, int FAR *optlen );
int setsockopt(SOCKET s, int level, int optname, const char FAR *optval, int optlen );

To get Socket option values, a SocketOptionLevel and a SocketOptionName must be supplied. The SocketOptionLevel is a simple enumeration that can take on the values listed in Table 12.3. The rough equivalent WinSock option names have been modified.

Table 12.3. SocketOptionLevel Enumeration Values
Member Name Description WinSock Equivalent
IP Socket options apply to IP Sockets. IPPROTO_IP
Socket Socket options apply to the Socket. SOL_SOCKET
Tcp Socket options apply to TCP Sockets. IPPROTO_TCP
Udp Socket options apply to UDP Sockets. IPPROTO_UDP

Similarly, SocketOptionName is an enumeration of values listed in Table 12.4.

Table 12.4. SocketOptionName Enumeration Values
Member Name Description WinSock Equivalent
AcceptConnection Accept a connection. SO_ACCEPTCONN
AddMembership Add an IP group membership. IP_ADD_MEMBERSHIP
AddSourceMembership Join a source group.  
BlockSource Block data from a source.  
Broadcast Permit sending broadcast messages on the Socket. SO_BROADCAST
BsdUrgent Use urgent data as defined in RFC 1222. This option can be set only once. After it is set, it cannot be turned off. TCP_BSDURGENT
ChecksumCoverage Set or get UDP checksum coverage.  
Debug Record debugging information. SO_DEBUG
DontFragment Refrain from fragmenting IP datagrams. IP_DONTFRAGMENT
DontLinger Close Socket gracefully without lingering. SO_DONTLINGER
DontRoute Do not route; send directly to interface addresses. SO_DONTROUTE
DropMembership Drop an IP group membership. IP_DROP_MEMBERSHIP
DropSourceMembership Drop a source group.  
Error Get error status and clear. SO_ERROR
ExclusiveAddressUse Bind a Socket for exclusive access. SO_EXCLUSIVEADDRUSE
Expedited Use expedited data as defined in RFC 1222. This option can be set only once, and after it is set, it cannot be turned off.  
HeaderIncluded Indicate that the application is providing the IP header for outgoing datagrams.  
IPOptions Specify IP options to be inserted into outgoing datagrams. IP_OPTIONS
IpTimeToLive Set the IP header time-to-live field. IP_TTL
KeepAlive Send keep-alives. SO_KEEPALIVE
Linger Linger on close if unsent data is present. SO_LINGER
MaxConnections Set the maximum queue length that can be specified by Listen.  
MulticastInterface Set the interface for outgoing multicast packets. IP_MULTICAST_IF
MulticastLoopback Allow IP multicast loopback. IP_MULTICAST_LOOP
MulticastTimeToLive Enable IP multicast time to live. IP_MULTICAST_TTL
NoChecksum Send UDP datagrams with checksum set to zero.  
NoDelay Disable the Nagle algorithm for send coalescing. TCP_NODELAY
OutOfBandInline Receive out-of-band data in the normal data stream. SO_OOBINLINE
PacketInformation Return information about received packets.  
ReceiveBuffer Send low water mark. SO_RCVBUF
ReceiveLowWater Receive low water mark. SO_RCVLOWAT
ReceiveTimeout Receive time out. SO_RCVTIMEO
ReuseAddress Allow the Socket to be bound to an address that is already in use. SO_REUSEADDR
SendBuffer Specify the total per-Socket buffer space reserved for sends. This is unrelated to the maximum message size or the size of a TCP window. SO_SNDBUF
SendLowWater Specify the total per-Socket buffer space reserved for receives. This is unrelated to the maximum message size or the size of a TCP window. SO_SNDLOWAT
SendTimeout Send timeout. SO_SNDTIMEO
Type Get Socket type. SO_TYPE
TypeOfService Change the IP header type of service field.  
UnblockSource Unblock a previously blocked source.  
UseLoopback Bypass hardware when possible. SO_USELOOPBACK

To see the default options, I wrote a program to display some of the options for a TCP, UDP, and raw Socket. A partial listing of the code for this sample is shown in Listing 12.15. The full source can be obtained from SocketOptionsSocketOptions.cs.

Listing 12.15. Socket Options Listing
using System;
using System.Net.Sockets;

namespace SocketOptions
{
    /// <summary>
    /// Summary description for Class1.
    /// </summary>
    class SocketOptionsTest
    {
        static void ListRawDefaultOptions(Socket s)
        {
            Object option;
            . . .
            Console.WriteLine("Raw Type {0} ",
                ((int)option == 1) ? "SOCK_STREAM" :
                ((int)option == 2) ? "SOCK_DGRAM" :
                ((int)option == 3) ? "SOCK_RAW" :
                ((int)option == 4) ? "SOCK_RDM" :
                ((int)option == 5) ? "SOCK_SEQPACKET" : "Unknown type");
            . . .
        }
        static void ListUDPDefaultOptions(Socket s)
        {
            Object option;
            . . .
            Console.WriteLine("UDP DontFragment {0} ", option);
            option = s.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Error);
            Console.WriteLine("UDP Error {0} ", option);
            option = s.GetSocketOption(SocketOptionLevel.IP,
                                       SocketOptionName.MulticastInterface);
            Console.WriteLine("UDP MulticastInterface {0} ", option);
            . . .
        }
        static void ListTCPDefaultOptions(Socket s)
        {
            Object option;
            . . .
            option = s.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive);
            Console.WriteLine("TCP KeepAlive {0} ", option);
            option = s.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger);
            Console.WriteLine("TCP Linger {0}  {1} ",
                                     ((LingerOption)option).Enabled,
                                     ((LingerOption)option).LingerTime);
            option = s.GetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay);
            Console.WriteLine("TCP NoDelay {0} ", option);
            . . .
            option = s.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName
.ReceiveBuffer);
            Console.WriteLine("TCP ReceiveBuffer {0} ", option);
            option = s.GetSocketOption(SocketOptionLevel.Socket,
                                               SocketOptionName.ReceiveTimeout);
            Console.WriteLine("TCP ReceiveTimeout {0} ", option);
            option = s.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName
.ReuseAddress);
            Console.WriteLine("TCP ReuseAddress {0} ", option);
            option = s.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer);
            Console.WriteLine("TCP SendBuffer {0} ", option);
            option = s.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName
.SendTimeout);
            Console.WriteLine("TCP SendTimeout {0} ", option);
            . . .
        }
        static void Main(string[] args)
        {
            Console.WriteLine("TCP Socket");
            Socket s = new Socket(AddressFamily.InterNetwork,
                                         SocketType.Stream, ProtocolType.Tcp);
            ListTCPDefaultOptions(s);
            s.Close();
            Console.WriteLine("UDP Socket");
            s = new Socket(AddressFamily.InterNetwork,
                                  SocketType.Dgram, ProtocolType.Udp);
            ListUDPDefaultOptions(s);
            s.Close();
            s = new Socket(AddressFamily.InterNetwork,
                                  SocketType.Raw, ProtocolType.Raw);
            ListRawDefaultOptions(s);
            s.Close();
        }
    }
}

The output of this program is shown in Figure 12.3.

Figure 12.3. Default values for various Socket options.


Many of these options are infrequently used. However, some are important, and the default values might not be sufficient. For example, setting SocketOptionName.NoDelay (TCP_NODELAY) to true disables the Nagel algorithm. The Nagel algorithm tries to stuff as many characters into a packet as possible by delaying the transmission of data until enough data is queued or until a sufficient time interval has passed from the system's receipt of the last character from the application before sending the packet. If data of a known size is being transferred back and forth in a peer-to-peer arrangement, and the minimal delay imposed by the Nagel algorithm causes the application to appear less “snappy,” then you can disable the Nagel algorithm.

Some other options that might be useful are SocketOptionName, ReceiveBuffer (SO_RCVBUF), and SocketOptionName.SendBuffer (SO_SNDBUF). The send and receive buffer size might be too small or too large depending on the application. If the application tends to send large chunks of data at a time, the data needs to be split into chunks that fit into the buffer size specified. If the buffer size is too small, then there might be (as a percentage of the total packet size) substantial overhead to send multiple packets. If the application uses only small chunks of data at a time, then a lot of wasted space would exist in each packet sent.

The SocketOptionName.ReuseAddress (SO_REUSEADDR) might need to be used if the applications require repeated connections and disconnections. When a Socket is closed, it might not go completely away immediately; when an application tries to bind (socket.Bind()) to the port, the user might receive an “Address already in use” exception. Figure 12.4 shows this exception. SocketOptionName.ReuseAddress prevents that error by reusing the address even if it is being used.

Figure 12.4. Reuse address exception.


Each of the Socket options has existed for quite some time, and new Socket options are being developed. It is good that the programmer has access to the low-level functionality of the Socket. Microsoft has made it easier to do network programming by providing reasonable defaults as well as providing access to the low-level details.

Using IOControl on a Socket

Another low-level operation that can be performed on Sockets is IOControl. For those who are familiar with programming traditional sockets, this is not new. All of the options cannot be explained in this book, but if you are interested, consult the documentation available for WSAIoctl or ioctlsocket.

One particularly interesting application of IOControl is to debug network traffic. A raw Socket is created and the IOControl method is used to enable SIO_RCVALL. Listing 12.16 illustrates the core functionality of the NetSniff sample.

Listing 12.16. IOControl in the NetSniff Sample
listenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.IP);
IPEndPoint localEP = new IPEndPoint( Dns.GetHostByName(Dns.GetHostName()).AddressList[0], 80);
this.listenerSocket.Bind(localEP);
// SIO_RCVALL
int code = unchecked((int)(0x80000000 | 0x18000000 | 1));
byte[] inBuf = new byte[4];
inBuf[0] = 1;
byte[] outBuf = new byte[4];
listenerSocket.IOControl(code, inBuf, outBuf);

This allows sniffing of bytes going to and from a certain port. Figure 12.5 shows the application after a short session at msdn.microsoft.com.

Figure 12.5. Network sniffer.


Full source for this application is in the NetSniff directory.

Asynchronous Socket Operation

A program often can't wait around for the results of a network operation. For example, perhaps a user wanted to download a large file. If the user were unable to perform any other function until the download completed, he would be unhappy. This would be a poor user interface design. The user at least wants the appearance that more than one operation is occurring at a time. In situations like this, the .NET Framework—and particularly the networking classes—provide asynchronous methods to perform certain operations.

Building an application that supports asynchronous operations is not a trivial task. Many programmers build asynchronous or multithread applications because they feel that it would be more efficient to do it that way. Speed or throughput should not be the only consideration when deciding to use the asynchronous methods. The application might not run faster because it is now asynchronous. In addition, the code takes some effort to get right and might be harder to maintain and debug.

Listing 12.17 shows a simple asynchronous client/server application. Strictly speaking, the server is asynchronous. The client sends some data to the server and the server echoes it back to the client (asynchronously). This code is rather lengthy, so it will be dissected into sections. Listing 12.17 introduces the server code with some standard namespace declarations and the definition of a class that will be used to hold state information. The full source for Listings 12.1712.24 can be found at AsyncP2PAsyncSrvAsyncSrv.cs. A listing of the client code is not included here because it primarily mimics the standard synchronous network calls that have been illustrated earlier in this chapter. The full source for the client can be found at AsyncP2PAsyncClntAsyncClnt.cs.

Listing 12.17. Asynchronous TCP Server
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

public class AsyncData
{
    public Socket socket;
    public byte[] RecBytes = new byte[256];
}

This is the preliminary setup for the application. The namespaces that you will be using are declared, and a simple class is defined to hold the asynchronous data that is sent and received. As you will see in Listings 12.1812.23, one of the arguments to the asynchronous network methods is an object that will be passed to the callback as state information. This is not the only way to pass state information. If the callback (delegate) is a non-static member of a class, it will callback on the instance of the class that was used to create the delegate. In other words, the class that implements the delegate function can hold state information. The following application does not use this fact. Other samples are included that hold state in the instance of the class that holds the callback.

Listing 12.18 continues the listing begun in Listing 12.17.

Listing 12.18. Asynchronous TCP Server Synchronization Events
public class AsyncSrv
{
    private static ManualResetEvent AcceptEvent = new ManualResetEvent(false);
    private static ManualResetEvent ReceiveEvent = new ManualResetEvent(false);
    private static ManualResetEvent SendEvent = new ManualResetEvent(false);

    private static AsyncData ad = new AsyncData();

Every asynchronous operation needs to make sure that access to the data is “serialized” and controlled. Events are created to make sure that access to the Socket is synchronized. Synchronization would include ensuring that the data is completely sent out before waiting for data to come back; likewise, it would include making sure that all of the data is read before more data is sent out. That is the purpose of the ManualResetEvent's, AcceptEvent, ReceiveEvent, and SendEvent.

Note

Synchronization and threading issues are discussed in more detail in Chapter 11, “Threading.”


The single instance of AsyncData is created here. It is only passed for use by the receiving portion of the application.

Listing 12.19 continues the code from Listing 12.18. It shows the creation of the Socket and entering into the main processing loop.

Listing 12.19. Asynchronous TCP Server Socket Creation and BeginAccept
public static Socket GetListenerSocket(string host, int port)
{
    Socket s = null;
    IPHostEntry iphe = Dns.Resolve(host);
    foreach(IPAddress ipa in iphe.AddressList)
    {
        IPEndPoint lep = new IPEndPoint(ipa, port);
        Socket ts = new Socket(lep.AddressFamily,
                               SocketType.Stream,
                               ProtocolType.Tcp);
        try
        {
            ts.Bind(lep);
            ts.Listen(2);
            s = ts;
            break;
        }
        catch (ArgumentNullException ae)
        {
            Console.WriteLine("ArgumentNullException: " + ae.ToString());
        }
        catch (SocketException se)
        {
            Console.WriteLine("SocketException: " + se.ToString());
        }
        catch (Exception e)
        {
            Console.WriteLine("Connection failed: " + e.ToString());
        }
    }
    return s;
}
public static void Main()
{
    Socket srv = GetListenerSocket(Dns.GetHostName(), 5000);
    if(srv == null)
        return;
    Console.WriteLine("Now listening on port 5000
");

    while (true)
    {
        AcceptEvent.Reset();
        ReceiveEvent.Reset();
        SendEvent.Reset();
        Console.WriteLine("calling BeginAccept");
        srv.BeginAccept(new AsyncCallback(AcceptCallback), srv);
        AcceptEvent.WaitOne();
    }
}

This code is merely an asynchronous sample of a TCP connection. Again, this code makes no assumptions about the address to which it is listening. All of this code is in the GetListenerSocket helper function. Creation of Socket, Bind, and Listen are identical to the calls needed to set up a synchronous TCP server; however, it is here that the application takes a different turn.

First, each of the ManualReset events is manually reset. Reset means the same as the Win32 ResetEvent in the unmanaged world. Resetting an event means the event is put in an unsignaled state. Any call to wait on that event blocks until the event is in a signaled state (a ManualReset event is put in a signaled state with a call to Set).

Next, the server calls BeginAccept with two arguments specifying an AsyncCallback delegate and the state data to be passed to the callback when the Accept occurs. The AsyncCallback delegate is constructed with a single argument that specifies the method to call, and then the callback is invoked. In this case, when the Accept occurs, the static method AcceptCallback is called. When BeginAccept is called, the call returns immediately with an object IAsyncResult. In general, with any of the asynchronous methods, the IAsyncResult object should be checked to ensure that the call did not complete synchronously. In this case, a call to IAsyncResult.CompletedSynchronously indicates how the call completed or if it completed at all.

The AsyncCallback delegate is constructed in such a way that the single argument to the construction (AcceptCallback) is called when the Accept occurs. Delegates are handled in more detail in Chapter 14, “Delegates and Events.”

Finally, the thread blocks on the AcceptEvent, waiting for a client Accept to occur with the WaitOne method. The WaitOne method is completely analogous to the Win32 WaitForSingleObject function in unmanaged code. What about a comparable function to WaitForMultipleObjects? A WaitAll is available that waits for an array of WaitHandles to become signaled or a timeout to occur. WaitAny waits for anyone of the array of WaitHandles to become signaled or a timeout to occur.

Listing 12.20 continues the code from Listing 12.19. This listing shows the first steps that the server performs after the callback is called, indicating that an Accept has occurred.

Listing 12.20. Asynchronous TCP Server Accepting a Connection from a Client
private static void AcceptCallback(IAsyncResult result)
{
    Socket temp = (Socket) result.AsyncState;
    Socket a = temp.EndAccept(result);

When this function is entered, an Accept has occurred. The first operation is to retrieve the state information passed as an argument to BeginAccept, which in this case is the Socket that the server is using for the Accept. After the Socket is retrieved from the AsyncState property, this Socket is then used to retrieve the results of the Accept operation, which is always another Socket that is connected to the client.

Listing 12.21 is a continuation of Listing 12.20. This listing shows the server printing information about the client that has just connected and initiation of the receipt of data from the client.

Listing 12.21. Asynchronous TCP Server Starting a Receive Operation
Console.WriteLine("EndAccept() Socket.EndPoint: " +
              a.RemoteEndPoint.ToString());
ad.socket = a;

IAsyncResult recres = a.BeginReceive(ad.RecBytes,
                                  0,
                                  256,
                                  0,
                                  new AsyncCallback(ReceiveCallback),
                                  ad);

ReceiveEvent.WaitOne();

When a server accepts a client connection, the Socket that is returned as a result of the Accept call is a Socket that will be used to communicate with the client. Because this Socket is connected with a client, it will have a RemoteEndPoint property that will contain information about the endpoint associated with the client that has just connected. The client's endpoint information is printed out in Listing 12.21. Next, the server needs to initiate receiving data from the client.

In this application, a single AsyncData instance is used exclusively for Receive. The instance of this class is static; therefore, only one instance of this class will exist. The Socket that is returned from the Accept call is assigned to the Socket member of this class, as you can see in Listing 12.21. Next, the read is started with a call to BeginReceive.

The arguments to BeginReceive are similar to Receive. Only the last two arguments are different. The first of these (the fifth argument) is a delegate for a callback to occur when data is available. The second is the state information that you want to pass to the callback. These two arguments are functionally the same as the arguments passed to BeginAccept.

As is the case with BeginAccept, the thread blocks on WaitOne while waiting for the Receive to finish.

Listing 12.22 continues where Listing 12.21 left off. Remember: This is still in the AcceptCallback routine.

Listing 12.22. Asynchronous TCP Server Sending an Asynchronous Message to the Client
    Byte[] byteSend = Encoding.ASCII.GetBytes( "This is a test of the emergency broadcast
 system");

    IAsyncResult sendres = a.BeginSend(byteSend,
                                   0,
                                   byteSend.Length,
                                   0,
                                  new AsyncCallback(SendCallback),
                                  a);
    SendEvent.WaitOne();

    a.Close();
    AcceptEvent.Set();

    return;
}

After the WaitOne call falls through because of the Receive completing, the next set of code sets up an asynchronous send. The arguments to BeginSend are identical to BeginReceive, with the callback set to the routine SendCallback.

Because the BeginSend completes immediately, the executing thread blocks on the SendEvent.WaitOne. After the send completes, and the SendEvent is signaled, and execution falls through to close the Socket and signal the AcceptEvent so that the server can issue another Accept.

Listing 12.23 continues the listing of the server code from Listing 12.22. This listing shows the callback that is called when data is received from the client.

Listing 12.23. Asynchronous TCP Server ReceiveCallback
private static void ReceiveCallback(IAsyncResult result)
{
    int bytes = ad.socket.EndReceive(result);

    Console.WriteLine("[AsyncSrv] ReceiveCallback: bytes = " + bytes);

    if (bytes > 0)
    {
        string data = Encoding.ASCII.GetString(ad.RecBytes, 0, bytes);
        Console.WriteLine("Received: [" + data + "]
");
    }

    ReceiveEvent.Set();
}

The receive callback gets the results of the Receive by calling EndReceive. In this case, the “result” is the number of bytes received. Notice that this callback makes use of the fact that the instance of AsyncData is a static member value. This same AsyncData information is available from result.AsyncState.

Listing 12.24 is a continuation of the server code listing from Listing 12.23. This listing illustrates the first steps that are taken when the “send” callback is called.

Listing 12.24. Asynchronous TCP Server SendCallback
    private static void SendCallback(IAsyncResult result)
    {
        Socket socket = (Socket) result.AsyncState;

        int bytes = socket.EndSend(result);
        Console.WriteLine("Sent: " + bytes + " bytes");

        SendEvent.Set();
    }
}

When this function is called, the Send has completed. The first thing that this function does is to call result.AsyncState to retrieve the state object that was passed as part of BeginSend. In this case, the state information is the Socket that is connected to the client. Calling EndSend retrieves the results of the Send, which is the number of bytes transferred. The event is then Set, causing it to signal any thread waiting on it that the Send has completed.

Because the server starts an Accept upon finishing with a request, a client can reconnect with the server multiple times. The output when both client and server are run on a single machine looks like Figure 12.6.

Figure 12.6. Asynchronous client/server.


Another asynchronous sample that retrieves the contents of a Web page is included in the Samples directory for this chapter, and it is called SimpleAsync. This sample looks much the same as the AsyncClient sample illustrated in Listings 12.2012.24, except that the connection is made to an HTTP server that serves up Web pages instead of the canned message. Look in the samples for SimpleAsync.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.190.217.134