< VB .NET Client Socket Project Example (Cont...) | Main | C++ .NET Protocols Header Definition Class 1 >

 


 

Chapter 8 Part 7:

Client (and Server) Sockets Communication

 

 

What do we have in this chapter 8 Part 7?

  1. Creating Socket

  2. Binding

  3. Connecting

  4. Transferring Data

  5. Sending Data

  6. Receiving Data

  7. Closing

  8. Socket Options

  9. Socket Ioctls

 

 

Creating Socket

 

The first step in the process is creating the socket by instantiating an instance of the Socket class, which is shown here:

 

C#

 

public socket(

    AddressFamily addressFamily,

    SocketType socketType,

    ProtocolType protocolType

    );

 

Visual Basic .NET

 

Public Sub New( _

    ByVal addressFamily As AddressFamily, _

    ByVal socketType As SocketType, _

    ByVal protocolType As ProtocolType _

    )

 

The three parameters to the constructor define exactly which network protocol is to be created. The first parameter is an enumerated type that defines the addressing protocol to be used. The two values of this enumeration that are of interest to us are InterNetwork for the IPv4 protocol and InterNetworkV6 for the IPv6 protocol.

The second and third parameters are closely related to one another and determine which upper-level protocol the socket being created is. The SocketType enumeration indicates the protocol’s semantics in terms of how data is sent and received. For example, the SocketType.Dgram value indicates that the protocol is message- (or datagram-) oriented such that message boundaries are preserved on receive. For example, if a datagram socket sends 100 bytes, a single-100 byte packet is put on the network wire and the receiving datagram socket will receive exactly 100 bytes in a single receive call. Table 8-1 lists the values of the SocketType enumeration and describes their meanings.

 

Table 8-1: Common SocketType Values

 

SocketType Member

Description

Dgram

Datagram-oriented, where each send on a datagram socket represents a packet on the network such that message boundaries are preserved at the receiver.

Raw

Raw protocol socket where the sender intends to build its own next protocol header as a part of the data payload.

Rdm

Reliable datagram socket where the socket is datagram- oriented but is also reliable.

Seqpacket

Sequential packet or pseudostream socket where each send on the socket represents a packet on the network but message boundaries are not preserved on the receiver side.

Stream

Stream-oriented socket where both the sender and receiver can lump data in any size during sending or receiving of data

Unknown

Unspecified socket type where the network stack will attempt to find a protocol that matches the given AddressFamily and ProtocolType parameters.

 

The SocketType seen through the VS 2008 intellisense

 

The last parameter indicates the exact protocol to be used for the indicated AddressFamily and SocketType. The protocols we’re interested in are ProtocolType.Tcp and ProtocolType.Udp. Note that for the TCP protocol, SocketType.Stream must be specified for the socket type, and for the UDP protocol, SocketType.Dgram is required. The TCP and UDP protocol have exact semantics; however, there are a few protocols, such as SPX, that allow for differing behavior (Stream and Seqpacket). If an invalid combination of socket type and protocol type are given, the SocketException error is thrown.

 

Binding

 

Once you have created a socket, it can’t be used for data transfer until it’s bound to a network interface. All data sent must originate from an address that’s associated with a physical network interface, and likewise, all data received must be read from a physical interface. The process of binding a socket to a local interface is simple. First an address object must be created that describes the local address that the socket is to be bound to. In the case of IPv4 and IPv6, this address object is the IPAddress class. Either an IPAddress object is initialized with a property of the IPAddress class itself or the Parse() method is used to parse the string representation of an address into the object. Table 8-2 lists the commonly used properties and methods for binding to an interface.

 

Table 8-2: IPAddress Members

 

Member

Property/Method

Description

Any

Property

IPv4 wildcard address: 0.0.0.0

Broadcast

Property

IPv4 broadcast address: 255.255.255.255

Loopback

Property

IPv4 loopback address: 127.0.0.1

Ipv6Any

Property

IPv6 wildcard address: ::

Ipv6Loopback

Property

IPv6 loopback address: ::1

Parse()

Method

Parses a string into the appropriate protocol

 

