< Chap 8 TOC: .NET Client Socket Programming | Main | C++ .NET Client Socket Program Example >

 


 

Chapter 8 Part 1:

Client (and Server) Sockets Communication

 

 

What we have in this chapter 8 Part 1?

  1. Introduction

  2. Overview

  3. Protocol Introduction

  4. TCP

  5. UDP

  6. Socket Basics

  7. C++ Simple Server Socket Program Example

 

 

Introduction

 

We will learn a lot in this chapter. Other than learning the methods/functions/subroutines used for client-server communication, various protocols available in the TCP/IP stack also will be discussed and used in the working program examples. In this Chapter, we will learn how to create the definition classes for some of the TCP/IP protocols. Then, we will try to use those classes (either the DLL or the source code) in the program examples. While trying the program examples through the step-by-step source code 'copy and paste', make sure the matching braces for C++ .NET and C# or the matching subroutine keywords for VB .NET.

 

Overview

 

Many developers have avoided network programming using the Windows Sockets (Winsock – C code) API (also equivalent Linux socket – C code and older C++ for Microsoft can be found in MFC library) because they think it’s too difficult to learn and implement. Often, these developers relied on simplified interfaces such as Microsoft ActiveX network controls, which provide simplicity but often offer poor performance. At some point, the developer usually needs fine control over the communication, which isn’t typically exposed through the simple interface. The Winsock API is large and complicated, which is why developers have looked elsewhere. However, the Microsoft Windows .NET Framework offers several classes, such as Socket, TcpListener, TcpClient, and UdpClient, that offer a simplified interface to get up and running with network programming while also exposing more complicated and powerful functionality.

The bulk of this chapter introduces the System.Net.Sockets class, which provides the ability for applications to communicate with one another over different protocols installed on the system. A number of transport protocols are available for Windows, such as IPv4, IPv6, IPX/SPX, and AppleTalk. However, this chapter and the next will focus only on IPv4 and IPv6, and more specifically, the TCP and UDP protocols over IPv4 and IPv6.

We introduce the Socket class first to introduce the concepts and underlying behavior of network programming that are sometimes hidden in the simplified TcpListener, TcpClient, and UdpClient classes. Also, for classes derived from these simplified classes, you can always obtain the underlying Socket object to perform the advanced functionality.

Additionally, our discussion of programming with the Socket class will be divided between the client and server operation. This chapter will cover the basics of the Socket class such as socket creation, setting socket options, establishing a connection, and sending and receiving data. Advanced topics such as IP multicasting and raw sockets will also be introduced. The next chapter will focus on the server-side aspects of socket programming, such as setting up a listening socket, accepting connections, and managing multiple clients. As such, the focus of the discussion in this chapter will be from the client’s perspective, but many of the concepts covered apply to server-side programming as well.

Many socket operations can be invoked either synchronously or asynchronously. This chapter will focus mainly on the synchronous method calls to keep the chapter simple. Each section will mention if a particular method also has an asynchronous counterpart using the .NET Framework asynchronous pattern as described in Chapter 3, but code samples will not be given. However, Chapter 9 will fully discuss asynchronous operations because servers typically require asynchronous calls for performance and scalability reasons, although this certainly isn’t always the case.

 

Protocol Introduction

 

Before getting into the details of creating and using sockets, it’s necessary to understand the protocols over which communication takes place. As mentioned earlier, the sockets interface is a generic interface that can be used to communicate over a variety of transport protocols. A transport protocol is a protocol that provides a standard mechanism for addressing each computer on the network. Chapter 7 discussed the IPv4 and IPv6 transport protocols and detailed how computers address one another using these protocols.

A transport protocol might encapsulate higher-level protocols that provide a certain set of characteristics to the application using that protocol. The TCP and UDP protocols can be encapsulated within either the IPv4 or IPv6 transport protocols. Figure 8-1 illustrates IPv4 protocol encapsulation. When an application sends data over a given transport protocol, the packet sent on the network will have a transport header, followed by any number of upper-layer protocols, and finally the data sent. In this example, we see an IPv4 header and a TCP header followed by the application’s data.

 

