Chapter 30
Working with sockets

This chapter describes the socket components that let you create an application that can communicate with other systems using TCP/IP and related protocols. Using sockets, you can read and write over connections to other machines without worrying about the details of the actual networking software. Sockets provide connections based on the TCP/IP protocol, but are sufficiently general to work with related protocols such as Xerox Network System (XNS), Digital's DECnet, or Novell's IPX/SPX family.

Using sockets, you can write network servers or client applications that read from and write to other systems. A server or client application is usually dedicated to a single service such as Hypertext Transfer Protocol (HTTP) or File Transfer Protocol (FTP). Using server sockets, an application that provides one of these services can link to client applications that want to use that service. Client sockets allow an application that uses one of these services to link to server applications that provide the service.

Implementing services

Sockets provide one of the pieces you need to write network servers or client applications. For many services, such as HTTP or FTP, third party servers are readily available. Some are even bundled with the operating system, so that there is no need to write one yourself. However, when you want more control over the way the service is implemented, a tighter integration between your application and the network communication, or when no server is available for the particular service you need, then you may want to create your own server or client application. For example, when working with distributed data sets, you may want to write a layer to communicate with databases on other systems.

Understanding service protocols

Before you can write a network server or client, you must understand the service that your application is providing or using. Many services have standard protocols that your network application must support. If you are writing a network application for a standard service such as HTTP, FTP, or even finger or time, you must first understand the protocols used to communicate with other systems. See the documentation on the particular service you are providing or using.

If you are providing a new service for an application that communicates with other systems, the first step is designing the communication protocol for the servers and clients of this service. What messages are sent? How are these messages coordinated? How is the information encoded?

Communicating with applications

Often, your network server or client application provides a layer between the networking software and an application that uses the service. For example, an HTTP server sits between the Internet and a Web server application that provides content and responds to HTTP request messages.

Sockets provide the interface between your network server or client application and the networking software. You must provide the interface between your application and the applications that use it. You can copy the API of a standard third party server (such as ISAPI), or you can design and publish your own API.

Services and ports

Most standard services are associated, by convention, with specific port numbers. We will discuss port numbers in greater detail later. For now, consider the port number a numeric code for the service.

If you are implementing a standard service, Windows socket objects provide methods for you to look up the port number for the service. If you are providing a new service, you can specify the associated port number in a SERVICES file on Windows 95 or NT machines. See the Microsoft documentation for Windows sockets for more information on setting up a SERVICES file.

Types of socket connections

Socket connections can be divided into three basic types, which reflect how the connection was initiated and what the local socket is connected to. These are

Once the connection to a client socket is completed, the server connection is indistinguishable from a client connection. Both end points have the same capabilities and receive the same types of events. Only the listening connection is fundamentally different, as it has only a single endpoint.

Client connections

Client connections connect a client socket on the local system to a server socket on a remote system. Client connections are initiated by the client socket. First, the client socket must describe the server socket it wishes to connect to. The client socket then looks up the server socket and, when it locates the server, requests a connection. The server socket may not complete the connection right away. Server sockets maintain a queue of client requests, and complete connections as they find time. When the server socket accepts the client connection, it sends the client socket a full description of the server socket to which it is connecting, and the connection is completed by the client.

Listening connections

Server sockets do not locate clients. Instead, they form passive "half connections" that listen for client requests. Server sockets associate a queue with their listening connections; the queue records client connection requests as they come in. When the server socket accepts a client connection request, it forms a new socket to connect to the client, so that the listening connection can remain open to accept other client requests.

Server connections

Server connections are formed by server sockets when a listening socket accepts a client request. A description of the server socket that completes the connection to the client is sent to the client when the server accepts the connection. The connection is established when the client socket receives this description and completes the connection.

Describing sockets

Sockets let your network application communicate with other systems over the network. Each socket can be viewed as an endpoint in a network connection. It has an address that specifies

A full description of a socket connection includes the addresses of the sockets on both ends of the connection. You can describe the address of each socket endpoint by supplying both the IP address or host and the port number.

Before you can make a socket connection, you must fully describe the sockets that form its end points. Some of the information is available from the system your application is running on. For instance, you do not need to describe the local IP address of a client socket--this information is available from the operating system.

The information you must provide depends on the type of socket you are working with. Client sockets must describe the server they want to connect to. Listening server sockets must describe the port that represents the service they provide.