The IPAddress member seen through the Visual Studio 2008 intellisense

 

The following code illustrates how to initialize several different IPAddress objects with properties by using the Parse() method:

 

C#

 

IPAddress  bindAddress, parsedAddress;

 

bindAddress = IPAddress.Any;         // Holds the IPv4 wildcard address

bindAddress = IPAddress.IPv6Any; // Now holds the IPv6 wildcard address

 

try

{

    parsedAddress = IPAddress.Parse("169.254.0.1");

    parsedAddress = IPAddress.Parse("fe80::2ff:abcd:1234%3");

}

catch ( FormatException err )

{

    Console.WriteLine("Invalid IP address: {0}", err.Message);

}

 

Visual Basic .NET

 

Dim bindAddress As IPAddress, parsedAddress As IPAddress

 

bindAddress = IPAddress.Any         ' Holds the IP4 wildcard address

bindAddress = IPAddress.IPv6Any  ' Now holds the IPv6 wildcard address

 

Try

    parsedAddress = IPAddress.Parse("169.254.0.1")

    ParsedAddress = IPAddress.Parse("fe80::2ff:abcd:1234%3")

Catch err As FormatException

    Console.WriteLine("Invalid IP Address: {0}", err.Message)

End Try

 

Notice that the IPAddress class can contain either an IPv4 or IPv6 address. If an invalid IP address is passed to the Parse() method, a FormatException is thrown. Take note that the support for the IPv6 protocol in the IPAddress class was introduced in version 1.1 of the .NET Framework. Earlier versions of the IPAddress class can’t be used to parse and describe IPv6 addresses.

Next create an EndPoint object corresponding the address object. An endpoint is a combination of the local address and a local port number. A port number is a 16-bit integer used by both the TCP and UDP protocols for multiplexing multiple sockets on a single interface. This way, if a UDP packet arrives on interface 10.10.10.1 and port 5150, the stack will deliver the data to the socket bound to the same interface and port. To create an endpoint for either the IPv4 or IPv6 protocol, an IPEndPoint object needs to be created. The following code creates an end point describing the IPv4 wildcard address on port 5150.

 

C#

 

IPAddress  bindAddress = IPAddress.Any;

IPEndPoint  bindEndPoint = new IPEndPoint( bindAddress, 5150 );

 

Visual Basic .NET

 

Dim bindAddress As IPAddress = IPAddress.Any

Dim bindEndPoint As IPEndPoint = New IPEndPoint(bindAddress, 5150)

 

Once the socket is created and an IPEndPoint is built, the Bind() method needs to be called with IPEndPoint, as shown in the following code. Notice that the IPAddress is created from a user-input string that can be either an IPv4 or IPv6 string because the Parse() method will parse either. Therefore, we make sure to parse the string first and then create the Socket with the proper address family by passing the AddressFamily property of the IPAddress to the socket creation call. If the address families of IPEndPoint and the created socket do not match, a SocketException occurs.

 

C#

 

IPAddress  bindAddress = IPAddress.Parse( userInputString );

IPEndPoint bindEndPoint = new IPEndPoint( bindAddress, 5150 );

Socket     mySocket = null;

 

try

{

    mySocket = new Socket(

        bindAddress.AddressFamily,

        SocketType.Stream,

        ProtocolType.Tcp);

    mySocket.Bind( bindEndPoint );

}

catch ( SocketException err )

{

    if ( mySocket != null )

        mySocket.Close();

}

 

Visual Basic .NET

 

Dim bindAddress As IPAddress = IPAddress.Parse(userInputString)

Dim bindEndPoint As IPEndPoint = New IPEndPoint(bindAddress, 5150)

Dim mySocket As Socket

 

mySocket = Nothing

Try

    mySocket = New Socket(bindAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp)

    mySocket.Bind( bindEndPoint )

Catch err As SocketException

    If (Not mySocket Is Nothing) Then

        mySocket.Close()

    End If

End Try

 

Note that binding a socket is not always required. The following operations will implicitly bind a socket to a local interface:

 

  1. Connect()
  2. SendTo() (for a datagram socket)

 

