Skip to content

11. Internet Programming

11.1. General

11.1.1. Internet Protocols

Here we provide an introduction to Internet communication protocols, also known as the TCP/IP suite (Transmission Control Protocol/Internet Protocol), named after the two main protocols. It may be helpful for the reader to have a general understanding of how networks work, particularly the TCP/IP protocols, before tackling the development of distributed applications. The following text is a partial translation of a passage found in the document "Lan Workplace for Dos - Administrator's Guide" by NOVELL, a document from the early 1990s.


The general concept of creating a network of heterogeneous computers stems from research conducted by the DARPA (Defense Advanced Research Projects Agency) in the United States. The DARPA developed the suite of protocols known as TCP/IP, which allows heterogeneous machines to communicate with one another. These protocols were tested on a network called ARPAnet, which later became the INTERNET network. The TCP/IP protocols define formats and rules for transmission and reception that are independent of the network architecture and the hardware used.

The network designed by DARPA and managed by the TCP/IP protocols is a packet-switched network. Such a network transmits information over the network in small pieces called packets. Thus, if a computer transmits a large file, it will be broken down into small pieces that are sent over the network to be reassembled at the destination. TCP/IP defines the format of these packets, namely:

  1. packet source
  2. destination
  3. length
  4. type

11.1.2. The OSI model

The TCP/IP protocols closely follow the open network model known as OSI (Open Systems Interconnection Reference Model) defined by the ISO (International Standards Organisation). This model describes an ideal network where communication between machines can be represented by a seven-layer model:

Each layer receives services from the layer below it and provides its own services to the layer above it. Suppose two applications running on different machines, A and B, want to communicate: they do so at the Application layer. They do not need to know all the details of how the network works: each application passes the information it wishes to transmit to the layer below it—the Presentation layer. The application therefore only needs to know the rules for interfacing with the Presentation layer.

Once the information is in the Presentation layer, it is passed according to other rules to the Session layer and so on, until the information reaches the physical medium and is physically transmitted to the destination machine. There, it will undergo the reverse process of what it underwent on the sending machine.

At each layer, the sender process responsible for sending the information sends it to a receiver process on the other machine belonging to the same layer as itself. It does so according to certain rules known as the layer protocol. We therefore have the following final communication diagram:

The roles of the different layers are as follows:

Physique
Ensures the transmission of bits over a physical medium. This layer includes data processing terminal equipment (E.T.T.D.) such as terminals or computers, as well as data circuit termination equipment (E.T.C.D.) such as modulators/demodulators, multiplexers, and concentrators. Key points at this level are:
  • the choice of information encoding (analog or digital)
  • the choice of transmission mode (synchronous or asynchronous).
Liaison de données
Hides the physical characteristics of the Physical Layer. Detects and corrects transmission errors.
Réseau
Manages the path that information sent over the network must follow. This is called routing: determining the route that information must take to reach its destination.
Transport
Enables communication between two applications, whereas the previous layers only allowed communication between machines. A service provided by this layer can be multiplexing: the transport layer can use a single network connection (from machine to machine) to transmit data belonging to multiple applications.
Session
This layer provides services that allow an application to open and maintain a working session on a remote machine.
Présentation
It aims to standardize the representation of data across different machines. Thus, data originating from machine A will be "formatted" by machine A’s Presentation layer according to a standard format before being sent over the network. Upon reaching the Presentation layer of the destination machine B, which will recognize them thanks to their standard format, they will be formatted differently so that the application on machine B can recognize them.
Application
At this level, we find applications that are generally close to the user, such as email or file transfer.

11.1.3. The TCP/IP model

The OSI model is an ideal model that has never yet been implemented. The TCP/IP protocol suite approximates it in the following form:

Physical Layer

In local area networks, Ethernet or Token-Ring technology is generally used. We will only discuss Ethernet technology here.

Ethernet

This is the name given to a packet-switched local area network technology invented at Xerox in the early 1970s and standardized by Xerox, Intel, and Digital Equipment in 1978. The network physically consists of a coaxial cable approximately 1.27 cm in diameter and up to 500 m in length. It can be extended using repeaters, with no more than two repeaters separating any two devices. The cable is passive: all active components are located on the machines connected to the cable. Each machine is connected to the cable via a network interface card comprising:

  1. a transceiver that detects the presence of signals on the cable and converts analog signals to digital signals and vice versa.
  2. a coupler that receives digital signals from the transceiver and transmits them to the computer for processing, or vice versa.

The main features of Ethernet technology are as follows:

  1. Capacity of 10 megabits per second.
  2. Bus topology: all machines are connected to the same cable
  • Broadcast network—A transmitting machine sends information over the cable with the address of the receiving machine. All connected machines then receive this information, and only the intended recipient retains it.
  • The access method is as follows: the transmitter wishing to transmit listens to the cable—it then detects whether or not a carrier wave is present, the presence of which would indicate that a transmission is in progress. This is the Carrier Sense Multiple Access (CSMA) technique. If no carrier is present, a transmitter may decide to transmit in turn. Several transmitters may make this decision. The transmitted signals mix together: this is called a collision. The transmitter detects this situation: while transmitting over the cable, it also listens to what is actually passing through it. If it detects that the information traveling over the cable is not the one it transmitted, it concludes that a collision has occurred and will stop transmitting. The other transmitters that were transmitting will do the same. Each will resume transmission after a random delay depending on the individual transmitter. This technique is called CD (Collision Detect). The access method is thus called CSMA/CD.
  • 48-bit addressing. Each machine has an address, referred to here as the physical address, which is written on the card connecting it to the cable. This address is called the machine’s Ethernet address.

Network Layer

At this layer, we find the protocols IP, ICMP, ARP, and RARP.

IP (Internet Protocol)
Delivers packets between two network nodes
ICMP 
(Internet Control Message Protocol)
ICMP facilitates communication between the IP protocol program on one machine and that on another machine. It is therefore a message exchange protocol within the IP protocol itself.
ARP
(Address Resolution Protocol)
maps a machine's Internet address to its physical address
RARP
(Reverse Address Resolution Protocol)
maps a machine's physical address to its Internet address

Transport/Session Layers

This layer includes the following protocols:

TCP (Transmission Control Protocol)
Ensures reliable delivery of information between two clients
UDP (User Datagram Protocol)
Ensures unreliable delivery of information between two clients

Application/Presentation/Session Layers

Various protocols are found here:

TELNET
Terminal emulator allowing machine A to connect to machine B as a terminal
FTP (File Transfer Protocol)
Enables file transfers
TFTP (Trivial File Transfer Protocol)
enables file transfers
SMTP (Simple Mail Transfer protocol)
enables the exchange of messages between network users
DNS (Domain Name System)
converts a machine name into the machine's Internet address
XDR (eXternal Data Representation)
created by Sun MicroSystems, it specifies a standard, machine-independent representation of data
RPC(Remote Procedures Call)
also defined by Sun, it is a communication protocol between remote applications, independent of the transport layer. This protocol is important: it relieves the programmer of the need to know the details of the transport layer and makes applications portable. This protocol is based on the XDR protocol
NFS (Network File System)
Also defined by Sun, this protocol allows one machine to "see" the file system of another machine. It is based on the previous RPC protocol

11.1.4. How Internet Protocols Work

Applications developed in the TCP/IP environment generally use several of the protocols in this environment. An application program communicates with the highest layer of the protocols. This layer passes the information to the layer below it, and so on, until it reaches the physical medium. There, the information is physically transferred to the destination machine, where it will pass through the same layers again—this time in reverse—until it reaches the application intended to receive the sent information. The following diagram shows the path of the information:

Let’s take an example: the FTP application, defined at the Application layer, which enables file transfers between machines.

  1. The application delivers a sequence of bytes to be transmitted to the transport layer.
  2. The transport layer divides this sequence of bytes into segments TCP, and adds the segment number to the beginning of each segment. The segments are passed to the Network layer governed by the IP protocol.
  3. The IP layer creates a packet encapsulating the received TCP segment. At the beginning of this packet, it places the Internet addresses of the source and destination machines. It also determines the physical address of the destination machine. The entire packet is passed to the Data Link & Physical Layer, that is, to the network card that connects the machine to the physical network.
  4. There, the IP packet is in turn encapsulated in a physical frame and sent to its recipient over the cable.
  5. On the destination machine, the Data Link & Physical Layer does the opposite: it decapsulates the IP packet from the physical frame and passes it to the IP layer.
  6. The IP layer verifies that the packet is correct: it calculates a checksum based on the received bits, which it must find in the packet header. If this is not the case, the packet is discarded.
  7. If the packet is deemed valid, the IP layer decapsulates the TCP segment contained within it and passes it up to the transport layer.
  8. The transport layer, layer TCP in our example, examines the segment number to restore the correct order of the segments.
  9. It also calculates a checksum for the segment TCP. If it is found to be correct, the TCP layer sends an acknowledgment to the source machine; otherwise, the segment TCP is rejected.
  10. All that remains for the TCP layer to do is to transmit the data portion of the segment to the application intended to receive it in the layer above.

11.1.5. Addressing issues on the Internet

A network node can be a computer, a smart printer, a file server—anything, in fact, that can communicate using the TCP/IP protocols. Each node has a physical address with a format that depends on the type of network. On an Ethernet network, the physical address is encoded in 6 bytes. An address on an X.25 network is a 14-digit number.

A node’s Internet address is a logical address: it is independent of the hardware and network used. It is a 4-byte address that identifies both a local network and a node on that network. The Internet address is usually represented as four numbers—the values of the four bytes—separated by a dot. Thus, the address of the machine Lagaffe at the Faculty of Sciences in Angers is written as 193.49.144.1, and that of the machine Liny as 193.49.144.9. From this, we can deduce that the Internet address of the local network is 193.49.144.0. There can be up to 254 nodes on this network.

Because Internet addresses or IP addresses are independent of the network, a machine on network A can communicate with a machine on network B without concerning itself with the type of network it is on: it simply needs to know its IP address. The IP protocol of each network handles the conversion between IP addresses and physical addresses, in both directions.

All IP addresses must be unique. In France, the INRIA is responsible for assigning IP addresses. In fact, this organization issues an address for your local network, for example 193.49.144.0 for the network of the Faculty of Sciences in Angers. The administrator of this network can then assign the IP addresses 193.49.144.1 through 193.49.144.254 as they see fit. This address is generally recorded in a specific file on each machine connected to the network.

11.1.5.1. The IP address classes

A IP address is a sequence of 4 bytes, often written as I1.I2.I3.I4, which actually contains two addresses:

  1. the network address
  2. the address of a node on that network

Depending on the size of these two fields, IP addresses are divided into 3 classes: classes A, B, and C.

Class A

The address IP: I1.I2.I3.I4 has the form R1.N1.N2.N3 where

R1 is the network address

N1.N2.N3 is the address of a machine on that network

More precisely, the form of a Class A address IP is as follows:

The network address is 7 bits long and the host address is 24 bits long. Therefore, there can be 127 Class A networks, each containing up to 224 hosts.

Class B

Here, the address IP: I1.I2.I3.I4 has the form R1.R2.N1.N2 where

R1.R2 is the network address

N1.N2 is the address of a machine on that network

More precisely, the format of a Class B address IP is as follows:

The network address is 2 bytes (14 bits exactly), as is the node address. Therefore, there can be 2¹⁴ Class B networks, each containing up to 2¹⁶ nodes.

Class C

In this class, the address IP: I1.I2.I3.I4 has the form R1.R2.R3.N1 where

R1.R2.R3 is the address of network

N1 is the address of a machine on that network

More precisely, the form of a Class C address IP is as follows:

The network address is 3 bytes (minus 3 bits) and the host address is 1 byte. Therefore, there can be 2²¹ Class C networks, each containing up to 256 hosts.

Since the address of the Lagaffe machine at the Angers Faculty of Sciences is 193.49.144.1, we see that the most significant byte is 193, which is 11000001 in binary. We can deduce that the network is a Class C network.

Reserved Addresses

  1. Certain IP addresses are network addresses rather than node addresses within the network. These are the ones where the node address is set to 0. Thus, the address 193.49.144.0 is the network address of the Faculty of Sciences in Angers. Consequently, no node in a network can have the address zero.
  2. When, in an address such as IP, the node address consists entirely of 1s, we have a broadcast address: this address refers to all nodes on the network.
  3. In a Class C network, which theoretically allows for 2⁸ = 256 nodes, if we remove the two prohibited addresses, we are left with only 254 authorized addresses.

11.1.5.2. Internet Address <--> Physical Address Conversion Protocols

We have seen that when data is transmitted from one machine to another, as it passes through the IP layer, it is encapsulated in packets. These have the following form:

The IP packet therefore contains the Internet addresses of the source and destination machines. When this packet is transmitted to the layer responsible for sending it over the physical network, additional information is added to form the physical frame that will ultimately be sent over the network. For example, the format of a frame on an Ethernet network is as follows:

In the final frame, there are the physical addresses of the source and destination machines. How are they obtained?

The sending machine, knowing the address IP of the machine with which it wants to communicate, obtains the latter’s physical address by using a specific protocol called ARP (Address Resolution Protocol).

  • It sends a special type of packet called a ARP packet containing the address IP of the machine whose physical address is being sought. It also includes its own address IP as well as its physical address.
  • This packet is sent to all nodes on the network.
  • These nodes recognize the special nature of the packet. The node that recognizes its address IP in the packet responds by sending its physical address to the packet’s sender. How is it able to do this? It found the sender’s address IP and physical address in the packet.
  • The sender therefore receives the physical address it was looking for. It stores it in memory so that it can use it later if other packets need to be sent to the same recipient.

A machine’s IP address is normally stored in one of its files, which it can consult to retrieve it. This address can be changed: simply edit the file. The physical address, however, is stored in the network card’s memory and cannot be changed.

When an administrator wishes to reorganize the network, they may need to change the IP addresses of all nodes and thus edit the various configuration files for each node. This can be tedious and prone to errors if there are many machines. One method involves not assigning a IP address to the machines: instead, a special code is entered into the file where the machine would normally find its IP address. Upon discovering that it does not have a IP address, the machine requests it using a protocol called RARP (Reverse Address Resolution Protocol). It then sends a special packet over the network called a RARP packet, analogous to the previous ARP packet, in which it includes its physical address. This packet is sent to all nodes, which then recognize a RARP packet. One of them, called the RARP server, has a file containing the physical address <--> IP address mapping for all nodes. It then responds to the sender of the RARP packet by sending back its IP address. An administrator wishing to reconfigure the network therefore only needs to edit the mapping file on the RARP server. This server should normally have a fixed address IP, which it should be able to know without having to use the RARP protocol itself.

11.1.6. The network layer known as the IP layer of the Internet

The IP protocol (Internet Protocol) defines the format that packets must take and how they must be handled during transmission or reception. This particular type of packet is called a IP datagram. We have already introduced it:

The important point is that, in addition to the data to be transmitted, the IP datagram contains the Internet addresses of the source and destination machines. This way, the receiving machine knows who is sending it a message.

Unlike a network frame, whose length is determined by the physical characteristics of the network it traverses, the length of the IP datagram is fixed by the software and will therefore be the same across different physical networks. We have seen that as we move down from the network layer to the physical layer, the datagram IP is encapsulated in a physical frame. We gave the example of the physical frame of an Ethernet network:

Physical frames travel from node to node toward their destination, which may not be on the same physical network as the sending machine. The packet IP can therefore be successively encapsulated in different physical frames at the nodes that connect two networks of different types. It is also possible that the packet IP is too large to be encapsulated in a physical frame. The IP software on the node where this problem occurs then breaks the IP packet into fragments according to specific rules, each of which is then sent over the physical network. They will only be reassembled at their final destination.

11.1.6.1. Routing

Routing is the method of directing IP packets to their destination. There are two methods: direct routing and indirect routing.

Direct routing

Direct routing refers to the transmission of a IP packet directly from the sender to the recipient within the same network:

  1. The machine sending a IP datagram has the recipient’s address IP.
  2. It obtains the recipient’s physical address via the ARP protocol or from its tables, if that address has already been obtained.
  3. It sends the packet over the network to this physical address.

Indirect routing

Indirect routing refers to the transmission of a packet IP to a destination located on a network other than the one to which the sender belongs. In this case, the network address portions of the IP addresses of the source and destination machines are different. The source machine recognizes this. It then sends the packet to a special node called a router, a node that connects a local network to other networks and whose address IP it finds in its tables—an address initially obtained either from a file, from permanent memory, or via information circulating on the network.

A router is connected to two networks and has an address IP within these two networks.

In our example above:

. Network No. 1 has the Internet address 193.49.144.0 and Network No. 2 has the address 193.49.145.0.

. Within network #1, the router has the address 193.49.144.6 and the address 193.49.145.3 within network #2.

The router’s role is to take the packet IP that it receives—which is contained in a physical frame typical of network #1—and place it into a physical frame that can travel over network #2. If the address IP of the packet’s destination is in network #2, the router will send the packet directly to it; otherwise, it will send it to another router, connecting network #2 to network #3, and so on.

11.1.6.2. Error and Control Messages

Also in the network layer—at the same level as the IP protocol—is the ICMP protocol (Internet Control Message Protocol). It is used to send messages regarding the internal operation of the network: failed nodes, congestion at a router, etc. ICMP messages are encapsulated in IP packets and sent over the network. The IP layers of the various nodes take the appropriate actions based on the ICMP messages they receive. Thus, an application itself never sees these network-specific issues.

A node will use the ICMP information to update its routing tables.

11.1.7. The transport layer: the UDP and TCP protocols

11.1.7.1. The UDP protocol: User Datagram Protocol

The UDP protocol allows for an unreliable exchange of data between two points, meaning that the successful delivery of a packet to its destination is not guaranteed. The application, if it wishes, can handle this itself, for example by waiting for an acknowledgment after sending a message before sending the next one.

So far, at the network level, we have discussed machine addresses such as IP. However, on a single machine, multiple processes can run simultaneously, and all of them can communicate with one another. Therefore, when sending a message, you must specify not only the IP address of the destination machine, but also the "name" of the destination process. This name is actually a number, called a port number. Certain numbers are reserved for standard applications: port 69 for the TFTP (Trivial File Transfer Protocol) application, for example.

The packets handled by the UDP protocol are also called datagrams. They have the following form:

These datagrams will be encapsulated in IP packets, then in physical frames.

11.1.7.2. The TCP protocol: Transfer Control Protocol

For secure communications, the UDP protocol is insufficient: the application developer must create a protocol themselves to verify that packets are routed correctly. The TCP protocol (Transfer Control Protocol) avoids these issues. Its characteristics are as follows:

  1. The process wishing to transmit first establishes a connection with the process that will receive the information it is about to transmit. This connection is established between a port on the sending machine and a port on the receiving machine. A virtual path is thus created between the two ports, which will be reserved exclusively for the two processes that have established the connection.
  2. All packets sent by the source process follow this virtual path and arrive in the order in which they were sent, which was not guaranteed in the UDP protocol since packets could follow different paths.
  3. The transmitted information appears continuous. The sending process sends information at its own pace. This information is not necessarily sent immediately: the TCP protocol waits until it has enough to send. It is stored in a structure called a TCP segment. Once this segment is filled, it will be transmitted to the IP layer, where it will be encapsulated in a IP packet.
  4. Each segment sent by the TCP protocol is numbered. The receiving TCP protocol verifies that it is receiving the segments in sequence. For each segment received correctly, it sends an acknowledgment to the sender.
  5. When the sender receives this acknowledgment, it notifies the sending process. The sending process can thus confirm that a segment has arrived safely, which was not possible with the UDP protocol.
  6. If, after a certain amount of time, the TCP protocol that sent a segment does not receive an acknowledgment, it retransmits the segment in question, thereby ensuring the quality of the information delivery service.
  7. The virtual circuit established between the two communicating processes is full-duplex: this means that information can flow in both directions. Thus, the destination process can send acknowledgments even while the source process continues to send information. This allows, for example, the source TCP protocol to send multiple segments without waiting for an acknowledgment. If, after a certain amount of time, it realizes that it has not received an acknowledgment for a specific segment No. n, it will resume sending segments from that point.

11.1.8. The Application Layer

Above the UDP and TCP protocols, there are various standard protocols:

TELNET

This protocol allows a user on machine A on the network to connect to machine B (often called the host machine). TELNET emulates a so-called universal terminal on machine A. The user therefore behaves as if they had a terminal connected to machine B. Telnet is based on the TCP protocol.

FTP: (File Transfer Protocol)

This protocol enables file exchange between two remote machines as well as file operations such as creating directories, for example. It relies on the TCP protocol.

TFTP: (Trivial File Transfer Control)

This protocol is a variant of FTP. It is based on the UDP protocol and is less sophisticated than FTP.

DNS: (Domain Name System)

When a user wishes to exchange files with a remote machine, using FTP for example, they must know the Internet address of that machine. For example, to perform FTP on the Lagaffe machine at the University of Angers, you would need to run FTP as follows: FTP 193.49.144.1

This requires a directory mapping machines to IP addresses. In this directory, the machines would likely be designated by symbolic names such as:

DPX2/320 machine at the University of Angers

Sun machine at the University of Angers

It is clear that it would be more convenient to refer to a machine by a name rather than by its address IP. This raises the issue of name uniqueness: there are millions of interconnected machines. One might imagine a centralized body assigning the names. That would undoubtedly be quite cumbersome. Control over names has in fact been distributed across domains. Each domain is managed by an organization that is generally very lean and has complete freedom in choosing machine names. Thus, machines in France belong to the fr domain, which is managed by Inria in Paris. To further simplify matters, control is distributed even further: domains are created within the fr domain. Thus, the University of Angers belongs to the univ-Angers domain. The department managing this domain has complete freedom to name the machines on the University of Angers network. For now, this domain has not been subdivided. But in a large university with many networked machines, it could be.

