The classes in the System.Net.Sockets namespace provide sockets support in .NET-let's begin with a quick description of each of them.
Class
Description
MulticastOption
The MulticastOption class sets IP address values for joining or leaving an IP multicast group. This class is discussed further in Chapter 7.
NetworkStream
The NetworkStream implements the underlying stream class from which data is sent or received. It is a high-level abstraction representing a connection to a TCP/IP communication channel. We saw NetworkStream in Chapter 2.
TcpClient
The TcpClient builds upon the Socket class to provide TCP services at a higher level. TcpClient provides several methods for sending and receiving data over a network. Chapter 5 discusses TcpClient in more detail.
TcpListener
This class also builds upon the low level Socket class; its main purpose is in server applications. This class listens for the incoming client connections and notifies the application of any connections. We'll look into this class when we talk about TCP in Chapter 5.
UdpClient
UDP is a connectionless protocol, therefore a different type of functionality is required to implement UDP services in.NET.The UdpClient class serves the purpose of implementing UDP services - we'll look more at this in Chapter 6.
SocketException
This is the exception thrown when an error occurs in a socket. We'll look at this class later in this chapter.
Socket
Our last class in the System.Net.Sockets namespace is the Socket class itself. This class provides the basic functionality of a socket application.
System.Net.Sockets.Socket Class
The Socket class plays an important role in network programming, performing both client and server operations. Mostly, method calls to this class handle the necessary security checks, such as checking security permissions, and then are marshaled to counterparts in the Windows Sockets API.
Before we look at an example using the Socket class, let's first take a look at a list of some of the important System.Net.Sockets.Socket properties:
Property
Description
AddressFamily
Gets the address family of the socket-the value is from the Socket.AddressFamily enumeration
Available
Returns the amount of available data to read
Blocking
Gets or sets the value which indicates whether the socket is in blocking mode or not
Connected
Returns the value that informs whether the socket is still connected with the remote host
LocalEndPoint
Gets the local endpoint
ProtocolType
Get the protocol type of the socket
RemoteEndPoint
Gets the remote endpoint of a socket
SocketType
Gets the type of the socket
and some of the System.Net.Sockets.Socket methods:
Method
Description
Accept()
Creates a new socket to handle the incoming connection request.
Bind()
Associates a socket with the local endpoint for listening to incoming connections.
Close()
Forces the socket to close itself.
Connect()
Establishes a connection with the remote host.
GetSocketOption()
Returns the value of a SocketOption.
IOControl()
Sets low-level operating modes for the socket. This method provides low-level access to the underlying socket instance of the Socket class.
Listen()
Places the socket in listening mode. This method is exclusive to server applications.
Receive()
Receives data from a connected socket.
Poll()
Determines the status of the socket.
Select()
Checks the status of one or more sockets.
Send()
Sends data to the connected socket.
SetSocketOption()
Sets a SocketOption.
Shutdown()
Disables send and receive on a socket.
Creating a TCP Stream Socket Application
In the following example, we use TCP to provide sequenced, reliable two-way byte streams. Here we'll build a complete application involving both the client and the server. First we demonstrate how to construct a TCP stream-based socket server and then a client application to test our server. The following program creates a server that receives connection requests from clients. The server is built synchronously, therefore the execution of the thread is blocked until it accepts a connection from the client. The application demonstrates a simple server that replies to the client. The client ends its connection by sending
Here's the complete code for SocketServer.cs:
using System;
using System.Net.Sockets;
using System.Net;
using System.Text;
public class SocketServer
{
public static void Main(string [] args)
{
// establish the local end point for the socket
IPHostEntry ipHost = Dns.Resolve("localhost");
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 11000);
// create a Tcp/Ip Socket
Socket sListener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
// bind the socket to the local endpoint and
// listen to the incoming sockets
try
{
sListener.Bind(ipEndPoint);
sListener.Listen(10);
// Start listening for connections
while (true)
{
Console.WriteLine("Waiting for a connection on port {0}",ipEndPoint);
// program is suspended while waiting for an incoming connection
Socket handler = sListener.Accept();
string data = null;
// we got the client attempting to connect
while(true)
{
byte[] bytes = new byte[1024];
int bytesRec = handler.Receive(bytes);
data += Encoding.ASCII.GetString(bytes, 0, bytesRec);
if (data.IndexOf("
{
break;
}
}
// show the data on the console
Console.WriteLine("Text Received: {0}",data);
string theReply = "Thank you for those " + data.Length.ToString()
+ " characters...";
byte[] msg = Encoding.ASCII.GetBytes(theReply);
handler.Send(msg);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
}
}
catch(Exception e)
{
Console.WriteLine(e.ToString());
}
} // end of Main
}
The first step is to establish the local endpoint for the socket. Before opening a socket for listening, a socket must establish a local endpoint address. The unique address of a TCP/IP service is defined by combining the IP address of the host with the port number of the service to create an endpoint for the service. The Dns class provides methods that return information about the network addresses supported by the local network device. When the local network device has more than one network address, or if the local system supports more than one network device, the Dns class returns information about all network addresses, and the application must choose the proper address for the service from the array.
We create an IPEndPoint for a server by combining the first IP address returned by Dns.Resolve() for the host computer with a port number.
// establish the local end point for the socket
IPHostEntry ipHost = Dns.Resolve("localhost");
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 11000);
The IPEndPoint class here represents the localhost on port number 11000.
Next, we create a stream socket with a new instance of the Socket class. Having established a local endpoint for listening, we can create the socket:
// create a TCP/IP Socket
Socket sListener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
The AddressFamily enumeration indicates the addressing schemes that a Socket instance can use to resolve an address. Some important parameters are provided in the following table.
AddressFamily value
Description
InterNetwork
Address for IP Version 4.
InterNetworkV6
Address for IP Version 6.
Ipx
IPX or SPX Address.
NetBios
NetBios Address
The SocketType parameter distinguishes between a TCP and a UDP Socket. Other possible values are provided as follows:
SocketType value
Description
Dgram
Supports datagrams. Dgram requires the Udp ProtocolType and the InterNetwork AddressFamily.
Raw
Supports access to the underlying transport protocol.
Stream
Supports stream sockets. Stream requires the Tcp ProtocolType and the InterNetwork AddressFamily.
The third and the last parameter defines the protocol type that is the requested protocol for the socket. Some important values for the ProtocolType parameter are:
ProtocolType value
Description
Raw
Raw packet protocol.
Tcp
Transmission Control Protocol.
Udp
User Datagram Protocol.
IP
Internet Protocol.
Our next step is to name the socket with the Bind() method. When the socket opens with the constructor, the socket has no name assigned to it. However, a descriptor is reserved for the socket created. To assign a name to the server socket, we call Bind(). To be identified by a client socket, a TCP stream socket server must name its socket.
try
{
sListener.Bind(ipEndPoint);
The Bind() method associates a socket with a local endpoint. You must call Bind() before any attempt to call Listen() or Accept().
Now that our socket is created and a name has been bound to it, we can listen for incoming connections with Listen(). In the listening state, the socket will poll for incoming connection attempts.
sListener.Listen(10);
The parameter defines the backlog, which specifies the maximum number of pending connections in the queue. In the code above, the parameter allows a queue of 10 connections.
Now that we're listening, our next step is to accept the client connection with Accept(). Accept() is used to receive the client connection that completes the name association between client and server. The Accept() method blocks the caller thread until the connection is present.
The Accept() method extracts the first connection request from the queue of pending requests and creates a new socket to handle it. Although a new socket is created, the original socket continues to listen and can be used with multithreading to accept multiple client connections. Any server application must close the listening socket along with the incoming client sockets created by Accept().
while (true)
{
Console.WriteLine("Waiting for a connection on port {0}",ipEndPoint);
// program is suspended while waiting for an incoming connection
Socket handler = sListener.Accept();
Once the client and server are connected with each other, you can send and receive messages using the Send() and Receive() methods of the Socket class.
The Send() method writes outgoing data to the connected socket. The Receive() method reads the incoming data on a stream-based socket. When using a TCP-based system, the sockets must be connected before using Send() or Receive(). The exact protocol definition between the two communicating entities needs to be made clear beforehand, so that there will be no deadlocks between the client and server applications caused by not knowing who will send the data first.
string data = null;
// we got the client attempting to connect
while(true)
{
byte[] bytes = new byte[1024];
// the data received from the client
int bytesRec = handler.Receive(bytes);
// bytes are converted to string
data += Encoding.ASCII.GetString(bytes,0,bytesRec);
// checking the end of message
if (data.IndexOf("
{
break;
}
}
// show the data on the console
Console.WriteLine("Text Received: {0}",data);
The Receive() method receives the data from the socket and fills the byte array passed as an argument. The return value of the method actually determines the number of bytes read. In the code above, after receiving the data, we check for the end of message characters in the converted string. If the end of message characters are not found in the string, then the code again starts to listen for incoming data, otherwise we display the message to the console.
After exiting from the loop, we prepare a new byte array with our reply to pass back to the client. After conversion, the Send() method is used to send to it the client.
string theReply = "Thank you for those " + data.Length.ToString()
+ " characters...";
byte[] msg = Encoding.ASCII.GetBytes(theReply);
handler.Send(msg);
When data exchange between the server and the client ends, close the socket with Close(). To ensure that there is no data left behind, always call Shutdown() before calling Close(). There should always be a corresponding Close() call to each successful socket instance.
handler.Shutdown(SocketShutdown.Both);
handler.Close();
}
}
SocketShutdown is an enumeration, which can specify three different values for shutting down the socket:
SocketShutdown Value
Description
Both
Shuts down a socket for both sending and receiving
Receive
Shuts down a socket for receiving
Send
Shuts down a socket for sending
The socket is closed when the Close() method is called, which also sets the Connected property of the socket to false.
Building a TCP-based Client
The functions used to make a client application are more or less similar to the server application. Like the server, we employ the same methods for establishing the endpoint, creating a socket instance, sending and receiving data, and closing the socket.
Here's the complete code for SocketClient.cs-we'll explain it in a moment.
using System;
using Systern.Net.Sockets;
using Systern.Net;
using Systern.Text;
public class SocketClient
{
public static void Main(string [] args)
{
// data buffer for incoming data
byte[] bytes = new byte[1024];
// connect to a Remote device
try
{
// Establish the remote end point for the socket
IPHostEntry ipHost = Dns.Resolve("127.0.0.1");
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 11000);
Socket sender = new Socket(AddressFamily.Internetwork,
SocketType.Stream, ProtocolType.Tcp);
// Connect the socket to the remote endpoint
sender.Connect(ipEndPoint);
Console.WriteLine("Socket connected to {0}",
sender.RemoteEndPoint.ToString());
string theMessage = "This is a test";
byte[] msg = Encoding.ASCII.GetBytes(theMessage+"
// Send the data through the socket
int bytesSent = sender.Send(msg);
// Receive the response from the remote device
int bytesRec = sender.Receive(bytes);
Console.WriteLine("The Server says : {0}",
Encoding.ASCII.GetString(bytes,0, bytesRec));
// Release the socket
sender.Shutdown(SocketShutdown.Both);
sender.Close();
}
catch(Exception e)
{
Console.WriteLine("Exception: {0}", e.ToString());
}
}
}
The only new method used here is the Connect() method, which is used to connect to the remote server. Let's look at how it's used in the client. First, we need to establish the remote endpoint:
// Establish the remote endpoint for the socket
IPHostEntry ipHost = Dns.Resolve("127.0.0.1");
IPAddress ipAddr = ipHost.AddressList[0];
IPEndPoint ipEndPoint = new IPEndPoint(ipAddr, 11000);
Socket sender = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
Now we can connect our socket to the remote endpoint:
sender.Connect(ipEndPoint);
Given the socket, the Connect() method establishes the connection between the socket and the remote host specified by the endpoint parameter. Once we're connected, we can send our data, and receive a response:
string theMessage = "This is a test";
byte[] msg = Encoding.ASCII.GetBytes(theMessage+"
// Send the data through the socket
int bytesSent = sender.Send(msg);
// Receive the response from the remote device
int bytesRec = sender.Receive(bytes);
Console.WriteLine("The Server says : {0}",
Encoding.ASCII.GetString(bytes,0, bytesRec));
Finally, we release the socket by calling Shutdown(), shutting it down for both sending and receiving, and then call Close():
sender.Shutdown(SocketShutdown.Both);
sender.Close();
The screenshots below show our server and client in action:
Exception Management in System. Net. Sockets
The Socket class generally throws the SocketException when some error occurs in the network. SocketException is thrown by a variety of problems, we'll look at two cases here-problems in the Socket constructor, and problems when connecting to ports.
The Socket constructor takes three parameters; AddressFamily, SocketType, and the ProtocolType, throwing a SocketException if there is a mismatch or incompatibility between the three combinations.
For example, attempting to create a Socket instance with the following parameters throws a SocketException:
Socket sSocket = new Socket(AddressFamily.InterNetworkV6,
SocketType.Stream,
ProtocolType.Tcp);
When the exception is thrown, its Message property (inherited from Exception) is the following:
An address incompatible with the requested protocol was used
IP version 6 is not supported by TCP, so the AddressFamily parameter InterNetworkV6 is incompatible with a ProtocolType of Tcp.
Another method that is a good candidate for this exception handling mechanism is the Connect() method. This method throws an exception if it fails to establish a connection between the local and remote endpoints specified in the Connect() parameters. Let's put this to use.
A Port Scanner
We've looked at a simple client-server application-now let's make something more interesting here with the help of the SocketException.
In the following example, we make our own port scanner, which tries to connect to the localhost on each port specified in the loop-the scanner scans the first 1024 ports for our demonstration. We report successful connections, and if the connection fails, we catch the SocketException that is thrown.
This port scanner can be used to check out open ports on your computer; these open ports could be a potential weakness in your system, exploitable by "rogue" applications…
Here's the complete code for PortScanner.cs:
using System;
using System.Net.Sockets;
using System.Net;
public class SocketConn
{
public static void Main(string [] args)
{
IPAddress address = IPAddress.Parse("127.0.0.1");
for (int i=1: i < 1024; i++)
{
Console.WriteLine("Checking port {0}", i);
try
{
IPEndPoint endPoint = new IPEndPoint(address,i);
Socket sSocket = new Socket(AddressFamily.Internetwork,
SocketType.Stream,
ProtocolType.Tcp);
sSocket.Connect(endPoint);
Console.WriteLine("Port {0} is listening",i);
}
catch(SocketException ignored)
{
if (ignored.ErrorCode != 10061)
Console.WriteLine(ignored.Message);
}
}
}
}
The code works by making a connection with each port specified in the loop. If the port is opened, then the socket establishes a connection and prints out the line giving information about the port. If the port is closed, a SocketException will be thrown. When such an exception is thrown, its Message property would return the following:
No connection could be made because the target machine actively refused it
stating that a connection could not be made with the server. The SocketException has an ErrorCode property that holds an integer value that represents the last operating system error to occur. In the case of the exception thrown when an attempt is made to connect to a closed port, the ErrorCode value is 10061. In our catch block to handle the SocketException, we check if the ErrorCode is different from this value and display the actual error message-this is to advise the reader in case something other than a failure to connect to the specified port happens:
catch(SocketException ignored)
{
if (ignored.ErrorCode != 10061)
Console.WriteLine(ignored.Message);
}
The ErrorCode property actually calls the native method GetLastError() to get the error number. You can find the list of error codes in the WinError.h file in the following folder:
Microsoft Visual Studio .NET\Vc7\PlatformSDK\Include
Here's a summary of the output when I run this piece of code on my machine running Windows 2000 Server.
Checking port 1
Checking port 2
...
Port 21 is listening
...
Port 25 is listening
...
Port 80 is listening
Cleaning Up Connections
Code in a finally block will be executed regardless of whether an exception is thrown or not, making it an ideal candidate for Shutdown() and Close() methods. This will ensure that the socket is always closed before the program ends.
try
{
...
}
catch( .. )
{
...
}
finally
{
if (socketOpened.Connected)
{
socketOpened.Shutdown(Socket Shutdown.Both);
socketOpened.Close();
}
}
Here we use the Connected property to determine if the socket is still open. The Connected property returns the connection state of the socket, with a value of true indicating that the socket is open. If this is the case, then the socket is closed.
Socket Options
The .NET Framework provides SetSocketOption() and GetSocketOption() methods to set and retrieve socket options. The SetSocketOption() method calls through to the setsockopt function of the Windows Socket API.
The SetSocketOption() method requires a SocketOptionLevel, a SocketOptionName, and the value to set for the socket option-either a byte array, int, or object.
The SocketOptionLevel enumeration defines the option level for the socket-when we set or retrieve an option for a socket, this enumeration is used to specify what level in the OSI model the option is applied at (for example, whether the option is set at the TCP level, at the level of the individual socket, and so on):
SocketOptionLevel Value
Description
IP
Socket options apply to IP sockets
Socket
Socket options apply to the socket itself
Tcp
Socket options apply to TCP sockets
Udp
Socket options apply to UDP sockets
The second required parameter of SetSocketOptions() is SocketOptionName; it defines the parameter's name whose value is being set. The values for the SocketOptionName enumeration can be found in the .NET Framework SDK Documentation, we'll look at a few options here.
ReuseAddress
By default, only one socket may be bound to a local address that is already in use. However, you may want to bind more than one socket to a local address. Consider the following code that attempts to bind two sockets to the same local endpoint:
// Establish the local end. point for the socket
IPEndPoint ipEndPoint = new
IPEndPoint(Dns.GetHostByName(Dns.GetHostName()).AddressList[0], 11000);
// Create a Tcp/ip Socket
Socket sListener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
// Bind the socket to the local endpoint and
// Listen to the incoming sockets
sListener.Bind(ipEndPoint);
sListener.Listen(10);
// create a new Socket
Socket sListener2 = new Socket(AddressFamily.Internetwork,
SocketType.Stream, ProtocolType.Tcp);
// Bind the socket to the local endpoint and
// Listen to the incoming sockets
sListener2.Bind(ipEndPoint);
sListener2.Listen(10);
Trying to bind two sockets to a single endpoint will throw a SocketException with the following Message:
Only one usage of each socket address (protocol/network address/port)
is normally permitted
The solution to this is the ReuseAddress option; by using this option and a non-zero integer value you can set the socket to allow multiple bindings:
sListener2.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReuseAddress, 1);
sListener2.Bind(ipEndPoint);
Note the use of the non-zero integer-this will actually be interpreted as a Boolean value of true by the Windows Sockets API setsockopt function that is called by SetSocketOption().
Linger Time
Through the Linger value of SocketOptionName you can determine how the socket should behave when there is some data queued to be sent and the socket is closed. The LingerOption class contains information about the socket's linger time, and some of its important members are listed below:
Property
Description
Enabled
Determines whether to linger after the socket is closed or not
LingerTime
The time (in seconds) to linger if the Enabled property is true
If Enabled is false, then the socket will immediately close itself whenever a call to Close() method is executed. If Enabled is true, the remaining data will continue to be sent to the destination until the time specified by the LingerTime property has passed. The connection will then be closed when either of the two things occurs-either the time is over or all the data is sent. Note that if there is no data present in the queue then the socket is immediately closed, regardless of the Enabled property. The socket is also closed immediately if the LingerTime is specified as 0.
These two properties can also be set in the LingerOption class's constructor. The first parameter sets the Enabled property, and the second sets the LingerTime property. The value held by the LingerOption object is then passed to the SetSocketOption() method as below-note the explicit cast to object required to pass the LingerOption:
// Create a new LingerOption class and set properties in the constructor
LingerOption lingerOpts = new LingerOption(true,5);
mySocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger,
(object)lingerOpts);
Let's demonstrate the GetSocketOption() method to retrieve the value for the LingerTime- GetSocketOption() has three overloads, each one returning a different data type-void, byte array, or object. The overload that returns the object requires only the SocketOptionLevel and SocketOptionName as parameters. The overload that has no return value fills a byte array that you specify as an extra parameter. The overload that returns the byte array requires you to specify the length of the information you expect returned with an extra int parameter.
We use the overload that returns an object-a cast to a LingerOption object allows us to retrieve the LingerTime property:
object o = mySocket.GetSocketOption(SocketOptionLevel.Socket,
SoeketOptionName.Linger);
Console.WriteLine(((LingerOption)o).LingerTime);