Protocol encapsulation illustration

 

Figure 8-1: Protocol encapsulation

 

TCP

 

TCP is a connection-oriented, reliable, stream-oriented protocol and is used for such things as Web traffic. When a TCP connection is established, a virtual circuit is formed. The computer initiating the connection sends a request to the destination. If the message is accepted, the destination computer will acknowledge it and respond with its own connection request back to the source. This way, each side can send traffic independently of one another. In effect, there are two separate data channels for sending and receiving data.

Traffic sent on a TCP connection is stream-oriented (not to be confused with the Stream class), which means that the protocol controls how and when data is sent and received on the connection. So, if the sender issues two send commands of 100 bytes each, the network stack on the receiver can return any number of bytes with a receive call. Applications that use streaming protocols should never make any assumptions on how many bytes are to be received in a single receive call, which is important if fixed-size messages are being sent. The receiving application must have logic to call receive enough times to retrieve an entire message. There’s no maximum send size when sending data on a TCP connection, aside from resource limitations. For example, attempting to send a 100 megabyte (MB) buffer is likely to fail because of resource limits.

The advantage of TCP is that the communication is reliable. When data is sent, the underlying network stack ensures that the data is delivered reliably. If the data is lost or corrupted, the protocol will retransmit as necessary. If the data is lost and can’t be recovered, an error will be returned to the application. The drawback to TCP is the overhead involved in setting up the virtual circuit and handling reliable data transmission, which is expensive, especially if the application needs to communicate with multiple end points or if the amount of data to send to each end point is fairly small.

 

UDP

 

UDP is a connectionless, unreliable, message-oriented protocol. UDP does not require any connection to be established. The sender simply calls the send method with a destination address where the packet should be sent. Delivery of the packet is not guaranteed by the protocol. If it’s absolutely imperative that the recipient receives the packet, it’s up to the application’s logic to ensure that it’s retransmitted until the receiver receives the data.

UDP is message oriented, which means that it preserves the message boundaries. For example, if the sender sends three 100-byte packets, the receiver will call receive three times, each returning 100 bytes. Preserving the message boundaries is extremely useful when the data being sent is of a fixed size and the receiver does not require any logic to assemble a single discrete message, the message is preserved by the protocol. Note the maximum size of a UDP packet is 65,535 bytes; however, it’s advisable to send small packets because large packets usually require fragmentation, which increases the probability that one fragment will be lost, causing the whole packet to be discarded at the receiver. Another benefit of UDP is that it’s connectionless, there’s no overhead associated with establishing a connection. The sender simply indicates the destination of each packet.

Of course, the disadvantage of UDP is that it’s unreliable. If the network is congested or other problems occur, it’s possible that a UDP message will get lost, and both the sender and receiver will have no idea that it occurred unless the application builds loss detection as a part of the data being sent.

 

Socket Basics

 

Now that you have a basic understanding of the common IP-based networking protocols, it’s time to get into the specifics of creating sockets as well as what steps are involved in using sockets to send and receive data. Your first question is probably “What exactly is a socket?” A socket is an object that describes a network resource that’s the underlying network protocol that the application is using for communication. The following sections will describe how an application creates a socket and puts it into a state so that it can be used to send and receive data. The Socket class resides in the System.Net namespace.

The socket basics illustrated in the following program examples. The sample illustrates using the Socket class for simple TCP and UDP cases.

 

C++ Simple Server Socket Program Example

 

Create a new CLR console application project and you might want to use SimpleSocketServerCP as the name.

 

C++ Simple Server Socket Program Example - a new CLE console application project creation in Visual Studio 2008

 

Add the following code.

 

 

 

// SimpleSocketServerCP.cpp : main project file.

/// <summary>

/// This is a simple TCP and UDP based server.

/// </summary>

 

#include "stdafx.h"

 

using namespace System;

using namespace System::Net;

using namespace System::Net::Sockets;

 

/// <summary>

