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. Table 3.7 shows the combinations that can be used for IP communications.
Table 3.7: 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 Table 3.8.
Table 3.8: 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
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();
} }
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.
Table 3.9 lists the available SocketOptionLevels.
Table 3.9: 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. Table 3.10 lists the available SocketOptionNames.
Table 3.10: 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. Table 3.11 shows the available methods to use for each.
Table 3.11: 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. Table 3.12 shows the available SocketFlag values.
Table 3.12: 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 Table 3.13.
Table 3.13: 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 Table 3.11). 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.