When one of these methods is called on an unbound socket, the network stack will bind the socket to an appropriate interface on a randomly chosen port in the range of 1024 to 5000. The advantage to explicitly binding the socket yourself is the ability to choose a specific port from the entire range of available ports, which will be discussed in more detail in the next chapter.

The most common cause of exceptions when calling the Bind() method is when a socket of the same protocol is already bound to the requested port. For example, if UDP socket A is bound to address and port 10.10.10.1:5150, UDP socket B will get an exception if it attempts to bind to the same address and port. Note that if socket B is a TCP socket, no error will occur because the port reservations are on a per-protocol basis. In the “Socket Options” section later in this chapter, we’ll cover several options that can allow several sockets of the same protocol to be bound to the same address and port. We’ll also discuss this problem in detail in Chapter 9 because servers typically run into this problem more often than client sockets.

 

Connecting

 

Once a socket is created and optionally bound, it can be used as a client socket connected to a remote server. When a TCP socket is connected, the underlying protocol establishes a virtual circuit where the server actively accepts the connection if it chooses. If a connect is performed on a UDP socket, a destination address is associated with the socket so that if data is sent, the destination does not have to be supplied on each and every send call. Note that connecting a UDP socket by no means implies that a socket is waiting to receive the data on the destination; it’s simply a time-saving measure.

Connecting a socket is similar to binding except that instead of a local address in the end point, a remote address is specified. The port number used to create the IPEndPoint must be a valid port number on which the remote server is listening. In the case of TCP, if there’s no active listening socket on the given port, a SocketException occurs because the remote computer will forcefully reject the connection. Again, for UDP, there will be no indication as to whether a socket is listening on the destination computer.

Most client applications typically resolve the server’s host name using the Dns class to retrieve the list of addresses registered to the server computer. For more information about the Dns class, see Chapter 7. A robust client application should perform the following steps:

 

  1. Call Dns.Resolve with the server’s host name.
  2. Iterate through the IPHostEntry.AddressList.
  3. Create a Socket based on the address family of the current IPAddress returned.
  4. Call Connect() on the created socket to the current IPAddress.
  5. If the connection request fails, go to step 2.
  6. If the connection request succeeds, break out of the loop.

 

Performing these steps will ensure a positive user experience because an application should attempt to connect to the requested server on every address returned, not just the first address returned from DNS. The following code illustrates a simple TCP client that follows the above steps:

 

C#

 

Socket       tcpSocket = null;

IPHostEntry  resolvedServer;

IPEndPoint   serverEndPoint;

 

try

{

    resolvedServer = Dns.Resolve( "server-name" );

    foreach( IPAddress addr in resolvedServer.AddressList )

    {

        serverEndPoint = new IPEndPoint( addr, 5150 );

        tcpSocket = new Socket(

            addr.AddressFamily,

            SocketType.Stream,

            ProtocolType.Tcp

            );

        try

        {

            tcpSocket.Connect( serverEndPoint );

        }

        catch

        {

            // Connect failed so try the next one make sure to close the socket we opened

            if ( tcpSocket != null )

                tcpSocket.Close();

            continue;

        }

        break;

    }

}

catch ( SocketException err )

{

    Console.WriteLine("Client connection failed: {0}", err.Message);

}

// Now use tcpSocket to communicate to the server

 

Visual Basic .NET

 

Dim tcpSocket As Socket

Dim resolvedServer As IPHostEntry

Dim serverEndPoint As IPEndPoint

Dim addr As IPAddress

 

tcpSocket = Nothing

Try

    resolvedServer = Dns.Resolve("server-name")

    For Each addr In resolvedServer.AddressList

        serverEndPoint = New IPEndPoint(addr, 5150)

        tcpSocket = New Socket( _

            addr.AddressFamily, _

            SocketType.Stream, _

            ProtocolType.Tcp _

            )

        Try

            tcpSocket.Connect(serverEndPoint)

        Catch

' Connect failed so try the next one