Describing the host

The host is the system that is running the application that contains the socket. You can describe the host for a socket by giving its IP address, which is a string of four numeric (byte) values in the standard Internet dot notation, such as

123.197.1.2

A single system may support more than one IP address.

IP addresses are often difficult to remember and easy to mistype. An alternative is to use the host name. Host names are aliases for the IP address that you often see in Uniform Resource Locators (URLs). They are strings containing a domain name and service, such as

http://www.wSite.Com

Most Intranets provide host names for the IP addresses of systems on the Internet. On Windows 95 and NT machines, if a host name is not available, you can create one for your local IP address by entering the name into the HOSTS file. See the Microsoft documentation on Windows sockets for more information on the HOSTS file.

Server sockets do not need to specify a host. The local IP address can be read from the system. If the local system supports more than one IP address, server sockets will listen for client requests on all IP addresses simultaneously. When a server socket accepts a connection, the client socket provides the remote IP address.

Client sockets must specify the remote host by providing either its host name or IP address.

Choosing between a host name and an IP address

Most applications use the host name to specify a system. Host names are easier to remember, and easier to check for typographical errors. Further, servers can change the system or IP address that is associated with a particular host name. Using a host name allows the client socket to find the abstract site represented by the host name, even when it has moved to a new IP address.

If the host name is unknown, the client socket must specify the server system using its IP address. Specifying the server system by giving the IP address is faster. When you provide the host name, the socket must search for the IP address associated with the host name, before it can locate the server system.

Using ports

While the IP address provides enough information to find the system on the other end of a socket connection, you also need a port number on that system. Without port numbers, a system could only form a single connection at a time. Port numbers are unique identifiers that enable a single system to host multiple connections simultaneously, by giving each connection a separate port number.

Earlier, we described port numbers as numeric codes for the services implemented by network applications. This is actually just a convention that allows listening server connections to make themselves available on a fixed port number so that they can be found by client sockets. Server sockets listen on the port number associated with the service they provide. When they accept a connection to a client socket, they create a separate socket connection that uses a different, arbitrary, port number. This way, the listening connection can continue to listen on the port number associated with the service.

Client sockets use an arbitrary local port number, as there is no need for them to be found by other sockets. They specify the port number of the server socket to which they want to connect so that they can find the server application. Often, this port number is specified indirectly, by naming the desired service.

Using socket components

The Internet palette page includes two socket components (client sockets and server sockets) that allow your network application to form connections to other machines, and that allow you to read and write information over that connection. Associated with each of these socket components are Windows socket objects, which represent the endpoint of an actual socket connection. The socket components use the Windows socket objects to encapsulate the Windows socket API calls, so that your application does not need to be concerned with the details of establishing the connection or managing the socket messages.

If you want to work with the Windows socket API calls, or customize the details of the connections that the socket components make on your behalf, you can use the properties, events, and methods of the Windows socket objects.

Using client sockets

Add a client socket component (TClientSocket) to your form or data module to turn your application into a TCP/IP client. Client sockets allow you to specify the server socket you want to connect to, and the service you want that server to provide. Once you have described the desired connection, you can use the client socket component to complete the connection to the server.

Each client socket component uses a single client Windows socket object (TClientWinSocket) to represent the client endpoint in a connection.

Specifying the desired server

Client socket components have a number of properties that allow you to specify the server system and port to which you want to connect. You can specify the server system by its host name using the Host property. If you do not know the host name, or if you are concerned about the speed of locating the server, you can specify the IP address of the server system by using the Address property. You must specify either a host name or an IP address. If you specify both, the client socket component will use the host name.

In addition to the server system, you must specify the port on the server system that your client socket will connect to. You can specify the server port number directly using the Port property, or indirectly by naming the desired service using the Service property. If you specify both the port number and the service, the client socket component will use the service name.

Forming the connection

Once you have set the properties of your client socket component to describe the server you want to connect to, you can form the connection at runtime by calling the Open method. If you want your application to form the connection automatically when it starts up, set the Active property to True at design time, using the Object Inspector.

Getting information about the connection

After completing the connection to a server socket, you can use the client Windows socket object associated with your client socket component to obtain information about the connection. Use the Socket property to get access to the client Windows socket object. This Windows socket object has properties that enable you to determine the address and port number used by the client and server sockets to form the end points of the connection. You can use the SocketHandle property to obtain a handle to the socket connection to use when making Windows socket API calls. You can use the Handle property to access the window that receives messages from the socket connection. The ASyncStyles property determines what types of messages that window handle receives.

