menu
  Home  ==>  papers  ==>  web  ==>  socket_programming   

Windows Socket Programming - Felix John COLIBRI.

  • abstract : presents Windows Socket Programming using the WinSock API calls from Delphi
  • key words : socket, WinSock, Send, Recv, Bind, Accept, Connect, Client / Server
  • software used : Windows XP, Delphi 6
  • hardware used : Pentium 1.400Mhz, 256 M memory, 140 G hard disc
  • scope : Delphi 1 to 8 for Windows
  • level : Delphi developer
  • plan :


1 - Introduction

We will present a simple project showing how to communicate between a Client and a Server using Windows Sockets.




2 - Socket Communication

2.1 - The File Model

The goals of Sockets was to communicate between two PCs linked by a network using calls similar to file handling calls: open, read/write, close.

The main difference between a file system and a network is that the file system is always available and ready (the Os offers services which are always present), whereas the remote PC has to be located and checked for availability.

So the basic model of communication between 2 PCs thru a network is a Client Server model:

  • the Server is started and waits for Clients
  • when any client wants to communicate, it searches the Server, then opens a communication, and this commnication will continue until either the Server or the Client decide to stop.
The identification of the Server is done using a 4 byte address (80.15.235.71), which is linked to a human readable string alias (www.borland.com). Nodes (routers) in the network keep lists of the name4 byte address associations, which allows a Client to find a Server using either the name or the 4 byte address.

When the Client has located the Server, it must tell the server what the purpose of the communication is: retrieve some mail, send a file, download a web page etc.. This is somehow similar the to file extension: .EXE, .TXT etc. The type of service is encoded in a number called a service or a Port. For instance 80 is the port for retrieving web pages, 110 the port for retrieving mails etc. The port is associated with a protocol which specifies the format and structure of the communication. For mail retrieval, the Server is prepared to send a list of available e-mails, send then content of a specified e-mail or delete a specified e-mail. And the Client can get the list, get or delete an e-mail.

In our example, we will invent a file retrieval service:

  • the Port will be 5678
  • the Client will sent a file name followed by Return LineFeed
  • the Server will send the file size followed by Return LineFeed and the file bytes


2.2 - The Socket

A socket is basically a record containing 4 information
  • the local address and Port
  • the remote address and Port
socket parameters

This record is stored in Windows tables, and managed as an opaque structure:

  • we create the structure with a Socket call, which returns an handle of type tSocket (a positive integer identifying the structure)
  • we use this handle for all the socket calls (Connect, Send etc)
  • we free the structure with a CloseSocket call


When the communication starts, only the local parameters are known, but during connection, the remote part is filled
  • the Server is located at, say, 43.3.1.0, and knows how to handle 5678 requests. The Server is started:

    server start

  • a Client starts at address 3.22.0.1, and tries to connect to (43.3.1.0, 5678):

    client start

  • the Server receives the request, and both socket records are filled:

    client start



2.3 - One Server, Several Clients

The Server is supposed to communicate with several Clients. Therefore it cannot store the remote parameters in a single socket. It has to spawn separate sockets for each incoming Client.

Therefore:

  • the Server uses a Server socket for welcoming the potential Clients
  • any new Client first connects to the Server socket:

    socket parameters

  • the Server creates a new "server client socket", links this new socket to the Client, and the communication will flow between this socket and the Client, while the Server socket will be free to watch for other Clients

    socket parameters

  • and each new Client uses its own "server client socket":

    socket parameters



2.4 - The basic primitives

The communication uses a library (DLL), providing routines for creating, connecting, reading, writing and closing sockets.

For the Server:

  • Socket creates the record, and returns a handle to this record
  • Bind tells which address the Server is prepared to receive (usually all)
  • Listen start the Client welcoming mode
  • when an Client arrives, Accept creates the socket for this Client
  • Recv and Send exchange the data
  • Close terminates the communication to all Clients
For the Client:
  • Socket creates the record, and returne a handle to this record
  • Connect sends the requested 4 byte address on the network and returns when the Server has created the client socket
  • Recv and Send exchange the data
  • Close terminates the communication


2.5 - Windows Sockets

