C# Socket Programming





C# Socket Programming

The .NET Framework network classes were created to provide easy interfaces to the native Winsock network API for programmers using the .NET programming languages. Now that you have seen how the Winsock API handles network programming, you’re ready to examine C#’s way of handling sockets. The following sections describe the C# network programming classes and how to use them in network programs.

IP Addresses in C#

One of the biggest advantages you will notice in the .NET network library is the way IP address/port pairs are handled. It is a fairly straightforward process that represents a welcome improvement over the old, confusing Unix way. .NET defines two classes in the System.Net namespace to handle various types of IP address information:

IPAddress

An IPAddress object is used to represent a single IP address. This value can then be used in the various socket methods to represent the IP address. The default constructor for IPAddress is as follows:

public IPAddress(long address)

The default constructor takes a long value and converts it to an IPAddress value. In practice, this default is almost never used. (How many times do you happen to have the long value of an IP address handy?) Instead, several methods in the IPAddress class can be used to create and manipulate IP addresses. Figure defines these methods.

Figure: IPAddress Methods

Method

Description

Equals

Compares two IP addresses

GetHashCode

Returns a hash value for an IPAddress object

GetType

Returns the type of the IP address instance

HostToNetworkOrder

Converts an IP address from host byte order to network byte order

IsLoopBack

Indicates whether the IP address is considered the loopback address

NetworkToHostOrder

Converts an IP address from network byte order to host byte order

Parse

Converts a string to an IPAddress instance

ToString

Converts an IPAddress to a string representation of the dotted decimal format of the IP address

The Parse() method is most often used to create IPAddress instances:

IPAddress newaddress = IPAddress.Parse("192.168.1.1");

This format allows you to use a standard dotted quad IP address in string format and convert it to an IPAddress object.

The IPAddress class also provides four read-only fields that represent special IP addresses for use in programs:

Any Used to represent any IP address available on the local system

Broadcast Used to represent the IP broadcast address for the local network

Loopback Used to represent the loopback address of the system

None Used to represent no network interface on the system

Listing 3.1 shows an example program that demonstrates using the IPAddress class methods and fields.

Listing 3.1: The AddressSample.cs program
Start example
using System;
using System.Net;
class AddressSample
{
  public static void Main ()
  {
   IPAddress test1 = IPAddress.Parse("192.168.1.1");
   IPAddress test2 = IPAddress.Loopback;
   IPAddress test3 = IPAddress.Broadcast;
   IPAddress test4 = IPAddress.Any;
   IPAddress test5 = IPAddress.None;
   IPHostEntry ihe =
         Dns.GetHostByName(Dns.GetHostName());
   IPAddress myself = ihe.AddressList[0];
   if (IPAddress.IsLoopback(test2))
     Console.WriteLine("The Loopback address is: {0}",
             test2.ToString());
   else
   Console.WriteLine("Error obtaining the loopback address");
   Console.WriteLine("The Local IP address is: {0}\n",
            myself.ToString());
   if (myself == test2)
     Console.WriteLine("The loopback address is the Â
        same as local address.\n");
   else
     Console.WriteLine("The loopback address is not the local address.\n");
   Console.WriteLine("The test address is: {0}",
           test1.ToString());
   Console.WriteLine("Broadcast address: {0}",
           test3.ToString());
   Console.WriteLine("The ANY address is: {0}",
           test4.ToString());
   Console.WriteLine("The NONE address is: {0}",
           test5.ToString());
  }
}
End example

The AddressSample.cs program shows a few of the things that can be done with IPAddress objects. One of the more interesting ones is the method used to obtain the local IP address:

   IPHostEntry ihe =
         Dns.GetHostByName(Dns.GetHostName());
   IPAddress myself = ihe.AddressList[0];

This code was introduced in Chapter 2, “IP Programming Basics.” It uses the GetHostByName() and GetHostName() methods of the System.Net.Dns class to determine the local IP address(es) and create an IPHostEntry object. Chapter 4, "DNS and C#" will describe the IPHostEntry object in much more detail, but for now it is sufficient to say that it contains the AddressList property, which is an array of IPAddress objects. The AddressSample.cs program takes the first address in the list and assigns it to the myself IP address object.

The output from this program should look similar to this:

C:\>AddressSample
The Loopback address is: 127.0.0.1
The Local IP address is: 192.168.1.6
The loopback address is not the local address.
The test address is: 192.168.1.1
Broadcast address: 255.255.255.255
The ANY address is: 0.0.0.0
The NONE address is: 255.255.255.255
C:\>

What’s interesting about this output is the resulting values of the Any and None addresses. These values might look backward to what you would expect: the Any IPAddress object points to the 0.0.0.0 address, which you might think represents nothing. However, this address is most often used when a system has multiple network interfaces and you do not want to bind a socket to any particular interface. The None IPAddress object points to the 255.255.255.255 address, which is often used when a system wants to create a dummy socket and not bind it to any interfaces.

IPEndPoint

Similar to the Unix sockaddr_in structure, the .NET Framework uses the IPEndPoint object to represent a specific IP address/port combination. An IPEndPoint object is used when binding sockets to local addresses, or when connecting sockets to remote addresses. We’ll first examine all the pieces of IPEndPoint and then look at a program that puts it to work.

Two constructors are used to create IPEndPoint instances:

  • IPEndPoint(long address, int port)

  • IPEndPoint(IPAddress address, int port)

Both constructors use two parameters: an IP address value, represented as either a long value or an IPAddress object; and the integer port number. As you can probably guess, the most common constructor used is the IPAddress form.

Figure describes the methods that can be used with IPEndPoint objects.

Figure: IPEndPoint Methods

Method

Description

Create

Creates an EndPoint object from a SocketAddress object

Equals

Compares two IPEndPoint objects

GetHashCode

Returns a hash value for an IPEndPoint object

GetType

Returns the type of the IPEndPoint instance

Serialize

Creates a SocketAddress instance of the IPEndPoint instance

ToString

Creates a string representation of the IPEndPoint instance

The SocketAddress class is a special class within the System.Net namespace. It represents a serialized version of an IPEndPoint object. This class can be used to store an IPEndPoint instance, which can then be re-created using the IPEndPoint.Create() method. The format of the SocketAddress class is as follows:

  • 1 byte represents the AddressFamily of the object.

  • 1 byte represents the size of the object.

  • 2 bytes represent the port number of the object.