Closing the connection

When you have finished communicating with a server application over the socket connection, you can shut down the connection by calling the Close method. The connection may also be closed from the server end. If that is the case, you will receive notification in an OnDisconnect event.

Using server sockets

Add a server socket component (TServerSocket) to your form or data module to turn your application into a TCP/IP server. Server sockets allow you to specify the service you are providing or the port you want to use to listen for client requests. You can use the server socket component to listen for and accept client connection requests.

Each server socket component uses a single server Windows socket object (TServerWinSocket) to represent the server endpoint in a listening connection. It also uses a server client Windows socket object (TServerClientWinSocket) for the server endpoint of each active connection to a client socket that the server accepts.

Specifying the port

Before your server socket can listen to client requests, you must specify the port that your server will listen on. You can specify this port using the Port property. If your server application is providing a standard service that is associated by convention with a specific port number, you can specify the port number indirectly using the Service property. It is a good idea to use the Service property, as it is easy to miss typographical errors made when setting the port number. If you specify both the Port property and the Service property, the server socket will use the service name.

Listening for client requests

Once you have set the port number of your server socket component, you can form a listening connection at runtime by calling the Open method. If you want your application to form the listening connection automatically when it starts up, set the Active property to True at design time, using the Object Inspector.

Connecting to clients

A listening server socket component automatically accepts client connection requests when they are received. You receive notification every time this occurs in an OnClientConnect event.

Getting information about connections

Once you have opened a listening connection with your server socket, you can use the server Windows socket object associated with your server socket component to obtain information about the connection. Use the Socket property to get access to the server Windows socket object. This Windows socket object has properties that enable you to find out about all the active connections to client sockets that were accepted by your server socket component. Use the SocketHandle property to obtain a handle to the socket connection to use when making Windows socket API calls. Use the Handle property to access the window that receives messages from the socket connection.

Each active connection to a client application is encapsulated by a server client Windows socket object (TServerClientWinSocket). You can access all of these through the Connections property of the server Windows socket object. These server client Windows socket objects have properties that enable you to determine the address and port number used by the client and server sockets which form the end points of the connection. You can use the SocketHandle property to obtain a handle to the socket connection to use when making Windows socket API calls. You can use the Handle property to access the window that receives messages from the socket connection. The ASyncStyles property determines what types of messages that window handle receives.

Closing server connections

When you want to shut down the listening connection, call the Close method. This shuts down all open connections to client applications, cancels any pending connections that have not been accepted, and then shuts down the listening connection so that your server socket component does not accept any new connections.

When clients shut down their individual connections to your server socket, you are informed by an OnClientDisconnect event.

Responding to socket events

When writing applications that use sockets, most of the work usually takes place in event handlers of the socket components. Some sockets generate events when it is time to begin reading or writing over the socket connection. These are described in "Reading and writing events".

Client sockets receive an OnDisconnect event when the server ends a connection, and server sockets receive an OnClientDisconnect event when the client ends a connection.

Both client sockets and server sockets generate error events when they receive error messages from the connection.

Socket components also receive a number of events in the course of opening and completing a connection. If your application needs to influence how the opening of the socket proceeds, or if it should start reading or writing once the connection is formed, you will want to write event handlers to respond to these client events or server events.

Error events

Client sockets generate an OnError event when they receive error messages from the connection. Server sockets generate an OnClientError. You can write an OnError or OnClientError event handler to respond to these error messages. The event handler is passed information about

You can respond to the error in the event handler, and change the error code to 0 to prevent the socket from raising an exception.

Client events

When a client socket opens a connection, the following events occur:

  1. An OnLookup event occurs prior to an attempt to locate the server socket. At this point you can not change the Host, Address, Port, or Service properties to change the server socket that is located. You can use the Socket property to access the client Windows socket object, and use its SocketHandle property to make Windows API calls that affect the client properties of the socket. For example, if you want to set the port number on the client application, you would do that now before the server client is contacted.
  2. The Windows socket is set up and initialized for event notification.
  3. An OnConnecting event occurs after the server socket is located. At this point, the Windows Socket object available through the Socket property can provide information about the server socket that will form the other end of the connection. This is the first chance to obtain the actual port and IP address used for the connection, which may differ from the port and IP address of the listening socket that accepted the connection.
  4. The connection request is accepted by the server and completed by the client socket.
  5. An OnConnect event occurs after the connection is established. If your socket should immediately start reading or writing over the connection, write an OnConnect event handler to do it.