/// Winsock ioctl code which will disable ICMP errors from being propagated to a UDP socket.

/// This can occur if a UDP packet is sent to a valid destination but there is no socket

/// registered to listen on the given port.

/// </summary>

/// http://msdn.microsoft.com/en-us/library/cc242275.aspx

/// http://msdn.microsoft.com/en-us/library/bb736550(VS.85).aspx

/// 0x9800000C == 2550136844 (uint) == -1744830452 (int) == 0x9800000C

const int SIO_UDP_CONNRESET = -1744830452;

 

// Alternative:

// const long IOC_IN = 0x80000000;

// const long IOC_VENDOR = 0x18000000;

// const long SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;

 

/// <summary>

/// This routine repeatedly copies a string message into a byte array until filled.

/// </summary>

/// <param name="dataBuffer">Byte buffer to fill with string message</param>

/// <param name="message">String message to copy</param>

static void FormatBuffer(array<Byte>^ dataBuffer, String^ message)

{

    array<Byte>^ byteMessage = Text::Encoding::ASCII->GetBytes(message);

    int index = 0;

 

    // First convert the string to bytes and then copy into send buffer

    while (index < dataBuffer->Length)

    {

        for (int j = 0; j < byteMessage->Length; j++)

        {

            dataBuffer[index] = byteMessage[j];

            index++;

 

            // Make sure we don't go past the send buffer length

            if (index >= dataBuffer->Length)

            {

                break;

            }

        }

    }

}

 

/// <summary>

/// Prints simple usage information.

/// </summary>

static void usage()

{

    Console::WriteLine("Executable_file_name [-l bind-address] [-m message] [-n count] [-p port]");

    Console::WriteLine("                 [-t tcp|udp] [-x size]");

    Console::WriteLine("Available options:");

    Console::WriteLine("  -l bind-address        Local address to bind to");

    Console::WriteLine("  -m message             Text message to format into send buffer");

    Console::WriteLine("  -n count               Number of times to send a message");

    Console::WriteLine("  -p port                Local port to bind to");

    Console::WriteLine("  -t udp | tcp           Indicates which protocol to use");

    Console::WriteLine("  -x size                Size  of send and receive buffer");

    Console::WriteLine();

}

 

    /// <summary>

    /// This is the main routine that parses the command line and invokes the server with the

    /// given parameters. For TCP, it creates a listening socket and waits to accept a client

    /// connection. Once a client connects, it waits to receive a "request" message. The

    /// request is terminated by the client shutting down the connection. After the request is

    /// received, the server sends a response followed by shutting down its connection and

    /// closing the socket. For UDP, the socket simply listens for incoming packets. The "request"

    /// message is a single datagram received. Once the request is received, a number of datagrams

    /// are sent in return followed by sending a few zero byte datagrams. This way the client

    /// can determine that the response has completed when it receives a zero byte datagram.

    /// </summary>

    /// <param name="args">Command line arguments</param>

int main(array<System::String ^> ^args)