'    Make sure to close the socket we opened

            If (Not tcpSocket Is Nothing) Then

                tcpSocket.Close()

            End If

            GoTo ContinueLoop

        End Try

ContinueLoop:

    Next

Catch err As SocketException

    Console.WriteLine("Client connection failed: {0}", err.Message)

End Try

' Now use tcpSocket to communicate to the server

 

This code uses the synchronous connect method, but there’s also an asynchronous method, BeginConnect(). The asynchronous method is useful if the client needs to perform other tasks while the connection is taking place or if it needs to establish multiple concurrent connections.

 

Transferring Data

 

We’ve now covered how to create a socket and establish a connection for TCP or associate a destination for a UDP socket. Now it’s time to get down to the nitty-gritty of transferring data on the socket. The next two sections will cover sending and receiving data on a socket for both the TCP and UDP protocol.

 

Sending Data

 

There are two basic methods for sending data: Send() and SendTo(). The former is used for connection-oriented sockets, which includes UDP sockets that have been associated with a destination via a connect method, as described earlier. The latter method is used only for connectionless protocols such as UDP. The only difference between the two methods is that SendTo() includes a destination address parameter in addition to the parameters used with Send(). The following code shows sending data with the Send() method:

 

 

C#

 

Socket   clientSocket = null;

byte [ ]  dataBuffer = new byte [ 1024 ];

 

// Create a TCP socket and connect to a server

try

{

    clientSocket.Send( dataBuffer );

}

catch ( SocketException err )

{

    Console.WriteLine("Send failed: {0}", err.Message);

}

 

Visual Basic .NET

 

Dim clientSocket As Socket

Dim dataBuffer(1024) As Byte

 

' Create a TCP socket and connect to a server

Try

    clientSocket.Send(dataBuffer)

Catch err As SocketException

    Console.WriteLine("Send failed: {0}", err.Message)

End Try

 

This code shows the simplest use of the Send() method: simply sending a byte array of data. Several other instances of the Send() method exist that take additional parameters such as number of bytes from the array to send, offset in the array to start sending data, and a socket flag that controls how the data is sent. The socket flag is the SocketFlags enumerated type, which is described in Table 8-3.

 

Table 8-3: Socket Flags

 

SocketFlag

Description

DontRoute

This flag indicates that data should be sent from the interface the socket is bound to. Most transport protocols ignore this request. It’s valid for sending only.

MaxIOVectorLength

This flag is not valid for any current protocols.

None

This flag indicates no flag values.

OutOfBand

When sending, this flag indicates that data should be sent as urgent data. When receiving, it indicates that the next byte read should be urgent data. This flag is supported by TCP.

Partial

When sending, this flag indicates that this send is part of a larger block of data to send. When receiving, it indicates that the data returned is part of a larger message.

Peek

When specified, this flag indicates that data is returned to the application but is not removed from the network stack buffers. This flag is valid only for receiving.

 

Most of the flags are rarely used or don’t apply to the TCP or UDP protocol (such as the Partial or the MaxIOVectorLength flag). The OutOfBand flag is used on connected TCP sockets when the sender needs to send data that’s of higher importance than the data already sent on the stream, hence the name urgent or out-of-band (OOB) data. The receiver can then process this data separate from the normal data already buffered by the local network stack. Use of OOB data is discouraged because two different RFCs (793 and 1122) describe how OOB should be implemented in TCP, and they’re not always compatible across different operating systems. Therefore, depending on which operating systems the client and server are running on, sending and receiving OOB data might not work if the OOB implementations differ.

The next sending method is SendTo(), which is used for unconnected datagram sockets such as with the UDP protocol. As mentioned earlier, the only difference between Send() and SendTo() is that SendTo() also takes the destination IPEndPoint. The following code illustrates sending a UDP datagram:

 

C#

 

IPAddress   destAddress = IPAddress.Parse("10.10.10.1");

IPEndPoint  destEndPoint = new IPEndPoint(destAddress, 5150);

Socket      udpSocket;

byte [ ]     message = System.Text.Encoding.ASCII.GetBytes("hello world");

 