On Unix, where the sockets were first created, all communication is handled in threads:
  • on the Server side:
    • the main program starts a Server thread and continues performing other tasks
    • this thread waits for Clients, and then starts a thread for each incoming Client. All Recv and Send are blocking routines: the control returns only after completion. Since the blocking calls are within threads, the main program can still perform other tasks. And while the Client has not finished reading or writing whatever the Server is sending ar receiving, there is nothing else to do anyway.
  • on the Client side
    • the main program starts a Client thread and continues on other jobs
    • this thread tries to connect, and blocks until the Server answers. Each Send and Recv is also blocking
The first Windows OS was non preemptive. If a Client was blocked waiting for the Server to connect, or to read and write, the whole application had to wait and was frozen. Threads did not exist at this time.

So, in order to avoid this stop and go mode, some flexibility was introduced allowing the application to still somehow react to user actions. The whole socket library was casted in an event mold using Windows messages:

  • on the the Server side:
    • after Listen, the control returns to the program.
    • when a Client connects, the program receives a fd_connect message, and the program can call Accept to create the new server client socket
    • when this new server client socket can send data (there is nothing in the sending buffer), a fd_write message is issued
    • after the socket has received some data, an fd_read message is issued
    • an fd_close notification tells that the Clients terminated the communication
  • on the Client side:
    • after Connect, the control returns to the program
    • and fd_connect message is sent when the connection is established
    • the same fd_write and fd_read messages are sent do notify the program about write availability or data reception
    • fd_close tells when the server client socket closed
This was built into the WinSock library.



Note that:

  • on the Client side, the event model allows the application to perform other tasks, but if the main purpose the application is the communication with the Server, introducing messages only adds some responsiveness. All the user can do is to move the window around or play with the mouse. Events won't fetch buffers faster or increase bandwidth
  • on the Server side, message scheduling is much more usefull: if the Server handles slow and fast Clients, they are decoupled by the message scheduling: the whole Server does not wait until a slow Client has finished its work
So using the WinSock library, we can work in three different modes:
  • use the message notification (asynchronous mode)
  • use blocking (synchronous) sockets with threads, since threads are now available in Windows 98, NT and later OSes
  • use blocking (synchronous) sockets without threads (not often used)


Assuming that we do not use the fd_write notification, here is an example of Client / Server dialog:

socket parameters



3 - The Delphi Code

3.1 - The Server Client Socket List

We placed the server client sockets in a container derived from tStringList. The c_server_client_socket is defined by (partial):

 c_server_client_socket=
     Class(c_basic_object)
       m_socket_handletSocket;
       m_c_reception_bufferc_byte_buffer;

       Constructor create_server_client_socket(p_nameString;
           p_socket_handletSocket);

       procedure handle_fd_read_notification;
       procedure send_file(p_file_nameString);

       Destructor DestroyOverride;
     end// c_server_client_socket

When fd_read is received, the following code retrieves the request, using Recv:

procedure c_server_client_socket.handle_fd_read_notification;
  var l_remainingInteger;
      l_pt_start_receptionPointer;
      l_packet_bytesInteger;
      l_eol_positionInteger;
      l_file_nameString;
  begin
    with m_c_reception_buffer do
    begin
      l_remaining:= m_buffer_sizem_write_index;

      // -- if not at least a tcp-ip chunk, increase the size
      if l_remainingk_tcp_ip_chunk
        then begin
            // -- reallocate
            double_the_capacity;
            l_remaining:= m_buffer_sizem_write_index;
          end;

      // -- add the received data to the current buffer
      l_pt_start_reception:= @ m_oa_byte_buffer[m_write_index];

      // -- get the data from the client socket
      l_packet_bytes:= Recv(m_socket_handlel_pt_start_reception^, l_remaining, 0);
      if l_packet_bytes< 0
        then display_socket_error_halt(m_socket_handle,
            'sc.fd_read (Recv)'WSAGetLastError)
        else
          if l_packet_bytes> 0
            then begin
                m_write_index:= m_write_indexl_packet_bytes;
                l_eol_position:= f_return_line_feed_position(0);
                if l_eol_position> 0
                  then begin
                      // -- -3: remove eol
                      l_file_name:= f_extract_string_start_end(0, l_eol_position- 3);
                      send_file(l_file_name);
                    end;
              end// if l_packet_bytes
    end// with m_c_reception_buffer
  end// handle_fd_read_notification