The machine DPX2/320 at the University of Angers has been named Lagaffe, while a PC 486DX50 has been named liny. How do we refer to these machines from the outside? By specifying the hierarchy of domains to which they belong. Thus, the full name of the machine Lagaffe will be:

Lagaffe.univ-Angers.fr

Within domains, relative names can be used. Thus, within the fr domain and outside the univ-Angers domain, the machine Lagaffe can be referenced as

Lagaffe.univ-Angers

Finally, within the univ-Angers domain, it can be referenced simply as

Lagaffe

An application can therefore refer to a machine by its name. Ultimately, however, you still need to obtain the machine’s Internet address. How is this done? Suppose that from machine A, you want to communicate with machine B.

  • If machine B belongs to the same domain as machine A, its address IP will likely be found in a file on machine A.
  • Otherwise, machine A will find, in another file or the same one as before, a list of several name servers with their addresses IP. A name server is responsible for mapping a machine name to its address IP. Machine A will send a special request to the first name server on its list, called a DNS request, which includes the name of the machine being searched for. If the queried server has this name in its records, it will send the corresponding address IP to machine A. Otherwise, the server will also find in its files a list of name servers it can query. It will then do so. Thus, a number of name servers will be queried, not haphazardly but in a way that minimizes the number of requests. If the machine is finally found, the response will be sent back to machine A.

XDR: (eXternal Data Representation)

Created by sun MicroSystems, this protocol specifies a standard, machine-independent representation of data.

RPC: (Remote Procedure Call)

Also defined by Sun, this is a transport-layer-independent communication protocol for remote applications. This protocol is important: it relieves the programmer of the need to know the details of the transport layer and makes applications portable. This protocol is based on the XDR protocol

NFS: Network File System

Also defined by Sun, this protocol allows one machine to "see" the file system of another machine. It is based on the previous RPC protocol.

11.1.9. Conclusion

In this introduction, we have presented some of the main features of Internet protocols. To explore this field further, readers may consult Douglas Comer’s excellent book:

Title TCP/IP: Architecture, Protocols, Applications.

Author Douglas COMER

Publisher InterEditions

11.2. The .NET classes of IP address management

A machine on the Internet is uniquely identified by an Internet Protocol (IP) address, which can take two forms:

  1. IPv4: encoded on 32 bits and represented by a string of the form "I1.I2.I3.I4" where In is a number between 1 and 254. These are currently the most common IP addresses.
  2. IPv6: 128-bit encoded and represented by a string of the form "[I1.I2.I3.I4.I5.I6.I7.I8]" where In is a string of 4 hexadecimal digits. In this document, we will not use IPv6 addresses.

A machine can also be identified by a unique name. This name is not required, as applications ultimately always use the IP addresses of the machines. They are there to make life easier for users. Thus, it is easier to use a browser to request URL http://www.ibm.com than the URL http://129.42.17.99, although both methods are possible.

A machine can have multiple IP addresses if it is physically connected to multiple networks at the same time. It then has a IP address on each network.

A IP address can be represented in two ways in .NET:

  1. as a string "I1.I2.I3.I4" or "[I1.I2.I3.I4.I5.I6.I7.I8]"
  2. as an object of type IPAddress

The IPAddress class

Among the methods M, properties P, and constants C of the class IPAddress, the following are found:

AddressFamily AddressFamily
P
Address family IP. The type AddressFamily is an enumeration. The two common values are:
AddressFamily.InterNetwork: for an address IPv4
AddressFamily.InterNetworkV6: for an address IPv6
IPAddress Any
C
the address IP "0.0.0.0". When a service is associated with this address, it means that it accepts clients on all IP addresses of the machine on which it operates.
IPAddress LoopBack
C
The address IP "127.0.0.1". Known as a "loopback address." When a service is associated with this address, it means that it only accepts clients requests from the same machine.
IPAdress None
C
the address IP "255.255.255.255". When a service is associated with this address, it means that it accepts no clients.
bool TryParse(string ipString, out IPAddress address)
M
attempts to convert the address IP ipString in the form "I1.I2.I3.I4" into a IPAddress address object. Returns true if the operation was successful.
bool IsLoopBack
M
returns true if the address IP is "127.0.0.1"
string ToString()
M
converts the address IP to the form "I1.I2.I3.I4" or "[I1.I2.I3.I4.I5.I6.I7.I8]"

The mapping between addresses IP and nomMachine is provided by a distributed Internet service called DNS (Domain Name System). The static methods of the Dns class allow you to make the address association IP <--> nomMachine:

GetHostEntry (string hostNameOrdAddress)
returns an address IPHostEntry from an address IP in the form of a string or from a machine name. Throws an exception if the machine cannot be found.
GetHostEntry (IPAddress ip)
returns a IPHostEntry address from a IP address of type IPAddress. Throws an exception if the machine cannot be found.
string GetHostName()
Returns the name of the machine on which the program executing this instruction is running
IPAddress[] GetHostAddresses(string hostNameOrdAddress)
Returns the IP addresses of the machine identified by its name or one of its IP addresses.

A IPHostEntry instance encapsulates the IP addresses, aliases, and name of a machine. The IPHostEntry type is as follows:

IPAddress[] AddressList
P
array of the machine's IP addresses
String[] Aliases
P
the machine's aliases. These are the names corresponding to the machine's various addresses.
string HostName
P
the machine's primary hostname

Consider the following program, which displays the name of the machine on which it is running and then interactively provides the mappings between IP addresses and machine names:


using System;
using System.Net;
 
namespace Chap9 {
    class Program {
        static void Main(string[] args) {
            // displays the name of the local machine
            // then interactively provides information on network machines
            // identified by name or address IP
 
            // local machine
            Console.WriteLine("Machine Locale= {0}" ,Dns.GetHostName());
 
            // interactive Q&A
            string machine;
            IPHostEntry ipHostEntry;
            while (true) {
                // enter the name or IP address of the machine you are looking for
                Console.Write("Machine recherchée (rien pour arrêter) : ");
                machine = Console.ReadLine().Trim().ToLower();
                // finished?
                if (machine == "") return;
                // management exception
                try {
                    // machine search
                    ipHostEntry = Dns.GetHostEntry(machine);
                    // machine name
                    Console.WriteLine("Machine : " + ipHostEntry.HostName);
                    // machine IP addresses
                    Console.Write("Adresses IP : {0}" , ipHostEntry.AddressList[0]);
                    for (int i = 1; i < ipHostEntry.AddressList.Length; i++) {
                        Console.Write(", {0}" , ipHostEntry.AddressList[i]);
                    }
                    Console.WriteLine();
                    // machine aliases
                    if (ipHostEntry.Aliases.Length != 0) {
                        Console.Write("Alias : {0}" , ipHostEntry.Aliases[0]);
                        for (int i = 1; i < ipHostEntry.Aliases.Length; i++) {
                            Console.Write(", {0}" , ipHostEntry.Aliases[i]);
                        }
                        Console.WriteLine();
                    }
                } catch {
                    // the machine doesn't exist
                    Console.WriteLine("Impossible de trouver la machine [{0}]",machine);
                }
            }
        }
    }
}

The execution yields the following results:

Machine Locale= LISA-AUTO2005A
Machine recherchée (rien pour arrêter) : localhost
Machine : LISA-AUTO2005A
Adresses IP : 127.0.0.1
Machine recherchée (rien pour arrêter) : 127.0.0.1
Machine : LISA-AUTO2005A
Adresses IP : 127.0.0.1
Machine recherchée (rien pour arrêter) : istia.univ-angers.fr
Machine : istia.univ-angers.fr
Adresses IP : 193.49.146.171
Machine recherchée (rien pour arrêter) : 193.49.146.171
Machine : istia.istia.univ-angers.fr
Adresses IP : 193.49.146.171
Machine recherchée (rien pour arrêter) : xx
Impossible de trouver la machine [xx]

11.3. The Basics of Web Programming

11.3.1. General

Consider communication between two remote machines A and B:

When an application AppA on machine A wants to communicate with an application AppB on machine B on the Internet, it must know several things:

  1. the address IP or the name of machine B
  2. the port number used by the AppB application. Indeed, machine B may host numerous applications that operate over the Internet. When it receives information from the network, it must know which application that information is intended for. The applications on machine B access the network through interfaces also known as communication ports. This information is contained in the packet received by machine B so that it can be delivered to the correct application.
  3. The communication protocols understood by machine B. In our study, we will use only the TCP-IP protocols.
  4. The communication protocol accepted by the AppB application. Indeed, machines A and B will "communicate" with each other. What they will exchange will be encapsulated in the TCP-IP protocols. However, when, at the end of the chain, the AppB application receives the information sent by the AppA application, it must be able to interpret it. This is analogous to the situation where two people, A and B, communicate by telephone: their conversation is carried by the telephone. Speech is encoded as signals by phone A, transmitted over telephone lines, and arrives at phone B to be decoded. Person B then hears the speech. This is where the concept of a communication protocol comes into play: if A speaks French and B does not understand that language, A and B will not be able to communicate effectively.

Therefore, the two communicating applications must agree on the type of communication protocol they will use. For example, communication with an FTP service is not the same as with a POP service: these two services do not accept the same commands. They use different communication protocols.

11.3.2. Characteristics of the TCP protocol

Here, we will only examine network communications using the TCP transport protocol. Let us review its characteristics:

  1. The process that wishes to transmit first establishes a connection with the process that will receive the information it is about to transmit. This connection is made between a port on the sending machine and a port on the receiving machine. A virtual path is thus created between the two ports, which will be reserved exclusively for the two processes that have established the connection.
  2. All packets sent by the source process follow this virtual path and arrive in the order in which they were sent
  3. The transmitted information appears continuous. The sending process sends information at its own pace. This information is not necessarily sent immediately: the TCP protocol waits until it has enough to send. It is stored in a structure called a TCP segment. Once this segment is filled, it will be transmitted to the IP layer, where it will be encapsulated in a IP packet.
  4. Each segment sent by the TCP protocol is numbered. The receiving TCP protocol verifies that it is receiving the segments in sequence. For each segment received correctly, it sends an acknowledgment to the sender.
  5. When the sender receives this acknowledgment, it notifies the sending process. The sending process can thus confirm that a segment has been successfully delivered.
  6. If, after a certain amount of time, the TCP protocol that sent a segment does not receive an acknowledgment, it retransmits the segment in question, thereby ensuring the quality of the information delivery service.
  7. The virtual circuit established between the two communicating processes is full-duplex: this means that information can flow in both directions. Thus, the destination process can send acknowledgments even while the source process continues to send information. This allows, for example, the source TCP protocol to send multiple segments without waiting for an acknowledgment. If, after a certain amount of time, it realizes that it has not received an acknowledgment for a specific segment No. n, it will resume sending segments from that point.

11.3.3. The client-server relationship

Communication over the Internet is often asymmetric: machine A initiates a connection to request a service from machine B, specifying that it wants to open a connection to machine B’s SB1 service. Machine B accepts or refuses. If it accepts, machine A can send its requests to service SB1. These requests must comply with the communication protocol understood by service SB1. A request-response dialogue is thus established between machine A, referred to as the client machine, and machine B, referred to as the server machine. One of the two partners will close the connection.

11.3.4. Client Architecture

The architecture of a network program requesting the services of a server application will be as follows:

ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
    tant que ce n'is not finished
        préparer une demande
        l'send to machine B
        attendre et récupérer la réponse
        la traiter
    fin tant que
finsi
fermer la connexion

11.3.5. Server architecture

The architecture of a program providing services will be as follows:

ouvrir le service sur la machine locale
tant que le service est ouvert
    se mettre à l'listens for connection requests on a so-called listening port
    lorsqu'there is a request, have it processed by another task on another port called the service port
fin tant que

The server program handles a client’s initial connection request differently from its subsequent requests for service. The program does not provide the service itself. If it did, it would no longer be listening for connection requests while the service is active, and the clients requests would not be served. It therefore proceeds differently: as soon as a connection request is received on the listening port and then accepted, the server creates a task responsible for providing the service requested by the client. This service is provided on another port of the server machine called the service port. This allows multiple clients to be served at the same time.

A service task will have the following structure:

tant que le service n'has not been fully rendered
        attendre une demande sur le port de service
        lorsqu'there is one, elaborate the answer
        transmettre la réponse via le port de service
fin tant que
libérer le port de service

11.4. Learn about the communication protocols of the Internet

11.4.1. Introduction

When a client connects to a server, a dialogue is established between them. The nature of this dialogue is what is known as the server's communication protocol. Among the most common Internet protocols are the following:

  1. HTTP: HyperText Transfer Protocol—the protocol for communicating with a web server (HTTP server)
  2. SMTP: Simple Mail Transfer Protocol—the protocol for communicating with an email sending server (SMTP server)
  3. POP: Post Office Protocol - the protocol for communicating with an email storage server (server POP). This involves retrieving received emails, not sending them.
  4. FTP: File Transfer Protocol—the protocol for communicating with a file storage server (server FTP).

All these protocols are text-based: the client and server exchange lines of text. If you have a client capable of:

  • establish a connection with a Tcp server
  • display the text lines sent by the server on the console
  • send the text lines that a user would type to the server

then we are able to communicate with a Tcp server using a text-based protocol, provided we know the rules of that protocol.