udpSocket = new Socket(

    destAddress.AddressFamily,

    SocketType.Dgram,

    ProtocolType.Udp

    );

try

{

    udpSocket.SendTo(message, destEndPoint);

}

catch (SocketException err)

{

    Console.WriteLine("SendTo failed: {0}", err.Message);

}

 

Visual Basic .NET

 

Dim destAddress As IPAddress = IPAddress.Parse("10.10.10.1")

Dim destEndPoint As IPEndPoint = New IPEndPoint(destAddress, 5150)

Dim udpSocket As Socket

Dim message() As Byte = System.Text.Encoding.ASCII.GetBytes("hello world")

 

udpSocket = new Socket( _

    destAddress.AddressFamily, _

    SocketType.Dgram, _

    ProtocolType.Udp _

    )

Try

    udpSocket.SendTo( message, destEndPoint )

Catch err As SocketException

    Console.WriteLine("SendTo failed: {0}", err.Message )

End Try

 

The value returned from both the Send() and SendTo() methods is the number of bytes transferred on the socket. Note that the send methods will send all the data requested unless an error occurs or the socket has been put into non- blocking mode. By default, all sockets created are blocking. A socket is put into non-blocking mode by calling the IOControl() method, in which case, there’s no guarantee that the number of bytes actually sent will be the number of bytes requested. In this case, it’s the application’s responsibility to ensure that all data has been sent. Non-blocking mode will be discussed in more detail in the “Socket Options” and “Socket Ioctls” sections later in this chapter.

One common problem encountered when sending data on the socket is that the Send() and SendTo() methods require byte arrays, which is not always the type of data being sent. In this case, the BitConverter class is useful for converting common types into byte arrays via the GetBytes() method.

As you can see, sending data on a socket is simple and straightforward. If an error occurs while sending data, such as if the TCP connection is broken or if there’s no route to the destination, a SocketException will be generated. Finally, the asynchronous versions of the Send() and SendTo() methods are BeginSend() and BeginSendTo(), respectively.

 

Receiving Data

 

The methods for receiving data are Receive() and ReceiveFrom(). To receive data on a socket, it must either be connected as with a TCP socket or a connected UDP socket, or it must be bound to a local interface as with unconnected UDP sockets. The parameters to the receive methods are very similar to the send methods. A byte buffer is specified to receive the data into, an optional number of bytes to receive is set, SocketFlags are set for controlling the receive operation, and the IPEndPoint describing who sent the data (used with the ReceiveFrom() method) is set. If the number of bytes to receive is not set, the size of the byte array is used. The following code creates a receiving UDP socket that calls ReceiveFrom():

 

C#

 

IPAddress   bindAddress = IPAddress.Any;

IPEndPoint  bindEndPoint = new IPEndPoint( bindAddress, 5150 );

Socket      udpSocket;

byte [ ]     receiveBuffer = new byte [ 1024 ];

IPEndPoint  senderEndPoint = new IPEndPoint(bindAddress.AddressFamily, 0);

EndPoint  castSenderEndPoint = (EndPoint) senderEndPoint;

int       rc;

 

udpSocket = new Socket(

    bindAddress.AddressFamily,

    SocketType.Dgram,

    ProtocolType.Udp

    );

try

{

    udpSocket.Bind( bindEndPoint );

    rc = udpSocket.ReceiveFrom( receiveBuffer, ref castSenderEndPoint );

    senderEndPoint = (IPEndPoint) castSenderEndPoint;

    Console.WriteLine("Received {0} bytes from {1}", rc, senderEndPoint.ToString());

}

catch ( SocketException err )

{

    Console.WriteLine("Error occurred: {0}", err.Message);

}

finally

{

    udpSocket.Close();

}

 

Visual Basic .NET

 

Dim bindAddress As IPAddress = IPAddress.Any

Dim bindEndPoint As IPEndPoint = New IPEndPoint(bindAddress, 5150)

Dim udpSocket As Socket

Dim receiveBuffer(1024) As Byte

Dim senderEndPoint As IPEndPoint = New IPEndPoint(bindAddress.AddressFamily, 0)