The file is sent to the Client using Send:

procedure c_server_client_socket.send_file(p_file_nameString);
  var l_file_nameString;
      l_answerString;
      l_resultInteger;
      l_pt_start_emissionPointer;
  begin
    l_file_name:= '..\socket\_data\'p_file_name;

    with c_byte_buffer.create_byte_buffer('load', 0) do
    begin
      load_from_file(l_file_name);

      l_answer:= IntToStr(m_write_index)+ k_new_line;
      l_result:= Send(m_socket_handlel_answer[1], Length(l_answer), 0);
      if l_result< 0
        then display_socket_error_halt(m_socket_handle'c.send_string (Send)',
             WSAGetLastError);

      l_pt_start_emission:= @ m_oa_byte_buffer[0];
      l_result:= Send(m_socket_handlel_pt_start_emission^, m_write_index, 0);
      if l_result< 0
        then display_socket_error_halt(m_socket_handle'c.send_string (Send)',
             WSAGetLastError);
      Free;
    end// with c_byte_buffer
  end// send_file

Each c_server_client_socket is managed by any kind of object list, like the following tStringList container (partial):

 c_server_client_socket_list=
     Class(c_basic_object)
       m_c_server_client_socket_listtStringList;

       Constructor create_server_client_socket_list(p_nameString);

       function f_c_server_client_socket(
           p_server_client_socket_indexInteger): c_server_client_socket;
       function f_index_of(p_server_client_socket_handletSocket): Integer;
       function f_c_find_server_client_socket_by_handle(
           p_server_client_socket_handletSocket): c_server_client_socket;
       function f_c_add_unique_server_client_socket(
           p_server_client_socket_handletSocket): c_server_client_socket;
       procedure delete_server_client_socket(p_server_client_socket_indexInteger);
     end// c_server_client_socket_list



3.2 - The Server

The Server is handled in the main form of the server application. We declare the following globals:

var g_wsa_datatwsaData;
    g_server_socket_handletSocketInvalid_socket;
    g_c_server_client_socket_listc_server_client_socket_listNil;



The WinSock library is started with:

procedure TForm1.wsaStartup_Click(SenderTObject);
  var l_versionWord;
      l_resultInteger;
  begin
    // -- l_version:= $0202;
    l_version:= $0101;
    l_result:= wsaStartup(l_versiong_wsa_data);
    if l_result<> 0
      then display_socket_error_halt(Invalid_socket,
          's.wsaStartup 'IntToStr(l_result), WSAGetLastError);
  end// wsaStartup

The socket structure is initialized with:

procedure TForm1.Socket_Click(SenderTObject);
  begin
    // -- allocate the Socket record, and get back its handle
    g_server_socket_handle:= Socket(PF_INETSOCK_STREAMIPPROTO_IP);

    if g_server_socket_handleINVALID_SOCKET
      then display_socket_error_halt(INVALID_SOCKET's.Socket',
            WSAGetLastError);

    // -- also create the client socket list
    g_c_server_client_socket_list:= c_server_client_socket_list
        .create_server_client_socket_list('server_client_list');
  end// Socket_clic

We specify which notification we want to receiveve:

procedure TForm1.wsaAsyncSelect_Click(SenderTObject);
  var l_resultInteger;
  begin
    l_result:= wsaAsyncSelect(g_server_socket_handleHandle,
        wm_asynch_select,
        FD_ACCEPTFD_READFD_WRITEFD_CLOSE);
    if l_result<> 0
      then display_socket_error_halt(g_server_socket_handle,
          's.wsaAsyncSelect'WSAGetLastError);
  end// wsaAsyncSelect

And specify the Port we are offering, and start waiting for Clients:

procedure TForm1.Bind_Click(SenderTObject);
    // -- assigns an address to the socket
  var l_resultInteger;
      l_address_socket_intSockAddrIn;
      l_portInteger;
  begin
    l_port:= StrToInt(port_edit_.Text);

    FillChar(l_address_socket_insizeof(l_address_socket_in), 0);
    with l_address_socket_in do
    begin
      sin_family:= af_Inet;
      sin_port:= hToNs(l_port);
      sin_addr.s_addr:= InAddr_Any// $00000000
    end;

    l_result:= Bind(g_server_socket_handlel_address_socket_in,
        sizeof(l_address_socket_in));
    if l_result<> 0
      then display_socket_error_halt(g_server_socket_handle's.Bind',
          WSAGetLastError);
  end// bind_Click

procedure TForm1.Listen_Click(SenderTObject);
  var l_resultInteger;
  begin
    l_result:= Listen(g_server_socket_handlek_listen_queue_size);
    if l_result<> 0
      then display_socket_error_halt(g_server_socket_handle's.Listen',
          WSAGetLastError);
  end// Listen_Click



Now the interesting part: whenever the Winsock library wants to notify us, it calls a Windows Message handler:

  • The handler is declared in the tForm1 CLASS as a MESSAGE method:

    const wm_asynch_selectwm_User;

    type TForm1class(TForm)
                   // -- buttons etc
                   procedure handle_wm_async_select(var MsgTMessage);
                       message wm_asynch_select;
                 end// tForm1

  • the handler itself looks like this

    procedure tForm1.handle_wm_async_select(var MsgTMessage);
      // -- wParam: hSocket, lo(lParam): notification, hi(lParam): error
      var l_paramInteger;
          l_errorl_notificationInteger;
          l_socket_handleInteger;

      // -- here the notification procedures

      begin // handle_wm_async_select
        l_param:= Msg.lParam;
        l_socket_handle:= Msg.wParam;
        l_error:= wsaGetSelectError(l_param);
        l_notification:= wsaGetSelectEvent(l_param);

        if l_error<= wsaBaseErr
          then
            case l_notification of
              FD_ACCEPThandle_fd_accept_notification(l_socket_handle);
              FD_CONNECTdisplay('no server fd_connect');
              FD_WRITEhandle_fd_write_notification(l_socket_handle);
              FD_READhandle_fd_read_notification(l_socket_handle);
              FD_CLOSEhandle_fd_close_notification(l_socket_handle);
            end // case
          else display_socket_error_halt(l_socket_handle,
             's.handle_wm_async_select '
             + f_socket_notification_name(l_notification), l_error);
      end// handle_wm_async_select

  • and for each kind of message, we use a separate procedure. Here is the procedure which creates a new server client socket:

    procedure handle_fd_accept_notification(p_sockettSocket);
      var l_address_socket_intSockAddrIn;
          l_address_sizeInteger;
          l_server_client_sockettSocket;
      begin
        l_address_size:= sizeof(l_address_socket_in);
        l_server_client_socket:= Accept(p_socket, @l_address_socket_in
           @l_address_size);

        // -- add to the list to be able to close them
        g_c_server_client_socket_list
            .f_c_add_unique_server_client_socket(l_server_client_socket);
      end// handle_fd_accept_notification

    and the notification of data reception:

    procedure handle_fd_read_notification(p_sockettSocket);
      var l_c_server_client_socketc_server_client_socket;
      begin
        // -- find this server client socket
        l_c_server_client_socket:= g_c_server_client_socket_list
            .f_c_find_server_client_socket_by_handle(p_socket);
        l_c_server_client_socket.handle_fd_read_notification;
      end// handle_fd_read_notification



fd_write, fd_close and CloseSocket_Click are handled in a similar fashion.



Here is a snapshot of the Server (after receiving a Client Connect):

client



3.3 - The Client

The client is now easy to code, since there is only one socket to take care of.

The globals are:

var g_wsa_datatwsaData;
    g_client_socket_handletSocketInvalid_Socket;
    g_requested_pageString'';
    g_c_reception_bufferc_byte_bufferNil;
    g_file_sizeInteger= 0;



wsaStartup and Socket are same as for the Server.

We select fd_connect, fd_write, fd_read and fd_close:

procedure TForm1.wsaAsyncSelect_Click(SenderTObject);
  var l_resultInteger;
  begin
    l_result:= wsaAsyncSelect(g_client_socket_handleHandle,
        wm_asynch_select,
        FD_CONNECTFD_READFD_WRITEFD_CLOSE);
    if l_result<> 0
      then display_socket_error_halt(g_client_socket_handle's.wsaAsyncSelect',
          WSAGetLastError);
  end// wsaAsyncSelect