Server events

Server socket components form two types of connections: listening connections and connections to client applications. The server socket receives events during the formation of each of these connections.

Events when listening

Just before the listening connection is formed, the OnListen event occurs. At this point you can obtain the server Windows socket object through the Socket property. You can use its SocketHandle property to make changes to the socket before it is opened for listing. For example, if you want to restrict the IP addresses the server uses for listening, you would do that in an OnListen event handler.

Events with client connections

When a server socket accepts a client connection request, the following events occur:

  1. The server socket generates an OnGetSocket event, passing in the Windows socket handle for the socket that forms the server endpoint of the connection. If you want to provide your own customized descendant of TServerClientWinSocket, you can create one in an OnGetSocket event handler, and that will be used instead of TServerClientWinSocket.
  2. An OnAccept event occurs, passing in the new TServerClientWinSocket object to the event handler. This is the first point when you can use the properties of TServerClientWinSocket to obtain information about the server endpoint of the connection to a client.
  3. If ServerType is stThreadBlocking an OnGetThread event occurs. If you want to provide your own customized descendant of TServerClientThread, you can create one in an OnGetThread event handler, and that will be used instead of TServerClientThread. For more information on creating custom server client threads, see "Writing server threads".
  4. If ServerType is stThreadBlocking, an OnThreadStart event occurs as the thread begins execution. If you want to perform any initialization of the thread, or make any Windows socket API calls before the thread starts reading or writing over the connection, use the OnThreadStart event handler.
  5. The client completes the connection and an OnClientConnect event occurs. With a non-blocking server, you may want to start reading or writing over the socket connection at this point.

Reading and writing over socket connections

The reason you form socket connections to other machines is so that you can read or write information over those connections. What information you read or write, or when you read it or write it, depends on the service associated with the socket connection.

Reading and writing over sockets can occur asynchronously, so that it does not block the execution of other code in your network application. This is called a non-blocking connection. You can also form blocking connections, where your application waits for the reading or writing to be completed before executing the next line of code.

Non-blocking connections

Non-blocking connections read and write asynchronously, so that the transfer of data does not block the execution of other code in you network application. To create a non-blocking connection

When the connection is non-blocking, reading and writing events inform your socket when the socket on the other end of the connection tries to read or write information.

Reading and writing events

Non-blocking sockets generate reading and writing events that inform your socket when it needs to read or write over the connection. With client sockets, you can respond to these notifications in an OnRead or OnWrite event handler. With server sockets, you can respond to these events in an OnClientRead or OnClientWrite event handler.

The Windows socket object associated with the socket connection is provided as a parameter to the read or write event handlers. This Windows socket object provides a number of methods to allow you to read or write over the connection.

To read from the socket connection, use the ReceiveBuf or ReceiveText method. Before using the ReceiveBuf method, use the ReceiveLength method to get an estimate of the number of bytes the socket on the other end of the connection is ready to send.

To write to the socket connection, use the SendBuf, SendStream, or SendText method. If you have no more need of the socket connection after you have written your information over the socket, you can use the SendStreamThenDrop method. SendStreamThenDrop closes the socket connection after writing all information that can be read from the stream. If you use the SendStream or SendStreamThenDrop method, do not free the stream object. The socket frees the stream automatically when the connection is closed.

Note: SendStreamThenDrop will close down a server connection to an individual client, not a listening connection.

Blocking connections

When the connection is blocking your socket must initiate reading or writing over the connection rather than waiting passively for a notification from the socket connection. Use a blocking socket when your end of the connection is in charge of when reading and writing takes place.

For client sockets, set the ClientType property to ctBlocking to form a blocking connection. Depending on what else your client application does, you may want to create a new execution thread for reading or writing, so that your application can continue executing code on other threads while it waits for the reading or writing over the connection to be completed.

For server sockets, set the ServerType property to stThreadBlocking to form a blocking connection. Because blocking connections hold up the execution of all other code while the socket waits for information to be written or read over the connection, server socket components always spawn a new execution thread for every client connection when the ServerType is stThreadBlocking.