Dim castSenderEndPoint As EndPoint = CType(senderEndPoint, EndPoint)

Dim rc As Integer

 

udpSocket = New Socket(bindAddress.AddressFamily, SocketType.Dgram, ProtocolType.Udp)

Try

    udpSocket.Bind(bindEndPoint)

    rc = udpSocket.ReceiveFrom(receiveBuffer, castSenderEndPoint)

    senderEndPoint = CType(castSenderEndPoint, IPEndPoint)

    Console.WriteLine("Received {0} bytes from {1}", rc, senderEndPoint.ToString())

Catch err As SocketException

    Console.WriteLine("Error occurred: {0}", err.Message)

Finally

    udpSocket.Close()

End Try

 

The receiver code isn’t all that different from the sender code, except for the extra work involved in casting the IPEndPoint, which contains the address of whom ever sent the datagram packet. Also, the value returned from the receive methods is the number of bytes actually received into the data buffer. For blocking sockets, this value will be at least one byte.

If an error occurs during receive, a SocketException is thrown. For a TCP connection, an error can occur if the connection is broken for whatever reason (for example, the remote process dies, network connectivity is lost, and so on). A UDP socket can receive this exception if the same socket has sent data to an end point where there is no socket to receive the data. This error occurs because the remote machine will send an Internet Control Message Protocol (ICMP) port unreachable message indicating that no socket is listening for the data. The sending computer’s network stack will receive this error and propagate it back to the sending socket, which can cause the exception to be thrown if the socket is now attempting to receive data. Finally, a UDP socket will receive an exception if the buffer passed to the Receive or ReceiveFrom() method is too small to hold the received data. In this case, the exception is thrown and the remaining portion of the message is truncated and discarded. The asynchronous versions of Receive() and ReceiveFrom() are BeginReceive() and BeginReceiveFrom(), respectively.

 

Closing

 

When an application has completed communications on a socket, it needs to close the connection to free the resource. In the case of sockets, closing the connection involves freeing both the handle and memory resources associated with the Socket class object as well as network resources such as the local interface and port on which the socket is bound.

When closing a TCP connection, the connection itself needs to be closed in a graceful manner, that is, the TCP peer should be notified that the connection is closing so that both sides can ensure that all data has been received on the connection. This type of closing is known as a graceful shutdown. To initiate a graceful shutdown, each side in the TCP connection should call the Shutdown method with the SocketShutdown.Send flag. Once this call is done, the protocol stack sends a transport-level disconnect to the peer to indicate that the local participant will no longer be sending any additional data. A socket detects a graceful close when a call to the Receive method returns zero bytes. The following code sample shows a simple client that sends a request to the server and waits for the response.

 

C#

 

Socket   tcpSocket;

Byte [ ]  receiveBuffer = new byte [ 1024 ], requestBuffer = new byte [ 1024 ];

int      rc;

 

// Establish a TCP connection, such that tcpSocket is valid

try

{

    // Initialize the requestBuffer and send request to server

    tcpSocket.Send( requestBuffer );

    // Since this socket will not be sending anything shut it down

    tcpSocket.Shutdown( SocketShutdown.Send );

    while (1)

    {

        rc = tcpSocket.Receive( receiveBuffer );

        if ( rc > 0 )

        {

            // Process data

        }

        else if ( rc == 0 )

        {

            tcpSocket.Close();

            break;

        }

    }

}

catch ( SocketException err )

{

    Console.WriteLine("An error occurred: {0}", err.Message);

}

 

Visual Basic .NET

 

Dim tcpSocket As Socket

Dim receiveBuffer(1024) As Byte

Dim requestBuffer(1024) As Byte

Dim rc As Integer

 

' Establish a TCP connection, such that tcpSocket is valid

Try

' Initialize the requestBuffer and send request to server

    tcpSocket.Send(requestBuffer)

' Since this socket will not be sending anything shut it down

    tcpSocket.Shutdown(SocketShutdown.Send)

    Do While True

        rc = tcpSocket.Receive(requestBuffer)

        If (rc > 1) Then