Connection is established by calling Connect

procedure TForm1.Connect_Click(SenderTObject);
  var l_socket_address_intSockAddrIn;
      l_ip_zarray[0..255] of char;
      l_resultl_errorInteger;
      l_portInteger;
      l_server_ipString;
  begin
    l_port:= StrToInt(port_edit_.Text);
    l_server_ip:= server_ip_edit_.Text;

    FillChar(l_socket_address_insizeof(l_socket_address_in), 0);
    with l_socket_address_in do
    begin
      sin_family:= pf_Inet;
      // -- the requested service
      sin_port:= hToNs(l_port);
      // -- the server IP address
      StrPCopy(l_ip_zl_server_ip);
      sin_addr.s_Addr:= inet_addr(l_ip_z);
    end// with m_socket_address_in

    // -- connect
    l_result:= Connect(g_client_socket_handlel_socket_address_in,
        sizeof(l_socket_address_in));

    if l_result<> 0
      then
        begin
          l_error:= WSAGetLastError;
          if l_errorwsaEWouldBlock
            then display('err wsaWouldBlock: wait...')
            else display_socket_error_halt(g_client_socket_handle'c.Connect',
                    l_error);
        end;

    g_c_reception_buffer:= c_byte_buffer.create_byte_buffer('reception_buffer',
        k_buffer_max);
  end// Connect_Click



And we create a message handler nearly identical to the server procedure, with only fd_connect handling (instead of fd_accept), and specific fd_read handling:

procedure handle_fd_read_notification(p_sockettSocket);
  var l_remainingInteger;
      l_pt_start_receptionPointer;
      l_packet_bytesInteger;
      l_eol_positionInteger;
  begin
    with g_c_reception_buffer do
    begin
      l_remaining:= m_buffer_sizem_write_index;

      // -- if not at least a tcp-ip chunk, increase the room
      if l_remainingk_tcp_ip_chunk
        then begin
            // -- reallocate
            double_the_capacity;
            l_remaining:= m_buffer_sizem_write_index;
          end;

      // -- add the received data to the current buffer
      l_pt_start_reception:= @ m_oa_byte_buffer[m_write_index];

      // -- get the data from the client socket
      l_packet_bytes:= Recv(g_client_socket_handlel_pt_start_reception^, 
          l_remaining, 0);
      if l_packet_bytes< 0
        then display_socket_error_halt(g_client_socket_handle,
              'c.fd_read (Recv)'WSAGetLastError)
        else
          if l_packet_bytes> 0
            then begin
                m_write_index:= m_write_indexl_packet_bytes;

                // -- fetch the size
                l_eol_position:= f_return_line_feed_position(0);
                if (g_file_size= 0) and (l_eol_position> 0)
                  then begin
                      g_file_size:= StrToInt(f_extract_string_start_end(0,
                          l_eol_position- 3));
                      remove_from_start(l_eol_position);
                    end;

                set_panel_color(show_clLime);
              end// if l_packet_bytes
    end// with g_c_reception_buffer
  end// handle_fd_read_notification



The received buffer can then be displayed.



And here is a snapshot of the Client (after Connect but before sending the request):

client




4 - What's Next ?

4.1 - The libraries

ALL TCP/IP application use the low level Socket, Bind, Listen, Accept, Send, Recv and CloseSocket primitives. Unless directly reading and writing packets from network card (using a driver) there's nothing else.

However the basic calls are usually packaged in libraries which offer higher level services: requesting Web pages, sending files etc.

Many such libraries have been created:

  • the first Pascal code I saw was from Andreas HORSTEMEIER
  • several commercial librairies the were offered for Delphi
  • Delphi itself first uses then NetManage library, and then switched to Indy
  • 2 mainstream Delphi source librairies are popular:
    • ICS from Francois PIETTE, which uses asynchronous windows sockets
    • Indy, previously Winshoes, which has smaller units (finer grain) and work with blocking sockets


4.2 - To Block or not to Block