  • The remaining bytes represent the IP address of the object.

In addition to the methods, the IPEndPoint class also contains three properties that can be set or obtained from an instance:

Address Gets or sets the IP address property

AddressFamily Gets the IP address family

Port Gets or sets the TCP or UDP port number

Each of these properties can be used with an IPEndPoint instance to obtain information about individual parts of the IPEndPoint object. The Address and Port properties can also be used to set the individual values within an existing IPEndPoint object.

There are also two fields that can be used with the IPEndPoint object to obtain the available port ranges from a system:

MaxPort The maximum value that can be assigned to a port number

MinPort The minimum value that can be assigned to a port number

Example of IPEndPoint at Work

Listing 3.2 shows a sample program that demonstrates the IPEndPoint class and its methods, properties, and fields.

Listing 3.2: The IPEndPointSample.cs program
Start example
using System;
using System.Net;
class IPEndPointSample
{
  public static void Main ()
  {
   IPAddress test1 = IPAddress.Parse("192.168.1.1");
   IPEndPoint ie = new IPEndPoint(test1, 8000);
   Console.WriteLine("The IPEndPoint is: {0}",
           ie.ToString());
   Console.WriteLine("The AddressFamily is: {0}",
           ie.AddressFamily);
   Console.WriteLine("The address is: {0}, and the Â
        port is: {1}\n", ie.Address, ie.Port);
   Console.WriteLine("The min port number is: {0}",
        IPEndPoint.MinPort);
   Console.WriteLine("The max port number is: {0}\n",
        IPEndPoint.MaxPort);
   ie.Port = 80;
   Console.WriteLine("The changed IPEndPoint value Â
         is: {0}", ie.ToString());
   SocketAddress sa = ie.Serialize();
   Console.WriteLine("The SocketAddress is: {0}",
           sa.ToString());
  }
}
End example

The IPEndPointSample.cs program demonstrates several important IPEndPoint features. Note that you can display the complete IPEndPoint object as one string, or you can extract individual parts of the object:

Console.WriteLine("The IPEndPoint is: {0}",
         ie.ToString());
Console.WriteLine("The AddressFamily is: {0}",
         ie.AddressFamily);
Console.WriteLine("The address is: {0}, and the Â
         port is: {1}\n", ie.Address, ie.Port);

The program also demonstrates how to change the port value of the IPEndPoint object individually, using the Port property:

ie.Port = 80;

This allows you to change individual address and port values within the object without having to create a new object.

The output from this program should look like this:

C:\>IPEndPointSample
The IPEndPoint is: 192.168.1.1:8000
The AddressFamily is: InterNetwork
The address is: 192.168.1.1, and the port is: 8000
The min port number is: 0
The max port number is: 65535
The changed IPEndPoint value is: 192.168.1.1:80
The SocketAddress is: Â
InterNetwork:16:{0,80,192,168,1,1,0,0,0,0,0,0,0,0}
C:\>

Using C# Sockets

The System.Net.Sockets namespace contains the classes that provide the actual .NET interface to the low-level Winsock APIs. This section gives a brief overview of the C# Socket class.

Subsequent chapters will build on this overview, presenting detailed descriptions and examples of several types of socket programs.

Socket Construction

The core of the System.Net.Sockets namespace is the Socket class. It provides the C# managed code implementation of the Winsock API. The Socket class constructor is as follows:

Socket(AddressFamily af, SocketType st,
      ProtocolType pt)

As you can see, the basic format of the Socket constructor mimics the original Unix socket() function. It uses three parameters to define the type of socket to create:

  • An AddressFamily to define the network type

  • A SocketType to define the type of data connection

  • A ProtocolType to define a specific network protocol

Each of these parameters is represented by a separate enumeration within the System.Net_.Sockets namespace. Each enumeration contains the values that can be used. For normal IP communications on networks, the AddressFamily.InterNetwork value should always be used for the AddressFamily. With the InterNetwork AddressFamily, the SocketType parameter must match a particular ProtocolType parameter. You are not allowed to mix and match SocketTypes and ProtocolTypes. Figure shows the combinations that can be used for IP communications.

Figure: IP Socket Definition Combinations

SocketType

Protocoltype

Description

Dgram

Udp

Connectionless communication

Stream

Tcp

Connection-oriented communication

Raw

Icmp

Internet Control Message Protocol

Raw

Raw

Plain IP packet communication

Using the enumeration values makes it easy to remember all the options (though it does make for some fairly long Socket() statements!). For example:

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

Several properties of the Socket class can be used to retrieve information from a created Socket object, as described in Figure.

Figure: Socket Properties

Property

Description

AddressFamily

Gets the address family of the Socket

Available

Gets the amount of data that is ready to be read

Blocking

Gets or sets whether the Socket is in blocking mode

Connected

Gets a value that indicates if the Socket is connected to a remote device

Handle

Gets the operating system handle for the Socket

LocalEndPoint

Gets the local EndPoint object for the Socket

ProtocolType

Gets the protocol type of the Socket

RemoteEndPoint

Gets the remote EndPoint information for the Socket

SocketType

Gets the type of the Socket

Note 

All of the Socket class properties except the LocalEndPoint and RemoteEndPoint are available for a socket immediately after it is created. The LocalEndPoint and RemoteEndPoint properties can only be used on bound sockets.

Listing 3.3 is a simple program that demonstrates the Socket properties. Because the LocalEndPoint property needs a bound Socket object, I used the Bind() method to bind the socket to the loopback address of the system (127.0.0.1).

Listing 3.3: The SockProp.cs sample socket properties program
Start example
using System;
using System.Net;
using System.Net.Sockets;
class SockProp
{
  public static void Main ()
  {
   IPAddress ia = IPAddress.Parse("127.0.0.1");
   IPEndPoint ie = new IPEndPoint(ia, 8000);
   Socket test = new Socket(AddressFamily.InterNetwork,
          SocketType.Stream, ProtocolType.Tcp);
   Console.WriteLine("AddressFamily: {0}",
          test.AddressFamily);
   Console.WriteLine("SocketType: {0}",
          test.SocketType);
   Console.WriteLine("ProtocolType: {0}",
          test.ProtocolType);
   Console.WriteLine("Blocking: {0}", test.Blocking);
   test.Blocking = false;
   Console.WriteLine("new Blocking: {0}",test.Blocking);
   Console.WriteLine("Connected: {0}", test.Connected);
   test.Bind(ie);
   IPEndPoint iep = (IPEndPoint)test.LocalEndPoint;
   Console.WriteLine("Local EndPoint: {0}",
           iep.ToString());
   test.Close();
  }
}
End example

The output of the program should look like this:

C:\>SockProp
AddressFamily: InterNetwork
SocketType: Stream
ProtocolType: Tcp
Blocking: True
New Blocking: False
Connected: False
Local EndPoint: 127.0.0.1:8000
C:\>

By setting the Blocking property to false, you can use non-blocking sockets, similar to the Unix fcntl() function—but more on that later.

Socket Options

Like Unix sockets, .NET sockets allow you to set protocol options for the created Socket object. However, because C# is an object-oriented language, there is a twist.

Instead of being a stand-alone function, the call is a method. Like its Unix counterpart, SetSocketOption() configures the socket parameters that you want to tweak to customize the communication parameters. The SetSocketOption() method is overloaded, using three different formats:

SetSocketOption(SocketOptionLevel sl,
   SocketOptionName sn, byte[] value)
SetSocketOption(SocketOptionLevel sl,
   SocketOptionName sn,int value)
SetSocketOption(SocketOptionLevel sl,
   SocketOptionName sn, object value)

The parameters used are similar to the Unix setsockopt() function. The sl defines the type of socket option to set. Figure lists the available SocketOptionLevels.

Figure: SocketOptionLevel Values

Value

Description

IP

Options for IP sockets

Socket

Options for the socket

Tcp

Options for TCP sockets

Udp

Options for UDP sockets

The sn defines the specific socket option that will be set within the SocketOptionLevel. Figure lists the available SocketOptionNames.

Figure: SocketOptionName Values

Value

SocketOptionLevel

Description

AcceptConnection

Socket

If true, socket is in listening mode

AddMembership

IP

Adds an IP group membership

AddSourceMembership

IP

Joins a source group

BlockSource

IP

Blocks data from a source

Broadcast

Socket

If true, permits sending broadcast messages

BsdUrgent

IP

Uses urgent data (can only be set once and cannot be turned off)

ChecksumCoverage

Udp

Sets or gets UDP checksum coverage

Debug

Socket

Records debugging information if true

DontFragment

IP

Doesn’t fragment the IP packet

DontLinger

Socket

Closes socket gracefully without waiting for data

DontRoute

Socket

Sends packet directly to interface addresses

DropMembership

IP

Drops an IP group membership

DropSourceMembership

IP

Drops a source group

Error

Socket

Gets and clears the error status

ExclusiveAddressUse

Socket

Enables a socket to be bound for exclusive access

Expedited

IP

Uses expedited data (can only be set once, and cannot turned off)

HeaderIncluded

IP

Indicates that the data sent to the socket will include the IP header

IPOptions

IP

Specifies IP options to be used in outbound packets

IpTimeToLive

IP

Sets the IP packet time-to-live value

KeepAlive

Socket

Sends TCP keep-alive packets

Linger

Socket

Waits after closing the socket for any extra data

MaxConnections

Socket

Sets the maximum queue length used

MulticastInterface

IP

Sets the interface used for multicast packets

MulticastLoopback

IP

IP multicast loopback

MulticastTimeToLive

IP

Sets the IP multicast time to live

NoChecksum

Udp

Sends UDP packets with checksum set to zero

NoDelay

Tcp

Disables the Nagle algorithm for TCP packets

OutOfBandInline

Socket

Allows receiving out-of-band data

PacketInformation

IP

Returns information about received packets

ReceiveBuffer

Socket

Sets the total per-socket buffer reserved for receiving packets

ReceiveLowWater

Socket

Receives low water mark

ReceiveTimeout

Socket

Receives time-out

ReuseAddress

Socket

Allows the socket to be bound to a port address that is already in use

SendBuffer

Socket

Sets the total per-socket buffer reserved for sending packets

SendLowWater

Socket

Sends low water mark

SendTimeout

Socket

Sends timeout value

Type

Socket

Gets socket type

TypeOfService

IP

Sets the IP type-of-service field

UnblockSource

IP

Sets the socket to non-blocking mode

UseLoopback

Socket

Bypasses the network interface when possible

The value parameter defines the value of the socket option name to use. The format of the value is different depending on the SocketOptionName used.

Once the socket is created and modified, you are ready to either wait for incoming connections, or connect to remote devices. The following sections describe how to communicate using the C# Socket class, using connection-oriented and connectionless communication sockets in C#.

Using Connection-Oriented Sockets

Once again, the .NET Framework concepts are similar to Unix network programming. In the .NET Framework, you can create connection-oriented communications with remote hosts across a network. Because C# is an object-oriented language, all the Unix socket functions are implemented as methods of the Socket class. By referring to the method from the Socket instance, you can perform network operations using the indicated socket.

Note 

This section describes the C# methods in the Sockets class that are used for connection-oriented communication. Chapter 5, "Connection-Oriented Sockets" will expand on this explanation, showing much more detailed information along with lots of examples.

The Server Functions

Similar to the Unix server, once a server socket is created, it must be bound to a local network address on the system. The Bind() method is used to perform this function:

Bind(EndPoint address)

The address parameter must point to a valid IPEndPoint instance, which includes a local IP address and a port number. After the socket is bound to a local address, you use the Listen() method to wait for incoming connection attempts from clients:

Listen(int backlog)

The backlog parameter defines the number of connections that the system will queue, waiting for your program to service. Any attempts by clients beyond that number of waiting connections will be refused. You should remember that specifying a large number here might have performance consequences for your server. Each pending connection attempt uses buffer space in the TCP buffer area. This means less buffer space available for sent and received packets.

After the Listen() method is performed, the server is ready to accept any incoming connections. This is done with the Accept() method. The Accept() method returns a new socket descriptor, which is then used for all communication calls for the connection.

Here’s a sample of C# server code that sets up the necessary socket pieces:

IPHostEntry local = Dns.GetHostByName(Dns.GetHostName());
IPEndPoint iep = new IPEndPoint(local.AddressList[0],
             8000);
Socket newserver = new Socket(AddressFamily.InterNetwork,
    SocketType.Stream, ProtocolType.Tcp);
newserver.Bind(iep);
newserver.Listen(5);
Socket newclient = newserver.Accept();

This program will block at the Accept() statement, waiting for a client connection. Once a client connects to the server, the newclient Socket object will contain the new connection information and should be used for all communication with the remote client. The newserver Socket object will still be bound to the original IPEndPoint object and can be used to accept more connections with another Accept() method. If no more Accept() methods are called, the server will not respond to any more client connection attempts.

After the client connection has been accepted, the client and server can begin transferring data. The Receive() and Send() methods are used to perform this function. Both of these methods are overloaded with four forms of the method. Figure shows the available methods to use for each.

Figure: The Receive() and Send() Methods

Method

Description

Receive(byte[] data)

Receives data and places it in the specified byte array

Receive(byte[] data, SocketFlags sf)

Sets socket attributes, receives data, and places it in the specified byte array

Receive(byte[] data, int size, SocketFlags sf)

Sets socket attributes, receives the specified size of data, and places it in the specified byte array

Receive(byte[] data, int offset, int size, SocketFlags sf)

Sets socket attributes, receives the size bytes of data, and stores it at offset offset in the data byte array

Send(byte[] data)

Sends the data specified in the byte array

Send(byte[] data, SocketFlags sf)

Sets socket attributes and sends the data specified in the bytes array

Send(byte[] data, int size, SocketFlags sf)

Sets socket attributes and sends the specified size of data in the specified byte array

Send(byte[] data, int offset, int size, SocketFlags sf)

Sets socket attributes and sends size bytes of data starting at offset offset in the data byte array

The simple form of the Send() and Receive() methods sends a single byte array of data, or receives data and places it into the specified byte array. If you want to specify any special SocketFlags, you can add them into the method call as well. Figure shows the available SocketFlag values.

Figure: SocketFlag Values

Value

Description

DontRoute

Sends data without using the internal routing tables

MaxIOVectorLength

Provides a standard value for the number of WSABUF structures used to send and receive data

None

Uses no flags for this call

OutOfBand

Processes out-of-band data

Partial

Partially sends or receives message

Peek

Only peeks at the incoming message

The other parameters available on the Receive() and Send() methods allow you to specify how many bytes of data to send or receive and where in the buffer you want the data. This will be demonstrated in much greater detail in Chapter 5.

The Client Functions

The client device must also bind an address to the created Socket object, but it uses the Connect() method rather than Bind(). As with Bind(),Connect()requires an IPEndPoint object for the remote device to which the client needs to connect:

IPAddress host = IPAddress.Parse("192.168.1.1");
IPEndPoint hostep = new IPEndPoint(host, 8000);
Socket sock = new Socket(AddressFamily.InterNetwork,
       SocketType.Stream, ProtocolType.Tcp);
sock.Connect(hostep);

The Connect() method will block until the connection has been established. If the connection cannot be established, it will produce an exception (see the section "Socket Exceptions" later in this chapter).

Once the connection has been established, the client can use the Send() and Receive() methods of the Socket classsimilar to the way the server uses them. When communication is done, the Socket instance must be closed. Like the Unix socket, the Socket class usesboth a shutdown() method to gracefully stop a session, and a close() method to actually close the session. The shutdown() method uses one parameter to determine how the socket will shutdown. Available values for Socket.Shutdown() are described in Figure.

Figure: Socket.Shutdown() Values

Value

Description

SocketShutdown.Both

Prevents both sending and receiving data on the socket

SocketShutdown.Receive

Prevents receiving data on the socket. An RST will be sent if additional data is received.

SocketShutdown.Send

Prevents sending data on the socket. A FIN will be sent after all remaining buffer data is sent.

Here is the typical way to gracefully close a connection:

sock.Shutdown(SocketShutdown.Both);
sock.Close();

This allows the Socket object to gracefully wait until all data has been sent from its internal buffers.

Using Connectionless Sockets

.NET uses the same functionality for connectionless sockets as that employed by the Unix model. When you create a socket with the SocketType.Dgram socket type, the UDP protocol is used to transmit packets across the network. Similar to the Unix model, you must set up the Bind() method for the server to bind the socket to a particular port. Also similar to the Unix model, the server and client do not need to use the Listen() or Connect() methods.

Because there is no connection for communication, the standard Receive() and Send() methods will not work. Instead, you must use the special ReceiveFrom() and SendTo() methods. The formats for these methods comprise the same base parameters as the Receive() and Send() methods (as seen in Figure). In addition, there is an extra parameter that is a reference to an EndPoint object. This parameter defines where the data is going (for SendTo) or where it is coming from (for ReceiveFrom). For example, the simplest format of the methods would be as follows:

ReceiveFrom(byte[], ref EndPoint)
SendTo(byte[], ref EndPoint)

For UDP communications, the EndPoint object will point to an IPEndPoint object. If you are new to C#, you may not have seen the ref keyword before. The ref keyword indicates that the method will access the EndPoint object by reference in memory, and not by its value. This is a popular technique in C and C++ programming, but it is not seen very often in C# programs.

Non-blocking Programming

The .NET Socket class I/O methods use blocking by default, just like the Unix network programming model. When a program reaches a network function that blocks, such as Receive(), the program waits there until the function completes, such as when data is received from the socket. Three C# techniques are available to avoid using blocking network calls: non-blocking sockets, multiplexed sockets, and asynchronous sockets.

Non-blocking Sockets

As mentioned earlier in the “Using C# Sockets” sections, C# Socket objects contain properties that can be queried for their values. However, one of the properties, Blocking, can also be set. You can set the Blocking property of a socket to false, putting the socket into non-blocking mode.

When the socket is in non-blocking mode, it will not wait for an I/O method to complete. Rather, it will check the method; if it can’t be completed, the method will fail and the program will go on. For example, with Blocking set to false, the Receive() method will not wait for data to appear on the socket. Instead, the method will return a value of 0, indicating that no data was available on the socket.

Mutiplexed Sockets

Just as in Unix, the Socket class provides the Select() method. This method is used to multiplex multiple Socket instances to watch for the ones that are ready to be read or written to. In C#, however, the Select() method is used somewhat differently. Here is the format of the Select() method:

Select(IList read, IList write, IList error,
      int microseconds)

The read, write, and error parameters are IList objects, which are arrays that contain created sockets to monitor. The microseconds parameter defines the amount of time (in microseconds) the Select() method will wait for the events to happen.

The following is a small code fragment showing how the Select() method can be used:

ArrayList socketList = new ArrayList(5);
SocketList.Add(sock1);
SocketList.Add(sock2);
Socket.Select(socketList, null, null, 1000);
byte[] buffer = new byte[1024];
for (i = 0; i < socketList.Length - 1; i++)
{
  socketList[i].Receive(buffer);
  ConsoleWriteLine(Encoding.ASCII.GetString(buffer));
}

Notice that the Select() method will monitor both sock1 and sock2 for incoming data. If no data is present on either socket, the Receive() method will not block the program.

Asynchronous Socket Programming

It is no surprise that the .NET Framework uses the asynchronous socket model introduced by the Windows Winsock API. This method allows you to use a separate method when a socket is ready to receive or send data. Instead of using a Receive() method to wait for data from a client, you can use the BeginReceive() method, which will register a delegate to be called when data is available on the socket. Within the delegate method, you must use the EndReceive() method to stop the asynchronous read and retrieve the data from the socket. This concept is explained in more detail in Chapter 8, "Asynchronous Socket Programming."

C# Socket Exceptions

One feature of socket programming included in .NET Framework that is used neither by Unix nor the Winsock API is socket exceptions. So far, all of the examples and code fragments shown in this chapter assumed one important thing: that all socket calls will succeed. This is a dangerous assumption to make in today’s networking world.

As shown in Chapter 1, “The C# Language,” C# uses the try-catch mechanism to catch errors and exceptions as the program is running. You should always plan thoroughly for these exceptions and carefully determine what actions should be taken when an exception occurs.

All of the Socket class methods use the SocketException exception. Any socket programming you do should always check for SocketException exceptions and then attempt to recover from the error, or at least warn the user of the problem.

Listing 3.4 shows a simple socket client program that uses exception programming to determine if any errors occur during the normal network session. By using try-catch blocks around individual network calls, you can single out where problems occur in your program and give your customers better information so they can take the appropriate actions to fix the problem.

Listing 3.4: The SocketExcept.cs program
Start example
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class SocketExcept
{
  public static void Main ()
  {
   IPAddress host = IPAddress.Parse("192.168.1.1");
   IPEndPoint hostep = new IPEndPoint(host, 8000);
   Socket sock = new Socket(AddressFamily.InterNetwork,
          SocketType.Stream, ProtocolType.Tcp);
   try
   {
     sock.Connect(hostep);
   } catch (SocketException e)
   {
     Console.WriteLine("Problem connecting to host");
     Console.WriteLine(e.ToString());
     sock.Close();
     return;
   }
   try
   {
     sock.Send(Encoding.ASCII.GetBytes("testing"));
   } catch (SocketException e)
   {
     Console.WriteLine("Problem sending data");
     Console.WriteLine( e.ToString());
     sock.Close();
     return;
   }
   sock.Close();
  }
}
End example

 Python   SQL   Java   php   Perl 
 game development   web development   internet   *nix   graphics   hardware 
 telecommunications   C++ 
 Flash   Active Directory   Windows