' Process the data

        ElseIf (rc = 1) Then

            Exit Do

        End If

    Loop

Catch err As SocketException

    Console.WriteLine("An error occurred: {0}", err.Message)

End Try

 

Notice that because this client sends a single request, it calls Shutdown() to indicate to the server that no more data will be sent. A TCP connection is full duplex, which means that the two send channels are independent of each other. One socket can indicate shutdown while the peer can send data as long as it wants. If the socket attempts to send additional data after shutdown, a SocketException will occur.

The SocketShutdown enumerated type also includes Receive and Both values, but these values are not of interest to TCP because the underlying transport protocol has no equivalent semantic for indicating to the peer that it will stop receiving data. Indicating Both shuts down both the sending and receiving channels. However, if you do use either of these values, there will be no indication to the peer, but if any receive method is called, a SocketException will occur. Once the socket has performed a shutdown and has received indication that the peer has also shut down the connection (by a receive method returning zero), the local resources associated with the socket are freed by calling the Close() method.

For UDP sockets, there’s no connection to gracefully shutdown because UDP is a connectionless protocol. Therefore, when an application is finished communicating with a UDP socket, it simply needs to call the Close() method.

 

Socket Options

 

A number of properties can be set or modified on the socket, which affects the socket’s behavior. An example of a property is how much data the underlying network stack buffers for the socket, which can be important if a socket is receiving very large UDP datagrams and wants the local stack to be able to buffer multiple packets.

Two methods can change certain characteristics of a socket: GetSocketOption() and SetSocketOption(). GetSocketOption() retrieves the current property value, and SetSocketOption() sets the property’s value. The parameter list for these two methods is the same. The first parameter is a SocketOptionLevel enumerated type, and the second is the SocketOptionName enumerated type. The value of the options can be one of three types, depending on the option level and name: integer, byte array, or object.

SocketOptionLevel indicates on what network level the option name is being applied. Socket options can be applied at the socket level (Socket) or at the protocol level (IPv4, IPv6, UDP, or TCP). SocketOptionName is an enumerated type that indicates the property being queried or set. Note that a particular SocketOptionName typically applies to a single SocketOptionLevel, although this is not always the case. Table 8-4 describes some of the common SocketOptionName properties. The multicast-related options will be covered in the “IP Multicasting” section later in this chapter.

 

Table 8-4: Common SocketOptionName Options

 

Option Name

Option Level

Description

Broadcast

Socket

Boolean value that enables the sending of broadcast packets on a socket. Only valid for protocols that support broadcast data.

DontLinger

Socket

Boolean value that disables any linger value set with the Linger option.

ExclusiveAddressUse

Socket

Boolean value that disallows any subsequent socket from binding to the same port regardless of whether the socket sets the ReuseAddress option.

HeaderIncluded

IP or IPv6

Boolean value that’s used with raw sockets and indicates that the protocol header is included as part of data to send.

IpTimeToLive

IP or IPv6

Sets the integer time to live (TTL) value in the IP header.

KeepAlive

Tcp

Boolean value that enables TCP keepalives. Note that by default keepalives are sent on the order of hours.

Linger

Socket

Sets a time limit on unacknowledged data after the Close method has been called on a connection-oriented socket. A LingerOption object is passed to the call.

NoDelay

Tcp

Boolean value that disables the Nagle
algorithm.

ReceiveBuffer

Socket

Sets the integer size in bytes (default of 8 KB) of the per-socket buffer maintained by the stack for receiving data.

ReceiveTimeout

Socket

Sets the timeout value for receive operations in milliseconds. An exception is thrown if timeout occurs.

ReuseAddress

Socket

When enabled, indicates a socket can be bound to an address even if another socket is bound to the same address and port.

SendBuffer

Socket

Sets the integer size in bytes (default of 8 KB) of the per-socket buffer maintained by the stack for sending data.

SendTimeout

Socket

Sets the timeout value for send operations in milliseconds. If the send can’t complete in the specified time, an exception is thrown.

 

Warning: The ReceiveTimeout and SendTimeout socket options should never be used on TCP sockets because data might be lost when a timeout occurs.