Which is better ? Both sir. Its a matter of style and personal choice. Just some hints:
  • blocking mode code is simpler, since the steps are linear, in a DOS write / read fashion, everything between a BEGIN and an END. However this BEGIN and END is in a thread, so you have to battle with threads (Execute, Synchronize, Terminate, communication with the main GUI thread, critical sections etc)
  • asynchronous mode avoids thread hassles, but you have to split the work in multiple events called whenever the opposite party has finished a small part of the work
Basically both are the same: while the other party is busy or the work in progress not finished, you have to wait. The code having called Recv for instance is put on the back burner either by Windows (which does not send the fd_receive message) or the thread scheduler (which keeps the thread in waiting mode until the reception is completed).

WinSock was only created because Windows 3 had no efficent process mechanism (CreateProcess is very expensive, and communication between the spawned processes akward) whereas the fork in Unix is much lighter. Threads were created later to allow multitasking in Windows, and asynchronous sockets are no longer a necessity. But you still have the choice.




5 - Download the Sources

Here are the source code files:

Those .ZIP files contain:
  • the main program (.DPR, .DOF, .RES), the main form (.PAS, .DFM), and any other auxiliary form
  • any .TXT for parameters
  • all units (.PAS) for units
Those .ZIP
  • are self-contained: you will not need any other product (unless expressly mentioned).
  • can be used from any folder (the pathes are RELATIVE)
  • will not modify your PC in any way beyond the path where you placed the .ZIP (no registry changes, no path creation etc).
To use the .ZIP:
  • create or select any folder of your choice
  • unzip the downloaded file
  • using Delphi, compile and execute
To remove the .ZIP simply delete the folder.



As usual:

  • please tell us at fcolibri@felix-colibri.com if you found some errors, mistakes, bugs, broken links or had some problem downloading the file. Resulting corrections will be helpful for other readers
  • we welcome any comment, criticism, enhancement, other sources or reference suggestion. Just send an e-mail to fcolibri@felix-colibri.com.
  • or more simply, enter your (anonymous or with your e-mail if you want an answer) comments below and clic the "send" button
    Name :
    E-mail :
    Comments * :
     

  • and if you liked this article, talk about this site to your fellow developpers, add a link to your links page ou mention our articles in your blog or newsgroup posts when relevant. That's the way we operate: the more traffic and Google references we get, the more articles we will write.



6 - The author

Felix John COLIBRI works at the Pascal Institute. Starting with Pascal in 1979, he then became involved with Object Oriented Programming, Delphi, Sql, Tcp/Ip, Html, UML. Currently, he is mainly active in the area of custom software development (new projects, maintenance, audits, BDE migration, Delphi Xe_n migrations, refactoring), Delphi Consulting and Delph training. His web site features tutorials, technical papers about programming with full downloadable source code, and the description and calendar of forthcoming Delphi, FireBird, Tcp/IP, Web Services, OOP  /  UML, Design Patterns, Unit Testing training sessions.
Created: may-05. Last updated: sep-16 - 107 articles, 228 .ZIP sources, 1174 figures
Copyright © Felix J. Colibri   http://www.felix-colibri.com 2004 - 2016. All rigths reserved
Back:    Home  Papers  Training  Delphi developments  Links  Download
the Pascal Institute

Felix J COLIBRI

+ Home
  + articles_with_sources
    + database
    + web_internet_sockets
      – tcp_ip_sniffer
      – socket_programming
      – socket_architecture
      – simple_web_server
      – simple_cgi_web_server
      – cgi_database_browser
      – whois
      – web_downloader
      – web_spider
      – rss_reader
      – news_message_tree
      – indy_news_reader
      – delphi_web_designer
      – intraweb_architecture
      – ajax_tutorial
      – bayesian_spam_filter
      + asp_net
      – mobile_pos_software
    + oop_components
    + uml_design_patterns
    + debug_and_test
    + graphic
    + controls
    + colibri_utilities
    + colibri_helpers
    + delphi
    + firemonkey
    + compilers
    + vcl
  + delphi_training
  + delphi_developments
  + sweet_home
  – download_zip_sources
  + links
Contacts
Site Map
– search :

RSS feed  
Blog