The telnet program found on Unix or Windows machines is one such client. On Windows machines, there is also a tool called PuTTY, and that is the one we will use here. PuTTY can be downloaded from [http://www.putty.org/]. It is an executable (.exe) file that can be used directly. We will configure it as follows:

  • [1]: the address IP of the server Tcp to which we want to connect, or its name
  • [2]: the listening port of the Tcp server
  • [3]: Use Raw mode, which indicates a raw connection to Tcp.
  • [4]: use Never mode to prevent the PuTTY client window from closing if the server terminates the connection.
  • [6,7]: number of columns/rows in the console
  • [5]: the maximum number of lines kept in memory. A HTTP server can send many lines. You need to be able to scroll through them.
  • [8,9]: To retain the previous settings, name the configuration [8] and save it [9].
  • [11,12]: To retrieve a saved configuration, select [11] and load it as [12].

With this tool configured, let’s explore some protocols: TCP.

11.4.2. The HTTP protocol (HyperText Transfer Protocol)

Let’s connect our client [1] to the web server on machine istia.univ-angers.fr, port 80:

In the PuTTY console, we construct the following HTTP request:

GET / HTTP/1.1
Host: istia.univ-angers.fr:80
Connection: close

HTTP/1.1 200 OK
Date: Sat, 03 May 2008 07:53:47 GMT
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
X-Powered-By: PHP/4.4.4-8+etch4
Set-Cookie: fe_typo_user=0d2e64b317; path=/
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html;charset=iso-8859-1

693f
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"                                                                        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
         <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr_FR" lang="fr_FR">
....
         </html>
0
  • Lines 1-4 are the client's request, typed on the keyboard
  • Lines 5-19 are the server's response
  • line 1: syntax GET UrlDocument HTTP/1.1 - we are requesting Url /, c.a.d. The root of the website [istia.univ-angers.fr].
  • Line 2: Host: machine:port syntax
  • Line 3: Syntax Connection: [mode de la connexion]. The [close] mode instructs the server to close the connection once it has sent its response. The [Keep-Alive] mode requests that it remain open.
  • Line 4: blank line. Lines 1–3 are called HTTP headers. There may be others besides those shown here. The end of the HTTP headers is indicated by a blank line.
  • Lines 5–13: the HTTP headers of the server’s response—these also end with a blank line.
  • lines 14-19: the document sent by the server, in this case a HTML document
  • Line 5: HTTP/1.1 msg code syntax—the code 200 indicates that the requested document was found.
  • Line 6: Server date and time
  • Line 7: Software identification for the web service—in this case, an Apache server running on Linux/Debian
  • line 8: the document was dynamically generated by PHP
  • Line 9: client identification cookie—if the client wants to be recognized on its next connection, it must resend this cookie in its headers: HTTP.
  • line 10: indicates that after serving the requested document, the server will close the connection
  • line 11: the document will be transmitted in chunks rather than as a single block
  • line 12: document type: in this case, a document HTML
  • line 13: the empty line that signals the end of the server’s HTTP headers
  • Line 14: Hexadecimal number indicating the number of characters in the first block of the document. When this number is 0 (line 19), the client will know that it has received the entire document.
  • Lines 15–18: portion of the received document.

The connection has been closed and the PuTTY client is inactive. Let’s reconnect [1] and clear the screen of previous displays [2,3]:

The dialog this time is as follows:

GET /inconnu HTTP/1.1
Host: istia.univ-angers.fr:80
Connection: Close

HTTP/1.1 404 Not Found
Date: Sat, 03 May 2008 08:16:02 GMT
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=iso-8859-1

11a
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
                                                  <HTML><HEAD>
                                                              <TITLE>404 Not Found</TITLE>
                                                                                          </HEAD><BODY>
                                                                                                       <H1>Not Found</H1>
 The requested URL /inconnu was not found on this server.<P>
                                                            <HR>
                                                                <ADDRESS>Apache/1.3.34 Server at www.istia.univ-angers.fr Port 80</ADDRESS>
                   </BODY></HTML>

0
  1. Line 1: A non-existent document was requested
  2. line 5: the server HTTP responded with a 404 status code, indicating that the requested document was not found.

If we request this document using the Firefox browser:

Image

If we view the source code for [Affichage/Code source]:

1
2
3
4
5
6
7
8
9
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<HTML><HEAD>
<TITLE>404 Not Found</TITLE>
</HEAD><BODY>
<H1>Not Found</H1>
The requested URL /inconnu was not found on this server.<P>
<HR>
<ADDRESS>Apache/1.3.34 Server at www.istia.univ-angers.fr Port 80</ADDRESS>
</BODY></HTML>

We obtain lines 13–22 received by our PuTTY client. The advantage of this is that it also shows us the HTTP headers of the response. It is also possible to view these using Firefox.

11.4.3. The SMTP protocol ( )

SMTP servers generally operate on port 25 [2]. We connect to the [1] server. Here, you should generally choose a server

belonging to the same domain as the machine, since IP servers are usually configured to accept only requests from machines belonging to the same domain. Furthermore, quite often, firewalls or antivirus software on personal computers are configured to block connections to port 25 from external machines. It may therefore be necessary to reconfigure this firewall or antivirus software.

The SMTP dialog in the PuTTY client window is as follows:

220 neuf-infra-smtp-out-sp604001av.neufgp.fr neuf telecom Service relais mail ready
HELO istia.univ-angers.fr
250 neuf-infra-smtp-out-sp604002av.neufgp.fr hello [84.100.189.193], Banniere OK , pret pour envoyer un mail
mail from: @expéditeur
250 2.1.0 <@expéditeur> sender ok
rcpt to: @destinataire
250 2.1.5 <@destinataire> destinataire ok
data
354 enter mail, end with "." on a line by itself
ligne1
ligne2
.
250 2.0.0 LwiU1Z00V4AoCxw0200000 message ok
quit
221 2.0.0 neuf-infra-smtp-out-sp604002av.neufgp.fr neuf telecom closing connection

Below, (D) is a client request, (R) is a server response.

  1. line 1: (R) welcome message from the server SMTP
  2. line 2: (D) command HELO to say hello
  3. Line 3: (R) Server response
  4. line 4: (D) sender address, for example mail from: someone@gmail.com
  5. line 5: (R) server response
  6. line 6: (D) recipient address, for example rcpt to: someoneelse@gmail.com
  7. line 7: (R) server response
  8. line 8: (D) indicates the start of the message
  9. line 9: (R) server response
  10. lines 10–12: (R) the message to be sent, ending with a line containing only a period.
  11. line 13: (R) server response
  12. line 14: (D) the client signals that it is finished
  13. line 15: (R) server response, which then closes the connection

11.4.4. The POP protocol (Post Office Protocol)

POP servers generally operate on port 110 [2]. Connect to the [1] server. The POP dialogue in the PuTTY client window is as follows:

+OK Hello there.
user xx
+OK Password required.
pass yy
+OK logged in.
list
+OK POP3 clients that break here, they violate STD53.
1 10105
2 55875
...
64 1717
.
retr 64
+OK 1717 octets follow.
Return-Path: <xx@neuf.fr>
X-Original-To: xx@univ-angers.fr
Delivered-To: xx@univ-angers.fr
....
Date: Sat,  3 May 2008 10:59:25 +0200 (CEST)
From: xx@neuf.fr
To: undisclosed-recipients:;

ligne1
ligne2
.
quit
+OK Bye-bye.
  • line 1: (R) welcome message from the server POP
  • line 2: (D) the client provides its ID POP, c.a.d. the login used to read its mail
  • line 3: (R) the server's response
  • line 4: (D) the client's password
  • line 5: (R) the server's response
  • line 6: (D) the client requests a list of their emails
  • Lines 7–12: (R) the list of messages in the client’s mailbox, in the form [N° du message taille en octets du message]
  • line 13: (D) message #64 is requested
  • lines 14–25: (R) message #64, with lines 15–22 containing the message headers and lines 23–24 containing the message body.
  • line 26: (D) the client indicates that it is finished
  • line 27: (R) server response, after which the server closes the connection.

11.4.5. The FTP Protocol (File Transfer Protocol)

The FTP protocol is more complex than those presented previously. To view the text exchanged between the client and the server, you can use a tool such as FileZilla [http://www.filezilla.fr/].

FileZilla is a FTP client that provides a Windows interface for file transfers. User actions on the Windows interface are translated into FTP commands that are logged in [1]. This is a good way to discover the commands of the FTP protocol.

11.5. The .NET classes for web programming

11.5.1. Choosing the right class

The .NET framework offers various classes for working with the network:

  • The Socket class operates closest to the network. It allows for fine-grained management of the network connection. The term "socket" refers to an electrical outlet. The term has been extended to refer to a software network socket. In a TCP-IP communication between two machines, A and B, two sockets communicate with each other. An application can work directly with sockets. This is the case with application A above. A socket can be a client or server socket.
  • If you wish to work at a lower level than that of the Socket class, you can use the classes
  • TcpClient to create a client Tcp
  • TcpListener to create a server Tcp

These two classes provide the application using them with a simpler view of network communication by handling the technical details of socket management on its behalf.

  • .NET provides classes specific to certain protocols:
  • the class SmtpClient to manage the SMTP communication protocol with a SMTP email server
  • the WebClient class to manage the HTTP or FTP communication protocols with a web server.

Note that the Socket class is sufficient on its own to handle all tcp-ip communication, but we will primarily seek to use higher-level classes to facilitate the writing of the tcp-ip application.

11.5.2. The TcpClient class

The TcpClient class is the appropriate class in most cases for creating a client for a TCP service. Among its C constructors, M methods, and P properties, it includes the following:

TcpClient(string hostname, int port)
C
creates a tcp connection to the service running on the specified port (port) of the specified machine (hostname). For example, new TcpClient("istia.univ-angers.fr", 80) to connect to port 80 of the machine istia.univ-angers.fr
Socket Client
P
the socket used by the client to communicate with the server.
NetworkStream GetStream()
M
Obtains a stream for reading from and writing to the server. This stream enables client-server communication.
void Close()
M
closes the connection. The socket and the NetworkStream stream are also closed
bool Connected()
P
true if the connection has been established

The NetworkStream class represents the network stream between the client and the server. It is derived from the Stream class. Many client-server applications exchange text lines terminated by the line-end characters "\r\n". Therefore, it is useful to use StreamReader and StreamWriter objects to read and write these lines in the network stream. Thus, if a machine M1 has established a connection with a machine M2 using a TcpClient client1 object and they are exchanging lines of text, it can create its read and write streams as follows:

StreamReader in1=new StreamReader(client1.GetStream());
StreamWriter out1=new StreamWriter(client1.GetStream());
out1.AutoFlush=true;

The statement

out1.AutoFlush=true;

means that the write stream from client1 will not pass through an intermediate buffer but will go directly to the network. This point is important. Generally, when client1 sends a line of text to its partner, it expects a response. This response will never arrive if the line was actually buffered on machine M1 and never sent to machine M2.

To send a line of text to machine M2, we write:

client1.WriteLine("un texte");

To read the response from M2, we write:

string réponse=client1.ReadLine();

We now have the elements to write the basic architecture of a web client with the following basic communication protocol with the server:

  • the client sends a request contained in a single line
  • the server sends a response contained in a single line

using System;
using System.IO;
using System.Net.Sockets;
 
namespace ... {
    class ... {
        static void Main(string[] args) {
            ...
            try {
                // connect to the service
                using (TcpClient tcpClient = new TcpClient(serveur, port)) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // unbuffered output stream
                                writer.AutoFlush = true;
                                // request-response loop
                                while (true) {
                                    // demand comes from the keyboard
                                    Console.Write("Demande (bye pour arrêter) : ");
                                    demande = Console.ReadLine();
                                    // finished?
                                    if (demande.Trim().ToLower() == "bye")
                                        break;
                                    // send the request to the server
                                    writer.WriteLine(demande);
                                    // we read the server response
                                    réponse = reader.ReadLine();
                                    // the answer is processed
                                    ...
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // error
                ...
            }
        }
    }
}
  1. Line 11: Create client connection—the using statement ensures that the resources associated with it will be released when the using block ends.
  2. line 12: opening the network stream in a using clause
  3. line 13: creation and use of the read stream within a using clause
  4. line 14: creation and use of the write stream within a using clause
  5. line 16: do not buffer the output stream
  6. Lines 18–31: The client request/server response cycle
  7. line 26: the client sends its request to the server
  8. line 28: the client waits for the server's response. This is a blocking operation, similar to reading from the keyboard. The wait ends when a string terminated by "\n" is received or when the stream ends. The latter occurs if the server closes the connection it established with the client.

11.5.3. The TcpListener class

The TcpListener class is the appropriate class in most cases for creating a TCP service. Among its C constructors, M methods, and P properties, it includes the following:

TcpListener(int port)
C
creates a TCP service that will listen for requests from clients on a port passed as a parameter (port) called the listening port. If the machine is connected to multiple IP networks, the service listens on each of the networks.
TcpListener(IPAddress ip, int port)
C
Same as above, but listening only occurs at the specified address ip.
void Start()
M
starts listening for requests clients
TcpClient AcceptTcpClient()
M
accepts a client request. It then opens a new connection with the client, called a service connection. The port used on the server side is random and chosen by the system. It is called the service port. AcceptTcpClient returns the TcpClient object associated on the server side with the service connection.
void Stop()
M
stops listening for requests clients
Socket Server
P
the server's listening socket

The basic structure of a TCP server that would communicate with its clients clients according to the following protocol:

  1. the client sends a request contained in a single line
  2. the server sends a response contained in a single line

might look like this:


using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Net;
 
namespace ... {
    public class ... {
            ...
            // we create the listening service
            TcpListener ecoute = null;
            try {
                // create the service - it will listen on all the machine's network interfaces
                ecoute = new TcpListener(IPAddress.Any, port);
                // launch it
                ecoute.Start();
                // service loop
                TcpClient tcpClient = null;
                // infinite loop - will be stopped by Ctrl-C
                while (true) {
                    // waiting for a customer
                    tcpClient = ecoute.AcceptTcpClient();
                    // the service is provided by another task
                    ThreadPool.QueueUserWorkItem(Service, tcpClient);
                    // next customer
                }
            } catch (Exception ex) {
                // we report the error
                ...
            } finally {
                // end of service
                ecoute.Stop();
            }
        }
 
        // -------------------------------------------------------
        // provides service to a customer
        public static void Service(Object infos) {
            // the customer is picked up and served
            Client client = infos as Client;
            // operation link TcpClient
            try {
                using (TcpClient tcpClient = client.CanalTcp) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // unbuffered output stream
                                writer.AutoFlush = true;
                                // loop read request/write response
                                bool fini=false;
                                while (! fini) != null) {
                                    // waiting for customer request - blocking operation
                                    demande=reader.ReadLine();
                                    // response preparation
                                    réponse=...;
                                    // reply to customer
                                    writer.WriteLine(réponse);
                                    // next request
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // error
                ...
            } finally {
                // end customer
                ...
            }
        }
    }
}
  1. Line 14: The listening service is created for a given port and a given IP address. It is important to remember here that a machine has at least two IP addresses: the address "127.0.0.1," which is its loopback address, and the address "I1.I2.I3.I4" that it has on the network to which it is connected. It may have other addresses IP if it is connected to multiple networks IP. IPAddress.Any refers to all the addresses IP of a machine.
  2. Line 16: The listening service starts. It had been created previously but was not yet listening. Listening means waiting for requests from clients.
  3. Lines 20–26: The client request/client service loop is repeated for each new client
  4. line 22: a client’s request is accepted. The AcceptTcpClient method returns a TcpClient instance known as the service:
    • the client made the request using its own client-side TcpClient instance, which we will call TcpClientDemande
    • the server accepts this request with AcceptTcpClient. This method creates a server-side instance TcpClient, which we will call TcpClientService. We then have an open Tcp connection with the instances TcpClientDemande <--> TcpClientService at both ends.
    • The client/server communication that takes place next occurs over this connection. The listening service is no longer involved.
  5. Line 24: To allow the server to process multiple clients instances simultaneously, the service is handled by threads, with one thread per client.
  6. Line 32: The listening service is closed
  7. Line 38: The method executed by the service thread for a client. It receives as a parameter the TcpClient instance already connected to the client to be served.
  8. Lines 38–71: The code here is similar to that of the basic Tcp client discussed earlier.

11.6. Examples of clients / TCP servers

11.6.1. An echo server

We propose to write an echo server that will be launched from a DOS window using the command:

ServeurEcho port

The server runs on the port passed as a parameter. It simply sends back to the client the request that the client sent to it. The program is as follows:


using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Net;
 
// call: serveurEcho port
// echo server
// returns the line sent to the customer
 
namespace Chap9 {
    public class ServeurEcho {
        public const string syntaxe = "Syntaxe : [serveurEcho] port";
 
        // main program
        public static void Main(string[] args) {
 
            // is there an argument?
            if (args.Length != 1) {
                Console.WriteLine(syntaxe);
                return;
            }
            // this argument must be integer >0
            int port = 0;
            if (!int.TryParse(args[0], out port) || port<=0) {
                Console.WriteLine("{0} : {1}Port incorrect", syntaxe, Environment.NewLine);
                return;
            }
            // we create the listening service
            TcpListener ecoute = null;
            int numClient = 0;    // next customer no
            try {
                // create the service - it will listen on all the machine's network interfaces
                ecoute = new TcpListener(IPAddress.Any, port);
                // launch it
                ecoute.Start();
                // follow-up
                Console.WriteLine("Serveur d'écho lancé sur le port {0}", ecoute.LocalEndpoint);
                // service threads
                ThreadPool.SetMinThreads(10, 10);
                ThreadPool.SetMaxThreads(10, 10);
                // service loop
                TcpClient tcpClient = null;
                // infinite loop - will be stopped by Ctrl-C
                while (true) {
                    // waiting for a customer
                    tcpClient = ecoute.AcceptTcpClient();
                    // the service is provided by another task
                    ThreadPool.QueueUserWorkItem(Service, new Client() { CanalTcp = tcpClient, NumClient = numClient });
                    // next customer
                    numClient++;
                }
            } catch (Exception ex) {
                // we report the error
                Console.WriteLine("L'erreur suivante s'est produite sur le serveur : {0}", ex.Message);
            } finally {
                // end of service
                ecoute.Stop();
            }
        }
 
        // -------------------------------------------------------
        // provides service to an echo server client
        public static void Service(Object infos) {
            // the customer is picked up and served
            Client client = infos as Client;
            // renders service to the customer
            Console.WriteLine("Début de service au client {0}", client.NumClient);
            // operation link TcpClient
            try {
                using (TcpClient tcpClient = client.CanalTcp) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // unbuffered output stream
                                writer.AutoFlush = true;
                                // loop read request/write response
                                string demande = null;
                                while ((demande = reader.ReadLine()) != null) {
                                    // console monitoring
                                    Console.WriteLine("<--- Client {0} : {1}", client.NumClient, demande);
                                    // echo from demand to customer
                                    writer.WriteLine("[{0}]", demande);
                                    // console monitoring
                                    Console.WriteLine("---> Client {0} : {1}", client.NumClient, demande);
                                    // service stops when customer sends "bye
                                    if (demande.Trim().ToLower() == "bye")
                                        break;
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // error
                Console.WriteLine("L'erreur suivante s'est produite lors du service au client {0} : {1}", client.NumClient, e.Message);
            } finally {
                // end customer
                Console.WriteLine("Fin du service au client {0}", client.NumClient);
            }
        }
    }
 
    // customer info
    internal class Client {
        public TcpClient CanalTcp { get; set; }        // customer liaison
        public int NumClient { get; set; }            // customer no
    }
}

The structure of the echo server conforms to the basic architecture of Tcp servers described earlier. We will only comment on the "client service" section:

  1. line 79: the client's request is read
  2. line 83: it is returned to the client enclosed in square brackets
  3. line 79: the service stops when the client closes the connection

In a DOS window, we use the C# project executable:

...\Chap9\02\bin\Release>dir
 03/05/2008  11:46             7 168 ServeurEcho.exe
...>ServeurEcho 100
Serveur d'echo launched on port 0.0.0.0:100

We then launch two clients Putty instances and connect them to port 100 on the localhost machine:

 

The echo server's console output becomes:

1
2
3
Serveur d'echo launched on port 0.0.0.0:100
Début de service au client 0
Début de service au client 1

Client 1 and then client 0 send the following texts:

  • [1]: client #1
  • [2]: client #0
  • [3]: the echo server console
  • in [4]: Client 1 disconnects with the "bye" command.
  • in [5]: the server detects this

The server can be stopped with Ctrl-C. Client #0 then detects this: [6].

11.6.2. A client for the echo server

We will now write a client for the previous server. It will be called as follows:

ClientEcho nomServeur port

It connects to the machine nomServeur on port port and then sends lines of text to the server, which echoes them back.


using System;
using System.IO;
using System.Net.Sockets;
 
namespace Chap9 {
    // connects to an echo server
    // any line typed on the keyboard is received as an echo
    class ClientEcho {
        static void Main(string[] args) {
            // syntax
            const string syntaxe = "pg machine port";
 
            // number of arguments
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }
 
            // note the server name
            string serveur = args[0];
 
            // port must be integer >0
            int port = 0;
            if (!int.TryParse(args[1], out port) || port <= 0) {
                Console.WriteLine("{0}{1}port incorrect", syntaxe, Environment.NewLine);
                return;
            }
 
            // we can work
            string demande = null;        // customer request
            string réponse = null;        // server response
            try {
                // connect to the service
                using (TcpClient tcpClient = new TcpClient(serveur, port)) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // unbuffered output stream
                                writer.AutoFlush = true;
                                // request-response loop
                                while (true) {
                                    // demand comes from the keyboard
                                    Console.Write("Demande (bye pour arrêter) : ");
                                    demande = Console.ReadLine();
                                    // finished?
                                    if (demande.Trim().ToLower() == "bye")
                                        break;
                                    // send the request to the server
                                    writer.WriteLine(demande);
                                    // we read the server response
                                    réponse = reader.ReadLine();
                                    // the answer is processed
                                    Console.WriteLine("Réponse : {0}", réponse);
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // error
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
            }
        }
    }
}

The structure of this client conforms to the basic general architecture proposed for clients and Tcp. Here are the results obtained in the following configuration:

  1. the server is running on port 100 in a DOS window
  2. On the same machine, two clients instances are running in two other DOS windows

In client window A (No. 0), the following is displayed:

1
2
3
4
5
6
...\Chap9\03\bin\Release>ClientEcho localhost 100
Demande (bye pour arrêter) : ligne1A
Réponse : [ligne1A]
Demande (bye pour arrêter) : ligne2A
Réponse : [ligne2A]
Demande (bye pour arrêter) :

In client B (No. 1):

1
2
3
4
5
6
...\Chap9\03\bin\Release>ClientEcho localhost 100
Demande (bye pour arrêter) : ligne1B
Réponse : [ligne1B]
Demande (bye pour arrêter) : ligne2B
Réponse : [ligne2B]
Demande (bye pour arrêter) :

On the server:

...\Chap9\02\bin\Release>ServeurEcho 100
Serveur d'echo launched on port 0.0.0.0:100
Début de service au client 0
<--- Customer 0 : ligne1A
---> Customer 0 : ligne1A
<--- Customer 0 : ligne2A
---> Customer 0 : ligne2A
Début de service au client 1
<--- Customer 1: ligne1B
---> Customer 1: ligne1B
<--- Customer 1: ligne2B
---> Customer 1: ligne2B

Client A #0 disconnects:

1
2
3
4
Demande (bye pour arrêter) : ligne1A
Réponse : [ligne1A]
...
Demande (bye pour arrêter) : bye

The server console:

1
2
3
Serveur d'echo launched on port 0.0.0.0:100
...
Fin du service au client 0

11.6.3. A generic TCP client

We will write a generic Tcp client that will be launched as follows: ClientTcpGenerique server port. It will function similarly to the PuTTY client but will have a console interface and will not require a configuration file.

In the previous application, the communication protocol was known: the client sent a single line, and the server responded with a single line. Each service has its own specific protocol, and the following situations may also arise:

  1. the client must send multiple lines of text before receiving a response
  2. a server’s response may consist of multiple lines of text

Therefore, the cycle of sending a single line to the server and receiving a single line from the server is not always suitable. To handle protocols more complex than the echo protocol, the generic Tcp client will have two threads:

  • the main thread will read the lines of text typed on the keyboard and send them to the server.
  • A secondary thread will run in parallel and be dedicated to reading the lines of text sent by the server. As soon as it receives one, it displays it on the console. The thread only stops when the server closes the connection. It therefore runs continuously.

The code is as follows:


using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
 
namespace Chap9 {
    // receives the characteristics of a service as a parameter in the form: server port
    // connects to the service
    // sends each line typed on the keyboard to the server
    // creates a thread to continuously read text lines sent by the server
    class ClientTcpGenerique {
        static void Main(string[] args) {
            // syntax
            const string syntaxe = "pg serveur port";
 
            // number of arguments
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }
 
            // note the server name
            string serveur = args[0];
 
            // port must be integer >0
            int port = 0;
            if (!int.TryParse(args[1], out port) || port <= 0) {
                Console.WriteLine("{0}{1}port incorrect", syntaxe, Environment.NewLine);
                return;
            }
            // connect to the service
            TcpClient tcpClient = null;
            try {
                tcpClient = new TcpClient(serveur, port);
            } catch (Exception ex) {
                // error
                Console.WriteLine("Impossible de se connecter au service ({0},{1}) : erreur {2}", serveur, port, ex.Message);
                // end
                return;
            }
 
            // launch a separate thread to read the text lines sent by the server
            ThreadPool.QueueUserWorkItem(Receive, tcpClient);
 
            // keyboard commands are read in the main thread
            Console.WriteLine("Tapez vos commandes (bye pour arrêter) : ");
            string demande = null;        // customer request
            try {
                // the customer connection is used
                using (tcpClient) {
                    // create a write stream to the server
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamWriter writer = new StreamWriter(networkStream)) {
                            // unbuffered output stream
                            writer.AutoFlush = true;
                            // request-response loop
                            while (true) {
                                demande = Console.ReadLine();
                                // finished?
                                if (demande.Trim().ToLower() == "bye")
                                    break;
                                // send the request to the server
                                writer.WriteLine(demande);
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // error
                Console.WriteLine("L'erreur suivante s'est produite dans le thread principal : {0}", e.Message);
            }
        }
 
        // client read thread <-- server
        public static void Receive(object infos) {
            // local data
            string réponse = null;    // server response
            // input flow creation
            try {
                using (TcpClient tcpClient = infos as TcpClient) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            // loop continuous reading of text lines in the input stream
                            while ((réponse = reader.ReadLine()) != null) {
                                // console display
                                Console.WriteLine("<-- {0}", réponse);
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                // error
                Console.WriteLine("Flux de lecture : l'erreur suivante s'est produite : {0}", ex.Message);
            } finally {
                // signals the end of the read thread
                Console.WriteLine("Fin du thread de lecture des réponses du serveur. Si besoin est, arrêtez le thread de lecture console avec la commande bye.");
            }
        }
    }
}
  • line 34: the client connects to the server
  • line 43: a thread is launched to read text lines from the server. It must execute the Receive method on line 73. The TcpClient instance, which was connected to the server, is passed to this method.
  • lines 57–64: the loop handles keyboard input and sends commands to the server. Keyboard input is handled by the main thread.
  • Lines 75–98: The Receive method executed by the thread that reads text lines. This method receives the TcpClient instance—which has been connected to the server—as a parameter.
  • Lines 84–87: The continuous loop for reading text lines sent by the server. It stops only when the server closes the open connection with the client.

Here are a few examples based on those used with the PuTTY client in Section 11.4. The client is run in a DOS console.

HTTP Protocol

...\Chap9\04\bin\Release>ClientTcpGenerique istia.univ-angers.fr 80
Tapez vos commandes (bye pour arrêter) :
GET /inconnu HTTP/1.1
Host: istia.univ-angers.fr:80
Connection: Close

<-- HTTP/1.1 404 Not Found
<-- Date: Sat, 03 May 2008 12:35:11 GMT
<-- Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29

<-- Connection: close
<-- Transfer-Encoding: chunked
<-- Content-Type: text/html; charset=iso-8859-1
<--
<-- 11a
<-- <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<-- <HTML><HEAD>
<-- <TITLE>404 Not Found</TITLE>
<-- </HEAD><BODY>
<-- <H1>Not Found</H1>
<-- The requested URL /unknown was not found on this server.<P>
<-- <HR>
<-- <ADDRESS>Apache/1.3.34 Server at www.istia.univ-angers.fr Port 80</ADDRESS>
<-- </BODY></HTML>
<--
<-- 0
<--
[Fin du thread de lecture des réponses du serveur]
bye

...\Chap9\04\bin\Release>

The reader is invited to review the explanations provided in Section 11.4.2. We will only comment on what is specific to the application:

  • line 28: after sending line 27, the HTTP server closed the connection, which caused the read thread to terminate. The main thread that reads commands typed on the keyboard is still active. The command on line 29, typed on the keyboard, stops it.

SMTP Protocol

...\Chap9\04\bin\Release>ClientTcpGenerique smtp.neuf.fr 25
Tapez vos commandes (bye pour arrêter) :
<-- 220 neuf-infra-smtp-out-sp604002av.neufgp.fr neuf telecom Service relais mail ready
HELO istia.univ-angers.fr
<-- 250 neuf-infra-smtp-out-sp604002av.neufgp.fr hello [84.100.189.193], Banner OK , ready to send mail
mail from: xx@neuf.fr
<-- 250 2.1.0 <xx@neuf.fr> sender ok
rcpt to: yy@univ-angers.fr
<-- 250 2.1.5 <yy@univ-angers.fr> recipient ok
data
<-- 354 enter mail, end with "." on a line by itself
ligne1
ligne2
.
<-- 250 2.0.0 M0jL1Z0044AoCxw0200000 message ok
quit
<-- 221 2.0.0 neuf-infra-smtp-out-sp604002av.neufgp.fr nine telecom closing connection
[Fin du thread de lecture des réponses du serveur]
bye

...\Chap9\04\bin\Release>

The reader is invited to review the explanations provided in section 11.4.3 and to test the other examples using the PuTTY client.

11.6.4. A generic Tcp server

Now we will look at a server

  1. that displays on the screen the commands sent by its clients
  2. sends them, in response, the lines of text typed on the keyboard by a user. It is therefore the user who acts as the server.

The program is launched in a DOS window using: ServeurTcpGenerique portEcoute, where portEcoute is the port to which the clients must connect. Client service will be handled by two threads:

  • the main thread, which:
    1. processes the clients one after another, not in parallel.
    2. will read the lines typed by the user on the keyboard and send them to the client. The user will signal with the "bye" command that they are closing the connection with the client. Because the console cannot be used for two clients simultaneously, our server handles only one client at a time.
  1. a secondary thread dedicated exclusively to reading the text lines sent by the client

The server never stops unless the user presses Ctrl-C on the keyboard.

Let’s look at a few examples. The server is running on port 100, and we’re using the generic paragraphe11.6.3 client to communicate with it. The client window looks like this:

1
2
3
4
5
6
7
...\Chap9\04\bin\Release>ClientTcpGenerique localhost 100
Tapez vos commandes (bye pour arrêter) :
commande 1 du client 1
<-- answer 1 to customer 1
commande 2 du client 1
<-- answer 2 to customer 1
bye

Lines beginning with <-- are those sent from the server to the client; the others are from the client to the server. The server window is as follows:

...\Chap9\05\bin\Release>ServeurTcpGenerique 100
Serveur générique lancé sur le port 0.0.0.0:100
Client 127.0.0.1:4165
Tapez vos commandes (bye pour arrêter) :
<-- order 1 from customer 1
réponse 1 au client 1
<-- order 2 from customer 1
réponse 2 au client 1
[Fin du thread de lecture des demandes du client]
bye

Lines beginning with <-- are those sent from the client to the server; the others are those sent by the server to the client. Line 9 indicates that the thread reading client requests has stopped. The server's main thread is still waiting for keyboard commands to send to the client. You must then type the "bye" command from line 10 to move on to the next client. The server is still active even though client 1 has finished. We launch a second client for the same server:

1
2
3
4
5
...\Chap9\04\bin\Release>ClientTcpGenerique localhost 100
Tapez vos commandes (bye pour arrêter) :
commande 3 du client 2
<-- answer 3 to customer 2
bye

The server window will then look like this:

1
2
3
4
5
6
Tapez vos commandes (bye pour arrêter) :
Client 127.0.0.1:4166
<-- order 3 from customer 2
réponse 3 au client 2
[Fin du thread de lecture des demandes du client]
bye

After line 6 above, the server is now waiting for a new client. You can stop it by pressing Ctrl-C.

Now let’s simulate a web server by launching our generic server on port 88:

1
2
3
...\Chap9\05\bin\Release>ServeurTcpGenerique 88

Serveur générique lancé sur le port 0.0.0.0:88

Now let’s open a browser and request the URL http://localhost:88/example.html page. The browser will then connect to port 88 on the localhost machine and request the page /example.html:

 

Now let’s look at our server window:

Serveur générique lancé sur le port 0.0.0.0:88
Client 127.0.0.1:4167
Tapez vos commandes (bye pour arrêter) :
<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/msword, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-appl
ication, application/x-silverlight, */*
<-- Accept-Language: fr,en-US;q=0.7,fr-FR;q=0.3
<-- UA-CPU: x86
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.
4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.590; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

We see the HTTP headers sent by the browser. This allows us to discover other HTTP headers than those already encountered. Let’s craft a response for our client. The user at the keyboard is the actual server here and can craft a response manually. Let’s recall the response sent by a web server in a previous example:

HTTP/1.1 200 OK
Date: Sat, 03 May 2008 07:53:47 GMT
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
X-Powered-By: PHP/4.4.4-8+etch4
Set-Cookie: fe_typo_user=0d2e64b317; path=/
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html;charset=iso-8859-1

693f
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"                                                                        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
         <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr_FR" lang="fr_FR">
....
         </html>
0

Let's try to provide a similar response using only the bare minimum:

HTTP/1.1 200 OK
Server: serveur tcp generique
Connection: close
Content-Type: text/html

<html>
<head><title>Serveur generique</title></head>
<body><h2>Reponse du serveur generique</h2></body>
</html>
bye
Flux de lecture des lignes de texte du client : l'erreur suivante s'est produite : Unable to read data from the transport connection: Une opération de blocage a été interrompue par un appel à WSACancelBlockingCall.
[Fin du thread de lecture des demandes du client]

In our response, we have limited ourselves to the HTTP headers in lines 1–4. We do not specify the size of the document we are sending (Content-Length) but simply state that we will close the connection (Connection: close) after sending it. This is sufficient for the browser. Upon seeing the connection closed, the browser will know that the server’s response is complete and will display the page HTML that was sent to it. This page consists of lines 6–9. The user then closes the connection to the client by typing the command “bye” on line 10. Upon receiving this keyboard command, the main thread closes the connection with the client. This triggers the exception on line 11. The thread reading the client’s text lines was abruptly interrupted by the closure of the connection with the client and threw an exception. After line 12, the server waits for a new client.

The client browser now displays the following:

If you go to View/Source to see what the browser received, you get [2], which is exactly what was sent from the generic server.

The code for the generic TCP server is as follows:


using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;
 
namespace Chap9 {
    public class ServeurTcpGenerique {
        public const string syntaxe = "Syntaxe : ServeurGénérique Port";
 
        // main program
        public static void Main(string[] args) {
 
            // is there an argument?
            if (args.Length != 1) {
                Console.WriteLine(syntaxe);
                Environment.Exit(1);
            }
            // this argument must be integer >0
            int port = 0;
            if (!int.TryParse(args[0], out port) || port <= 0) {
                Console.WriteLine("{0} : {1}Port incorrect", syntaxe, Environment.NewLine);
                Environment.Exit(2);
            }
            // we create the listening service
            TcpListener ecoute = null;
            try {
                // create the service
                ecoute = new TcpListener(IPAddress.Any, port);
                // launch it
                ecoute.Start();
                // follow-up
                Console.WriteLine("Serveur générique lancé sur le port {0}", ecoute.LocalEndpoint);
                while (true) {
                    // waiting for a customer
                    Console.WriteLine("Attente du client suivant...");
                    TcpClient tcpClient = ecoute.AcceptTcpClient();
                    Console.WriteLine("Client {0}", tcpClient.Client.RemoteEndPoint);
                    // launch a separate thread to read the lines of text sent by the client
                    ThreadPool.QueueUserWorkItem(Receive, tcpClient);
                    // keyboard commands are read in the main thread
                    Console.WriteLine("Tapez vos commandes (bye pour arrêter) : ");
                    string réponse = null;        // server response
                    // operate the customer connection
                    using (tcpClient) {
                        // create a write flow to the client
                        using (NetworkStream networkStream = tcpClient.GetStream()) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // unbuffered output stream
                                writer.AutoFlush = true;
                                // keyboard response loop
                                while (true) {
                                    réponse = Console.ReadLine();
                                    // finished?
                                    if (réponse.Trim().ToLower() == "bye")
                                        break;
                                    // we send the request to the customer
                                    writer.WriteLine(réponse);
                                }
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                // we report the error
                Console.WriteLine("Main : l'erreur suivante s'est produite : {0}", ex.Message);
            } finally {
                // end of listening
                ecoute.Stop();
            }
        }
 
        // read thread server <-- client
        public static void Receive(object infos) {
            // local data
            string demande = null;    // customer request
            string idClient=null;    // customer identity
 
            // operation customer connection
            try {
                using (TcpClient tcpClient = infos as TcpClient) {
                    // customer identity
                    idClient = tcpClient.Client.RemoteEndPoint.ToString();
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            // loop continuous reading of input stream text lines
                            while ((demande = reader.ReadLine()) != null) {
                                // console display
                                Console.WriteLine("<-- {0}", demande);
                            }
                        }
                    }
                }
            } catch (Exception ex) {
                // error
                Console.WriteLine("Flux de lecture des lignes de texte du client {1} : l'erreur suivante s'est produite : {0}", ex.Message,idClient);
            } finally {
                // signals the end of the read thread
                Console.WriteLine("Fin du thread de lecture des lignes de texte du client {0}. Si besoin est, arrêtez le thread de lecture console du serveur pour ce client, avec la commande bye.", idClient);
            }
        }
    }
}
  • line 29: the listening service is created but not started. It listens on all network interfaces of the machine.
  • line 31: the listening service is started
  • line 34: infinite wait loop for clients. The user will stop the server by pressing Ctrl-C.
  • line 37: waiting for a client—blocking operation. When the client arrives, the TcpClient instance returned by the AcceptTcpClient method represents the server side of an open connection with the client.
  • line 40: the read stream for client requests is handled by a separate thread.
  • line 45: use of the client connection in a using statement to ensure it is closed no matter what.
  • Line 47: The network stream is used in a using clause
  • Line 48: Creation of a write stream on the network stream within a using clause
  • Line 50: The write stream will be unbuffered
  • Lines 52–59: Loop for entering commands via the keyboard to be sent to the client
  • line 69: end of the listening service. This instruction will never be executed here since the server is stopped by Ctrl-C.
  • line 78: the Receive method, which continuously displays on the console the lines of text sent by the client. This is similar to what was seen for the generic client TCP.

11.6.5. A Web Client " "

In the previous example, we saw some of the HTTP headers sent by a browser:

<-- GET /exemple.html HTTP/1.1
<-- Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/msword, application/xaml+xml, application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-appl
ication, application/x-silverlight, */*
<-- Accept-Language: fr,en-US;q=0.7,fr-FR;q=0.3
<-- UA-CPU: x86
<-- Accept-Encoding: gzip, deflate
<-- User-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.
4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.590; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022)
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

We will write a web client that takes a URL as a parameter and displays the text sent by the server on the screen. We will assume that the server supports the HTTP 1.1 protocol. From the headers listed above, we will only use the following:

1
2
3
4
<-- GET /exemple.html HTTP/1.1
<-- Host: localhost:88
<-- Connection: close
<--
  1. The first header specifies the desired document
  2. the second specifies the server being queried
  3. the third that we want the server to close the connection after responding to us.

If, in line 1 above, we replace GET with HEAD, the server will send us only the headers HTTP and not the document specified in line 1.

Our web client will be called as follows: ClientWeb URL cmd, where URL is thedesired URL and cmd is one of the two keywords GET or HEAD to indicate whether we want only the headers (HEAD) or also the page content (GET). Let’s look at a first example:

...\Chap9\06\bin\Release>ClientWeb http://istia.univ-angers.fr:80 HEAD
HTTP/1.1 200 OK
Date: Sat, 03 May 2008 14:05:24 GMT
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
X-Powered-By: PHP/4.4.4-8+etch4
Set-Cookie: fe_typo_user=e668408ac1; path=/
Connection: close
Content-Type: text/html;charset=iso-8859-1

...\Chap9\06\bin\Release>
  • line 1, we only request the headers HTTP (HEAD)
  • lines 2-9: the server's response

If we use GET instead of HEAD in the Web client call, we get the same result as with HEAD, plus the body of the requested document.

The web client code is as follows:


using System;
using System.IO;
using System.Net.Sockets;
 
namespace Chap9 {
    class ClientWeb {
        static void Main(string[] args) {
            // syntax
            const string syntaxe = "pg URI GET/HEAD";
 
            // number of arguments
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }
 
            // note the URI required
            string stringURI = args[0];
            string commande = args[1].ToUpper();
 
            // URI validity check
            if(! stringURI.StartsWith("http://")){
                Console.WriteLine("Indiquez une Url de la forme http://machine[:port]/document");
                return;
            }
            Uri uri = null;
            try {
                uri = new Uri(stringURI);
            } catch (Exception ex) {
                // URI incorrect
                Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.Message);
                return;
            }
            // order verification
            if (commande != "GET" && commande != "HEAD") {
                // incorrect order
                Console.WriteLine("Le second paramètre doit être GET ou HEAD");
                return;
            }
 
            try {
                // connect to the service
                using (TcpClient tcpClient = new TcpClient(uri.Host, uri.Port)) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // unbuffered output stream
                                writer.AutoFlush = true;
                                // request URL - send HTTP headers
                                writer.WriteLine(commande + " " + uri.PathAndQuery + " HTTP/1.1");
                                writer.WriteLine("Host: " + uri.Host + ":" + uri.Port);
                                writer.WriteLine("Connection: close");
                                writer.WriteLine();
                                // we read the answer
                                string réponse = null;
                                while ((réponse = reader.ReadLine()) != null) {
                                    // the response is displayed on the console
                                    Console.WriteLine(réponse);
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // exception is displayed
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
            }
        }
    }
}

The only new feature in this program is the use of the Uri class. The program receives a URL (Uniform Resource Locator) or URI (Uniform Resource Identifier) in the form http://server:port/cheminPageHTML?param1=val1;param2=val2;.... The Uri class allows us to break down the URL string into its various components.

  • Lines 26–33: A Uri object is constructed from the stringURI string received as a parameter. If the URI string received as a parameter is not a valid URI (missing protocol, server, etc.), an exception is thrown. This allows us to verify the validity of the received parameter. Once the Uri object is constructed, we have access to the various elements of this Uri. Thus, if the uri object from the previous code was constructed from the string http://server:port/document?param1=val1&param2=val2;... we will have:
    • uri.Host=server,
    • uri.Port=port,
    • uri.Path=document,
    • uri.Query=param1=val1&param2=val2;...,
    • uri.pathAndQuery= cheminPageHTML?param1=val1&param2=val2;...,
    • uri.Scheme=http.

11.6.6. A web client that handles redirects

The previous web client does not handle a possible redirection from the URL it requested. Here is an example:

...\Chap9\06\bin\Release>ClientWeb http://www.ibm.com GET
HTTP/1.1 302 Found
Date: Sat, 03 May 2008 14:50:52 GMT
Server: IBM_HTTP_Server
Location: http://www.ibm.com/us/
Content-Length: 206
Kp-eeAlive: timeout=10, max=73
Connection: Keep-Alive
Content-Type: text/html

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="http://www.ibm.com/us/">here</a>.</p>
</body></html>
  • Line 2: The 302 Found code indicates a redirect. The address to which the browser should redirect is in the body of the document, line 16.

A second example:

...\Chap9\06\bin\Release>ClientWeb http://www.bull.com GET
HTTP/1.1 301 Moved Permanently
Date: Sat, 03 May 2008 14:52:31 GMT
Server: Apache/1.3.33 (Unix) WS_filter/2.1.15 PHP/4.3.4
X-Powered-By: PHP/4.3.4
Location: http://www.bull.com/index.php
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html

0
  1. Line 2: The 301 Moved Permanently code indicates a redirect. The address to which the browser should redirect is specified on line 6, in the HTTP Location header.

A third example:

1
2
3
4
5
6
7
...\Chap9\06\bin\Release>ClientWeb http://www.gouv.fr GET
HTTP/1.1 302 Moved Temporarily
Server: AkamaiGHost
Content-Length: 0
Location: http://www.premier-ministre.gouv.fr/en/
Date: Sat, 03 May 2008 14:56:53 GMT
Connection: close
  • Line 2: The 302 Moved Temporarily code indicates a redirect. The address to which the browser should redirect is indicated on line 5, in the HTTP Location header.

A fourth example with a IIS server local to the machine:

...\istia\Chap9\06\bin\Release>ClientWeb.exe http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.1
Date: Sun, 04 May 2008 10:16:56 GMT
Connection: close
Location: localstart.asp
Content-Length: 121
Content-Type: text/html
Set-Cookie: ASPSESSIONIDQQASDQAB=FDJLADLCOLDHGKGNIPMLHIIA; path=/
Cache-control: private
  1. Line 2: The 302 Object moved code indicates a redirect. The address to which the browser must redirect is indicated on line 5, in the HTTP Location header. Note that unlike the previous examples, the redirect address is relative. The full address is actually http://localhost/localstart.asp.

We propose to handle redirects when the first line of the HTTP headers contains the keyword "moved" (case-insensitive) and the redirect address is in the HTTP Location header.

If we revisit the last three examples, we get the following results:

Url: http://www.bull.com

...\Chap9\06B\bin\Release>ClientWebAvecRedirection http://www.bull.com HEAD
HTTP/1.1 301 Moved Permanently
Date: Sun, 04 May 2008 10:22:48 GMT
Server: Apache/1.3.33 (Unix) WS_filter/2.1.15 PHP/4.3.4
X-Powered-By: PHP/4.3.4
Location: http://www.bull.com/index.php
Connection: close
Content-Type: text/html


<--Redirect to URL http://www.bull.com/index.php-->

HTTP/1.1 200 OK
Date: Sun, 04 May 2008 10:22:49 GMT
Server: Apache/1.3.33 (Unix) WS_filter/2.1.15 PHP/4.3.4
X-Powered-By: PHP/4.3.4
Connection: close
Content-Type: text/html
  1. line 11: the redirect occurs to the address in line 6

Url : http://www.gouv.fr

...\Chap9\06B\bin\Release>ClientWebAvecRedirect
ion http://www.gouv.fr HEAD
HTTP/1.1 302 Moved Temporarily
Server: AkamaiGHost
Content-Length: 0
Location: http://www.premier-ministre.gouv.fr/en/
Date: Sun, 04 May 2008 10:30:38 GMT
Connection: close


<--Redirect to URL http://www.premier-ministre.gouv.fr/en/-->

HTTP/1.1 200 OK
Server: Apache
X-Powered-By: PHP/4.4.1
Last-Modified: Sun, 04 May 2008 10:29:48 GMT
Content-Type: text/html
Expires: Sun, 04 May 2008 10:40:38 GMT
Date: Sun, 04 May 2008 10:30:38 GMT
Connection: close
  • line 11: redirection occurs to the address in line 6

Url: http://localhost

...\Chap9\06B\bin\Release>ClientWebAvecRedirection.exe http://localhost HEAD
HTTP/1.1 302 Object moved
Server: Microsoft-IIS/5.1
Date: Sun, 04 May 2008 10:37:11 GMT
Connection: close
Location: localstart.asp
Content-Length: 121
Content-Type: text/html
Set-Cookie: ASPSESSIONIDQQASDQAB=GDJLADLCJCMPCHFFEJEFPKMK; path=/
Cache-control: private


<--Redirect to URL http://localhost/localstart.asp-->

HTTP/1.1 401 Access Denied
Server: Microsoft-IIS/5.1
Date: Sun, 04 May 2008 10:37:11 GMT
WWW-Authenticate: Negotiate
WWW-Authenticate: NTLM
WWW-Authenticate: Basic realm="localhost"
Connection: close
Content-Length: 4766
Content-Type: text/html
  • line 13: redirection occurs to the address in line 6
  • line 15: access to the page http://localhost/localstart.asp was denied.

The program handling the redirection is as follows:


using System;
using System.IO;
using System.Net.Sockets;
using System.Text.RegularExpressions;
 
namespace Chap9 {
    class ClientWebAvecRedirection {
        static void Main(string[] args) {
            // syntax
            const string syntaxe = "pg URI GET/HEAD";
 
            // number of arguments
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }
 
            // note the URI required
            string stringURI = args[0];
            string commande = args[1].ToUpper();
 
            // URI validity check
            if (!stringURI.StartsWith("http://")) {
                Console.WriteLine("Indiquez une Url de la forme http://machine[:port]/document");
                return;
            }
            Uri uri = null;
            try {
                uri = new Uri(stringURI);
            } catch (Exception ex) {
                // URI incorrect
                Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.Message);
                return;
            }
            // order verification
            if (commande != "GET" && commande != "HEAD") {
                // incorrect order
                Console.WriteLine("Le second paramètre doit être GET ou HEAD");
                return;
            }
 
            const int nbRedirsMax = 1;        // no more than one redirection accepted
            int nbRedirs = 0;                            // number of redirects in progress
 
            // regular expression to find a URL redirect
            Regex location = new Regex(@"^Location: (.+?)$");
            try {
                // you may have several URL to request if there are redirections
                while (nbRedirs <= nbRedirsMax) {
                    // redirection management
                    bool redir = false;
                    bool locationFound = false;
                    string locationString = null;
                    // connect to the service
                    using (TcpClient tcpClient = new TcpClient(uri.Host, uri.Port)) {
                        using (StreamReader reader = new StreamReader(tcpClient.GetStream())) {
                            using (StreamWriter writer = new StreamWriter(tcpClient.GetStream())) {
                                // unbuffered output stream
                                writer.AutoFlush = true;
                                // request URL - send HTTP headers
                                writer.WriteLine(commande + " " + uri.PathAndQuery + " HTTP/1.1");
                                writer.WriteLine("Host: " + uri.Host + ":" + uri.Port);
                                writer.WriteLine("Connection: close");
                                writer.WriteLine();
                                // read the first line of the answer
                                string premièreLigne = reader.ReadLine();
                                // screen echo
                                Console.WriteLine(premièreLigne);
 
                                // redirection?
                                if (Regex.IsMatch(premièreLigne.ToLower(), @"\s+moved\s*")) {
                                    // there is a redirection
                                    redir = true;
                                    nbRedirs++;
                                }
 
                                // next HTTP headers until you find the empty line signalling the end of the headers
                                string réponse = null;
                                while ((réponse = reader.ReadLine()) != "") {
                                    // the answer is displayed
                                    Console.WriteLine(réponse);
                                    // if there is a redirection, we search for the Location header
                                    if (redir && !locationFound) {
                                        // compare the current line with the relational expression location
                                        Match résultat = location.Match(réponse);
                                        if (résultat.Success) {
                                            // if found, note the URL of redirection
                                            locationString = résultat.Groups[1].Value;
                                            // we note that we found
                                            locationFound = true;
                                        }
                                    }
                                }
 
                                // the HTTP headers have been used up - write the empty line
                                Console.WriteLine(réponse);
                                // then move on to the body of the document
                                while ((réponse = reader.ReadLine()) != null) {
                                    Console.WriteLine(réponse);
                                }
                            }
                        }
                    }
                    // are we done?
                    if (!locationFound || nbRedirs > nbRedirsMax)
                        break;
                    // there is a redirection to be made - the new Uri is built
                    try {
                        if (locationString.StartsWith("http")) {
                            // address http complete
                            uri = new Uri(locationString);
                        } else {
                            // address http relative to the current uri
                            uri = new Uri(uri, locationString);
                        }
                        // log console
                        Console.WriteLine("\n<--Redirection vers l'URL {0}-->\n", uri);
                    } catch (Exception ex) {
                        // pb with the Uri
                        Console.WriteLine("\n<--L'adresse de redirection {0} n'a pas été comprise : {1} -->\n", locationString, ex.Message);
                    }
                }
            } catch (Exception e) {
                // exception is displayed
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
            }
        }
    }
}

Compared to the previous version, the changes are as follows:

  1. line 46: the regular expression to retrieve the redirect address in the HTTP Location: address header.
  2. line 49: the code that was previously executed for a single Uri can now be executed successively for multiple Uri entries.
  3. Line 66: Read the first line of the HTTP headers sent by the server. This line contains the keyword "moved" if the requested document has been moved.
  4. lines 71–75: Check whether the first line contains the keyword "moved." If so, note it.
  5. Lines 79–93: Read the remaining HTTP headers until an empty line is encountered, indicating their end. If the first line indicated a redirect, we then focus on the HTTP Location: header to store the redirect address in locationString.
  6. lines 98-100: the rest of the server response HTTP is displayed on the console.
  7. lines 105-106: The requested Uri has been fully processed and displayed. If there is no redirection to perform or if the number of allowed redirections has been exceeded, the program exits.
  8. Lines 108–122: If there is a redirect, the new Uri to be requested is calculated. A bit of work is required depending on whether the redirect address found was absolute (line 111) or relative (line 114).

11.7. The .NET classes specialized for a particular Internet protocol

In the previous examples of the web client, the HTTP protocol was handled using a TCP client. We therefore had to manage the specific communication protocol used ourselves. We could have similarly built a SMTP or POP client. The .NET framework provides specialized classes for the HTTP and SMTP protocols. These classes understand the communication protocol between the client and the server and spare the developer from having to manage them. We will now present them.

11.7.1. classeWebClient

There is a WebClient class capable of communicating with a web server. Consider the example of the web client from Section 11.6.5, which is handled here using the WebClient class.


using System;
using System.IO;
using System.Net;
namespace Chap9 {
    public class Program {
        public static void Main(string[] args) {
            // syntax: [prog] Uri
            const string syntaxe = "pg URI";
 
            // number of arguments
            if (args.Length != 1) {
                Console.WriteLine(syntaxe);
                return;
            }
 
            // note the URI required
            string stringURI = args[0];
 
            // URI validity check
            if (!stringURI.StartsWith("http://")) {
                Console.WriteLine("Indiquez une Url de la forme http://machine[:port]/document");
                return;
            }
            Uri uri = null;
            try {
                uri = new Uri(stringURI);
            } catch (Exception ex) {
                // URI incorrect
                Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.Message);
                return;
            }
 
            try {
                // web client creation
                using (WebClient client = new WebClient()) {
                    // added HTTP header 
                    client.Headers.Add("user-agent", "st");
                    using (Stream stream = client.OpenRead(uri)) {
                        using (StreamReader reader = new StreamReader(stream)) {
                            // display web server response
                            Console.WriteLine(reader.ReadToEnd());
                            // display headers server response
                            Console.WriteLine("---------------------");
                            foreach (string clé in client.ResponseHeaders.Keys) {
                                Console.WriteLine("{0}: {1}", clé, client.ResponseHeaders[clé]);
                            }
                            Console.WriteLine("---------------------");
                        }
                    }
                }
            } catch (WebException e1) {
                Console.WriteLine("L'exception suivante s'est produite : {0}", e1);
            } catch (Exception e2) {
                Console.WriteLine("L'exception suivante s'est produite : {0}", e2);
            }
        }
    }
}
  1. line 35: the web client is created but not yet configured
  2. line 37: a HTTP header is added to the HTTP request that is about to be made. We will see that other headers will be sent by default.
  3. line 38: the web client requests the Uri provided by the user and reads the sent document. [WebClient].OpenRead(Uri) opens the connection with Uri and reads the response. This is the purpose of the class. It handles the communication with the web server. The result of the OpenRead method is of type Stream and represents the requested document. The HTTP headers sent by the server, which precede the document in the response, are not part of it.
  4. Line 39: We use a StreamReader, and on line 41, its ReadToEnd method to read the entire response.
  5. Lines 44–46: The HTTP headers from the server’s response are displayed. [WebClient].ResponseHeaders represents a valued collection whose keys are the names of the HTTP headers and whose values are the character strings associated with these headers.
  6. Line 51: Exceptions that are thrown during a client/server exchange are of type WebException.