Using threads with blocking connections

Client sockets do not automatically spawn new threads when reading or writing using a blocking connection. If your client application has nothing else to do until the information has been read or written, this is what you want. If your application includes a user interface that must still respond to the user, however, you will want to generate a separate thread for the reading or writing.

When server sockets form blocking connections, they always spawn separate threads for every client connection, so that no client must wait until another client has finished reading or writing over the connection. By default, server sockets use TServerClientThread objects to implement the execution thread for each connection.

TServerClientThread objects simulate the OnClientRead and OnClientWrite events that occur with non-blocking connections. However, these events occur on the listening socket, which is not thread-local. If client requests are frequent, you will want to create your own descendent of TServerClientThread to provide thread-safe reading and writing.

Using TWinSocketStream

When implementing the thread for a blocking connection, you must determine when the socket on the other end of the connection is ready for reading or writing. Blocking connections do not notify the socket when it is time to read or write. To see if the connection is ready, use a TWinSocketStream object. TWinSocketStream provides methods to help coordinate the timing of reading and writing. Call the WaitForData method to wait until the socket on the other end is ready to write.

When reading or writing using TWinSocketStream, the stream times out if the reading or writing has not completed after a specified period of time. As a result of this timing out, the socket application won't hang endlessly trying to read or write over a dropped connection.

Note: You can not use TWinSocketStream with a non-blocking connection.

Writing client threads

To write a thread for client connections, define a new thread object using the New Thread Object dialog. For more information, see "Defining thread objects".

The Execute method of your new thread object handles the details of reading and writing over the thread connection. It creates a TWinSocketStream object, and uses that to read or write. For example:

procedure TMyClientThread.Execute;
var
  TheStream: TWinSocketStream;
  buffer: string;
begin
  { create a TWinSocketStream for reading and writing }
  TheStream := TWinSocketStream.Create(ClientSocket1.Socket, 60000);
  try
    { fetch and process commands until the connection or thread is terminated }
    while (not Terminated) and (ClientSocket1.Active) do
    begin
      try
        GetNextRequest(buffer); { GetNextRequest must be a thread-safe method }
        { write the request to the server }
        TheStream.Write(buffer, Length(buffer) + 1);
        { continue the communication (eg read a response from the server) }
        ...
      except
        if not(ExceptObject is EAbort) then
          Synchronize(HandleThreadException); { you must write HandleThreadException }
      end;
    end;
  finally
   TheStream.free;
  end;
end;

To use your thread, create it in an OnConnect event handler. For more information about creating and running threads, see "Executing thread objects".

Writing server threads

Threads for server connections are descendents of TServerClientThread. Because of this, you can't use the New Thread object dialog. Instead, declare your thread manually as follows:

 TMyServerThread = class(TServerClientThread);

To implement this thread, you override the ClientExecute method instead of the Execute method.

Implementing the ClientExecute method is much the same as writing the Execute method of the thread for a client connection. However, instead of using a client socket component that you place in your application from the Component palette, the server client thread must use the TServerClientWinSocket object that is created when the listening server socket accepts a client connection. This is available as the public ClientSocket property. In addition, you can use the protected HandleException method rather than writing your own thread-safe exception handling. For example:

procedure TMyServerThread.ClientExecute;
var
  Stream : TWinSocketStream;
  Buffer : array[0 .. 9] of Char;
begin
  { make sure connection is active }
  while (not Terminated) and ClientSocket.Connected do
  begin
    try
      Stream := TWinSocketStream.Create(ClientSocket, 60000);
      try
        FillChar(Buffer, 10, 0); { initialize the buffer }
        { give the client 60 seconds to start writing }
        if Stream.WaitForData(60000) then  
        begin
          if Stream.Read(Buffer, 10) = 0 then { if can't read in 60 seconds }
            ClientSocket.Close;               { close the connection }
          { now process the request }
          ...
        end
        else
          ClientSocket.Close; { if client doesn't start, close }
      finally
        Stream.Free;
      end;
    except
      HandleException;
    end;
  end;
end;

Warning: Server sockets cache the threads they use. Be sure the ClientExecute method performs any necessary initialization so that there are no adverse results from changes made when the thread last executed.

To use your thread, create it in an OnGetThread event handler. When creating the thread, set the CreateSuspended parameter to False.