11. Programming Internet
11.1. General
11.1.1. The Internet protocols
We give ici an introduction to the Internet communication protocols, also known as the protocol suite TCP/IP (Transfer Control Protocol / Internet Protocol), named after the two main protocols. It may be useful for the reader to have a general understanding of how networks work, and in particular of the TCP/IP protocols, before tackling the construction of distributed applications. The following text is a partial translation of a text found in the document "Lan Workplace for Dos - Administrator's Guide" from NOVELL, document from the early 90s.
The general concept of creating a network of heterogeneous computers comes from research carried out by the DARPA (Defense Advanced Research Projects Agency) in the USA. The DARPA developed the protocol suite known as TCP/IP, which enables heterogeneous machines to communicate with each other. These protocols have been tested on a network called ARPAnet, network, which later became the INTERNET. The TCP/IP protocols define transmission and reception formats and rules that are independent of network organization and the hardware used.
The network designed by DARPA and managed by the TCP/IP protocols is a DARPA network packet switching. Such a network transmits information over the network, in small chunks called packages. So, if a computer transmits a large file, it will be broken down into smaller pieces, which will be sent over the network to be recomposed at their destination. TCP/IP defines the format of these packets, i.e. :
- package origin
- destination
- length
- type
11.1.2. The OSI model
The TCP/IP protocols roughly follow the open network model called OSI (Open Systems Interconnection Reference Model) defined by theISO (International Standards Organization). This model describes an ideal network where communication between machines can be represented by a seven-layer model:
![]() |
Each layer receives services from the lower layer and offers its own to the higher layer. Suppose two applications on different machines A and B want to communicate: they do so at layer Application. They do not need to know all the details of how the network works: each application hands over the information it wishes to transmit to the layer below: the Presentation. The application only needs to know the rules for interfacing with the Presentation.
Once the information in the Presentation, it has been transferred to the Session and so on, until the information arrives on the physical medium and is physically transmitted to the destination machine. There, it undergoes the reverse treatment to that which it underwent on the sending machine.
At each layer, the sending process responsible for sending the information sends it to a receiving process on the other machine belonging to the same layer as itself. It does so according to certain rules known as the protocol layer. This gives us the following final communication diagram:
![]() |
The role of the different layers is as follows:
Ensures the transmission of bits on a physical medium. This layer includes data processing terminal equipment (E.T.T.D.) such as a terminal or computer, as well as data circuit termination equipment (E.T.C.D.) such as a modulator/demodulator, multiplexer or concentrator. Points of interest at this level are :
| |
Masks the physical characteristics of the physical layer. Detects and corrects transmission errors. | |
Manages the path taken by information sent over the network. This is called the routing : determine the route an item of information must take to reach its destination. | |
Allows communication between two applications, whereas previous layers only allowed machine-to-machine communication. One service provided by this layer is multiplexing: the transport layer can use the same network connection (from machine to machine) to transmit information belonging to several applications. | |
This layer contains services enabling an application to open and maintain a work session on a remote machine. | |
Its aim is to standardize the representation of data on different machines. In this way, data from machine A will be "dressed" by the Presentation from machine A, in a standard format, before being sent over the network. Once they reach the Presentation machine B, which will recognize them thanks to their standard format, they will be dressed in another way so that the machine B application can recognize them. | |
At this level, we find applications that are generally close to the user, such as e-mail and file transfer. |
11.1.3. The TCP/IP model
The OSI model is an ideal model that has never yet been realized. The TCP/IP protocol suite approaches it in the following form:
![]() |
Physical layer
For local area networks, we generally use Ethernet or Token-Ring. We present ici Ethernet technology only.
Ethernet
This is the name given to a packet-switched LAN technology invented at PARC Xerox in the early 1970s and standardized by Xerox, Intel and Digital Equipment in 1978. The network physically consists of a coaxial cable with a diameter of around 1.27 cm and a maximum length of 500 m. It can be extended by means of repeaters, no two machines can be separated by more than two repeaters. The cable is passive: all active elements are on the machines connected to the cable. Each machine is connected to the cable by a network access card comprising :
- a transmitter (transceiver) which detects the presence of signals on the cable and converts analog signals into digital and vice versa.
- a coupler that receives digital signals from the transmitter and transmits them to the computer for processing, or vice versa.
The main features of Ethernet technology are as follows:
- 10 Megabits/second capacity.
- Bus topology: all machines are connected to the same cable
![]() |
- Broadcasting network - A transmitting machine transfers information over the cable with the address of the destination machine. All connected machines then receive this information, and only the one for which it is intended retains it.
- The access method is as follows: the transmitter wishing to transmit listens to the cable - it then detects the presence or absence of a carrier wave, which would mean that a transmission is in progress. This is the CSMA (Carrier Sense Multiple Access). In the absence of a carrier, a transmitter may decide to transmit in turn. Several transmitters may take this decision. The transmitted signals are mixed: we say there is collision. The transmitter detects this situation: at the same time as it transmits on the cable, it listens to what is actually passing over it. If it detects that the information transiting on the cable is not the one it has transmitted, it deduces that there is a collision and stops transmitting. The other transmitters will do the same. Each will resume transmission after a random time depending on each transmitter. This technique is called CD (Collision Detect). The access method is called CSMA/CD.
- 48-bit addressing. Each machine has an address, called ici physical address, which is written on the card that connects it to the cable. This address is called the Ethernet of the machine.
Network layer
This layer includes the IP, ICMP, ARP and RARP protocols.
Delivers packets between two network nodes | |
ICMP enables communication between the IP protocol program of one machine and that of another. It is therefore a message exchange protocol within the IP protocol. | |
maps Internet machine address to physical machine address | |
maps physical machine address to Internet machine address |
Transport/Session layers
This layer includes the following protocols:
Ensures reliable information transfer between two customers | |
Ensures unreliable delivery of information between two customers |
Application/Presentation/Session layers
There are ici various protocols:
Terminal emulator allowing machine A to connect to machine B as a terminal | |
enables file transfers | |
enables file transfers | |
allows messages to be exchanged between network users | |
transforms a machine name into a Internet machine address | |
created by sun MicroSystems, it specifies a standard, machine-independent data representation | |
also defined by Sun, is a communication protocol between remote applications, independent of the transport layer. This protocol is important: it relieves the programmer of knowledge of transport layer details and makes applications portable. This protocol is based on the XDR protocol | |
still defined by Sun, this protocol allows one machine to "see" the file system of another. 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 this environment's protocols. An application program communicates with the highest protocol layer. This passes the information to the layer below, and so on until it reaches the physical medium. Here, the information is physically transferred to the machine where it passes through the same layers again, this time in the opposite direction, until it reaches the application to which the information has been sent. The following diagram shows the information path:
![]() |
Let's take an example: the FTP application, defined in the Application which enables file transfers between machines.
- The application delivers a sequence of bytes to be transmitted to the transport.
- Layer transport cuts this sequence of bytes into segments TCP, and adds the segment number to the beginning of each segment. Segments are passed to the Network layer, governed by the IP.
- The IP layer creates a packet encapsulating the received TCP segment. At the head of this packet, it places the Internet addresses of the source and destination machines. It also determines the physical address of the destination machine. All this is passed on to the Data link & Physical link, i.e. the network card that connects the machine to the physical network.
- Here, the IP package is in turn encapsulated in a frame and sent to its recipient over the cable.
- On the receiving machine, the Data link & Physical link does the opposite: it decapsulates the IP packet from the physical frame and passes it to the IP layer.
- Layer IP checks that the packet is correct: it calculates a sum based on the bits received (checksum), which must be found in the packet header. If this is not the case, the packet is rejected.
- If the packet is declared correct, the IP layer unencapsulates the TCP segment contained in it and passes it on to the IP layer transport.
- Layer transport, layer TCP in our example, examines the segment number to restore the correct segment order.
- It also calculates a checksum for the TCP segment. If it is found to be correct, the TCP layer sends an acknowledgement to the source machine, otherwise the TCP segment is rejected.
- All that's left for the TCP layer is to transmit the data part of the segment to the destination application in the layer above.
11.1.5. Addressing problems in Internet
A node of a network can be a computer, a smart printer, a file server, in fact anything that can communicate using the TCP/IP protocols. Each node has a physical address whose format depends on the type of network. On an Ethernet network, the physical address is encoded in 6 bytes. An X25 network address is a 14-digit number.
L'address Internet of a node is an address logic : it is independent of the hardware and network used. It's a 4-byte address identifying both a local network and a node on that network. The Internet address is usually represented as 4 numbers, the values of the 4 bytes, separated by a dot. For example, the address of the Lagaffe machine at the Faculty of Science in Angers is 193.49.144.1, and that of the Liny machine is 193.49.144.9. We deduce that the Internet address of the local network is 193.49.144.0. We can have up to 254 nodes on this network.
Because Internet or IP addresses are network-independent, a machine on network A can communicate with a machine on network B regardless of the type of network it's on: it just needs to know its IP address. The IP protocol on each network takes care of the IP <--> physical address conversion, in both directions.
Addresses IP must all be different. In France, INRIA is responsible for assigning IP addresses. In fact, this organization issues an address for your local network, e.g. 193.49.144.0 for the network of the Faculty of Science in Angers. The network administrator can then assign the IP addresses 193.49.144.1 to 193.49.144.254 as he sees fit. This address is usually written to a special file on each machine connected to the network.
11.1.5.1. IP address classes
A IP address is a sequence of 4 bytes, often referred to as I1.I2.I3.I4, which actually contains two addresses:
- network address
- the address of a node in this 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 in this network
More precisely, the form of a class A IP address is as follows:
The network address is 7 bits and the node address is 24 bits. We can therefore have 127 Class A networks, each with up to 224 knots.
Class B
Ici, 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 in this network
More precisely, the form of a class B IP address is as follows:
The network address is on 2 bytes (14 bits exactly), as is the node address. We can therefore have 214 class B networks, each comprising up to 216 knots.
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 network address
N1 is the address of a machine in this network
More precisely, the form of a class C IP address is as follows:
![]() |
The network address is 3 bytes (minus 3 bits) and the node address is 1 byte. We can therefore have 221 class C networks with up to 256 nodes.
Machine address Lagaffe of the Faculty of Science in Angers being 193.49.144.1, we can see that the most significant byte is 193, i.e. in binary 11000001. This means that the network is class C.
Reserved addresses
- Some IP addresses are network addresses rather than addresses of nodes in the network. These are addresses where the node address is set to 0. For example, address 193.49.144.0 is the IP address of the Faculté des Sciences d'Angers network. Consequently, no node on a network can have address zero.
- When the node address in a IP address contains only 1's, we have a broadcast address: this address designates all network nodes.
- In a class C network, theoretically allowing 28=256 nodes, if we remove the two prohibited addresses, we're left with 254 authorized addresses.
11.1.5.2. Conversion protocols Address Internet <--> Physical address
We have seen that when information is transmitted from one machine to another, it is encapsulated in packets as it passes through the IP layer. These take 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 on the physical network, other information is added to it to form the physical frame that will finally be sent on the network. For example, the format of a frame on an Ethernet network is as follows:
![]() |
The final frame contains the physical addresses of the source and destination machines. How are they obtained?
The sending machine, knowing the IP address of the machine it wishes to communicate with, obtains the latter's physical address using a special protocol called ARP (Address Resolution Protocol).
- It sends a special type of packet called a ARP packet, containing the IP address of the machine whose physical address we're looking for. It has also taken care to include its own IP address, as well as its physical address.
- This packet is sent to all network nodes.
- These recognize the special nature of the packet. The node that recognizes its IP address in the packet responds by sending the sender of the packet its physical address. How can it do this? It has found the sender's IP and physical addresses in the packet.
- The sender receives the physical address he was looking for. He stores it in memory so that he can use it later if he needs to send other packets to the same recipient.
A machine's IP address is normally recorded in one of its files, which it can consult to find out what it is. This address can be changed by editing the file. The physical address, on the other hand, is stored in a memory on the network card and cannot be changed.
When an administrator wishes to organize his network differently, he may have to change the IP addresses of all the nodes and therefore edit the various configuration files for the different nodes. This can be tedious and error-prone if there are a lot of machines. One method consists in not assigning a IP address to machines: a special code is written to the file in which the machine should find its IP address. Discovering that it doesn't have a IP address, the machine requests it using a protocol called RARP (Reverse Address Resolution Protocol). It then sends a special packet on a network, called a RARP packet, analogous to the ARP packet above, in which it puts its physical address. This packet is sent to all nodes, which then recognize a RARP packet. One of them, called server RARP, has a file containing the physical address <--> IP address of all nodes. It then replies to the sender of the RARP packet, sending back his IP address. An administrator wishing to reconfigure his network simply needs to edit the RARP server's correspondence file. This should normally have a fixed IP address, which he should be able to find out without having to use the RARP protocol himself.
11.1.6. The IP network layer of the internet
The IP protocol (Internet Protocol) defines the form that packets should take and how they should be handled when sent or received. This particular type of packet is called a datagram IP. We have already presented :
![]() |
The important thing is that, in addition to the data to be transmitted, the IP datagram contains the Internet addresses of the source and destination machines. In 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 over which it travels, the length of datagram IP is fixed by the software and will therefore be the same on different physical networks. As we have seen, the IP datagram is encapsulated in a physical frame as it descends from the network layer to the physical layer. We have given the example of the physical frame of an Ethernet network:
Physical frames travel from node to node towards their destination, which may not be on the same physical network as the sending machine. The IP packet may therefore be successively encapsulated in different physical frames at the nodes that link two different types of network. It is also possible that the IP packet is too large to be encapsulated in a physical frame. The IP software of the node where this problem occurs, then breaks down the IP packet into fragments according to precise rules, each of which is then sent over the physical network. They are not reassembled until they reach their final destination.
11.1.6.1. Routing
Routing is the method of routing IP packets to their destination. There are two methods: direct routing and indirect routing.
Direct routing
Direct routing refers to the routing of a IP packet directly from the sender to the recipient within the same network:
- The machine sending a IP datagram has the IP address of the recipient.
- It obtains the latter's physical address via the ARP protocol or from its tables, if this address has already been obtained.
- It sends the packet over the network to this physical address.
Indirect routing
Indirect routing refers to the routing of a IP packet to a destination on a network other than that to which the sender belongs. In this case, the network address parts of the IP addresses of the source and destination machines are different. The source machine recognizes this point. It then sends the packet to a special node called router (router), the node that connects a local network to other networks and whose IP address it finds in its tables, an address initially obtained either in a file or in permanent memory, or via information circulating on the network.
A router is attached to two networks and has a IP address within these two networks.
![]() |
In our example above :
. Network #1 has address Internet 193.49.144.0 and network #2 has address 193.49.145.0.
. Within network no. 1, the router has address 193.49.144.6, and within network no. 2, it has address 193.49.145.3.
The router's role is to put the IP packet it receives, which is contained in a physical frame typical of network no. 1, into a physical frame that can circulate on network no. 2. If the IP address of the packet's recipient is in network no. 2, the router will send the packet directly to it, otherwise it will send it to another router, connecting network no. 2 to network no. 3, and so on.
11.1.6.2. Error and control messages
Still in the network layer, at the same level as the IP protocol, there is the ICMP (Internet Control Message Protocol). It is used to send messages about the internal workings of the network: nodes down, congestion at a router, etc ... Messages ICMP are encapsulated in IP packets and sent over the network. The IP layers of the various nodes take appropriate action according to the ICMP messages they receive. In this way, an application itself never sees these network-specific problems.
A node will use ICMP information to update its routing tables.
11.1.7. The transport layer: UDP and TCP protocols
11.1.7.1. The UDP protocol: User Datagram Protocol
The UDP protocol enables an unreliable exchange of data between two points, i.e. the correct routing of a packet to its destination is not guaranteed. The application can manage this itself, for example by waiting for an acknowledgement of receipt after sending a message, before sending the next one.
For the moment, at network level, we've been talking about IP machine addresses. on a machine, different processes can coexist at the same time, and all of them can communicate. When sending a message, you must therefore 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 port number. Some numbers are reserved for standard applications: port 69 for the tftp (trivial file transfer protocol) for example.
Packets managed by the UDP protocol are also called datagrams. They take the following form:
These datagrams are 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 develop his own protocol to detect the correct routing of packets. The protocol TCP (Transfer Control Protocol) avoids these problems. Its features are as follows:
- The process that wishes to issue first establishes a connection with the process receiving the information it is going to send. 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 for the two processes that have made the connection.
- 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.
- The information sent is continuous. The transmitting process sends information at its own pace. This information is not necessarily sent immediately: the TCP protocol waits until it has enough information to send it. They are stored in a structure called segment TCP. Once completed, this segment is transmitted to the IP layer, where it is encapsulated in a IP packet.
- Each segment sent by the TCP protocol is numbered. The receiving TCP protocol checks that it has received the segments in sequence. For each segment correctly received, it sends an acknowledgement to the sender.
- When the latter receives it, it informs the sending process. This means that the sending process knows that a segment has arrived safely, which was not possible with the UDP protocol.
- If, after a certain time, the TCP protocol that transmitted a segment does not receive an acknowledgement, it retransmits the segment in question, thus guaranteeing the quality of the information routing service.
- The virtual circuit established between the two communicating processes is full-duplex : this means that information can flow in both directions. In this way, the destination process can send acknowledgements while the source process continues to send information. This allows the TCP source protocol, for example, to send several segments without waiting for an acknowledgement. If it realizes after a certain time that it has not received acknowledgement of a certain segment no n, it will resume segment broadcasting at this point.
11.1.8. The Applications layer
Above the UDP and TCP protocols, there are various standard protocols:
TELNET
This protocol allows a user on machine A of the network to connect to machine B (often called the host machine). TELNET emulates a universal terminal on machine A. The user then behaves as if he or she had a terminal connected to machine B. Telnet is based on the TCP protocol.
FTP : (File Transfer protocol)
This protocol enables files to be exchanged between two remote machines, as well as file manipulations such as directory creation. It is based 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, by FTP for example, he needs to know the Internet address of this 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 would require a directory mapping machine <--> address IP. In this directory, machines would probably be designated by symbolic names such as :
machine DPX2/320 from Angers University
sun machine from ISERPA in Angers
Clearly, it would be nicer to refer to a machine by name rather than by its IP address. Then there's the problem of name uniqueness: there are millions of interconnected machines. We could imagine a centralized body assigning names. This would undoubtedly be rather cumbersome. The control of names has in fact been distributed in **fields**. Each domain is managed by a very small organization, which is free to choose its own machine names. For example, machines in France belong to the **en**, domain managed by the Inria in Paris. To keep things simple, we distribute control even further: domains are created within the **en**. The University of Angers belongs to the **univ-Angers**. The department managing this domain is free to name the machines on the Université d'Angers network. For the moment, this domain has not been subdivided. But in a large university with many networked machines, it could be.
The DPX2/320 machine at the University of Angers was named *Lagaffe* while a PC 486DX50 was named *liny*. How do you reference these machines from the outside? By specifying the domain hierarchy to which they belong. For example, the full name of the Lagaffe machine would be :
**Lagaffe.univ-Angers.fr**
Within domains, relative names can be used. Thus, within the **en** and outside the field **univ-Angers**, the Lagaffe machine can be referenced by
**Lagaffe.univ-Angers**
Finally, within the *univ-Angers*, it can be referenced simply by
**Lagaffe**
An application can therefore reference a machine by name. At the end of the day, you still need to obtain the machine's Internet address. How is this achieved? Suppose you want to communicate from machine A to machine B.
- if machine B belongs to the same domain as machine A, we'll probably find its IP address in a file on machine A.
- otherwise, machine A will find a list of some name servers with their IP addresses. A name server is responsible for mapping a machine name to its IP address. Machine A will send a special request to the first name server in its list, called a DNS request, including the name of the machine it's looking for. If the queried server has this name in its tablets, it will send machine A the corresponding IP address. If not, the server will also find a list of name servers in its files, which it will send to machine A can question. It will then do so. In this way, a number of name servers will be queried, not anarchically, but in such a way as to minimize the number of queries. If the machine is finally found, the answer will go back down 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 communication protocol between remote applications, independent of the transport layer. This protocol is important: it relieves the programmer of knowledge of transport layer details and makes applications portable. This protocol is based on the XDR protocol
NFS : Network File System
Still defined by Sun, this protocol allows one machine to "see" the file system of another. It is based on the previous RPC protocol.
11.1.9. Conclusion
In this introduction, we've presented a few outlines of the Internet protocols. For a more in-depth look at this field, read the excellent book by Douglas Comer :
Title TCP/IP: Architecture, Protocols, Applications.
Author Douglas COMER
Publisher InterEditions
11.2. The .NET classes for IP address management
A machine on the Internet network is uniquely defined by a IP (Internet Protocol) address, which can take two forms:
- IPv4 : coded 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.
- IPv6: coded on 128 bits 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 won't use IPv6 addresses.
A machine can also be defined by an equally unique name. This name is not mandatory, as applications always end up using the IP addresses of the machines. It's easier, for example, to request the URL from a browser http://www.ibm.com that the URL http://129.42.17.99 although both methods are possible.
A machine can have several IP addresses if it is physically connected to several networks at the same time. It then has a IP address on each network.
An address IP can be represented in two ways in .NET :
- as string "I1.I2.I3.I4" or "[I1.I2.I3.I4.I5.I6.I7.I8]"
- in the form of a IPAddress
The IPAddress class
Among the M methods, P properties and C constants of the IPAddress, are the following:
P | address family IP. The type AddressFamily is an enumeration. The two most common values are : AddressFamily.InterNetwork : for a IPv4 address AddressFamily.InterNetworkV6 : for a IPv6 address | |
C | address IP "0.0.0.0". When a service is associated with this address, this means it accepts clients on all IP addresses of the machine on which it operates. | |
C | address IP "127.0.0.1". Known as the "loopback address". When a service is associated with this address, it means that it only accepts clients that are on the same machine as it. | |
C | address IP "255.255.255.255". When a service is associated with this address, this means that it accepts no customers. | |
M | tries to pass address IP ipString form "I1.I2.I3.I4" in the form of a IPAddress address. Makes true if the operation was successful. | |
M | returns true if address IP is "127.0.0.1" | |
M | renders address IP as "I1.I2.I3.I4" or "[I1.I2.I3.I4.I5.I6.I7.I8]" |
The association address IP <--> nomMachine is provided by a distributed internet service called DNS (Domain Name System) class. The static methods of the Dns make the association address IP <--> nomMachine :
returns an address IPHostEntry from a IP address as a string or from a machine name. Throws an exception if the machine cannot be found. | |
returns an address IPHostEntry from a IP address of type IPAddress. Throws an exception if the machine cannot be found. | |
returns the name of the machine running the program that plays this instruction | |
returns the IP addresses of the machine identified by its name or one of its IP addresses. |
An instance IPHostEntry encapsulates IP addresses, aliases and machine names. The type IPHostEntry is as follows:
P | table of machine IP addresses | |
P | the machine's DNS aliases. These are the names corresponding to the machine's various IP addresses. | |
P | machine's main host name |
Consider the following program which displays the name of the machine on which it is running, and then interactively provides address correspondences IP <--> machine name :
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);
// the machine's 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);
}
}
}
}
}
Execution gives the following results:
11.3. Programming basics internet
11.3.1. General
Consider the communication between two remote machines A and B :
![]() |
When an application AppA of a machine A wants communicate with an application AppB machine B of the Internet, it needs to know several things:
- address IP or machine name B
- the port number with which the application works AppB. Machine B can support a large number of applications working on the Internet. When it receives information from the network, it needs to know which application the information is intended for. Machine B's applications have access to the network via windows, also known as communication ports. This information is contained in the packet received by machine B so that it can be delivered to the right application.
- communication protocols understood by machine B. In our study, we'll be using TCP-IP protocols only.
- the dialog protocol accepted by the application AppB. In effect, machines A and B are going to "talk" to each other. What they say is encapsulated in TCP-IP protocols. However, at the end of the chain, the application AppB will receive the information sent by the AppA, it must be able to interpret it. This is analogous to the situation where two people, A and B, communicate by telephone: their dialogue is carried by the telephone. The speech is encoded in signal form by telephone A, transported over telephone lines, and arrives at telephone B to be decoded. Person B then hears speech. This is where the notion of dialogue protocol comes into play: if A speaks French and B doesn't understand the language, A and B won't be able to dialogue effectively.
The two communicating applications must therefore agree on the type of dialogue they will adopt. For example, dialogue with a ftp is not the same as with a service pop : these two services do not accept the same orders. They have a different dialogue protocol.
11.3.2. Characteristics of the TCP protocol
We will only study ici network communications using the TCP transport protocol. Let's recall ici, its characteristics:
- The process that wishes to issue first establishes a connection with the process receiving the information it is going to send. 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 for the two processes that have made the connection.
- All packets sent by the source process follow this virtual path and arrive in the order in which they were sent
- The information sent is continuous. The transmitting process sends information at its own pace. This information is not necessarily sent immediately: the TCP protocol waits until it has enough information to send it. They are stored in a structure called segment TCP. Once completed, this segment is transmitted to the IP layer, where it is encapsulated in a IP packet.
- Each segment sent by the TCP protocol is numbered. The receiving TCP protocol checks that it has received the segments in sequence. For each segment correctly received, it sends an acknowledgement to the sender.
- When the latter receives it, it indicates this to the sending process. This means that the sending process knows that a segment has arrived safely.
- If, after a certain time, the TCP protocol that transmitted a segment does not receive an acknowledgement, it retransmits the segment in question, thus guaranteeing the quality of the information routing service.
- The virtual circuit established between the two communicating processes is full-duplex : this means that information can flow in both directions. In this way, the destination process can send acknowledgements while the source process continues to send information. This allows the TCP source protocol, for example, to send several segments without waiting for an acknowledgement. If, after a certain period of time, it realizes that it has not received an acknowledgement for a certain segment, n° n, it will resume segment broadcasting at this point.
11.3.3. The client-server relationship
Often, communication on Internet is asymmetrical: machine A initiates a connection to request a service from machine B: it specifies that it wants to open a connection with machine B's SB1 service. Machine B accepts or refuses. If it accepts, machine A can send its requests to the SB1 service. These must conform to the dialog protocol understood by the SB1 service. A request-response dialogue is thus established between machine A, which we call machine customer and machine B, which we call server. One of the two partners will close the connection.
11.3.4. Customer architecture
The architecture of a network program requesting the services of a server application will be as follows:
```text
ouvrir la connexion avec le service SB1 de la machine B
si réussite alors
tant que ce n'est pas fini
préparer une demande
l'émettre vers la 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 offering services will be as follows:
ouvrir le service sur la machine locale
tant que le service est ouvert
se mettre à l'écoute des demandes de connexion sur un port dit port d'écoute
lorsqu'il y a une demande, la faire traiter par une autre tâche sur un autre port dit port de service
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, during the service period it would no longer be listening to connection requests, and customers would not be served. It therefore proceeds differently: as soon as a connection request is received on the listening port and accepted, the server creates a task responsible for rendering the service requested by the client. This service is rendered on another port on the server machine, called service port. This means you can serve several customers at the same time.
A service task will have the following structure:
tant que le service n'a pas été rendu totalement
attendre une demande sur le port de service
lorsqu'il y en a une, élaborer la réponse
transmettre la réponse via le port de service
fin tant que
libérer le port de service
11.4. Discover the communication protocols of the internet
11.4.1. Introduction
Once a client has connected to a server, a dialogue is established between them. The nature of this dialogue forms what is known as the server's communication protocol. Among the most common internet protocols are the following:
- HTTP : HyperText Transfer Protocol - the protocol for dialogue with a web server (HTTP server)
- SMTP: Simple Mail Transfer Protocol - the protocol for communicating with an e-mail server (SMTP server)
- POP : Post Office Protocol - the protocol for dialog with an e-mail storage server (POP server). The purpose is to retrieve incoming e-mails, not to send them.
- FTP : File Transfer Protocol - the protocol used to communicate with a file storage server (FTP server).
All these protocols have the distinctive feature of being text-line protocols: the client and server exchange lines of text. If we have a client capable of :
- create a connection with a Tcp server
- display text lines sent by the server to the console
- send text lines entered by a user to the server
then you'll be able to communicate with a Tcp server using a text line protocol, provided you know the rules of this protocol.
The program telnet found on Unix or Windows machines is such a client. On Windows machines, there is also a tool called putty and we're going to use ici. putty can be downloaded at [http://www.putty.org/]. It is a directly usable executable (.exe). We'll configure it as follows:
![]() |
- [1]: the IP address of the Tcp server you want to connect to, or its name
- [2]: server listening port Tcp
- [3]: take mode Raw which designates a raw Tcp connection.
- [4]: take mode Never to prevent the client window putty to close if the server closes the connection.
- [6,7]: number of console columns/rows
- [5]: maximum number of lines stored in memory. A HTTP server can send a lot of lines. You need to be able to "scroll" over them.
![]() |
- [8,9]: to retain previous settings, name the configuration [8] and save it [9].
- [11,12]: to retrieve a saved configuration, select it [11] and load it [12].
With this tool thus configured, let's take a look at some TCP protocols.
11.4.2. The HTTP protocol (HyperText Transfer Protocol)
Let's connect [1] our client TCP to the server web on the machine istia.univ-angers.fr [2], port 80 [3] :
![]() |
In the putty, we build dialogue HTTP next :
- lines 1-4 are the customer's request, typed on the keyboard
- lines 5-19 are the server response
- line 1: syntax GET UrlDocument HTTP/1.1 - we request the Url /, c.a.d. site root web [istia.univ-angers.fr].
- line 2: syntax Host: machine:port
- line 3: syntax Connection: [connection mode]. The [close] mode tells the server to close the connection once it has sent its response. Keep-Alive] mode tells the server to leave the connection open.
- line 4: empty line. Lines 1-3 are called headers HTTP. There may be others than those shown ici. The end of HTTP headers is indicated by an empty line.
- lines 5-13: HTTP headers in the server response - again ending with an empty line.
- lines 14-19: the document sent by the server, ici a document HTML
- line 5: syntax HTTP/1.1 msg code - code 200 indicates that the requested document has been found.
- line 6: server date and time
- line 7: software identification for service web - ici an Apache server on Linux / Debian
- line 8: the document was dynamically generated by PHP
- line 9: customer identification cookie - if the customer wants to be recognized the next time he connects, he must return this cookie in his HTTP headers.
- line 10: indicates that after serving the requested document, the server will close the connection
- line 11: the document will be transmitted in pieces (chunked) rather than as a single block.
- line 12: type of document : ici a document HTML
- line 13: the blank line that signals the end of the server's HTTP headers
- line 14: hexadecimal number indicating the number of characters in the 1st block of the document. When this number equals 0 (line 19), the customer will know that he has received the entire document.
- lines 15-18: part of the document received.
The connection has been closed and the customer putty is inactive. Let's reconnect [1] and clear the screen of previous displays [2,3] :
![]() |
The dialogue this time is as follows:
- line 1: a non-existent document has been requested
- line 5: server HTTP responded with code 404, meaning that the requested document was not found.
If you request this document with a Firefox browser :

If we ask to see the source code [Display/Source code] :
We obtain lines 13-22 received by our customer putty. The advantage of this is that it also shows us the HTTP headers of the response. It's also possible to get these with Firefox.
11.4.3. The protocol SMTP (Simple Mail Transfer Protocol)
![]() |
SMTP servers generally operate on port 25 [2]. We connect to server [1]. for Ici servers, you'll generally need a
belonging to the same IP domain as the machine, as most SMTP servers are configured to accept requests only from machines belonging to the same domain as themselves. Quite often, firewalls or anti-virus software on personal machines are configured not to accept connections to port 25 from an external machine. It may then be necessary to reconfigure [3] this firewall or antivirus.
The SMTP dialog in the client window putty is as follows:
Below (D) is a client request, (R) a server response.
- line 1: (R) server greeting SMTP
- line 2: (D) command HELO to say hello
- line 3: (R) server response
- line 4: (D) sender address, e.g mail from: someone@gmail.com
- line 5: (R) server response
- line 6: (D) recipient address, e.g rcpt to: someoneelse@gmail.com
- line 7: (R) server response
- line 8: (D) marks the beginning of the message
- line 9: (R) server response
- lines 10-12: (D) the message to be sent terminated by a line containing a period only.
- line 13: (R) server response
- line 14: (D) the customer signals that he is finished
- line 15: (R) response from the server, which then closes the connection
11.4.4. The POP protocol (Post Office Protocol)
![]() |
POP servers generally operate on port 110 [2]. We connect to server [1]. The POP dialog in the client window putty is as follows:
- line 1: (R) server welcome message POP
- line 2: (D) the customer gives his identifier POP, c.a.d. the login with which he reads his mail
- line 3: (R) the server response
- line 4: (D) customer password
- line 5: (R) the server response
- line 6: (D) the customer asks for a list of his letters
- lines 7-12: (R) list of messages in the customer's mailbox, in the form [message no. message size in bytes]
- line 13: (D) message no. 64 is requested
- lines 14-25: (R) message no. 64 with lines 15-22, the message headers, and lines 23-24 the message body.
- line 26: (D) the customer says he's finished
- line 27: (R) response from the server, which then closes the connection.
11.4.5. The FTP protocol (File Transfer Protocol)
The FTP protocol is more complex than those described above. To discover the lines of text exchanged between client and server, you can use a tool such as FileZilla [http://www.filezilla.fr/].
![]() |
Filezilla is a FTP client offering a windows interface for file transfers. User actions on the windows interface are translated into FTP commands, which are logged in [1]. This is a good way to discover the FTP protocol commands.
11.5. The .NET classes of internet programming
11.5.1. Choosing the right class
The .NET framework offers various classes for working with the :
![]() |
- the class Socket is the one that operates closest to the network. It enables fine-tuned management of the network connection. The term socket refers to a power socket. The term has been extended to designate a software network socket. In a TCP-IP communication between two machines A and B, these are two sockets that communicate with each other. An application can work directly with sockets. This is the case for application A above. A socket can be a customer or server.
- if you wish to work at a lower level than that of the class Socket, you can use the
- TcpClient to create a Tcp customer
- TcpListener to create a Tcp server
These two classes offer the application that uses them a simpler view of network communication, managing the technical details of socket management on its behalf.
- .NET offers specific classes for certain protocols :
- the class SmtpClient to manage the SMTP protocol for communication with a SMTP server for sending e-mails
- the class WebClient to manage HTTP or FTP protocols for communication with a web server.
The Socket is sufficient on its own to handle all tcp-ip communication, but we'll focus on using the higher-level classes to make writing the tcp-ip application easier.
11.5.2. The TcpClient class
The TcpClient is the class most suitable for creating the client of a TCP service. Its C constructors, M methods and P properties include the following:
C | creates a tcp link with the service operating on the specified port (port) of the indicated machine (hostname). For example new TcpClient("istia.univ-angers.fr",80) to connect to the machine's port 80 istia.univ-angers.fr | |
P | the socket used by the client to communicate with the server. | |
M | gets a read/write flow to the server. It is this flow that enables client-server exchanges. | |
M | closes the connection. Socket and flow NetworkStream are also closed | |
P | true if the connection has been established |
The class NetworkStream represents the network flow between client and server. She is derived from the Stream. Many client-server applications exchange lines of text terminated by the end-of-line characters "\r\n". This is why it's a good idea to use StreamReader and StreamWriter to read and write these lines in the network stream. So if a machine M1 has established a link with a machine M2 using a TcpClient object customer1 and they exchange lines of text, it can create its read and write flows as follows:
StreamReader in1=new StreamReader(client1.GetStream());
StreamWriter out1=new StreamWriter(client1.GetStream());
out1.AutoFlush=true;
Instruction
means that the customer1 will not pass through an intermediate buffer, but will go directly to the network. This is an important point. In general, when customer1 sends a line of text to its partner, it expects a response. The response will never come if the line was actually buffered on machine M1 and never sent to machine M2.
To send a line of text to machine M2, write :
To read M2's answer, write :
We now have the elements to write the basic architecture of a internet client with the following basic communication protocol with the server:
- the customer 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
...
}
}
}
}
- line 11: create customer login - the clause using ensures that the related resources will be released when the using.
- line 12: opening the network flow in a clause using
- line 13: creation and operation of the read flow in a clause using
- line 14: creation and operation of writing flow in a clause using
- line 16: do not buffer the output stream
- lines 18-31: the client request/server response cycle
- line 26: the client sends its request to the server
- line 28: the client waits for the server's response. This is a blocking operation, like reading from the keyboard. The wait ends with the arrival of a string terminated by "\n" or by an end of flow. The latter occurs if the server closes the connection it has opened with the client.
11.5.3. The TcpListener class
The class TcpListener is the most suitable class for creating a TCP service. Its C constructors, M methods and P properties include the following:
C | creates a TCP service that will wait (listen) for client requests on a port passed as a parameter (port) called listening port. If the machine is connected to several IP networks, the service listens on each network. | |
C | ditto, but listening only takes place on the ip address specified. | |
M | listens to customer requests | |
M | accepts a client's 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's called the service port. AcceptTcpClient results in the object TcpClient associated with the service connection on the server side. | |
M | stops listening to customer requests | |
P | the server's listening socket |
The basic structure of a TCP server exchanging data with its clients using the following protocol :
- the customer sends a request contained in a single line
- the server sends a response contained in a single line
might look something 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) {
// on signale l'erreur
...
} 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
...
}
}
}
}
- line 14: the listening service is created for a given port and a given IP address. Remember ici that a machine has at least two IP addresses: the "127.0.0.1" address, which is its loopback address on itself, and the "I1.I2.I3.I4" address it has on the network to which it is connected. It can have other IP addresses if it is connected to several IP networks. IPAddress.Any designates all IP addresses of a machine.
- line 16: the listening service starts. Previously, it had been created but was not yet listening. Listening means waiting for customer requests.
- lines 20-26: the waiting for customer request / customer service loop repeated for each new customer
- line 22: a customer's request is accepted. The AcceptTcpClient makes an instance TcpClient dite de service :
- the customer has made his request with his own authority TcpClient on the customer's side, which we'll call TcpClientDemande
- the server accepts this request with AcceptTcpClient. This method creates an instance of TcpClient on the server side, which we'll call TcpClientService. We then have a Tcp connection open with authorities at both ends TcpClientDemande <--> TcpClientService.
- the subsequent client/server communication takes place over this connection. The listening service is no longer involved.
- line 24: so that the server can handle several clients at once, the service is provided by threads, 1 thread per client.
- line 32: listening service closed
- line 38: the method executed by the client service thread. It receives the instance TcpClient already connected to the customer to be served.
- lines 38-71: code similar to that of the basic Tcp client studied above.
11.6. Examples of clients / servers TCP
11.6.1. An echo server
We propose to write an echo server that will be launched from a DOS window with the command :
ServeurEcho port
The server operates on the port passed as a parameter. It simply sends the request back to the client. 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) {
// on signale l'erreur
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; se t; } // customer liaison
public int NumClient { get; se t; } // customer no
}
}
The structure of the echo server is in line with the basic Tcp server architecture described above. We will only comment on the "customer service" part:
- line 79: the customer's request is read out
- line 83: it is returned to the customer surrounded by square brackets
- line 79: service stops when the client closes the connection
In a Dos window, we use the C# project executable :
We then launch two customers putty which we connect to the machine's port 100 localhost :
![]() |
The echo server console display changes to :
Client 1 and then client 0 send the following texts:
![]() |
- [1]: customer no. 1
- [2]: customer no. 0
- [3]: the echo server console
![]() |
- in [4]: client 1 disconnects with the command bye.
- in [5]: the server detects it
The server can be stopped by pressing Ctrl-C. Client 0 then detects it [6].
11.6.2. A client for the echo server
We now write a client for the previous server. It will be called as follows:
ClientEcho nomServeur port
It connects to the machine nomServeur on the port port 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;
}
// on peut travailler
string demande = nu ll; // customer request
string réponse = nu ll; // 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 Tcp. Here are the results obtained with the following configuration :
- the server is launched on port 100 in a Dos window
- on the same machine, two clients are launched in two different windows Dos
The window for customer A (n° 0) displays the following information:
In customer B's (n° 1) :
On the server :
Customer A n° 0 disconnects:
The server console :
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 operate in a similar way to the putty client, but will have a console interface and no option configuration.
In the previous application, the dialog protocol was known: the client sent a single line and the server responded with a single line. Each service has its own particular protocol, and the following situations can also be found:
- the client has to send several lines of text before getting a response
- a server response may include several lines of text
So the cycle of sending a single line to the server and receiving a single line from the server is not always appropriate. To handle protocols more complex than the echo protocol, the generic Tcp client will have two threads:
- the main thread reads the lines of text typed on the keyboard and sends them to the server.
- a secondary thread will work in parallel, reading lines of text sent by the server. As soon as it receives one, it displays it on the console. The thread doesn't stop until the server closes the connection. It therefore works 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 = nu ll; // customer request
try {
// operate the customer connection
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 = nu ll; // 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: client connects to server
- line 43: a thread is started to read text lines from the server. It must execute the Receive on line 73. We pass the instance TcpClient which has been connected to the server.
- lines 57-64: the keyboard command input / send command to server loop. Keyboard command input is handled by the main thread.
- lines 75-98 : the method Receive executed by the text line reading thread. This method receives the instance TcpClient which has been connected to the server.
- 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 customer putty in paragraph 11.4. The client is run in a Dos console.
Protocol HTTP
The reader is invited to reread the explanations given in paragraph 11.4.2. We only comment on what is specific to the application:
- line 28: after sending line 27, the HTTP server closed the connection, thus terminating the read thread. The main thread reading keyboard commands is still active. The command on line 29, typed from the keyboard, stops it.
Protocol SMTP
The reader is invited to reread the explanations given in paragraph 11.4.3 and test other examples used with the customer putty.
11.6.4. A server Tcp generic
We are now interested in a server
- which displays on-screen orders sent by its customers
- sends them the text lines typed by a user. The user acts as the server.
The program is launched in a Dos window by : ServeurTcpGenerique portEcoute, where portEcoute is the port on which clients should connect. Service to the client will be provided by two threads:
- the main thread which :
- will process customers one after the other, not in parallel.
- which will read the lines typed by the user and send them to the client. The user will send the command bye that it closes the connection with the client. Because the console cannot be used for two clients simultaneously, our server handles only one client at a time.
- a secondary thread dedicated exclusively to reading lines of text sent by the client
The server never stops, except when the user types Ctrl-C on the keyboard.
Let's look at a few examples. The server is launched on port 100 and we use the generic client in paragraph11.6.3, to talk to it. The customer's window is as follows:
Lines beginning with <-- are those sent from the server to the client, the others from the client to the server. The server window is as follows:
Lines beginning with <-- are those sent from the client to the server, the others those sent from the server to the client. Line 9 indicates that the client's request reading thread has stopped. The main server thread is still waiting for keyboard commands to be sent to the client. To do this, type the command bye from line 10 to move on to the next client. The server is still active, while client 1 is finished. We launch a second client for the same server:
The server window then looks like this:
After line 6 above, the server is waiting for a new client. It can be stopped by pressing Ctrl-C.
Let's now simulate a web server by launching our generic server on port 88 :
Let's take a browser and ask for the URL http://localhost:88/exemple.html. The browser will then connect to the 88 of the machine localhost then request the page /exemple.html :
![]() |
Let's take a look at our server window:
We discover the HTTP headers sent by the browser. This allows us to discover other HTTP headers than those already encountered. Let's draw up a response to our client. The user at the keyboard is ici the real server, and he can draw up a response by hand. Recall the response made by a Web server in a previous example:
Let's try to give an analogous answer, keeping to the bare minimum:
In our response, we have limited ourselves to the HTTP headers on lines 1-4. We do not give the size of the document we are going to send (Content-Length) but simply say that we are going to close the connection (Connection: close) after sending it. This is enough for the browser. When it sees the closed connection, it will know that the server's response is complete and will display the HTML page that was sent to it. This is the page shown on lines 6-9. The keyboard user then closes the connection to the client by typing the command bye, line 10. On this keyboard command, the main thread closes the connection with the client. This causes the exception in line 11. The thread reading the client's text lines was abruptly interrupted by the closing 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 above, we make Display/Source to see what the browser has received, we get [2], i.e. exactly what we sent from the generic server.
The generic TCP server code 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épon se = 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) {
// on signale l'erreur
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 = nu ll; // customer request
string idClient =nu ll; // 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 text lines in the input stream
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 to all the machine's network interfaces.
- line 31: listening service is started
- line 34: infinite customer waiting loop. The user stops the server with Ctrl-C.
- line 37: waiting for a customer - blocking operation. When the customer arrives, the TcpClient rendered by the AcceptTcpClient represents the server side of an open connection with the client.
- line 40: customer requests are read by a separate thread.
- line 45: use of client connection in a clause using to make sure it's closed, whatever happens.
- line 47: using the network flow in a clause using
- line 48: creation in a clause using from a write stream to the network stream
- line 50: the write stream will be unbuffered
- lines 52-59: keyboard input loop for orders to be sent to the customer
- line 69: end of listening service. This instruction will never be executed ici since the server is stopped by Ctrl-C.
- line 78: the method Receive which continuously displays lines of text sent by the client on the console. This is the same as for the generic TCP client.
11.6.5. A customer Web
In the previous example, we saw some of the HTTP headers sent by a :
We're going to write a Web client, to which we'll pass a URL as a parameter, and which will display on screen the text sent by the server. We'll assume that the server supports the HTTP 1.1 protocol. Of the above headers, we'll use only the following:
- the first header indicates the desired document
- the second is the queried server
- the third that we want the server to close the connection after responding to us.
If we replace GET with HEAD above line 1, the server will only send us HTTP headers and not the document specified on line 1.
Our client web will be called as follows: ClientWeb URL cmd, where URL is the desired URL and cmd one of the two keywords GET or HEAD to indicate whether only the headers are required (HEAD) or also the page content (GET). Let's look at a first example:
- line 1, we only request HTTP (HEAD) headers
- lines 2-9: server response
If we use GET instead of HEAD in the Web client call, we get the same result as with HEAD, plus the requested document body.
The customer code web 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) {
// on affiche l'exception
Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
}
}
}
}
The only new feature in this program is the use of the Uri. The program receives a URL (Uniform Resource Locator) or URI (Uniform Resource Identifier) of the form http://server:port/cheminPageHTML?param1=val1;param2=val2;.... The class Uri allows us to break down the URL chain into its individual elements.
- lines 26-33: an object Uri is built from the string stringURI received as parameter. If the URI string received as a parameter is not a valid URI (absence of protocol, server, etc.), an exception is thrown. This allows us to check the validity of the parameter received. Once the Uri constructed, we have access to the various elements of this Uri. So if the uri in the previous code was built from the string http://server:port/document?param1=val1¶m2=val2;... we have :
- uri.Host=server,
- uri.Port=port,
- uri.Path=document,
- uri.Query=param1=val1¶m2=val2;...,
- uri.pathAndQuery= cheminPageHTML?param1=val1¶m2=val2;...,
- uri.Scheme=http.
11.6.6. A Web client to manage redirects
The previous Web client does not handle any redirection of the URL it has requested. Here's an example:
- line 2: the code 302 Found indicates a redirection. The address to which the browser should redirect is in the body of the document, line 16.
A second example:
- line 2: the code 301 Moved Permanently indicates a redirection. The address to which the browser must redirect is indicated on line 6, in the HTTP header Rental.
A third example:
- line 2: the code 302 Moved Temporarily indicates a redirection. The address to which the browser must redirect is indicated on line 5, in the HTTP header Rental.
A fourth example with a IIS server local to the :
- line 2: the code 302 Object moved indicates a redirection. The address to which the browser must redirect is indicated on line 5, in the HTTP header Rental. Note that unlike the previous examples, the redirection address is relative. The full address is in fact http://localhost/localstart.asp.
We propose to manage redirects when the first line of HTTP headers contains the keyword moved (case insensitive) and that the redirection address is in the HTTP header Rental.
If we take the last three examples, we have the following results:
Url : http://www.bull.com
- line 11: redirection to the address on line 6
Url : http://www.gouv.fr
- line 11: redirection to the address on line 6
Url : http://localhost
- line 13: redirection to the address on line 6
- ligne 15: access to the page http://localhost/localstart.asp was refused.
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 nbRedirsMa x = 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);
}
}
}
}
// a-t-on fini ?
if (!locationFound || nbRedirs > nbRedirsMax)
break;
// there is a redirection to be made - we build the new Uri
try {
if (locationString.StartsWith("http")) {
// full http address
uri = new Uri(locationString);
} else {
// http address relative to current uri
uri = new Uri(uri, locationString);
}
// log console
Console.WriteLine("\n<--Redirection vers l'URL {0}-->\n", uri);
} catch (Exception ex) {
// pb with Uri
Console.WriteLine("\n<--L'adresse de redirection {0} n'a pas été comprise : {1} -->\n", locationString, ex.Message);
}
}
} catch (Exception e) {
// on affiche l'exception
Console.WriteLine("L'erreur suivante s'est produite : {0}", e.Message);
}
}
}
}
Compared with the previous version, the changes are as follows:
- line 46: the regular expression to retrieve the redirection address in the HTTP header Location: address.
- line 49: code previously executed for a single Uri can now be executed successively for several Uri.
- line 66: reads the 1st line of HTTP headers sent by the server. It contains the keyword moved if the requested document has been moved.
- lines 71-75: check whether the 1st line contains the keyword moved. If so, we'll make a note of it.
- lines 79-93: read the other HTTP headers until you reach the empty line that signals their end. If the 1st line announced a redirection, we then focus on the HTTP header Location: address to store the redirection address in locationString.
- lines 98-100: the rest of the HTTP server response is displayed at the console.
- lines 105-106: the Uri requested has been fully evaluated and displayed. If there are no redirections to be made, or if the number of redirections allowed has been exceeded, the program is exited.
- lines 108-122: if there's a redirection, we calculate the new Uri to request. There's a bit of gymnastics involved, depending on whether the redirection address found was absolute (line 111) or relative (line 114).
11.7. .NET classes specialized in a particular internet protocol
In the previous examples of the web client, the HTTP protocol was managed with a TCP client. We therefore had to manage the particular communication protocol ourselves. Similarly, we could have built a SMTP or POP client. The .NET framework offers specialized classes for the HTTP and SMTP protocols. These classes know the communication protocol between client and server, and save the developer the trouble of having to manage them. We now present them.
11.7.1. The classeWebClient
There is a WebClient that can communicate with a web server. Let's consider the example of the web client in paragraph 11.6.5, processed ici with the class WebClient.
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);
}
}
}
}
- line 35: client web is created but not yet configured
- line 37: a HTTP header is added to the HTTP request. We'll discover that other headers will be sent by default.
- line 38: the web client requests the Uri given by the user and reads the document sent. [WebClient].OpenRead(Uri) opens the connection with Uri and reads the answer. This is where the class comes in. It handles the dialog with the web server. The result is the method OpenRead type is Stream and represents the requested document. The HTTP headers sent by the server and preceding the document in the response are not part of it.
- line 39: a StreamReader and line 41, its method ReadToEnd to read the full answer.
- lines 44-46: HTTP headers are displayed in the server response. [WebClient].ResponseHeaders represents a valuated collection whose keys are the names of HTTP headers, and whose values are the strings associated with these headers.
- line 51: exceptions raised during a client/server exchange are of type WebException.
Let's look at a few examples.
The generic TCP server built in paragraph 6.4.6 :
The previous web client is launched as follows:
The Uri requested is that of the generic server. The generic server then displays the HTTP headers sent to it by the web client:
This shows :
- customer web sends 3 HTTP headers by default (lines 3, 5, 6)
- line 4: the header we generated ourselves (line 37 of the code)
- that client web defaults to method GET (line 3). Other methods include POST and HEAD.
Now let's ask for a non-existent resource:
- line 2: we had an exception of type WebException because the server responded with the code 404 Not Found to indicate that the requested resource did not exist.
Finally, let's request an existing resource:
The file istia.univ-angers.txt produced by the order is as follows:
- line 1: the HTML document requested.
- lines 3-10: HTTP response headers in an order that is not necessarily that in which they were sent.
The class WebClient has methods for receiving a document (methods DownLoad) or to send them ( UpLoad) :
to download a resource as an array of bytes (image, for example) | |
to download a resource and save it as a local file | |
to download a resource and retrieve it as a string (e.g. html file) | |
the counterpart ofOpenRead but to send data to the server | |
the counterpart of DownLoadData but to the server | |
the counterpart of DownLoadFile but to the server | |
the counterpart of DownLoadString but to the server | |
to send the data from a POST command to the server and retrieve the results in the form of an array of bytes. The POST command requests a document, while at the same time 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 name UpLoad of the method. They are sent behind the empty HTTP header line in the form param1=value1¶m2=value2&... :
The same document could be requested using the GET method:
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¶m2=value2&... in the case of GET. |
11.7.2. The WebRequest / WebResponse classes
Sometimes the class WebClient is not flexible enough to do what you want. Let's take the example of the web client with redirection studied in paragraph 11.6.6. We need to send the HTTP header:
We have seen that the HTTP headers sent by default by the web client are as follows:
We've also seen that it's possible to add HTTP headers to previous ones using the [WebClient].Headers. Only line 1 is not a header belonging to the Headers because it doesn't have the shape key: value. I can't figure out how to change the GET to HEAD in line 1 from class WebClient (maybe I looked it up wrong?). When the class WebClient has reached its limits, we can move on to the WebRequest / WebResponse :
- WebRequest : represents the entire Web customer request.
- WebResponse : represents the entire server response Web
We said that the WebClient managed schematics http:, https:, ftp:, file:. The requests and responses of these different protocols do not have the same form. It is therefore necessary to manipulate the exact type of these elements rather than their generic type WebRequest and WebResponse. We will therefore use the :
- HttpWebRequest, HttpWebResponse for a HTTP customer
- FtpWebRequest, FtpWebResponse for a FTP customer
We now deal with the HttpWebRequest and HttpWebresponse the example of the web client with redirection studied in paragraph 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) {
// on affiche l'exception
Console.WriteLine("L'erreur suivante s'est produite : {0}", e2.Message);
}
}
}
}
- line 40: an object of type WebRequest is created with the static method WebRequest.Create(Uri uri) where uri is the uri of the document to be downloaded. Because we know that the protocol of Uri is HTTP, the type of the result is changed to HttpWebRequest to access specific elements of the Http protocol.
- line 41: we set the GET / POST / HEAD method for the 1st line of HTTP headers. Ici will be GET or HEAD.
- line 42: in a private corporate network, company machines are often isolated from the internet for security reasons. To achieve this, the private network uses internet addresses that the internet routers do not route. The private network is connected to internet by special machines called proxy which are connected to both the company's private network and the internet. This is an example of machines with multiple IP addresses. A machine on the private network cannot itself establish a connection with a server on the internet, a web server for example. It must ask a proxy machine to do this for it. A proxy machine proxy can house servers proxy for different protocols. We speak of proxy HTTP to designate the service responsible for making HTTP requests on behalf of machines on the private network. If such a proxy server HTTP exists, it must be indicated in the field [WebRequest].proxy. For example, write :
if the HTTP proxy operates on the machine's port 3128 pproxy.istia.uang. We put null in the field [WebRequest].proxy if the machine has direct access to the internet and doesn't have to go through a proxy.
- line 44: the method GetResponse() requests the document identified by its Uri and returns an object WebRequestResponse transform ici into an object HttpWebResponse. 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 case of redirection, this may be different from the Uri of the server initially queried. Note that the code does not manage redirection. It is handled automatically by the GetResponse. Once again, this is the advantage of high-level classes over basic classes in the Tcp protocol.
- [HttpWebResponse].StatusCode, [HttpWebResponse].StatusDescription represent the 1st line of the answer, for example : HTTP/1.1 200 OK. StatusCode is 200 and StatusDescription is OK.
- line 50: [HttpWebResponse].Headers is the collection of HTTP headers in the response.
- line 55: [HttpWebResponse].GetResponseStream : is the stream used to obtain the document contained in the response.
- line 61: an exception of type WebException
- line 63: [WebException].Response is the response that caused the exception to be raised.
Here's an example:
- lines 1 and 3: the server that responded is not the same as the one that was queried. There has therefore been a redirection.
- lines 5-11: HTTP headers sent by the server
11.7.3. Application: a proxy client for a web translation server
We'll now show how the preceding classes allow us to exploit the resources of web.
11.7.3.1. The application
There are several translation sites on web. The one that will be used ici is the site http://trans.voila.fr/traduction_voila.php :
![]() | The text to be translated is inserted in [1], the translation direction is chosen in [2]. The translation is requested in [3] and obtained in [4]. |
We're going to write a Windows application that is a client of the above application. It will do nothing more than the [trans.voila.fr] site application. Its interface will be as follows:
![]() |
11.7.3.2. Application architecture
The application will have the following 2-layer 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 it uses,
- [3]: the other for the Windows interface
11.7.3.4. The [dao] project
The [dao] project consists of the following elements:
- IServiceTraduction.cs : the interface presented to layer [ui]
- ServiceTraduction : implementation of this interface
- WebTraductionsException : an application-specific exception
The interface IServiceTraduction 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: property LanguesTraduites returns the dictionary of languages accepted by the translation server. This dictionary has entries of the form ["fe", "French-English"] where the value designates a translation direction, ici from French to English, and the "fe" key is a code used by the translation server trans.voila.fr.
- line 8: the method Translate is the translation method:
- text is the text to be translated
- deQuoiVersQuoi is one of the keys to the dictionary of translated languages
- the method translates the text
ServiceTraduction is an implementation class of the IServiceTraduction. We describe it in detail 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) {
}
}
}
- line 7: an error code
11.7.3.5. The customer web [ServiceTraduction]
Let's go back to the architecture of our application:
![]() |
The class [ServiceTraduction] we need to write is a client of the web translation service [trans.voila.fr]. To write it, we need to understand
- what the translation server expects from its client
- what he sends back to his customer
Let's take a look at the client/server dialogue involved in translation. Let's take the example presented in the introduction to the application:
![]() | The text to be translated is inserted in [1], the translation direction is chosen in [2]. The translation is requested in [3] and obtained in [4]. |
To obtain the translation [4], the browser sent the following GET request (displayed in its address field):
http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection=fe&stext=ce+chien+est+malade
It's pretty simple to understand:
- http://trans.voila.fr/traduction_voila.php is the Url of the translation service
- isText=1 semble vouloir dire qu'on a affaire à du texte
- translationDirection refers to the meaning of the translation, ici French-English
- stext is the text to be translated in a form we call Url encoded. Some characters cannot appear in a Url. This is the case, for example, of the space which has been ici encoded by a +. The .Net framework offers the static method System.Web.HttpUtility.UrlEncode to do this encoding work.
We conclude that to query the translation server, our [ServiceTraduction] class can use the string
where {0} and {1} will be replaced by the translation direction and the text to be translated respectively.
How do I know which translation directions are accepted by the server? In the screenshot above, the translated languages are in the drop-down list. If we look in the browser (View / source) at the Html code of the page, we find this for the drop-down list:
This isn't very clean Html code, insofar as each <option> tag should normally be closed by a </option> tag. That said, the value give us the list of translation codes to be sent to the server. In the dictionary LanguesTraduites interface IServiceTraduction, the keys will be the attributes value above and the values and texts displayed by the drop-down list.
Now let's take a look (View / Source) at where the translation returned by the translation server is located in the Html page:
The translation is right in the middle of the returned Html page. How do I find it? You can use a regular expression with the sequence <div class="txtTrad">...</div> because the <div class="txtTrad"> is only present at this point on the Html page. The C# regular expression used to retrieve the translated text is :
We now have the elements we need to write the implementation class ServiceTraduction 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: property LanguesTraduites interface IServiceTraduction - externally initialized
- line 13: property UrlServeurTraduction is the Url to request from the translation server: http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection={0}&stext={1} where the {0} marker must be replaced by the translation direction and the {1} marker by the text to be translated - externally initialized
- line 14: the property ProxyHttp is the Http proxy to be used, for example : pproxy.istia.uang:3128 - externally initialized
- 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>" - externally initialized
- in our application, these four properties will be initialized by Spring.
- lines 20-22: check 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 to form part of a Url
- line 26: the Uri of the translation service is built. If the UrlServeurTraduction is the chain http://trans.voila.fr/traduction_voila.php?isText=1&translationDirection={0}&stext={1}, the {0} marker is replaced by the translation direction and the {1} marker by the text to be translated.
- line 28: the translation search model in the html response from the translation server is built.
- lines 33, 60: the translation server query operation takes place in a try / catch mode
- line 35: the object HttpWebRequest which will be used to query the translation server is built with the Uri of the requested document.
- line 36: the query method is GET. This instruction could be dispensed with, as GET is probably the default method for the HttpWebRequest.
- line 37: we set the property Proxy object HttpWebRequest.
- line 39: the request to the translation server is made and its response is retrieved HttpWebResponse.
- lines 41-42: a StreamReader to read each line of the server's html response.
- lines 45-53: look for the translation in each line of the answer. When we've found it, we stop reading the Html response and close all the streams we've opened.
- lines 55-57: if no translation has been found in the html response, prepare an exception of type WebTraductionsException to say it.
- lines 60-62: if an exception has occurred during the client/server exchange, it is encapsulated in an exception of type WebTraductionsException to say it.
- lines 64-68: if an exception has been registered, it is thrown, otherwise the translation found is returned.
Our example assumes that the Http proxy does not require authentication. If this 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 ici WebRequest / WebResponse rather than WebClient because we don't have to exploit the entire Html response from the translation server. Once we've found the translation in this response, we don't need the rest of the lines in the response. The class WebClient doesn't allow you to do that.
Here is a test program for the ServiceTraduction :
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:
The solution project [dao] is compiled into a DLL HttpTraductions.dll :
![]() |
11.7.3.6. The application's graphical interface
Let's go back to the architecture of our application:
![]() |
We now write the [ui] layer. This is the subject of project [ui] in the solution under construction:
![]() |
The [lib] folder [3] contains some of the DLL referenced by the [4] project:
- those required for Spring : Spring.Core, Common.Logging, antlr.runtime
- layer [dao] : HttpTraductions
The [App.config] file 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&translationDirection={0}&stext={1}"/>
<!--
<property name="ProxyHttp" value="pproxy.istia.uang:3128"/>
-->
<property name="RegexTraduction" value="<div class="txtTrad">(.*?)</div>"/>
<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: objects to be instantiated by Spring. There will be only one, the one on line 18, which instantiates the translation service with the class ServiceTraduction found in DLL HttpTraductions.
- line 19: property UrlServeurTraduction class ServiceTraduction. There is a problem with the & character in Url. This character has a meaning in a Xml file. It must therefore be protected. This is also the case for other characters we'll come across in the rest of the file. They must be replaced by a sequence [&code;] : & by [&], < by [<] > by [>], " by ["].
- line 21: property ProxyHttp class ServiceTraduction. An uninitialized property remains null. Not setting this property means that there is no Http proxy.
- line 23: property RegexTraduction class ServiceTraduction. In the regular expression, we had to replace the [< > "] characters with their protected equivalents.
- lines 24-33: ownership LanguesTraduites class ServiceTraduction.
The [Program.cs] program is executed when the application is started. 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 Impôts version 6, in paragraph 7.6.2.
- the translation service is created on line 27 by Spring. If this creation was successful, the form [Form1] will be displayed (lines 52-55), otherwise the error form [Form2] will be displayed (lines 36-48).
The [Form2] form is the one used in Impôts version 6 and was explained in paragraph 7.6.4.
The form [Form1] is as follows:
![]() |
n° | type | name | role |
1 | TextBox | textBoxTexteATraduire | input box for text to be translated MultiLine=true |
2 | ComboBox | comboBoxLangues | list of translation directions |
3 | Button | buttonTraduire | to request the translation of text [1] in the sense [2] |
4 | TextBox | textBoxTraduction | translation of the text [1] |
The form code [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 has been initialized by [Program.cs], line 53. When the Form1_Load (line 20) or buttonTraduire_Click (line 40) are executed, so this field is already initialized.
- line 12: the dictionary of translated languages with entries of type ["French-English", "fe"], c.a.d. the opposite of the dictionary LanguesTraduites rendered by the translation service.
- line 20: the method Form1_Load is executed when the form is loaded.
- lines 22-27: using the dictionary serviceTraduction.LanguesTraduites ["fe", "Français-Anglais"] to build the dictionary languagesInversées ["French-English", "fe"].
- line 29: languesCombo is the array of dictionary keys languagesInversées, c.a.d. an array of elements ["French-English"]
- line 30: this table is sorted to present the translation directions in alphabetical order in the combo
- lines 31-33: the language combo is completed.
- line 40: the method executed when the user clicks on the [Translate] button
- line 46: simply call the serviceTraduction.Traduire to request a translation. The 1st parameter is the text to be translated, the second is the translation direction code. This code is found in the languagesInversées from the item selected in the language combo.
- line 48: if there is an exception, it is displayed instead of the translation.
11.7.3.7. Conclusion
This application showed that the web clients of the .NET framework enabled us to exploit the resources of web. The technique is similar each time:
- determine the Uri to query. This Uri is the plupart of time set.
- question him
- find what you're looking for in the server response using regular expressions
This technique is random. Over time, the Uri queried or the regular expression used to find the expected result may change. It's therefore a good idea to place both these items of information in a configuration file. But this may not be enough. We'll see in the next chapter that there are more stable resources on web: web services.
11.7.4. A SMTP (Simple Mail Tranport Protocol) client with SmtpClient class
A SMTP client is a client of a SMTP mail server. The .NET class SmtpClient fully encapsulates the needs of such a client. The developer doesn't need to know the details of the SMTP protocol. We are familiar with it. It was presented in paragraph 11.4.3.
We present the SmtpClient as part of a basic Windows application for sending e-mails with attachments. The application will connect to port 25 of a SMTP server. Remember that on most PC windows, firewalls or other antivirus software block connections to port 25, so it's 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 interface [SendMailForm.cs] is as follows:
![]() |
n° | type | name | role |
1 | TextBox | textBoxServeur | server name SMTP to connect to |
2 | NumericUpDown | numericUpDownPort | the port to connect to |
3 | TextBox | textBoxExpediteur | message sender's address |
4 | TextBox | textBoxTo | recipient addresses in the form: address1,address2, ... |
5 | TextBox | textBoxCc | addresses of copied recipients (CC=Carbon Copy) in the form: address1,address2, ... |
6 | TextBox | textBoxBcc | blind copy recipient addresses (BCC=Blind Carbon Copy) in the form: address1,address2, ... All addresses in these three input fields will receive the same message with the same attachments. The recipients of the message will know the addresses in fields 4 and 5, but not those in field 6. The Bcc is therefore a way of copying someone without the other recipients of the message knowing. |
7 | Button | buttonAjouter | to add an attachment to the mail |
8 | ListBox | listBoxPiecesJointes | list of attachments |
9 | TextBox | textBoxSujet | mail subject |
10 | TextBox | textBoxMessage | the message text. MultiLine=true |
11 | Button | buttonEnvoyer | to send the message and any attachments |
12 | TextBox | textBoxResult | displays a summary of the message sent, or an error message if a problem has been encountered |
13 | Button | buttonEffacer | to delete [12] |
OpenfileDialog | openFileDialog1 | non-visual control to select an attachment in 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 form code [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 won't be commenting on this code, which doesn't present any new features. To understand the method buttonAjouter_Click from line 14, the reader is invited to reread paragraph 7.5.1.
The method buttonEnvoyer_Click of line 55, which sends the mail, is as follows:
private void buttonEnvoyer_Click(object sender, EventArgs e) {
try {
// hourglass
Cursor = Cursors.WaitCursor;
// the customer Smtp
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: client Smtp is created. It needs two parameters: the server name SMTP and the port on which the server operates
- line 8: a MailMessage is created. It encapsulates the entire message to be sent.
- line 10: e-mail address Sender of the sender is filled in. An e-mail address is an instance of type MailAddress constructed from the string "xx@yy.zz". This string must have the expected form for an e-mail address, otherwise an exception is thrown. In this case, it will be displayed in the textBoxResultat (line 63) in an unfriendly form.
- lines 13-19: recipients' e-mail addresses are placed in the list To of the message. These addresses are retrieved from the textBoxTo. The regular expression on line 13 retrieves the various addresses, separated by commas.
- lines 21-26: repeat the same process to initialize the field CC messaging with addresses copied from the textBoxCc.
- lines 28-33: repeat the same process to initialize the field Bcc messaging with blind copy addresses in the textBoxBcc.
- line 35: the field Subject is initialized with the subject of the field textBoxSujet.
- line 37: the field Body is initialized with the message text textBoxMessage.
- lines 39-41: attachments are attached to the message. Each attachment is added as an object Attachment in the field Attachments of the message. An object Attachment is instantiated from the full path of the part to be attached in the local file system.
- line 43: the message is sent using the Send customer Smtp.
- lines 45-60: write shipment summary in field textBoxResultat and reset the form.
- line 63: error display
11.8. A generic asynchronous Tcp client
11.8.1. Presentation
In all the examples in this chapter, client/server communication was in blocking mode, also known as synchronous mode:
- when a client connects to a server, it waits for the server's response to this 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 provide service to the client operate in the same way as above.
In graphical user interfaces, it is often necessary to avoid blocking the user during long operations. The case often cited is that of downloading a large file. While the file is being downloaded, the user must be free to continue interacting with the graphical interface.
We propose ici to rewrite the generic Tcp client of the paragraph 11.6.3 with the following changes:
- the interface will be graphical
- the communication tool with the server will be a Socket
- the communication mode will be asynchronous:
- the client will initiate a connection to the server, but won't be stuck waiting for it to be established
- the customer will initiate a shipment to the server but won't get stuck waiting for it to finish
- the client will initiate the reception of data from the server, but will not remain blocked waiting for the data to be received.
Let's recall at what level the object is located Socket in client/server communication Tcp :
![]() |
The class Socket is the one that operates closest to the network. It enables fine-tuned management of the network connection. The term socket refers to a power socket. The term has been extended to designate a software network socket. In TCP-IP communication between two machines A and B, these are two sockets that communicate with each other. An application can work directly with sockets. This is the case for application A above. A socket can be a customer or server.
11.8.2. Asynchronous Tcp client GUI
The Visual Studio application is as follows:
![]() |
[ClientTcpAsynchrone.cs] is the graphical interface. It is as follows:
![]() |
n° | 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 indicate the end-of-line mark to be used by the client: LF "\n" or RCLF "\r\n" |
4 | Button | buttonConnexion | to connect to port [2] of server [1]. The button is labelled [Connect] when the client is not connected to a server, [Disconnect] when connected. |
5 | TextBox | textBoxMsgToServeur | message to be sent to the server once the connection has been made. When the user presses [Enter], the message is sent with the end-of-line mark selected in [3] |
6 | ListBox | listBoxEvts | list displaying the main client/server link events: connection, disconnection, flow closure, communication errors, etc |
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:
- the user connects his Tcp graphical client to a Tcp service via [1, 2, 3, 4].
- an asynchronous thread continuously accepts all data sent by the Tcp server and displays it in the list [7]. This thread is dissociated from other interface activities.
- users can send messages to the server at their own pace, thanks to [5]. Each message is sent by an asynchronous thread. Unlike the receive thread, which never stops, the send thread is terminated as soon as the message has been sent. A new asynchronous thread will be used for the next message.
- client/server communication ends when one of the partners closes the connection. The user can take this initiative with button [4] which, once the connection has been established, is labelled [Disconnect].
Here's a screenshot of an execution:
![]() |
- in [1]: connection to a POP service
- in [2]: display of events that occurred during connection
- in [3]: the message sent by server POP at the end of the connection
- in [4]: the [Connect] button has become the [Disconnect] button
![]() |
- in [1], we sent the command quit to server POP. The server replied +OK goodbye and closed the connection
- in [2], this server-side closure was detected. The client then closed the connection on its side.
- in [3], the [Disconnect] button has reverted to a [Connect] button
11.8.3. Asynchronous server connection
Pressing the [Connect] button executes 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 can be labelled [Connect] or [Disconnect].
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 classroom Socket is part of the System.Net.Sockets.
A certain amount of data must be shared between several form methods. These are as follows:
- line 7: customer is the communication socket with the server
- lines 6 and 8: the client will receive messages in an array of bytes data.
- line 9: answer is the response sent by the server.
- line 10: finLigne is the end-of-line mark used by the client Tcp - is initialized by default to RCLF but can be modified by the user using radio buttons [3].
The procedure connection in line 19 connects to the Tcp server:
- lines 21-25: check that the server name is not empty. If this is not the case, the event is logged in listBoxEvts method logEvent line 49.
- line 27: signals that the connection is about to take place
- line 30: create object Socket required for Tcp-Ip communication. The manufacturer admits three parameters:
- AddressFamily addressFamily : the family of IP client and server addresses, ici IPv4 addresses (AddressFamily.InterNetwork)
- SocketType socketType : socket type. The socket type SocketType.Stream is suitable for Tcp-Ip connections
- ProtocolType protocolType : the type of internet protocol used, ici the Tcp protocol
- line 32: the connection is made asynchronously. The connection is launched, but execution continues without waiting for its end. The [Socket].BeginConnect has four parameters :
- IPAddress ipAddress : the Ip address of the machine running the service to be connected to
- Int32 port : wearing the service
- AsyncCallBack asyncCallBack : AsyncCallBack is a type delegate :
The method asyncCallBack passed as the 3rd parameter of the BeginConnect must be a method accepting a IAsyncCallBack and return no results. This is the method that will be called when the connection has been made. We pass ici as the 3rd parameter, the method connected on line 41.
- (continued)
- Object state : an object to pass to the asyncCallBack. This method receives (see delegate above) a parameter ar type IAsyncResult. The object state can be retrieved from ar.AsyncState (line 43). We pass ici as the 4th parameter, the client socket.
- line 38: the method is terminated. The user can interact with the GUI again. The connection takes place in the background, in parallel with GUI event handling. Also in parallel, the connected of line 41 will be called at the end of the connection, whether it ends well or badly.
The method code connected 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 parameter result received by the method. Remember that this object is the one passed as the 4th parameter to the BeginConnect.
- line 7: the connection attempt is terminated by the EndConnect to which the parameter result received by the method.
- line 9: the event is logged in the event list
- line 11: the [Connect] button becomes a [Disconnect] button so that the user can request disconnection.
- line 13: the server response is initialized. It will be updated by repeated calls to the asynchronous method BeginReceive.
- line 14: 1st call to asynchronous method BeginReceive. It is called with the following parameters :
- byte[] buffer : the buffer in which to place the data to be received - ici the buffer is data
- int offset : from which buffer position to place data the data to be received - ici the offset is 0, c.a.d. that data is placed from the 1st byte of the buffer.
- int size : buffer size in bytes - ici size is tailleBuffer.
- SocketFlags socketFlags : socket configuration - ici no configuration
- AsyncCallBack asyncCallBack : the method to be called when reception is complete. This will be the case either because the buffer has received data or because the connection has been closed. in the case of Ici, the callback method is the reading on line 22.
- Object state : the object to be passed to the callback method asyncCallBack. Ici, the client socket is passed again.
Note that all this takes place without any action on the part of the user, other than the initial connection request via the [Connect] button. At the end of the connected, another method is executed in the background: the reading which 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 method reading is triggered in the background when the data has received data or the connection has been closed by the server.
- line 9: the asynchronous read request is terminated by EndReceive. Again, this method must be called with the parameter received by the callback function. The EndReceive returns the number of bytes received in the read buffer.
- line 10: if the number of bytes is zero, the connection has been closed by the server.
- line 12: the event is noted in the event list
- line 14: an exception is processed
- lines 16-17: note the event in the event list and record the error
- line 20: check whether to close the connection
- line 22: close the client-side connection with a disconnect which we'll look at later.
- line 24: server response, c.a.d. global variable answer is displayed in the listBoxDialogue using a private method displayServerResponse.
- line 26: end of asynchronous method reading
- line 29: the bytes received are put into a string in UTF8 format.
- line 31: they are added to the answer under construction
- line 33: the answer is displayed in the list listBoxDialogue.
- line 35: we go back to waiting for data from the server
Ultimately, the asynchronous method reading never stops. It continuously reads data from the server and displays it in the listBoxDialogue. It only stops when the connection is closed by either the server or the user himself.
11.8.4. Server disconnection
Pressing the [Disconnect] button executes 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 can be labelled [Connect] or [Disconnect].
The method disconnect ensures customer disconnection:
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 customer exists and is connected
- line 6: disconnection is announced in listBoxEvts. The property client.RemoteEndPoint gives the pair (Address Ip, port) of the other end of the connection, c.a.d ici of the server.
- line 8: the socket data stream is closed with the ShutDown. A socket's data flow is bidirectional: the socket transmits and receives data. The the method ShutDown can be : ShutDown.Receive to close the receive stream, Shutdonw.Send to close the emission flow or ShutDown.Both to close the two streams.
- line 9: release socket resources
- line 11: the [Disconnect] button becomes the [Connect] button
- lines 12-15: exception handling
11.8.5. Asynchronous data transfer to the server
When the user validates the message in the textBoxMsgToServeur, 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 [Enter] key and the client socket is connected, then the message in the textBoxMsgToServeur is sent with the envoyerMessage.
Visit method envoyerMessage 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 customer's end-of-line mark is added to the message and placed in the byte array message.
- line 6: an asynchronous transmission is started using the BeginSend. The parameters of BeginSend are identical to those of the BeginReceive. At the end of the asynchronous message transmission operation, the writing will be called.
- line 8: the message sent is added to the list listBoxDialogue to monitor the client/server dialog
- line 10: the message sent is deleted from the graphical interface
The recall method writing 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));
}
}
- line 4: the recall method writing receives a result parameter of type IAsyncResult.
- line 3: in parameter result, retrieve the client socket. This socket was the 5th parameter of the BeginSend.
- line 5: the asynchronous send operation is terminated.
You don't have to wait for a message to be sent before giving it back to the user. This means that the user can send a second message even though the first message has not yet been sent.
11.8.6. Display of events and client/server dialog
Events are displayed using the logEvents :
// 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 method logEvents receives the message to be added to the list as a parameter listBoxEvts.
- line 3: the component cannot be used directly listBoxEvents. Indeed, the logEvents is called by two types of threads :
- the main thread owning the GUI, for example when it signals that a connection attempt is in progress
- a secondary thread for asynchronous operation. This type of thread does not own components, and its access to a C component must be controlled by a C.Invoke. This operation tells control C that a thread wants to perform an operation on it. The Invoke has two parameters :
- a delegate. This callback function will be executed by the GUI owner thread, not by the thread running the C.Invoke.
- an object to be passed to the callback function.
Ici the first parameter passed to the Invoke 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 entered in listBoxEvts.
Line 3, the first parameter passed to the Invoke is the logEventCallBack on line 6. It corresponds to the delegate's signature writeLog. The second parameter passed to the Invoke is the message that will be passed as a parameter to the logEventCallBack.
The operation Invoke is a synchronous operation. Execution of the secondary thread is blocked until the thread owning the control executes the callback method.
- line 6: the callback method executed by the GUI thread receives the message to be displayed in the control listBoxEvts.
- line 9: the event is logged in 1st position in the list, so that the most recent events are at the top of the list.
Client/server dialog messages are displayed using the logDialogue :
// 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.
Messages received by the client are displayed using the displayServerResponse :
private void afficherRéponseServeur(String msg, bool dernièreLigne) {
...
}
The first parameter is the message to be displayed. This message can be a series of lines. In fact, the client reads data from the server in blocks of tailleBuffer (1024) bytes. Within these 1024 bytes, various lines can be found, identified by their "\n" end-of-line mark. The last line may be incomplete, its end-of-line mark being in the 1024 bytes that follow. The method finds the lines ending in "\n" in the message and then asks logDialogue to display them. The method's second parameter indicates whether to display the last line found or leave it in the buffer to be completed by the next message. The code is rather complex and of no interest ici. It will therefore not be commented on.
11.8.7. Conclusion
The same example could be treated with synchronous operations. Ici the asynchronous aspect of the graphical interface does little for the user. However, if he logs in and then realizes that the server is "no longer responding", he has the option of disconnecting, thanks to the fact that the GUI continues to respond to events during the execution of asynchronous operations. This rather complex example enabled us to introduce some new notions:
- the use of sockets
- the use of asynchronous methods. What we have seen is part of a standard. Other asynchronous methods exist and operate on the same model.
- the updating of GUI controls by secondary threads.
Asynchronous Tcp / Ip communication offers more serious advantages for a server than those shown by 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 only serve N clients simultaneously. If all N threads perform a blocking (synchronous) operation, there are no more threads available for a new client until one of the blocking operations completes and frees a thread. If asynchronous rather than synchronous operations are performed on threads, a thread is never blocked and can be quickly recycled for new clients.
11.9. Sample application, version 8: Tax calculation server
11.9.1. The architecture of the new version
We return to the tax calculation application that has already been covered in various forms. Let's recall its latest version, version 7 of paragraph 9.8.
![]() |
The data was in a database and the [ui] layer was a graphical user interface:
![]() |
We are going to reproduce this architecture and distribute it on two machines:
![]() |
- a [server] machine will host the [metier] and [dao] layers of version 7. A Tcp/Ip [server] [1] layer will be built to allow internet clients to query the tax calculation service.
- a [client] machine will host the [ui] layer of version 7. A Tcp/Ip [client] [2] layer will be built to enable the [ui] layer to query the tax calculation service.
The architecture changes profoundly ici. Version 7 was a single-user Windows application. Version 8 becomes a internet client/server application. The server will be able to serve several clients simultaneously.
First, we'll write the [server] part of the application.
11.9.2. Tax calculation server
11.9.2.1. The Visual Studio project
![]() |
The Visual studio project will be as follows:
![]() |
- in [1], the project. It includes the following elements:
- [ServeurImpot.cs]: the Tcp/Ip tax calculation server in the form of a console application.
- [dbimpots.sdf]: the SQL Server compact database from version 7 described in paragraph 9.8.5.
- [App.config]: application configuration file.
- in [2], the [lib] folder contains the DLL needed for the project:
- [ImpotsV7-dao]: the [dao] layer in version 7
- [ImpotsV7-metier]: the [metier] layer in version 7
- [antlr.runtime, CommonLogging, Spring.Core] for Spring
- in [3], the project references
11.9.2.2. Application configuration
File [App.config] is operated by Spring. Its contents are 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 Server compact database
- lines 21-23: [metier] layer configuration.
This is the configuration file used in version 7's [ui] layer. It was presented in paragraph 9.8.4.
11.9.2.3. Server operation
On server startup, the server application instantiates the [metier] and [dao] layers, then displays an administration console interface:
The administration console accepts the following commands:
to start the service on a given port | |
to stop the service. It can then be restarted on the same or another port. | |
to activate the client/server dialog echo on the console | |
to deactivate echo | |
to display the active/inactive status of the service | |
to quit the application |
Let's start the server:
Let's now run the asynchronous graphical Tcp client studied earlier in this section 11.8.

The customer is logged in. He can send the following commands to the tax calculation server:
for a list of authorized commands | |
to calculate the tax liability of someone with nbEnfants children and a salary of salaireAnnuel euros. married is o if married, n or else. | |
to close the connection with the server |
Here is an example of a dialogue:
![]() |
On the server side, the console displays the following:
Let's switch on the echo and start a new dialog from the graphics client:
![]() |
The admin console then displays the following:
- line 1: client/server dialog echo enabled
- line 2: a customer has arrived
- line 3: he sent the [help] command
- lines 4-7: server response on 4 lines.
Stop the service:
- line 1: service stop requested (not the application itself)
- line 2: an exception due to the fact that the server blocked on a client expectation was abruptly interrupted by the closure of the listening service.
- line 3: service can now be restarted by start port or stopped by quit.
Before the listening service was stopped, a client was being served on another connection. This connection is not closed when the listening socket is closed. The client can continue to issue commands: the service thread associated with it before the listening service was closed continues to respond:

11.9.3. Server code Tcp for tax calculation
![]() |
1 ![]() |
The server code [ServeurImpot.cs] 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 layers [metier] and [dao]
IApplicationContext ctx = null;
metier = null;
try {
// context Spring
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) {
...
}
....
}
}
- lines 18-21: the [metier] and [dao] layers are instantiated by Spring configured by [App.config]. The global variable job of line 6 is then initialized.
- lines 24-25: configure the application's thread pool with 10 minimum and maximum threads.
- lines 30-86: loop for entering service administration commands (start, stop, quit, echo, status).
- line 32: server prompt for each new command
- line 34: read administrator command
- line 35: the order is broken down into fields for analysis
- lines 38-52: the order start port to launch the listening service
- line 40: if the service is already active, there's nothing to do
- line 45: check that the port is present and correct. If so, the global variable port of line 7 is positioned.
- line 49: the listening service will be managed by a secondary thread so that the main thread can continue executing console commands. If the doEcoute successful connection, the global variables service line 8 and assets on line 9 are initialized.
- lines 53-60: the order echo start / stop enables/disables echo of client/server dialog on console
- line 58: the global variable echo of line 7 is positioned
- lines 61-67: the order stop which stops the listening service.
- line 64: stop listening service
- lines 68-75: the order status which displays the service's active/inactive status
- lines 76-80: the order quit that stops everything.
The thread responsible for listening to customer requests executes the doEcoute next :
private static void doEcoute(Object data) {
// thread for listening to customer 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);
// customer service loop
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 studied in paragraph 11.6.1. We only comment on what is different:
- line 7: listening service launched
- line 9: notes that the service is now active
Line 21, customers are served by service threads running the doService next :
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, the code is similar to that of the echo server studied in paragraph 11.6.1 We only comment on what is different:
- line 15: once the client is connected, the server sends a welcome message.
- lines 19-52: the loop for reading customer commands. The loop stops when the customer sends the "goodbye".
- line 27: case of an empty order
- line 34: the request is broken down into fields for analysis
- line 37: order help : the customer requests a list of authorized orders
- line 40: order tax : the client requests a tax calculation. We respond with the message returned by the calculImpot which we'll be detailing shortly.
- line 44: order goodbye : the customer indicates that he is finished.
- line 45: we're getting ready to leave the reading loop of customer requests (lines 19-52)
- line 46: we reply to the customer with a good-bye message
- line 48: an incorrect order. An error message is sent to the customer.
Order processing tax is ensured by the calculImpot next :
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;
}
}
- line 1: the method receives the array of command fields as the 3rd parameter tax. If correctly formulated, it is of the form married tax nbEnfants salaireAnnuel. The result of the method is the response to be sent to the client.
- line 4: check that the command has 4 fields
- line 8: check that the married is valid
- line 14: check that the nbEnfants is valid
- line 19: check that the salaireAnnuel is valid
- line 25: tax is calculated using the CalculerImpot of the [metier] layer. Remember that this layer is encapsulated in a DLL.
- line 26: if the [metier] layer has returned a result, it is returned to the customer.
- line 28: if the [metier] layer has thrown an exception, the exception message is returned to the client.
11.9.4. The Tcp tax calculation server graphics client
11.9.4.1. The project Visual Studio
![]() |
The Visual Studio project for the graphics client will be as follows:
![]() |
- in [1], the two solution projects, one for each of the two application layers
- in [2], the Tcp client, which acts as a [metier] layer for the [ui] layer. We'll use both terms here.
- layer in [3], the [ui] layer in version 7, with one detail that we will discuss later
11.9.4.2. The diaper [metier]
The interface IImpotMetier has not changed. It is still the same as in version 7:
namespace Metier {
public interface IImpotMetier {
int CalculerImpot(bool marié, int nbEnfants, int salaire);
}
}
This interface is implemented by the following [ImpotMetierTcp] class:
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: name or address Ip of tax calculation server Tcp
- line 8: this server's listening port
- these two properties will be initialized by Spring when the [ImpotMetierTcp] class is instantiated.
- line 11: tax calculation method. When executed, the properties Server and Port are already initialized. The code follows the classic Tcp client approach
- line 13: the connection to the server is open
- 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: ici, remember that when the connection is opened, the server sends the client a 1st line, which is the "welcome" messageWelcome to the tax calculation server". This message is read and ignored.
- line 22: send a command like : impot o 2 60000 to calculate the tax liability of a married person with 2 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 badly formed (this won't happen ici) or if there was a problem calculating the tax. Ici, the latter case is not handled, but it would certainly have been "cleaner" to do so. In fact, if the line read is an error message, an exception will be thrown because the conversion to an integer will fail. The exception retrieved by the GUI will be a conversion error, whereas the original exception is of a completely different nature. The reader is invited to improve this code.
- lines 25-28: release all used resources with a "using" clause.
The [metier] layer is compiled in the DLL ImpotsV8-metier.dll :

11.9.4.3. The [ui] layer
![]() |
The [ui] [1,3] layer is the one studied in version 7 in paragraph 9.8.4, except for three details:
- the configuration of the [metier] layer in [App.config] is different because its implementation has changed
- the [Form1.cs] GUI has been modified to display a possible exception
- the [metier] layer is in the DLL [ImpotsV8-metier.dll].
The [App.config] file 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>
- line 16: instantiation of layer [metier] with class Metier.ImpotMetierTcp from DLL ImpotsV8-metier.dll
- lines 17-18: properties Server and Port class Metier.ImpotMetierTcp are initialized. The server will be on the localhost and will operate on port 27.
The graphical interface presented to the user is as follows:
![]() |
- in [1], we added a TextBox to display a possible exception. This field did not exist in the previous version.
Apart from this detail, the form code is the same as that described in paragraph 6.4.3. The reader is invited to refer to it. In [2], you can see an example of execution obtained with a server launched as follows :
Customer screenshot [2] corresponds to the lines in customer 9 above.
11.9.5. Conclusion
Once again, we were able to reuse existing code, either without modification (server layers [metier], [dao]) or with very little modification (client layer [ui]). This was made possible by our systematic use of interfaces and their instantiation with Spring. If, in version 7, we had put the business code directly into the GUI event handlers, this business code would not have been reusable. This is the major drawback of 1-layer architectures.
Finally, note that the [ui] layer has no knowledge of the fact that a remote server is calculating the tax amount for it.























