Let’s look at a few examples.

We launch the generic TCP server built in section 6.4.6:

...\Chap9\05\bin\Release>ServeurTcpGenerique.exe 88
Serveur générique lancé sur le port 0.0.0.0:88

We launch the previous web client as follows:

...\Chap9\09\bin\Release>09 http://localhost:88

The requested Uri is that of the generic server. The server then displays the HTTP headers sent to it by the web client:

1
2
3
4
5
6
7
Client 127.0.0.1:1415
Tapez vos commandes (bye pour arrêter) :
<-- GET / HTTP/1.1
<-- User-Agent: st
<-- Host: localhost:88
<-- Connection: Keep-Alive
<--

We can see that:

  • that the web client sends 3 headers by default: HTTP (lines 3, 5, 6)
  • line 4: the header we generated ourselves (line 37 of the code)
  • that the web client uses the GET method by default (line 3). There are other methods, including POST and HEAD.

Now let’s request a non-existent resource:

1
2
3
4
5
...\Chap9\09\bin\Release>09 http://istia.univ-angers.fr/unknown
L'the following exception occurred: System.Net.WebException: The remote server returned an error: (404) Not Found.
   at System.Net.WebClient.OpenRead(Uri address)
   at System.Net.WebClient.OpenRead(String address)
   at Chap9.WebClient1.Main(String[] args) in C:\data\2007-2008\c# 2008\poly\istia\Chap9\09\Program.cs:line 16
  • line 2: we encountered an exception of type WebException because the server responded with status code 404 Not Not Found to indicate that the requested resource did not exist.