A few notes about socket options. First, the majority of the options are Boolean values. When setting an option’s value, simply pass an integer where zero is false and non-zero is true. Retrieving the option’s value is a little more complex because a byte array must be used. The following code illustrates setting the ReceiveBuffer option and then retrieving the value just set:

 

C#

 

Socket   mySocket;

byte [ ]  optionBuffer = new byte [ 4 ];

int      sendBufferSize = 16384;

 

// First create a valid Socket – mySocket

mySocket.SetSocketOption(

    SocketOptionLevel.Socket,

    SocketOptionName.SendBuffer,

    sendBufferSize

    );

mySocket.GetSocketOption(

    SocketOptionLevel.Socket,

    SocketOptionName.SendBuffer,

    optionBuffer

);

sendBufferSize = BitConverter.ToInt32( optionBuffer, 0 );

Console.WriteLine("Retrieved SendBuffer size = {0}", sendBufferSize);

 

Visual Basic .NET

 

Dim mySocket As Socket

Dim optionBuffer(4) As Byte

Dim sendBufferSize As Integer = 16384

 

' First create a valid socket – mySocket

mySocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendBuffer, sendBufferSize)

mySocket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName, optionBuffer)

sendBufferSize = BitConverter.ToInt32(optionBuffer, 0)

Console.WriteLine("Retrieved SendBuffer size = {0}", sendBufferSize)

 

Second, some of the socket options can be set or retrieved, but not both. For example, the multicast-related options can be used only with SetSocketOption, and the AcceptConnection option can only be retrieved. The AcceptConnection option is not in Table 8-4 because it isn’t very useful; it simply indicates whether Listen() has been called on the socket.

The options ReuseAddress, ExclusiveAddressUse, and Linger will be covered in greater detail in Chapter 9, and HeaderIncluded and multicast-related options are covered later in this chapter.

 

Socket Ioctls

 

Socket I/O control codes (ioctls) are somewhat related to socket options. Socket ioctls typically return information about the socket or some network characteristic, such as enumerating all the local IP addresses. The Socket method for calling an ioctl is IOControl(). Unfortunately, the .NET Framework does not offer any predefined ioctls and their corresponding structures, so to call an ioctl, you might need to use the StructLayout class to define the data returned. The Microsoft Platform SDK contains information about the most common ioctls under the WSAIoctl entry. The following code shows the prototype for the IOControl() method.

 

C#

 

public int IOControl(int ioControlCode, byte [ ] optionInValue, byte [ ] optionOutValue);

 

Visual Basic .NET

 

Public Function IOControl(ByVal ioControlCode As Integer, ByVal optionInvValue() As Byte, ByVal optionOutValue() As Byte)

 

The ioControlCode value is the ioctl being called. These values can be found in the C header files winsock2.h and ws2tcpip.h. Note that the ioctls in the C header file are unsigned, which can lead to some annoying compilation errors in C# and Microsoft Visual Basic .NET because the ioControlCode is defined as a signed integer. In this case, the values simply have to be converted to the equivalent negative number, or the Unchecked operator may be used in C# to prevent integer overflow validation. The input value is the byte array of the expected value, and because few ioctl objects are defined in the .NET Framework, applications will have to build these byte arrays by hand to match the required ioctl object’s layout. Likewise, the output is a byte array specific to each ioctl that applications will have to decode appropriately.

The following RawSocket program example illustrates calling the IOControl with the Winsock ioctl SIO_ROUTING_INTERFACE_QUERY. This ioctl takes the equivalent of a SocketAddress structure (the equivalent of the Winsock sockaddr structure for a particular address family) that indicates the address of a remote destination. On output, a SocketAddress structure is returned that describes the local interface on which the destination is reachable; the SIO_ROUTING_INTERFACE_QUERY ioctl performs a route lookup.

First of all we create a class, ProtocolHeaderDefinition to store the definition of the header protocols. We provide examples for C++ and C#.

 

 

 


 

< VB .NET Client Socket Project Example (Cont...) | Main | C++ .NET Protocols Header Definition Class 1 >