{

    String^ textMessage = "This message is from server, sucker!!!";

    // Default values

    int localPort = 5150, sendCount = 10, bufferSize = 4096;

    // Default, listen to any interface. You may want to set it to specific address

    IPAddress^ localAddress = IPAddress::Any;

    // Default, Stream type socket

    SocketType sockType = SocketType::Stream;

    // Default, TCP type protocol

    ProtocolType sockProtocol = ProtocolType::Tcp;

 

    // Parse the command line

    if(args->Length != 0)

    {

    for (int i = 0; i < args->Length; i++)

    {

        try

        {

            if ((args[i][0] == '-') || (args[i][0] == '/'))

            {

                switch (Char::ToLower(args[i][1]))

                {

                    case 'l':       // Local interface to bind to

                        localAddress = IPAddress::Parse(args[++i]);

                        break;

                    case 'm':       // Text message to put into the send buffer

                        textMessage = args[++i];

                        break;

                    case 'n':       // Number of times to send the response

                        sendCount = Convert::ToInt32(args[++i]);

                        break;

                    case 'p':       // Port number for the destination

                        localPort = Convert::ToInt32(args[++i]);

                        break;

                    case 't':       // Specified TCP or UDP

                        i++;

                        if (String::Compare(args[i], "tcp", true) == 0)

                        {

                             sockType = SocketType::Stream;

                             sockProtocol = ProtocolType::Tcp;

                        }

                        else if (String::Compare(args[i], "udp", true) == 0)

                        {

                             sockType = SocketType::Dgram;

                             sockProtocol = ProtocolType::Udp;

                        }

                        else

                        {

                            usage();

                            return 0;

                        }

                        break;

                    case 'x':       // Size of the send and receive buffers

                        bufferSize = Convert::ToInt32(args[++i]);

                        break;

                    default:

                        usage();

                        return 0;

                }

            }

        }

        catch (Exception^ err)

        {

            Console::WriteLine("Error: " + err->Message);

            usage();

            return 0;

        }

    }

            }

            else

            {

                usage();

                return 0;

            }

 

     Socket^ serverSocket = nullptr;

 

    try

    {

        IPEndPoint^ localEndPoint = gcnew IPEndPoint(localAddress, localPort);

        IPEndPoint^ senderAddress = gcnew IPEndPoint(localAddress, 0);

        Console::WriteLine("Server: IPEndPoint - Local end point and sender addresses are OK...");

        EndPoint^ castSenderAddress;

        Socket^ clientSocket;

 

        Console::WriteLine("Server: Instantiating receiver and sender buffers...");

        array<Byte>^ receiveBuffer = gcnew array<Byte>(bufferSize);

        array<Byte>^ sendBuffer = gcnew array<Byte>(bufferSize);

        int rc;

 

        Console::WriteLine("Server: Formatting buffer content...");

        Console::WriteLine("Text to be sent: {0}", textMessage);

        FormatBuffer(sendBuffer, textMessage);

 

        // Create the server socket

        Console::WriteLine("Server: Creating the server socket...");

        serverSocket = gcnew Socket(localAddress->AddressFamily, sockType, sockProtocol);

 

        Console::WriteLine("Server: Socket() is OK...");

        // Bind the socket to the local interface specified

        Console::WriteLine("Server: Binding the socket to the local interface specified...");

        serverSocket->Bind(localEndPoint);

        Console::WriteLine("Server: Bind() is OK...");

 

        Console::WriteLine("Server: {0} server socket bound to {1}", sockProtocol, localEndPoint);

 

        if (sockProtocol == ProtocolType::Tcp)

        {

            // If TCP socket, set the socket to listening

            serverSocket->Listen(5);

            Console::WriteLine("Server: Listen() is OK, I'm listening for connection!");

        }

        else

        {

            // Header bytes

            // array<Byte>^ byteTrue = gcnew array<Byte>{ 0, 0, 0, 0 };     // == false

            array<Byte>^ byteTrue = gcnew array<Byte>(4);

            // The following can be null because we don't need the return values

            // array<Byte>^ byOut = gcnew array<Byte>(4);

 

            // Set the SIO_UDP_CONNRESET ioctl to true for this UDP socket. If this UDP socket

            //    ever sends a UDP packet to a remote destination that exists but there is

            //    no socket to receive the packet, an ICMP port unreachable message is returned

            //    to the sender. By default, when this is received the next operation on the

            //    UDP socket that send the packet will receive a SocketException. The native

            //    (Winsock) error that is received is WSAECONNRESET (10054). Since we don't want

            //    to wrap each UDP socket operation in a try/except, we'll disable this error

            //    for the socket with this ioctl call. IOControl is analogous to the WSAIoctl method of Winsock 2

            byteTrue[byteTrue->Length - 1] = 1;

            serverSocket->IOControl(SIO_UDP_CONNRESET, byteTrue, nullptr);

            // serverSocket->IOControl(SIO_UDP_CONNRESET, byteTrue, byOut);

            Console::WriteLine("Server: IOControl() is OK...");

        }

 

        // Service clients in a loop

        while (true)

        {

            if (sockProtocol == ProtocolType::Tcp)

            {

                // Wait for a client connection

                clientSocket = serverSocket->Accept();

                Console::WriteLine("Server: Accept() is OK...");

                Console::WriteLine("Server: Accepted connection from: {0}", clientSocket->RemoteEndPoint->ToString());

 

                // Receive the request from the client in a loop until the client shuts

                //    the connection down via a Shutdown.

                Console::WriteLine("Server: Preparing to receive using Receive()...");

                while (true)

                {

                    rc = clientSocket->Receive(receiveBuffer);

                    Console::WriteLine("Server: Read {0} bytes", rc);

 

                    if (rc == 0)

                        break;

                }

 

                // Send the indicated number of response messages

                Console::WriteLine("Server: Preparing to send using Send()...");

                for (int i = 0; i < sendCount; i++)

                {

                    rc = clientSocket->Send(sendBuffer);

                    Console::WriteLine("Server: Sent {0} bytes", rc);

                }

 

                // Shutdown the client connection

                clientSocket->Shutdown(SocketShutdown::Send);

                Console::WriteLine("Server: Shutdown() is OK...");

                clientSocket->Close();

                Console::WriteLine("Server: Close() is OK...");

            }

            else

            {

                castSenderAddress = (EndPoint^)senderAddress;

 

                // Receive the initial request from the client

                rc = serverSocket->ReceiveFrom(receiveBuffer, castSenderAddress);

                Console::WriteLine("Server: ReceiveFrom() is OK...");

                senderAddress = (IPEndPoint^)castSenderAddress;

                Console::WriteLine("Server: Received {0} bytes from {1}", rc, senderAddress->ToString());

 

                // Send the response to the client the requested number of times

                for (int i = 0; i < sendCount; i++)

                {

                    try

                    {

                        rc = serverSocket->SendTo(sendBuffer, senderAddress);

                        Console::WriteLine("Server: SendTo() is OK...");

                    }

                    catch(Exception^ err)

                    {

                        // If the sender's address is being spoofed we may get an error when sending

                        //    the response. You can test this by using IPv6 and using the RawSocket

                        //    sample to spoof a UDP packet with an invalid link local source address.

                        Console::WriteLine("Error: " + err->Message);

                        continue;

                    }

                    Console::WriteLine("Server: Sent {0} bytes to {1}", rc, senderAddress);

                }

 

                // Send several zero byte datagrams to indicate to client that no more data

                //    will be sent from the server. Multiple packets are sent since UDP

                //    is not guaranteed and we want to try to make an effort the client

                //    gets at least one.

                Console::WriteLine("Server: Preparing to send using SendTo(), on the way do sleeping, Sleep(250)...");

                for (int i = 0; i < 3; i++)

                {

                    serverSocket->SendTo(sendBuffer, 0, 0, SocketFlags::None, senderAddress);

                    // Space out sending the zero byte datagrams a bit. UDP is unreliable and

                    //   the local stack can even drop them before even hitting the wire!

                    Threading::Thread::Sleep(250);

                }

            }

        }

    }

    catch (SocketException^ err)

   {

        Console::WriteLine("Server: Socket error occurred - {0}", err->Message);

        Console::WriteLine("Server: Error code - {0}", err->ErrorCode);

    }

    finally

    {

        // Close the socket if necessary

        if (serverSocket != nullptr)

        {

            Console::WriteLine("Server: Closing the server socket using Close()...");

            serverSocket->Close();

        }

    }

    return 0;

}

 

Build and run the project. The following are sample outputs when the executable run at command prompt. We will test this server program functionality with the client that will be created in the next exercise.

 

C++ Simple Server Socket Program Example - a sample output

 

C++ Simple Server Socket Program Example - a sample output with TCP protocol

 

C++ Simple Server Socket Program Example - a sample output with UDP protocol

 

 

 


 

< Chap 8 TOC: .NET Client Socket Programming | Main | C++ .NET Client Socket Program Example >