Finally, let’s finish by requesting an existing resource:

...\istia\Chap9\09\bin\Release>09 http://istia.univ-angers.fr >istia.univ-angers.txt

The file istia.univ-angers.txt produced by the command is as follows:

<!DOCTYPE html
     PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr_FR" lang="fr_FR">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
...
</html>
---------------------
Keep-Alive: timeout=15, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: text/html;charset=iso-8859-1
Date: Sun, 04 May 2008 14:30:53 GMT
Set-Cookie: fe_typo_user=22eaaf283a; path=/
Server: Apache/1.3.34 (Debian) PHP/4.4.4-8+etch4 mod_jk/1.2.18 mod_perl/1.29
X-Powered-By: PHP/4.4.4-8+etch4
---------------------
  1. Line 1: the requested document HTML.
  2. Lines 3–10: The headers of the HTTP response in an order that is not necessarily the same as the order in which they were sent.

The WebClient class provides methods for receiving a document (DownLoad methods) or sending one (UpLoad methods):

DownLoadData
to download a resource as a byte array (e.g., an image)
DownLoadFile
to download a resource and save it to a local file
DownLoadString
to download a resource and retrieve it as a string (e.g., file html)
OpenWrite
the counterpart to OpenRead but for sending data to the server
UpLoadData
the counterpart of DownLoadData but to the server
UpLoadFile
the counterpart of DownLoadFile but to the server
UpLoadString
the counterpart of DownLoadString but to the server
UpLoadValues
to send the data from a POST command to the server and retrieve the results in the form of a byte array. The POST command requests a document while transmitting to the server the information it needs to determine the actual document to send. This information is sent as a document to the server, hence the method name UpLoad. It is sent after the empty line in the HTTP headers in the form param1=value1&param2=value2&...:
POST /document HTTP/1.1
...
[ligne vide]
param1=valeur1&param2=valeur2&...
The same document could be requested using the GET method:
GET /document?param1=valeur1&param2=valeur2&...
...
[ligne vide]
The difference between the two methods is that the browser displaying the requested Uri will display /document in the case of POST and /document?param1=value1&param2=value2&... in the case of GET.

11.7.2. The WebRequest / WebResponse classes

Sometimes the WebClient class is not flexible enough to do what we want. Let’s revisit the example of the web client with redirection discussed in Section 11.6.6. We need to send the HTTP header:

HEAD /document HTTP/1.1

We saw that the HTTP headers sent by default by the web client were as follows:

1
2
3
<-- GET / HTTP/1.1
<-- Host: machine:port
<-- Connection: Keep-Alive

We also saw that it was possible to add HTTP headers to the previous ones using the [WebClient].Headers collection. Only line 1 is not a header belonging to the Headers collection because it does not have the key:value format. I haven’t figured out how to change GET to HEAD in line 1 starting from the WebClient class (maybe I didn’t look hard enough?). When the WebClient class has reached its limits, you can move on to the WebRequest / WebResponse classes:

  • WebRequest: represents the entire request from the web client.
  • WebResponse: represents the entire web server response

We mentioned that the WebClient class handles the http:, https:, ftp:, and file: schemas. The requests and responses for these different protocols do not have the same format. Therefore, it is necessary to handle the exact type of these elements rather than their generic types WebRequest and WebResponse. We will therefore use the classes:

  1. HttpWebRequest, HttpWebResponse for a client HTTP
  2. FtpWebRequest, FtpWebResponse for a client FTP

We will now use the classes HttpWebRequest and HttpWebresponse to address the example of the web client with redirection discussed in Section 11.6.6. The code is as follows:


using System;
using System.IO;
using System.Net.Sockets;
using System.Net;
 
namespace Chap9 {
    class WebRequestResponse {
        static void Main(string[] args) {
            // syntax
            const string syntaxe = "pg URI GET/HEAD";
 
            // number of arguments
            if (args.Length != 2) {
                Console.WriteLine(syntaxe);
                return;
            }
 
            // note the URI required
            string stringURI = args[0];
            string commande = args[1].ToUpper();
 
            // URI validity check
            Uri uri = null;
            try {
                uri = new Uri(stringURI);
            } catch (Exception ex) {
                // URI incorrect
                Console.WriteLine("L'erreur suivante s'est produite : {0}", ex.Message);
                return;
            }
            // order verification
            if (commande != "GET" && commande != "HEAD") {
                // incorrect order
                Console.WriteLine("Le second paramètre doit être GET ou HEAD");
                return;
            }
 
            try {
                // configure the query
                HttpWebRequest httpWebRequest = WebRequest.Create(uri) as HttpWebRequest;
                httpWebRequest.Method = commande;
                httpWebRequest.Proxy = null;
                // it is executed
                HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
                // result
                Console.WriteLine("---------------------");
                Console.WriteLine("Le serveur {0} a répondu : {1} {2}", httpWebResponse.ResponseUri,(int)httpWebResponse.StatusCode, httpWebResponse.StatusDescription);
                // headers HTTP
                Console.WriteLine("---------------------");
                foreach (string clé in httpWebResponse.Headers.Keys) {
                    Console.WriteLine("{0}: {1}", clé, httpWebResponse.Headers[clé]);
                }
                Console.WriteLine("---------------------");
                // document
                using (Stream stream = httpWebResponse.GetResponseStream()) {
                    using (StreamReader reader = new StreamReader(stream)) {
                        // the response is displayed on the console
                        Console.WriteLine(reader.ReadToEnd());
                    }
                }
            } catch (WebException e1) {
                // the answer is retrieved
                HttpWebResponse httpWebResponse = e1.Response as HttpWebResponse;
                Console.WriteLine("Le serveur {0} a répondu : {1} {2}", httpWebResponse.ResponseUri, (int)httpWebResponse.StatusCode, httpWebResponse.StatusDescription);
            } catch (Exception e2) {
                // we display the exception
                Console.WriteLine("L'erreur suivante s'est produite : {0}", e2.Message);
            }
        }
    }
}
  • line 40: an object of type WebRequest is created using the static method WebRequest.Create(Uri uri) or uri is the uri of the document to be downloaded. Since we know that the protocol for Uri is HTTP, the result type is changed to HttpWebRequest in order to access the specific elements of the Http protocol.
  • Line 41: We set the method GET / POST / HEAD from the first line of the HTTP headers. Here, it will be GET or HEAD.
  • Line 42: In a corporate private network, it is common for the company’s machines to be isolated from the internet for security reasons. To achieve this, the private network uses internet addresses that internet routers do not route. The private network is connected to the internet via special machines called proxies, which are connected to both the company’s private network and the internet. This is an example of machines with multiple addresses: IP. A machine on the private network cannot establish a connection with an Internet server—such as a web server—on its own. It must ask a proxy machine to do so on its behalf. A proxy machine can host proxy servers for different protocols. The term "proxy" HTTP refers to the service that handles making requests HTTP on behalf of machines on the private network. If such a HTTP proxy server exists, it must be specified in the [WebRequest].proxy field. For example, you would write:
[WebRequest].proxy=new WebProxy("pproxy.istia.uang:3128");

if the HTTP proxy operates on port 3128 of the machine pproxy.istia.uang. Set the [WebRequest].proxy field to null if the machine has direct access to the Internet and does not need to go through a proxy.

  • Line 44: The GetResponse() method requests the document identified by its Uri and returns a WebRequestResponse object, which is converted here into a HttpWebResponse object. This object represents the server's response to the document request.
  • line 47:
    • [HttpWebResponse].ResponseUri: is the Uri of the server that sent the document. In the event of a redirect, this may differ from the Uri of the server initially queried. Note that the code does not handle redirection. It is handled automatically by the GetResponse method. Again, this is the advantage of high-level classes over the basic classes of the Tcp protocol.
    • [HttpWebResponse].StatusCode, [HttpWebResponse].StatusDescription represent the first line of the response, for example: HTTP/1.1 200 OK. StatusCode is 200 and StatusDescription is OK.
  • Line 50: [HttpWebResponse].Headers is the collection of headers HTTP from the response.
  • line 55: [HttpWebResponse].GetResponseStream: is the stream that allows you to retrieve the document contained in the response.
  • line 61: a WebException exception may occur
  • line 63: [WebException].Response is the response that caused the exception to be thrown.

Here is an example of the execution:

...\Chap9\09B\bin\Release>09B http://www.gouv.fr HEAD
---------------------
Le serveur http://www.premier-ministre.gouv.fr/ replied : 200 OK
---------------------
Connection: keep-alive
Content-Type: text/html; charset=iso-8859-1
Date: Mon, 05 May 2008 13:02:29 GMT
Expires: Mon, 05 May 2008 13:07:20 GMT
Last-Modified: Mon, 05 May 2008 12:56:59 GMT
Server: Apache
X-Powered-By: PHP/4.4.1
---------------------
  • Lines 1 and 3: The server that responded is not the same as the one that was queried. Therefore, a redirection occurred.
  • Lines 5–11: the HTTP headers sent by the server

11.7.3. Application: a proxy client for a web translation server

We will now demonstrate how the previous classes allow us to utilize web resources.

11.7.3.1. The application

There are translation sites on the web. The one used here is the site http://trans.voila.fr/traduction_voila.php:

The text to be translated is entered into [1], and the translation direction is selected in [2]. The translation is requested via [3] and obtained in [4].

We will write a Windows client application for the above application. It will do nothing more than the [trans.voila.fr] web application. Its interface will be as follows:

11.7.3.2. The application architecture

The application will have the following two-tier architecture:

11.7.3.3. The Visual Studio project

The Visual Studio project will be as follows:

  • In [1], the solution consists of two projects,
  • [2]: one for the [dao] layer and the entities used by it,
  • [3]: the other for the Windows interface

11.7.3.4. The [dao] project

The [dao] project consists of the following elements:

  1. IServiceTraduction.cs: the interface presented to layer [ui]
  2. ServiceTraduction: the implementation of this interface
  3. WebTraductionsException: an application-specific exception

The IServiceTraduction interface is as follows:


using System.Collections.Generic;
 
namespace dao {
    public interface IServiceTraduction {
        // languages used
        IDictionary<string, string> LanguesTraduites { get; }
        // translation
        string Traduire(string texte, string deQuoiVersQuoi);
    }
}
  • line 6: the property LanguesTraduites returns the dictionary of languages supported by the translation server. This dictionary has entries in the form ["fe","Français-Anglais"], where the value denotes a translation direction—in this case, from French to English—and the key "fe" is a code used by the trans.voila.fr translation server.
  • line 8: the Translate method is the translation method:
    • text is the text to be translated
    • deQuoiVersQuoi is one of the keys in the dictionary of translated languages
    • the method returns the translation of the text

ServiceTraduction is an implementation class of the IServiceTraduction interface. We detail it in the following section.

WebTraductionsException is the following exception class:


using System;
 
namespace entites {
    public class WebTraductionsException : Exception {
 
        // error code
        public int Code { get; set; }
 
        // manufacturers
        public WebTraductionsException() {
        }
        public WebTraductionsException(string message)
            : base(message) {
        }
        public WebTraductionsException(string message, Exception e)
            : base(message, e) {
        }
    }
}
  1. line 7: an error code

11.7.3.5. The [ServiceTraduction] web client

Let’s review the architecture of our application:

The [ServiceTraduction] class that we need to write is a client of the [trans.voila.fr] translation web service. To write it, we need to understand

  1. what the translation server expects from its client
  2. what it sends back to its client

Let’s look at an example of the client/server dialogue that occurs during a translation. Let’s revisit the example presented in the application’s introduction:

The text to be translated is inserted into [1], the translation direction is selected in [2]. The translation is requested by [3] and obtained in [4].

To obtain the translation [4], the browser sent the following request GET (displayed in its address bar):

http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection=fe&stext=ce+chien+est+malade

It’s pretty easy to understand:

  • http://trans.voila.fr/traduction_voila.php is the Url for the translation service
  • isText=1 seems to indicate that we are dealing with text
  • translationDirection indicates the translation direction, in this case French-English
  • stext is the text to be translated in a format known as Url encoding. This is because certain characters cannot appear in a Url file. For example, the space character has been encoded here as a +. The .Net framework provides the static method System.Web.HttpUtility.UrlEncode to perform this encoding.

We conclude that to query the translation server, our [ServiceTraduction] class can use the string

"http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection={0}&stext={1}"

where the placeholders {0} and {1} will be replaced by the translation direction and the text to be translated, respectively.

How do we know which translation directions are supported by the server? In the screenshot above, the available languages are listed in the dropdown menu. If you view the page’s source code in the browser (View / Source), you’ll find the following for the dropdown menu:

<select name="translationDirection" class="champs">
    <option selected value='fe'>French to English
    <option  value='ef'>English to French
    <option  value='fg'>French to German
    <option  value='gf'>German to French
    <option  value='fs'>French to Spanish
    <option  value='sf'>Spanish to French
    <option  value='fr'>French to Russian
    <option  value='rf'>Russian to French
    <option  value='es'>English to Spanish
    <option  value='se'>Spanish to English
    <option  value='eg'>English to German
    <option  value='ge'>German to English
    <option  value='ep'>English to Portuguese
    <option  value='pe'>Portuguese to English
    <option  value='ie'>Italian to English
    <option  value='gs'>German to Spanish
    <option  value='sg'>Spanish to German
</select>

This is not very clean Html code, in that each <option> tag should normally be closed by a </option> tag. That said, the value attributes provide the list of translation codes that must be sent to the server. In the LanguesTraduites dictionary of the IServiceTraduction interface, the keys will be the value attributes above, and the values will be the texts displayed by the drop-down list.

Now let’s look (View / Source) to see where the translation returned by the translation server is located on the Html page:

...                                                                
<strong>Texte traduit : </strong><div class="txtTrad">this dog is sick</div> 
...

The translation is located right in the middle of the returned Html page. How can we find it? We can use a regular expression with the sequence <div class="txtTrad">...</div> because the <div class="txtTrad"> tag is only present at this location on the Html page. The C# regular expression used to retrieve the translated text is as follows:

@"<div class=""txtTrad"">(.*?)</div>"

We now have the elements to write the implementation class ServiceTraduction for the interface IServiceTraduction:


using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Web;
using entites;
 
namespace dao {
    public class ServiceTraduction : IServiceTraduction {
        // automatic service configuration properties
        public IDictionary<string, string> LanguesTraduites { get; set; }
        public string UrlServeurTraduction { get; set; }
        public string ProxyHttp { get; set; }
        public String RegexTraduction { get; set; }
 
        // translation
        public string Traduire(string texte, string deQuoiVersQuoi) {
            // is the requested translation possible?
            if (!LanguesTraduites.ContainsKey(deQuoiVersQuoi)) {
                throw new WebTraductionsException(String.Format("Le sens de traduction [{0}] n'est pas reconnu")) { Code = 10 };
            }
            // text to translate
            string texteATraduire = HttpUtility.UrlEncode(texte);
            // uri to request
            string uri = string.Format(UrlServeurTraduction, deQuoiVersQuoi, texteATraduire);
            // regular expression to find the translation in the answer
            Regex patternTraduction = new Regex(RegexTraduction);
            // exception
            WebTraductionsException exception = null;
            // translation
            string traduction = null;
            try {
                // configure the query
                HttpWebRequest httpWebRequest = WebRequest.Create(uri) as HttpWebRequest;
                httpWebRequest.Method = "GET";
                httpWebRequest.Proxy = ProxyHttp == null ? null : new WebProxy(ProxyHttp); ;
                // it is executed
                HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
                // document
                using (Stream stream = httpWebResponse.GetResponseStream()) {
                    using (StreamReader reader = new StreamReader(stream)) {
                        bool traductionTrouvée = false;
                        string ligne = null;
                        while (!traductionTrouvée && (ligne = reader.ReadLine()) != null) {
                            // search for translation in current line
                            MatchCollection résultats = patternTraduction.Matches(ligne);
                            // translation found?
                            if (résultats.Count != 0) {
                                traduction = résultats[0].Groups[1].Value.Trim();
                                traductionTrouvée = true;
                            }
                        }
                        // translation found?
                        if (!traductionTrouvée) {
                            exception = new WebTraductionsException("Le serveur n'a pas renvoyé de réponse") { Code = 12 };
                        }
                    }
                }
            } catch (Exception e) {
                exception = new WebTraductionsException("Erreur rencontrée lors de la traduction", e) { Code = 11 };
            }
            // exception?
            if (exception != null) {
                throw exception;
            } else {
                return traduction;
            }
        }
    }
}
  • line 12: the LanguesTraduites property of the IServiceTraduction interface—initialized externally
  • line 13: the property UrlServeurTraduction is the Url to be requested from the translation server: http://trans.voila.fr/translation_here.php?isText=1&translationDirection={0}&stext={1} where the {0} placeholder must be replaced by the translation direction and the {1} placeholder by the text to be translated—initialized externally
  • line 14: the property ProxyHttp is the optional proxy Http to be used, for example: pproxy.istia.uang:3128 - initialized externally
  • Line 15: The property RegexTraduction is the regular expression used to retrieve the translation from the Html stream returned by the translation server, for example @"<div class=""txtTrad"">(.*?)</div>" - initialized externally
  • In our application, these four properties will be initialized by Spring.
  • Lines 20–22: We verify that the requested translation direction exists in the dictionary of translated languages. If not, an exception is thrown.
  • line 24: the text to be translated is encoded so it can be part of a Url
  • line 26: the Uri for the translation service is constructed. If the UrlServeurTraduction property is the string http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection={0}&stext={1}, the {0} placeholder is replaced by the translation direction and the {1} placeholder by the text to be translated.
  • line 28: the template for searching for the translation in the html response from the translation server is constructed.
  • Lines 33, 60: The operation to query the translation server takes place within a try/catch block
  • line 35: the HttpWebRequest object, which will be used to query the translation server, is constructed using the Uri from the requested document.
  • line 36: the query method is GET. This statement could be omitted, as GET is likely the default method of the HttpWebRequest object.
  • Line 37: The Proxy property of the HttpWebRequest object is set.
  • Line 39: The request is sent to the translation server, and its response, of type HttpWebResponse, is retrieved.
  • Lines 41–42: A StreamReader is used to read each line of the html response from the server.
  • Lines 45–53: In each line of the response, we search for the translation. Once found, we stop reading the Html response and close all open streams.
  • lines 55-57: if no translation was found in the html response, an exception of type WebTraductionsException is prepared to indicate this.
  • lines 60-62: if an exception occurred during the client/server exchange, we wrap it in a WebTraductionsException exception to indicate this.
  • Lines 64–68: If an exception has been logged, it is thrown; otherwise, the translation found is returned.

Our example assumes that the Http proxy does not require authentication. If that were not the case, we would write something like:


httpWebRequest.Proxy = ProxyHttp == null ? null : new WebProxy(ProxyHttp); ;
httpWebRequest.Proxy.Credentials=new NetworkCredential("login","password");

We used WebRequest / WebResponse here rather than WebClient because we do not need to process the entire Html response from the translation server. Once the translation is found in this response, we no longer need the rest of the response lines. The WebClient class does not allow us to do this.

Here is a test program for the ServiceTraduction class:


using System;
using System.Collections.Generic;
using dao;
using entites;
 
namespace ui {
    class Program {
        static void Main(string[] args) {
            try {
                // creation translation service
                ServiceTraduction serviceTraduction = new ServiceTraduction();
                // regular expression to find the translation
                serviceTraduction.RegexTraduction = @"<div class=""txtTrad"">(.*?)</div>";
                // url translation server
                serviceTraduction.UrlServeurTraduction = "http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection={0}&stext={1}";
                // dictionary of translated languages
                Dictionary<string, string> languesTraduites = new Dictionary<string, string>();
                languesTraduites["fe"]= "Français-Anglais";
                languesTraduites["fs"]= "Français-Espagnol";
                languesTraduites["ef"]= "Anglais-Français";
                serviceTraduction.LanguesTraduites = languesTraduites;
                // proxy
                //serviceTraduction.ProxyHttp = "pproxy.istia.uang:3128";
                // translation
                string texte = "ce chien est perdu";
                string deQuoiVersQuoi = "fe";
                Console.WriteLine("Traduction [{0}] de [{1}] : [{2}]", languesTraduites[deQuoiVersQuoi], texte, serviceTraduction.Traduire(texte, deQuoiVersQuoi));
                texte = "l'été sera chaud";
                deQuoiVersQuoi = "fs";
                Console.WriteLine("Traduction [{0}] de [{1}] : [{2}]", languesTraduites[deQuoiVersQuoi], texte, serviceTraduction.Traduire(texte, deQuoiVersQuoi));
                texte = "my tailor is rich";
                deQuoiVersQuoi = "ef";
                Console.WriteLine("Traduction [{0}] de [{1}] : [{2}]", languesTraduites[deQuoiVersQuoi], texte, serviceTraduction.Traduire(texte, deQuoiVersQuoi));
                texte = "xx";
                deQuoiVersQuoi = "ef";
                Console.WriteLine("Traduction [{0}] de [{1}] : [{2}]", languesTraduites[deQuoiVersQuoi], texte, serviceTraduction.Traduire(texte, deQuoiVersQuoi));
            } catch (WebTraductionsException e) {
                // error
                Console.WriteLine("L'erreur suivante de code {1} s'est produite : {0}", e.Message, e.Code);
            }
        }
    }
}

The results are as follows:

1
2
3
4
Traduction [Français-Anglais] de [ce chien est perdu] : [this dog is lost]
Traduction [Français-Espagnol] de [l'été sera chaud]: [el verano será caliente]
Traduction [Anglais-Français] de [my tailor is rich] : [mon tailleur est riche]
Traduction [Anglais-Français] de [xx] : [xx]

The solution's [dao] project is compiled into DLL and HttpTraductions.dll:

 

11.7.3.6. The application's graphical user interface

Let’s revisit the architecture of our application:

We are now writing the [ui] layer. This is the subject of the [ui] project in the solution under construction:

The [lib] [3] folder contains some of the DLL files referenced by the [4] project:

  • those required by Spring: Spring.Core, Common.Logging, antlr.runtime
  • the one for the [dao] layer: HttpTraductions

The file [App.config] contains the Spring configuration:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
 
    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <description>Traductions sur le web</description>
            <!-- translation service -->
            <object name="ServiceTraduction" type="dao.ServiceTraduction, HttpTraductions">
                <property name="UrlServeurTraduction" value="http://trans.voila.fr/traduction_voila.php?isText=1&amp;translationDirection={0}&amp;stext={1}"/>
                <!--
                <property name="ProxyHttp" value="pproxy.istia.uang:3128"/>
                -->
                <property name="RegexTraduction" value="&lt;div class=&quot;txtTrad&quot;&gt;(.*?)&lt;/div&gt;"/>
                <property name="LanguesTraduites">
                    <dictionary key-type="string" value-type="string">
                        <entry key="fe" value="Français-Anglais"/>
                        <entry key="ef" value="Anglais-Français"/>
...
                        <entry key="ei" value="Anglais-Italien"/>
                        <entry key="ie" value="Italien-Anglais"/>
                    </dictionary>
                </property>
            </object>
        </objects>
    </spring>
</configuration>
  • Line 15: the objects to be instantiated by Spring. There will be only one, the one on line 18, which instantiates the translation service using the ServiceTraduction class found in DLL HttpTraductions.
  • line 19: the UrlServeurTraduction property of the ServiceTraduction class. There is an issue with the & character in Url. This character has a specific meaning in a Xml file. It must therefore be protected. This is also the case for other characters that we will encounter later in the file. They must be replaced by a [&code;] sequence: & with [&amp;], < with [&lt;], > with [&gt;], " with [&quot;].
  • line 21: the property ProxyHttp of the class ServiceTraduction. An uninitialized property remains null. Not defining this property is equivalent to saying that there is no Http proxy.
  • Line 23: the property RegexTraduction of the class ServiceTraduction. In the regular expression, the characters [< > "] had to be replaced with their escaped equivalents.
  • Lines 24–33: the LanguesTraduites property of the ServiceTraduction class.

The program [Program.cs] is executed when the application starts. Its code is as follows:


using System;
using System.Text;
using System.Windows.Forms;
using dao;
using Spring.Context;
using Spring.Context.Support;
 
namespace ui {
    static class Program {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main() {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
 
            // --------------- Developer code
            // instantiation translation service
            IApplicationContext ctx = null;
            Exception ex = null;
            ServiceTraduction serviceTraduction = null;
            try {
                // spring context
                ctx = ContextRegistry.GetContext();
                // request a reference for the translation service
                serviceTraduction = ctx.GetObject("ServiceTraduction") as ServiceTraduction;
            } catch (Exception e1) {
                // memory exception
                ex = e1;
            }
            // form to display
            Form form = null;
            // was there an exception?
            if (ex != null) {
                // yes - create the error message to be displayed
                StringBuilder msgErreur = new StringBuilder(String.Format("Chaîne des exceptions : {0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
                Exception e = ex;
                while (e != null) {
                    msgErreur.Append(String.Format("{0}: {1}{2}", e.GetType().FullName, e.Message, Environment.NewLine));
                    msgErreur.Append(String.Format("{0}{1}", "".PadLeft(40, '-'), Environment.NewLine));
                    e = e.InnerException;
                }
                // creation of an error window to which the error message to be displayed is passed
                Form2 form2 = new Form2();
                form2.MsgErreur = msgErreur.ToString();
                // this will be the window to display
                form = form2;
            } else {
                // all went well
                // creation of a graphical interface [Form1] to which we pass the reference on the translation service
                Form1 form1 = new Form1();
                form1.ServiceTraduction = serviceTraduction;
                // this will be the window to display
                form = form1;
            }
            // window display
            Application.Run(form);
        }
    }
}

This code has already been used in the version 6 Taxes application, in section 7.6.2.

  1. The translation service is created on line 27 by Spring. If this creation was successful, the [Form1] form will be displayed (lines 52–55); otherwise, the [Form2] error form will be displayed (lines 36–48).

Form [Form2] is the one used in the version 6 Taxes application and was explained in section 7.6.4.

Form [Form1] is as follows:

No.
type
name
role
1
TextBox
textBoxTexteATraduire
text box for entering text to be translated
MultiLine=true
2
ComboBox
comboBoxLangues
list of translation directions
3
Button
buttonTraduire
to request the translation of the text [1] into the direction [2]
4
TextBox
textBoxTraduction
the translation of the text [1]

The code for form [Form1] is as follows:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using dao;
 
namespace ui {
    public partial class Form1 : Form {
        // translation service
        public ServiceTraduction ServiceTraduction { get; set; }
        // language dictionary
        Dictionary<string, string> languesInversées = new Dictionary<string, string>();
 
        // manufacturer
        public Form1() {
            InitializeComponent();
        }
 
        // initial form loading
        private void Form1_Load(object sender, EventArgs e) {
            // building an inverted language dictionary
            foreach (string code in ServiceTraduction.LanguesTraduites.Keys) {
                // languages
                string langues = ServiceTraduction.LanguesTraduites[code];
                // add (languages, code) to the inverted dictionary
                languesInversées[langues] = code;
            }
            // filling combo in alphabetical language order
            string[] languesCombo = languesInversées.Keys.ToArray();
            Array.Sort<string>(languesCombo);
            foreach (string langue in languesCombo) {
                comboBoxLangues.Items.Add(langue);
            }
            // 1st language selection
            if (comboBoxLangues.Items.Count != 0) {
                comboBoxLangues.SelectedIndex = 0;
            }
        }
 
        private void buttonTraduire_Click(object sender, EventArgs e) {
            // something to translate?
            string texte = textBoxTexteATraduire.Text.Trim();
            if (texte == "") return;
            // translation
            try {
                textBoxTraduction.Text = ServiceTraduction.Traduire(texte, languesInversées[comboBoxLangues.SelectedItem.ToString()]);
            } catch (Exception ex) {
                textBoxTraduction.Text = ex.Message;
            }
        }
    }
}
  • line 10: a reference to the translation service. This public property was initialized by [Program.cs], line 53. When the methods Form1_Load (line 20) or buttonTraduire_Click (line 40) are executed, this field is therefore already initialized.
  • Line 12: the dictionary of translated languages with entries of type ["Français-Anglais","fe"], c.a.d. The inverse of the LanguesTraduites dictionary returned by the translation service.
  • Line 20: The Form1_Load method runs when the form is loaded.
  • Lines 22–27: The dictionaries serviceTraduction.LanguesTraduites and ["fe","Français-Anglais"] are used to construct the dictionaries languesInversées and ["Français-Anglais", "fe"].
  • Line 29: languesCombo is the key table for the dictionaries languesInversées and c.a.d. An array of elements ["Français-Anglais"]
  • Line 30: This array is sorted so that the translation directions are displayed in the dropdown in alphabetical order
  • Lines 31–33: The language dropdown is populated.
  • line 40: the method executed when the user clicks the [Traduire] button
  • line 46: simply call the serviceTraduction.Traduire method to request the translation. The first parameter is the text to be translated, the second is the translation direction code. This code is found in the languesInversées dictionary based on the element selected in the language dropdown.
  • Line 48: If an exception occurs, it is displayed in place of the translation.

11.7.3.7. Conclusion

This application has demonstrated that the .clients web components of the .NET framework allow us to leverage web resources. The technique is similar in each case:

  1. determine the Uri to query. This Uri is usually preconfigured.
  2. query it
  3. find what we are looking for in the server’s response using regular expressions

This technique is unreliable. Over time, the Uri being queried or the regular expression used to find the expected result may change. It is therefore advisable to store these two pieces of information in a configuration file. However, this may prove insufficient. We will see in the next chapter that there are more stable resources on the web: web services.

11.7.4. A SMTP client (Simple Mail Transfer Protocol) with the SmtpClient class

A SMTP client is a client of a SMTP server, a mail-sending server. The NET class fully encapsulates the requirements of such a client. The developer does not need to know the details of the SMTP protocol. We are familiar with it. It was presented in Section 11.4.3.

We present the SmtpClient class as part of a basic Windows application that allows sending emails with attachments. The application will connect to port 25 of a SMTP server. Note that on most Windows systems, firewalls or antivirus software block connections to port 25. It is therefore necessary to disable this protection to test the application:

The SMTP client will have a single-layer architecture:

The Visual Studio project is as follows:

  

The application’s graphical user interface is as follows:

No.
Type
Name
role
1
TextBox
textBoxServeur
Server name SMTP to connect to
2
NumericUpDown
numericUpDownPort
the port to connect to
3
TextBox
textBoxExpediteur
sender's email address
4
TextBox
textBoxTo
recipient addresses in the format: address1,address2, ...
5
TextBox
textBoxCc
recipient addresses in carbon copy (CC=Carbon Copy) in the format: address1,address2, ...
6
TextBox
textBoxBcc
blind carbon copy recipient addresses (BCC=Blind Carbon Copy) in the format: address1,address2, ... All addresses in these three input fields will receive the same message with the same attachments. The recipients of the message will be able to see the addresses in fields 4 and 5 but not those in field 6. Bcc is therefore a way to copy someone without the other recipients of the message knowing.
7
Button
buttonAjouter
to add an attachment to the email
8
ListBox
listBoxPiecesJointes
list of attachments to the email
9
TextBox
textBoxSujet
Subject of the letter
10
TextBox
textBoxMessage
The message text.
MultiLine=true
11
Button
buttonEnvoyer
to send the message and any attachments
12
TextBox
textBoxRésultat
displays a summary of the sent message or an error message if a problem was encountered
13
Button
buttonEffacer
to clear [12]
 
OpenfileDialog
openFileDialog1
non-visual control that allows you to select an attachment from the local file system

In the previous example, the summary displayed in [12] is as follows:

Envoi réussi...
Sujet : votre demande
Destinataires : y2000@hotmail.com
Cc : 
Bcc : 
Pièces jointes :
C:\data\travail\2007-2008\recrutements 0809\ing3\documents\ing3.zip
Texte : Bonjour,

Vous trouverez ci-joint le dossier de candidature à l'ISTIA.

Cordialement,

ST

The code for form [SendMailForm.cs] is as follows:


using System;
using System.Windows.Forms;
using System.Net.Mail;
using System.Text.RegularExpressions;
using System.Text;
 
namespace Chap9 {
    public partial class SendMailForm : Form {
        public SendMailForm() {
            InitializeComponent();
        }
 
        // add an attachment
        private void buttonAjouter_Click(object sender, EventArgs e) {
            // set the openfileDialog1 dialog box
            openFileDialog1.InitialDirectory = Application.ExecutablePath;
            openFileDialog1.Filter = "Tous les fichiers (*.*)|*.*";
            openFileDialog1.FilterIndex = 0;
            openFileDialog1.FileName = "";
            // display the dialog box and retrieve the result
            if (openFileDialog1.ShowDialog() == DialogResult.OK) {
                // retrieve the file name
                listBoxPiecesJointes.Items.Add(openFileDialog1.FileName);
            }
        }
 
        private void textBoxServeur_TextChanged(object sender, EventArgs e) {
            setStatutEnvoyer();
        }
 
        private void setStatutEnvoyer() {
            buttonEnvoyer.Enabled = textBoxServeur.Text.Trim() != "" && textBoxTo.Text.Trim() != "" && textBoxSujet.Text.Trim() != "";
        }
 
        // remove an attachment
        private void buttonRetirer_Click(object sender, EventArgs e) {
            // selected attachment?
            if (listBoxPiecesJointes.SelectedIndex != -1) {
                // remove it
                listBoxPiecesJointes.Items.RemoveAt(listBoxPiecesJointes.SelectedIndex);
                // update the Remove button
                buttonRetirer.Enabled = listBoxPiecesJointes.Items.Count != 0;
            }
        }
 
        private void listBoxPiecesJointes_SelectedIndexChanged(object sender, EventArgs e) {
            // selected attachment?
            if (listBoxPiecesJointes.SelectedIndex != -1) {
                // update the Remove button
                buttonRetirer.Enabled = true;
            }
        }
 
        // sending the message with attachments
        private void buttonEnvoyer_Click(object sender, EventArgs e) {
....
        }
 
        private void textBoxTo_TextChanged(object sender, EventArgs e) {
            setStatutEnvoyer();
        }
 
        private void textBoxSujet_TextChanged(object sender, EventArgs e) {
            setStatutEnvoyer();
        }
 
        private void buttonEffacer_Click(object sender, EventArgs e) {
            textBoxResultat.Text = "";
        }
    }
}

We will not comment on this code, as it contains nothing new. To understand the buttonAjouter_Click method on line 14, the reader is invited to review section 7.5.1.

The buttonEnvoyer_Click method on line 55, which sends the email, is as follows:


private void buttonEnvoyer_Click(object sender, EventArgs e) {
            try {
                // hourglass
                Cursor = Cursors.WaitCursor;
                // smtp client
                SmtpClient smtpClient = new SmtpClient(textBoxServeur.Text.Trim(), (int)numericUpDownPort.Value);
                // the message
                MailMessage message = new MailMessage();
                // sender
                message.Sender = new MailAddress(textBoxExpéditeur.Text.Trim());
                message.From = message.Sender;
                // recipients
                Regex marqueur = new Regex("\\s*,\\s*");
                string[] destinataires = marqueur.Split(textBoxTo.Text.Trim());
                foreach (string destinataire in destinataires) {
                    if (destinataire.Trim() != "") {
                        message.To.Add(new MailAddress(destinataire));
                    }
                }
                // CC
                string[] copies = marqueur.Split(textBoxCc.Text.Trim());
                foreach (string copie in copies) {
                    if (copie.Trim() != "") {
                        message.CC.Add(new MailAddress(copie));
                    }
                }
                // BCC
                string[] blindCopies = marqueur.Split(textBoxBcc.Text.Trim());
                foreach (string blindCopie in blindCopies) {
                    if (blindCopie.Trim() != "") {
                        message.Bcc.Add(new MailAddress(blindCopie));
                    }
                }
                // subject
                message.Subject = textBoxSujet.Text.Trim();
                // message text
                message.Body = textBoxMessage.Text;
                // attachments
                foreach (string attachement in listBoxPiecesJointes.Items) {
                    message.Attachments.Add(new Attachment(attachement));
                }
                // sending the message
                smtpClient.Send(message);
                // Ok - a summary is displayed
                StringBuilder msg = new StringBuilder(String.Format("Envoi réussi...{0}", Environment.NewLine));
                msg.Append(String.Format("Sujet : {0}{1}", textBoxSujet.Text.Trim(), Environment.NewLine));
                textBoxSujet.Clear();
                msg.Append(String.Format("Destinataires : {0}{1}", textBoxTo.Text.Trim(), Environment.NewLine));
                textBoxTo.Clear();
                msg.Append(String.Format("Cc : {0}{1}", textBoxCc.Text.Trim(), Environment.NewLine));
                textBoxCc.Clear();
                msg.Append(String.Format("Bcc : {0}{1}", textBoxBcc.Text.Trim(), Environment.NewLine));
                textBoxBcc.Clear();
                msg.Append(String.Format("Pièces jointes :{0}", Environment.NewLine));
                foreach (string attachement in listBoxPiecesJointes.Items) {
                    msg.Append(String.Format("{0}{1}", attachement, Environment.NewLine));
                }
                msg.Append(String.Format("Texte : {0}{1}", textBoxMessage.Text, Environment.NewLine));
                listBoxPiecesJointes.Items.Clear();
                textBoxResultat.Text = msg.ToString();
            } catch (Exception ex) {
                // error is displayed
                textBoxResultat.Text = String.Format("L'erreur suivante s'est produite {0}", ex);
            }
            // normal slider
            Cursor = Cursors.Arrow;
        }
  • Line 6: The SMTP client is created. It requires two parameters: the server name SMTP and the port on which it operates
  • line 8: a message of type MailMessage is created. This is what will encapsulate the entire message to be sent.
  • line 10: the sender’s email address is specified. An email address is an instance of type MailAddress constructed from the string "xx@yy.zz". This string must have the expected format for an email address; otherwise, an exception is thrown. In this case, it will be displayed in the textBoxResultat field (line 63) in a non-user-friendly format.
  • Lines 13–19: The recipients’ email addresses are placed in the message’s To list. These addresses are retrieved from the textBoxTo field. The regular expression on line 13 retrieves the various addresses, which are separated by commas.
  • Lines 21–26: We repeat the same process to populate the message’s CC field with the addresses in the Bcc field from the textBoxCc field.
  • Lines 28–33: We repeat the same process to populate the message’s Bcc field with the blind carbon copy addresses from the textBoxBcc field.
  • Line 35: The message's Subject field is initialized with the subject from the textBoxSujet field.
  • Line 37: The message's Body field is initialized with the text from the textBoxMessage message.
  • Lines 39–41: The attachments are attached to the message. Each attachment is added as an Attachment object to the message’s Attachments field. An Attachment object is instantiated from the full path of the file to be attached in the local file system.
  • Line 43: The message is sent using the Send method of the SMTP client.
  • Lines 45-60: Write the send summary to the textBoxResultat field and reset the form.
  • Line 63: Display any errors

11.8. A generic asynchronous Tcp client

11.8.1. Overview

In all the examples in this chapter, client/server communication took place in blocking mode, also known as synchronous mode:

  • when a client connects to a server, it waits for the server’s response to that request before continuing.
  • When a client reads a line of text sent by the server, it is blocked until the server has sent it.
  • On the server side, the service threads that handle client requests operate in the same way as described above.

In graphical user interfaces, it is often necessary to prevent the user from being blocked during long operations. A frequently cited example is the download of a large file. During this download, the user must be free to continue interacting with the graphical user interface.

Here, we propose rewriting the generic Tcp client from Section 11.6.3 by making the following changes:

  • the interface will be graphical
  • the tool for communicating with the server will be a Socket object
  • the communication mode will be asynchronous:
    • the client will initiate a connection to the server but will not remain blocked waiting for it to be established
    • the client will initiate a data transmission to the server but will not remain blocked waiting for it to complete
    • The client will initiate the reception of data from the server but will not remain blocked waiting for it to finish.

Let’s review where the Socket object fits into client/server communication Tcp:

The Socket class operates closest to the network. It allows for fine-grained management of the network connection. The term "socket" refers to an electrical outlet. The term has been extended to refer to a software network socket. In a TCP-IP communication between two machines, A and B, two sockets communicate with each other. An application can work directly with sockets. This is the case with application A above. A socket can be a client or server socket.

11.8.2. The graphical interface of the asynchronous client Tcp

The Visual Studio application is as follows:

  

[ClientTcpAsynchrone.cs] is the graphical interface. It is as follows:

No.
type
Name
role
1
TextBox
textBoxNomServeur
Server name Tcp to connect to
2
NumericUpDown
numericUpDownPortServeur
the port to connect to
3
RadioButton
radioButtonLF
radioButtonRCLF
to specify the line-end character the client should use: LF "\n" or RCLF "\r\n"
4
Button
buttonConnexion
to connect to port [2] on server [1]. The button is labeled [Connecter] when the client is not connected to a server, and [Déconnecter] when it is connected.
5
TextBox
textBoxMsgToServeur
message to send to the server once the connection is established. When the user taps the [Entrée] key, the message is sent with the line break character selected in [3]
6
ListBox
listBoxEvts
List displaying the main events of the client/server connection: connection, disconnection, stream closure, communication errors
7
ListBox
listBoxDialogue
List displaying client/server dialog messages
8
Button
buttonRazEvts
To clear the list [6]
4
Button
buttonRazDialogue
to clear the list [7]

The operating principles of this interface are as follows:

  1. The user connects their graphical client Tcp to a service Tcp using [1, 2, 3, 4].
  2. An asynchronous thread continuously accepts all data sent by the Tcp server and displays it in the [7] list. This thread is decoupled from the interface’s other activities.
  3. The user can send messages to the server at their own pace using [5]. Each message is sent via an asynchronous thread. Unlike the receive thread, which never stops, the send thread terminates as soon as the message has been sent. A new asynchronous thread will be used for the next message.
  4. Client/server communication ends when one of the parties closes the connection. The user can initiate this using the [4] button, which, once the connection is established, is labeled [Déconnecter].

Here is a screenshot of an execution:

  1. in [1]: connection to a service POP
  2. in [2]: display of events that occurred during the connection
  3. in [3]: the message sent by the POP server upon completion of the connection
  4. in [4]: the [Connecter] button became the [Déconnecter] button
  1. in [1], the quit command was sent to the server POP. The server responded with +OK goodbye and closed the connection
  2. in [2], this server-side closure was detected. The client then closed the connection on its end.
  3. In [3], the button [Déconnecter] reverted to a button [Connecter]

11.8.3. Asynchronous connection to the server

Clicking the [Connecter] button triggers the execution of the following method:


        private void buttonConnexion_Click(object sender, EventArgs e) {
            // connection or disconnection?
            if (buttonConnexion.Text == "Déconnecter")
                déconnexion();
            else
                connexion();
}
  • Line 3: The button may be labeled [Connecter] or [Déconnecter].

The connection method is as follows:


using System.Net.Sockets;
...
 
namespace Chap9 {
    public partial class ClientTcp : Form {
        const int tailleBuffer = 1024;
        private Socket client = null;
        private byte[] data = new byte[tailleBuffer];
        private string réponse = null;
        private string finLigne = "\r\n";
 
        // delegates
        public delegate void writeLog(string log);
 
        public ClientTcp() {
            InitializeComponent();
        }
....................................
    private void connexion() {
            // data checks
            string nomServeur = textBoxNomServeur.Text.Trim();
            if (nomServeur == "") {
                logEvent("indiquez le nom du serveur");
                return;
            }
            // follow-up
            logEvent(String.Format("connexion en cours au serveur {0}", nomServeur));
            try {
                 // socket creation
                client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                // asynchronous connection
                client.BeginConnect(Dns.GetHostEntry(nomServeur).AddressList[0],(int)numericUpDownPortServeur.Value, connecté, client);
 
            } catch (Exception ex) {
                logEvent(String.Format("erreur de connexion : {0}", ex.Message));
                return;
            }
        }
 
         // the connection has been made
        private void connecté(IAsyncResult résultat) {
            // retrieve the client socket
            Socket client = résultat.AsyncState as Socket;
    ...
        }
 
 
        // process monitoring
        private void logEvent(string msg) {
....
        }
    }
}
  • Line 1: The Socket class is part of the System.Net.Sockets namespace.

A certain amount of data must be shared among several methods in the form. These are as follows:

  1. line 7: client is the socket used to communicate with the server
  2. lines 6 and 8: the client will receive its messages in a data byte array.
  3. line 9: response is the response sent by the server.
  4. line 10: finLigne is the end-of-line marker used by the client Tcp - is initialized by default to RCLF but can be modified by the user using the radio buttons [3].

The connection procedure on line 19 establishes the connection to the server Tcp:

  • lines 21–25: the server name is checked to ensure it is not empty. If it is not, the event is logged in listBoxEvts using the logEvent method on line 49.
  • line 27: we signal that the connection is about to take place
  • line 30: the Socket object required for communication between Tcp and Ip is created. The constructor accepts three parameters:
    • AddressFamily addressFamily: the address family IP of the client and server, here addresses IPv4 (AddressFamily.InterNetwork)
    • SocketType socketType: the socket type. The SocketType.Stream type is suitable for Tcp-Ip connections
    • ProtocolType protocolType: the type of Internet protocol used, in this case the Tcp protocol
  1. Line 32: The connection is established asynchronously. The connection is initiated, but execution continues without waiting for it to complete. The [Socket].BeginConnect method accepts four parameters:
    1. IPAddress ipAddress: the address Ip of the machine on which the service to be connected to is running
    2. Int32 port: the service port
    3. AsyncCallBack asyncCallBack: AsyncCallBack is a delegate type:
public void AsyncCallBack(IAsyncResult ar);

The method asyncCallBack passed as the third parameter to the method BeginConnect must be a method that accepts a type IAsyncCallBack and returns no result. This is the method that will be called once the connection has been established. Here, we pass the connect method from line 41 as the third parameter.

  • (continued)
    • State object: an object to be passed to the asyncCallBack method. This method receives (see delegate above) an ar parameter of type IAsyncResult. The state object can be retrieved in ar.AsyncState (line 43). Here, we pass the client socket as the fourth parameter.
  • Line 38: The method is complete. The user can once again interact with the GUI. The connection occurs in the background, in parallel with the handling of GUI events. Also in parallel, the connected method on line 41 will be called at the end of the connection, regardless of whether it succeeds or fails.

The code for the *connected* method is as follows:


// the connection has been made
        private void connecté(IAsyncResult résultat) {
            // retrieve the client socket
            Socket client = résultat.AsyncState as Socket;
            try {
                // end asynchronous operation
                client.EndConnect(résultat);
                // follow-up
                logEvent(String.Format("connecté au service {0}", client.RemoteEndPoint));
                // form
                buttonConnexion.Text = "Déconnecter";
                // asynchronous reading of data from the server
                réponse = "";
                client.BeginReceive(data, 0, tailleBuffer, SocketFlags.None, lecture, client);
            } catch (SocketException e) {
                logEvent(String.Format("erreur de connexion : {0}", e.Message));
                return;
            }
}
 
        // data reception
        private void lecture(IAsyncResult résultat) {
            // retrieve the client socket
            Socket client = résultat.AsyncState as Socket;
...
        }
 
  • Line 4: The client socket is retrieved from the result parameter received by the method. Note that this object is the one passed as the fourth parameter to the BeginConnect method.
  • Line 7: The connection attempt is terminated by the EndConnect method, to which the result parameter received by the method must be passed.
  • Line 9: The event is logged in the event list
  • Line 11: The [Connecter] button becomes a [Déconnecter] button so that the user can request to log out.
  • Line 13: The server response is initialized. It will be updated by repeated calls to the asynchronous method BeginReceive.
  • Line 14: First call to the asynchronous method BeginReceive. This method is called with the following parameters:
    • byte[] buffer: the buffer in which to place the data to be received—here the buffer is data
    • int offset: the starting position in the buffer where the data to be received is placed—here the offset is 0, c.a.d, meaning the data is placed starting from the first byte of the buffer.
    • int size: the size of the buffer in bytes—here the size is tailleBuffer.
    • SocketFlags socketFlags: socket configuration—here, no configuration
    • AsyncCallBack asyncCallBack: the callback method to be called when reception is complete. This will occur either because the buffer has received data or because the connection has been closed. Here, the callback method is the read method from line 22.
    • Object state: the object to pass to the callback method asyncCallBack. Here, we pass the client’s socket again.

Note that all of this occurs without any user action, other than the initial connection request via the [Connecter] button. At the end of the connected method, another method is executed in the background: the read method we are now examining.


// data reception
        private void lecture(IAsyncResult résultat) {
            // retrieve the client socket
            Socket client = résultat.AsyncState as Socket;
            int nbOctetsReçus = 0;
            bool erreur = false;
            try {
                // number of bytes received
                nbOctetsReçus = client.EndReceive(résultat);
                if (nbOctetsReçus == 0) {
                    // server no longer responds
                    logEvent("le serveur a fermé la connexion");
                }
            } catch (Exception e) {
                // we had a reception problem
                logEvent(String.Format("erreur de réception : {0}", e.Message));
                erreur = true;
            }
            // finished?
            if (nbOctetsReçus == 0 || erreur) {
                // the customer is disconnected as required
                déconnexion();
                // the end of the answer is displayed
                afficherRéponseServeur(réponse, true);
                // end reading
                return;
            }
            // retrieve the data received
            string données = Encoding.UTF8.GetString(data, 0, nbOctetsReçus);
            // we add them to the data already received
            réponse += données;
            // the answer is displayed
            afficherRéponseServeur(réponse, false);
            // we read on
            client.BeginReceive(data, 0, tailleBuffer, SocketFlags.None, lecture, client);
        }
  • line 2: the read method is triggered in the background when the data buffer has received data or the connection has been closed by the server.
  • line 9: the asynchronous read request is completed by EndReceive. Again, this method must be called with the parameter received by the callback function. The EndReceive method returns the number of bytes received in the read buffer.
  • Line 10: If the number of bytes is zero, it means the connection has been closed by the server.
  • Line 12: Log the event to the event list
  • Line 14: Handle any exceptions
  • Lines 16–17: Log the event in the event log and log the error
  • line 20: check if the connection needs to be closed
  • line 22: close the connection on the client side using a disconnection method that we will cover later.
  • line 24: the server response, c.a.d. The global variable response is displayed in the dialog list listBoxDialogue using a private method afficherRéponseServeur.
  • Line 26: End of the asynchronous read method
  • line 29: the received bytes are placed in a string in the format UTF8.
  • line 31: they are added to the response currently being constructed
  • line 33: the response is displayed in the list listBoxDialogue.
  • line 35: the process resumes waiting for data from the server

Ultimately, the asynchronous read method never stops. It continuously reads data from the server and displays it in the list listBoxDialogue. It only stops when the connection is closed either by the server or by the user themselves.

11.8.4. Disconnecting from the server

Clicking the [Déconnecter] button triggers the execution of the following method:


        private void buttonConnexion_Click(object sender, EventArgs e) {
            // connection or disconnection?
            if (buttonConnexion.Text == "Déconnecter")
                déconnexion();
            else
                connexion();
}
  1. Line 3: The button may be labeled [Connecter] or [Déconnecter].

The logout method logs the client out:


private void déconnexion() {
            // socket closure
            if (client != null && client.Connected) {
                try {
                    // follow-up
                    logEvent(String.Format("déconnexion du service {0}", client.RemoteEndPoint));
                    // disconnect
                    client.Shutdown(SocketShutdown.Both);
                    client.Close();
                    // form
                    buttonConnexion.Text = "Connecter";
                } catch (Exception ex) {
                    // follow-up
                    logEvent(String.Format("erreur de lors de la déconnexion : {0}", ex.Message));
                }
            }
        }
  • line 3: if the client exists and is logged in
  • line 6: the disconnection is reported in listBoxEvts. The client.RemoteEndPoint property returns the pair (Ip address, port) of the other end of the connection, c.a.d here from the server.
  • Line 8: The socket’s data stream is closed using the ShutDown method. A socket’s data stream is bidirectional: the socket sends and receives data. The parameter of the ShutDown method can be: ShutDown.Receive to close the receive stream, Shutdonw.Send to close the send stream, or ShutDown.Both to close both streams.
  • line 9: the resources associated with the socket are released
  • line 11: the [Déconnecter] button becomes the [Connecter] button
  • Lines 12–15: Handle any exceptions

11.8.5. Asynchronous data transmission to the server

When the user submits the message from the textBoxMsgToServeur field, the following method is executed:


        private void textBoxMsgToServeur_KeyPress(object sender, KeyPressEventArgs e) {
            // enter] key ?
            if (e.KeyChar == 13 && client.Connected) {
                envoyerMessage();
            }
}
  • lines 3-5: if the user has pressed the [Entrée] key and if the client socket is connected, then the message in the textBoxMsgToServeur field is sent using the envoyerMessage method.

The envoyerMessage method is as follows:


        private void envoyerMessage() {
            // send a message asynchronously
            // the message
            byte[] message = Encoding.UTF8.GetBytes(textBoxMsgToServeur.Text.Trim() + finLigne);
            // it is sent
            client.BeginSend(message, 0, message.Length, SocketFlags.None, écriture, client);
            // dialogue
            logDialogue("--> " + textBoxMsgToServeur.Text.Trim());
            // raz message
            textBoxMsgToServeur.Clear();
}
  • line 4: the client's end-of-line character is added to the message and stored in the message byte array.
  • line 6: an asynchronous transmission is started using the BeginSend method. The parameters of BeginSend are identical to those of the BeginReceive method. At the end of the asynchronous message transmission operation, the write method will be called.
  • Line 8: The sent message is added to the listBoxDialogue list to track the client/server dialogue
  • Line 10: The sent message is removed from the graphical interface

The write callback method is as follows:


        private void écriture(IAsyncResult résultat) {
            // result of message transmission
            Socket client = résultat.AsyncState as Socket;
            try {
                client.EndSend(résultat);
            } catch (Exception e) {
                // we had an emission problem
                logEvent(String.Format("erreur d'émission : {0}", e.Message));
            }
}
  1. Line 4: The write callback method receives a result parameter of type IAsyncResult.
  2. line 3: in the result parameter, we retrieve the client socket. This socket was the 5th parameter of the BeginSend method.
  3. Line 5: The asynchronous send operation is terminated.

We do not wait for the message transmission to finish before returning control to the user. The user can thus send a second message even while the transmission of the first one is not yet complete.

11.8.6. Displaying events and the client/server dialog

Events are displayed by the logEvents method:


        // process monitoring
        private void logEvent(string msg) {
            listBoxEvts.Invoke(new writeLog(logEventCallBack), msg);
        }
 
        private void logEventCallBack(string msg) {
            // message display
            msg = msg.Replace(finLigne, " ");
            listBoxEvts.Items.Insert(0, String.Format("{0:hh:mm:ss} : {1}", DateTime.Now, msg));
}
  • Line 2: The logEvents method receives the message to be added to the listBoxEvts list as a parameter.
  • line 3: the listBoxEvents component cannot be used directly. This is because the logEvents method is called by two types of threads:
    • the main thread that owns the graphical user interface, for example when it signals that a connection attempt is in progress
    • a secondary thread performing an asynchronous operation. This type of thread does not own the components, and its access to a C component must be controlled by a C.Invoke operation. This operation notifies the C control that a thread wishes to perform an operation on it. The Invoke method accepts two parameters:
      • a delegate-type callback function. This callback function will be executed by the thread that owns the graphical user interface, not by the thread executing the C.Invoke method.
      • an object that will be passed to the callback function.

Here, the first parameter passed to the Invoke method is an instance of the following delegate:


        public delegate void writeLog(string log);

The delegate writeLog has a parameter of type string and returns no result. The parameter will be the message to be logged in listBoxEvts.

Line 3: The first parameter passed to the Invoke method is the logEventCallBack method from line 6. It matches the signature of the writeLog delegate. The second parameter passed to the Invoke method is the message that will be passed as a parameter to the logEventCallBack method.

The Invoke operation is a synchronous operation. The execution of the secondary thread is blocked until the thread owning the control executes the callback method.

  1. Line 6: The callback method executed by the GUI thread receives the message to be displayed in the listBoxEvts control.
  2. Line 9: The event is logged at the top of the list so that the most recent events appear at the top of the list.

Client/server dialog messages are displayed by the logDialogue method:


        // dialogue follow-up
        private void logDialogue(string msg) {
            listBoxDialogue.Invoke(new writeLog(logDialogueCallBack), msg);
        }
        private void logDialogueCallBack(string msg) {
            // message display
            msg = msg.Replace(finLigne, " ");
            listBoxDialogue.Items.Add(String.Format("{0:hh:mm:ss} : {1}", DateTime.Now, msg));
}

The principle is the same as in the logEvent method.

Messages received by the client are displayed using the afficherRéponseServeur method:


        private void afficherRéponseServeur(String msg, bool dernièreLigne) {
...
}

The first parameter is the message to be displayed. This message may consist of multiple lines. This is because the client reads data from the server in blocks of tailleBuffer (1024) bytes. Within these 1024 bytes, there may be multiple lines, each identified by its line-end marker "\n". The last line may be incomplete, with its end-of-line marker located within the next 1024 bytes. The method identifies lines in the message that end with "\n" and then instructs logDialogue to display them. The method’s second parameter specifies whether to display the last line found or leave it in the buffer to be completed by the next message. The code is quite complex and is not relevant here. Therefore, it will not be commented on.

11.8.7. Conclusion

The same example could be handled using synchronous operations. Here, the asynchronous nature of the GUI offers little benefit to the user. Nevertheless, if the user connects and then realizes that the server is "no longer responding," they can disconnect because the GUI continues to respond to events while asynchronous operations are running. This rather complex example has allowed us to introduce new concepts:

  1. the use of sockets
  2. the use of asynchronous methods. What we have covered is part of a standard. Other asynchronous methods exist and operate on the same model.
  3. updating GUI controls via background threads.

Asynchronous Tcp / Ip communication offers more significant advantages for a server than those demonstrated in the previous example. We know that the server serves its clients using secondary threads. If its thread pool has N threads, this means it can serve only N clients simultaneously. If all N threads are performing a blocking (synchronous) operation, there are no threads available for a new client until one of the blocking operations completes and frees up a thread. If the threads perform asynchronous operations rather than synchronous ones, a thread is never blocked and can be quickly reused for new clients.

11.9. Sample application, version 8: Tax calculation server

11.9.1. The architecture of the new version

We revisit the tax calculation application previously covered in various forms. Recall its latest version, that of version 7 in Section 9.8.

The data was stored in a database, and the [ui] layer served as a graphical interface:

 

We will reuse this architecture and distribute it across two machines:

  • a machine [serveur] will host the layers [metier] and [dao] of version 7. A layer Tcp/Ip [serveur] [1] will be built to allow clients from the internet to query the tax calculation service.
  • A [client] machine will host the [ui] layer of the version 7. A Tcp/Ip [client] [2] will be built to allow the [ui] layer to query the tax calculation service.

The architecture changes significantly here. version 7 was a single-user Windows application. version 8 becomes an Internet client/server application. The server will be able to serve multiple clients instances simultaneously.

We will first write the client-side portion of the application.

11.9.2. The tax calculation server

11.9.2.1. The Visual Studio project

The Visual Studio project will be as follows:

  1. in [1], the project. It contains the following elements:
  2. [ServeurImpot.cs]: the Tcp/Ip tax calculation server in the form of a console application.
  3. [dbimpots.sdf]: the SQL Server Compact database for version 7 described in section 9.8.5.
  4. [App.config]: the application configuration file.
  5. In [2], the [lib] folder contains the DLL files required for the project:
    • [ImpotsV7-dao]: the layer [dao] of version 7
    • [ImpotsV7-metier]: the [metier] layer of the version 7
    • [antlr.runtime, CommonLogging, Spring.Core] for Spring
  6. in [3], the project references

11.9.2.2. Application configuration

The [App.config] file is used by Spring. Its content is as follows:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
 
    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
                <constructor-arg index="0" value="System.Data.SqlServerCe.3.5"/>
                <constructor-arg index="1" value="Data Source=|DataDirectory|\dbimpots.sdf;" />
                <constructor-arg index="2" value="select data1, data2, data3 from data"/>
            </object>
            <object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
                <constructor-arg index="0" ref="dao"/>
            </object>
        </objects>
    </spring>
</configuration>
  • lines 16-20: configuration of the [dao] layer associated with the SQL Compact Server database
  • lines 21-23: configuration of the [metier] layer.

This is the configuration file used in the [ui] layer of version 7. It was presented in section 9.8.4.

11.9.2.3. Server Operation

When the server starts, the server application instantiates the [metier] and [dao] layers and then displays an administration console interface:

  

The administration console accepts the following commands:

start port
to start the service on a given port
stop
to stop the service. It can then be restarted on the same port or a different one.
echo start
to enable echoing of client/server dialogue on the console
echo stop
to disable the echo
status
to display the service's active/inactive status
quit
to exit the application

Let's start the server:

1
2
3
Serveur de calcul d'tax >start 27
Serveur de calcul d'tax launched on port 27
Serveur de calcul d'tax >

Now let’s launch the Tcp asynchronous graphics client discussed earlier in Section 11.8.

Image

The client is connected. It can send the following commands to the tax calculation server:

aide
to get a list of allowed commands
impot marié nbEnfants salaireAnnuel
to calculate the tax for someone with nbEnfants children and an annual salary of salaireAnnuel euros. married is 0 if the person is married, 1 otherwise.
aurevoir
to close the connection to the server

Here is an example of a dialogue:

On the server side, the console displays the following:

1
2
3
4
Serveur de calcul d'tax >start 27
Serveur de calcul d'impôt >Tax calculation server launched on port 27
Début du service au client 0
Fin du service au client 0

Let’s enable echo and start a new session from the graphical client:

 

The administration console then displays the following:

1
2
3
4
5
6
7
echo start
Serveur de calcul d'tax >Start customer service 1
<--- Customer 1: help
---> Customer 1: Orders accepted
1-aide
2-impot marié(O/N) nbEnfants salaireAnnuel
3-aurevoir
  1. Line 1: Client/server echo is enabled
  2. line 2: a client has arrived
  3. line 3: it sent the command [aide]
  4. Lines 4-7: The server's response on 4 lines.

Let's stop the service:

1
2
3
stop
L'the following error occurred on the server: A blocking operation was interrupted by a call to WSACancelBlockingCall
Serveur de calcul d'tax >
  • Line 1: We request that the service be stopped (not the application itself)
  • line 2: an exception occurred because the server, which was blocked waiting for a client, was abruptly interrupted due to the shutdown of the listening service.
  • line 3: the service can now be restarted with start port or stopped with quit.

Before the listening service was stopped, a client was being served on another connection. This connection is not closed by closing the listening socket. The client can continue to send commands: the service thread that was associated with it before the listening service was stopped continues to respond to it:

Image

11.9.3. The Tcp tax calculation server code

1
  

The code for the [ServeurImpot.cs] server is as follows:


...
namespace Chap9 {
    public class ServeurImpot {
 
        // data shared between threads and methods
        private static IImpotMetier metier = null;
        private static int port;
        private static TcpListener service;
        private static bool actif = false;
        private static bool echo = false;
 
        // main program
        public static void Main(string[] args) {
            // instantiations [metier] and [dao] layers
            IApplicationContext ctx = null;
            metier = null;
            try {
                // spring context
                ctx = ContextRegistry.GetContext();
                // a reference is requested on the [metier] layer
                metier = (IImpotMetier)ctx.GetObject("metier");
 
                // thread pool configuration
                ThreadPool.SetMinThreads(10, 10);
                ThreadPool.SetMaxThreads(10, 10);
 
                // reads server administration commands typed on the keyboard in an endless loop
                string commande = null;
                string[] champs = null;
                while (true) {
                    // invite
                    Console.Write("Serveur de calcul d'impôt >");
                    // read command
                    commande = Console.ReadLine().Trim().ToLower();
                    champs = Regex.Split(commande, @"\s+");
                    // order execution
                    switch (champs[0]) {
                        case "start":
                            // active?
                            if (actif) {
                                //error
                                Console.WriteLine("Le serveur est déjà actif");
                            } else {
                                // port check
                                if (champs.Length != 2 || !int.TryParse(champs[1], out port) || port <= 0) {
                                    Console.WriteLine("Syntaxe : start port. Port incorrect");
                                } else {
                                    // we launch the listening service
                                    ThreadPool.QueueUserWorkItem(doEcoute, null);
                                }
                            }
                            break;
                        case "echo":
                            // echo start / stop
                            if (champs.Length != 2 || (champs[1] != "start" && champs[1] != "stop")) {
                                Console.WriteLine("Syntaxe : echo start / stop");
                            } else {
                                echo = champs[1] == "start";
                            }
                            break;
                        case "stop":
                            // end of service
                            if (actif) {
                                service.Stop();
                                actif = false;
                            }
                            break;
                        case "status":
                            // server status
                            if (actif) {
                                Console.WriteLine("Le service est lancé sur le port {0}", port);
                            } else {
                                Console.WriteLine("Le service n'est pas lancé}");
                            }
                            break;
                        case "quit":
                            // quit the application
                            Console.WriteLine("Fin du service");
                            Environment.Exit(0);
                            break;
                        default:
                            // incorrect order
                            Console.WriteLine("Commande incorrecte. Utilisez (start,stop,echo, status, quit)");
                            break;
                    }
                }
            } catch (Exception e1) {
                // exception display
                Console.WriteLine("L'erreur suivante s'est produite à l'initialisation de l'application : {0}", e1.Message);
                return;
            }
        }
 
 
        private static void doEcoute(Object data) {
...
        }
 
....
    }
}
  1. lines 18–21: the [metier] and [dao] layers are instantiated by Spring, which is configured by [App.config]. The global business variable on line 6 is then initialized.
  2. lines 24-25: the application’s thread pool is configured with a minimum and maximum of 10 threads.
  3. Lines 30–86: The loop for handling service administration commands (start, stop, quit, echo, status).
  4. Line 32: Server prompt for each new command
  5. Line 34: Read administrator command
  6. line 35: the command is split into fields for analysis
  7. lines 38-52: the start port command, which launches the listening service
    • line 40: if the service is already running, no action is required
    • line 45: we verify that the port exists and is correct. If so, the global port variable from line 7 is set.
    • line 49: the listening service will be managed by a secondary thread so that the main thread can continue to execute console commands. If the doEcoute method successfully establishes the connection, the global variables `service` on line 8 and `active` on line 9 are initialized.
  • Lines 53–60: The `echo start / stop` command, which enables/disables the echo of client/server dialogue on the console
    • Line 58: The global variable `echo` from line 7 is set
  • Lines 61–67: The `stop` command, which stops the listening service.
    • Line 64: The listening service is stopped
  • Lines 68–75: The `status` command, which displays the service’s active/inactive status
  • lines 76-80: the quit command, which stops everything.

The thread responsible for listening for requests from clients executes the following doEcoute method:


        private static void doEcoute(Object data) {
            // thread for listening to clients requests
            try {
                // create the service
                service = new TcpListener(IPAddress.Any, port);
                // launch it
                service.Start();
                // the server is active
                actif = true;
                // follow-up
                Console.WriteLine("Serveur de calcul d'impôt lancé sur le port {0}", port);
                // service loop at clients
                TcpClient tcpClient = null;
                // customer no
                int numClient = 0;
                // endless loop
                while (true) {
                    // waiting for a customer
                    tcpClient = service.AcceptTcpClient();
                    // the service is provided by another task
                    ThreadPool.QueueUserWorkItem(doService, new Client() { CanalTcp = tcpClient, NumClient = numClient });
                    // next customer
                    numClient++;
                }
            } catch (Exception ex) {
                // we report the error
                Console.WriteLine("L'erreur suivante s'est produite sur le serveur : {0}", ex.Message);
            }
        }
 
        // customer info
        internal class Client {
            public TcpClient CanalTcp { get; set; }        // customer liaison
            public int NumClient { get; set; }            // customer no
}

This code is similar to that of the echo server discussed in Section 11.6.1. We will only comment on the differences:

  • line 7: the listening service is started
  • line 9: we note that the service is now active

Line 21: the clients requests are handled by service threads executing the following doService method:


private static void doService(Object infos) {
            // the customer is picked up and served
            Client client = infos as Client;
            // renders service to the customer
            Console.WriteLine("Début du service au client {0}", client.NumClient);
            // operation link TcpClient
            try {
                using (TcpClient tcpClient = client.CanalTcp) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // unbuffered output stream
                                writer.AutoFlush = true;
                                // send a welcome message to the customer
                                writer.WriteLine("Bienvenue sur le serveur de calcul de l'impôt");
                                // loop read request/write response
                                string demande = null;
                                bool serviceFini = false;
                                while (!serviceFini && (demande = reader.ReadLine()) != null) {
                                    // console monitoring
                                    if (echo) {
                                        Console.WriteLine("<--- Client {0} : {1}", client.NumClient, demande);
                                    }
                                    // demand analysis
                                    demande = demande.Trim().ToLower();
                                    // empty request?
                                    if (demande.Length == 0) {
                                        // erroneous request
                                        writeClient(writer,client.NumClient,"Commande non reconnue. Utilisez la commande aide.");
                                        return;
                                    }
 
                                    // demand is broken down into fields
                                    string[] champs = Regex.Split(demande, @"\s+");
                                    // analysis
                                    switch (champs[0].ToLower()) {
                                        case "aide":
                                            writeClient(writer, client.NumClient, "Commandes acceptées\n1-aide\n2-impot marié(O/N) nbEnfants salaireAnnuel\n3-aurevoir");
                                            break;
                                        case "impot":
                                            // tax calculation
                                            writeClient(writer, client.NumClient, calculImpot(writer, client.NumClient, champs));
                                            break;
                                        case "aurevoir":
                                            serviceFini = true;
                                            writeClient(writer, client.NumClient, "Au revoir...");
                                            break;
                                        default:
                                            writeClient(writer, client.NumClient, "Commande non reconnue. Utilisez la commande aide.");
                                            break;
                                    }
                                }
                            }
                        }
                    }
                }
            } catch (Exception e) {
                // error
                Console.WriteLine("L'erreur suivante s'est produite lors du service au client {0} : {1}", client.NumClient, e.Message);
            } finally {
                Console.WriteLine("Fin du service au client {0}", client.NumClient);
            }
        }
 
        private static void writeClient(StreamWriter writer, int numClient, string message) {
            // echo console ?
            if (echo) {
                Console.WriteLine("---> Client {0} : {1}", numClient, message);
            }
            // send msg to customer
            writer.WriteLine(message);
}

Once again, this code is similar to that of the echo server discussed in Section 11.6.1. We will only comment on the differences:

  • line 15: once the client is connected, the server sends it a welcome message.
  • lines 19–52: the loop for reading client commands. The loop stops when the client sends the "goodbye" command.
  • line 27: case of an empty command
  • line 34: the request is broken down into fields for analysis
  • line 37: help command: the client requests a list of allowed commands
  • line 40: tax command: the client requests a tax calculation. The response is the message returned by the calculImpot method, which we will detail shortly.
  • line 44: goodbye command: the client indicates that they are finished.
  • line 45: we prepare to exit the loop reading requests from clients (lines 19–52)
  • line 46: we respond to the client with a goodbye message
  • line 48: an invalid command. We send the client an error message.

The "impot" command is handled by the following calculImpot method:


private static string calculImpot(StreamWriter writer, int numClient, string[] champs) {
            // request calculation married(Y/N) nbEnfants salaireAnnuel
            // 4 fields are required
            if (champs.Length != 4) {
                return "Commande calcul incorrecte. Utilisez la commande aide.";
            }
            // fields [1]
            string marié = champs[1];
            if (marié != "o" && marié != "n") {
                return "Commande calcul incorrecte. Utilisez la commande aide.";
            }
            // fields [2]
            int nbEnfants;
            if (!int.TryParse(champs[2], out nbEnfants)) {
                return "Commande calcul incorrecte. Utilisez la commande aide.";
            }
            // fields [3]
            int salaireAnnuel;
            if (!int.TryParse(champs[3], out salaireAnnuel)) {
                return "Commande calcul incorrecte. Utilisez la commande aide.";
            }
            // that's it - tax calculation
            int impot = 0;
            try {
                impot = metier.CalculerImpot(marié == "o", nbEnfants, salaireAnnuel);
                return impot.ToString();
            } catch (Exception ex) {
                return ex.Message;
            }
        }
  1. Line 1: The method receives the array of order fields as its third parameter. If the order has been correctly formatted, it is in the form: nbEnfants salaireAnnuel. The method returns the response to be sent to the client.
  2. line 4: we verify that the order has 4 fields
  3. line 8: we verify that the marié field is valid
  4. line 14: check that the nbEnfants field is valid
  5. line 19: check that the salaireAnnuel field is valid
  6. line 25: the tax is calculated using the CalculerImpot method of the [metier] layer. Note that this layer is encapsulated in a DLL.
  7. Line 26: If the [metier] layer returned a result, it is returned to the client.
  8. line 28: if the [metier] layer threw an exception, the exception message is returned to the client.

11.9.4. The graphical client for the Tcp tax calculation server

11.9.4.1. The Visual Studio " " project

The Visual Studio project for the graphical client will be as follows:

  1. In [1], the two projects in the solution, one for each of the application’s two layers
  2. In [2], the Tcp client acts as the [metier] layer for the [ui] layer. Therefore, we will use both terms.
  3. In [3], the [ui] layer of version 7, with one small detail that we will discuss

11.9.4.2. The layer [metier]

The IImpotMetier interface has not changed. It is still the same as that of version 7:


namespace Metier {
    public interface IImpotMetier {
        int CalculerImpot(bool marié, int nbEnfants, int salaire);
    }
}

The implementation of this interface is the following class [ImpotMetierTcp]:


using System.Net.Sockets;
using System.IO;
namespace Metier {
    public class ImpotMetierTcp : IImpotMetier {
 
        // information [server]
        private string Serveur { get; set; }
        private int Port { get; set; }
 
        // tAX CALCULATION
        public int CalculerImpot(bool marié, int nbEnfants, int salaire) {
                // connect to the service
                using (TcpClient tcpClient = new TcpClient(Serveur, Port)) {
                    using (NetworkStream networkStream = tcpClient.GetStream()) {
                        using (StreamReader reader = new StreamReader(networkStream)) {
                            using (StreamWriter writer = new StreamWriter(networkStream)) {
                                // unbuffered output stream
                                writer.AutoFlush = true;
                                // skip the welcome message
                                reader.ReadLine();
                                // request
                                writer.WriteLine(string.Format("impot {0} {1} {2}",marié ? "o" : "n",nbEnfants, salaire));
                                // answer
                                return int.Parse(reader.ReadLine());
                            }
                        }
                    }
                }
            }
        }
    }
  • line 7: the name or address Ip of the tax calculation server Tcp
  • line 8: the listening port of this server
  • These two properties will be initialized by Spring when the [ImpotMetierTcp] class is instantiated.
  • line 11: the tax calculation method. When it runs, the Server and Port properties are already initialized. The code follows the standard approach for a Tcp client
  • Line 13: The connection to the server is opened
  • Lines 14–16: We retrieve (line 14) the network stream associated with this connection, from which we derive a read stream (line 15) and a write stream (line 16).
  • line 18: the write stream must be unbuffered
  • line 20: here, it is important to remember that when the connection is opened, the server sends the client a first line, which is the welcome message "Welcome to the tax calculation server." This message is read and ignored.
  • Line 22: We send the server a command of the type: tax o 2 60000 to ask it to calculate the tax for a married person with two children and an annual salary of 60,000 euros.
  • Line 24: The server responds with the tax amount in the form "4282" or with an error message if the command was malformed (which won’t happen here) or if the tax calculation encountered a problem. Here, the latter case is not handled, but it would certainly have been "cleaner" to do so. Indeed, if the line read is an error message, an exception will be thrown because the conversion to an integer will fail. The exception caught by the GUI will be a conversion error, whereas the original exception is of an entirely different nature. The reader is invited to improve this code.
  • Lines 25–28: Release of all resources used with a "using" clause.

The [metier] layer is compiled into the DLL ImpotsV8-metier.dll:

Image

11.9.4.3. The [ui] layer

The [ui] [1,3] layer is the one discussed in version 7 in section 9.8.4, with three differences:

  • the configuration of layer [metier] in [App.config] is different because its implementation has changed
  • The [Form1.cs] graphical interface has been modified to display any exceptions
  • The [metier] layer is in the DLL [ImpotsV8-metier.dll].

The file [App.config] is as follows:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 
    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>
 
    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="metier" type="Metier.ImpotMetierTcp, ImpotsV8-metier">
                <property name="Serveur" value="localhost"/>
                <property name="Port" value="27"/>
            </object>
        </objects>
    </spring>
</configuration>
  1. line 16: instantiation of the [metier] layer using the Metier.ImpotMetierTcp class from DLL ImpotsV8-metier.dll
  2. lines 17-18: the Server and Port properties of the Metier.ImpotMetierTcp class are initialized. The server will be on the localhost machine and will operate on port 27.

The graphical interface presented to the user is as follows:

  • In [1], a TextBox has been added to display any exceptions. This field did not exist in the previous version.

Apart from this detail, the form code is the same as that already discussed in Section 6.4.3. The reader is invited to refer to that section. In [2], we see an example of the output obtained with a server launched as follows:

1
2
3
4
5
6
7
8
9
Serveur de calcul d'tax >start 27
Serveur de calcul d'tax launched on port 27
Serveur de calcul d'impôt >echo start
Serveur de calcul d'tax >
...
Début du service au client 9
<--- Customer 9: tax o 2 60000
---> Customer 9: 4282
Fin du service au client 9

The customer's screenshot [2] corresponds to the lines for Customer 9 above.

11.9.5. Conclusion

Once again, we were able to reuse existing code, either without modifications (server layers [metier], [dao]) or with very few modifications (client layer [ui]). This was made possible by our systematic use of interfaces and their instantiation with Spring. If, in version 7, we had placed the business logic directly in the GUI event handlers, that business logic would not have been reusable. This is the major drawback of single-layer architectures.

Finally, note that the [ui] layer has no knowledge of the fact that a remote server calculates the tax amount for it.