Tải bản đầy đủ (.pdf) (140 trang)

Professional C# Third Edition phần 9 ppsx

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (3.25 MB, 140 trang )

IPAddress
IPAddress represents an IP address. The address itself is available as the Address property, and may be
converted to a dotted decimal format with the
ToString() method. IPAddress also implements a
static
Parse() method, which effectively performs the reverse conversion of ToString()—converting
from a dotted decimal string to an
IPAddress.
IPAddress ipAddress = IPAddress.Parse(“234.56.78.9”);
long address = ipAddress.Address;
string ipString = ipAddress.ToString();
In the previous example, the long integer address is assigned 156121322, and the string ipString is
assigned the text “234.56.78.9”.
IPAddress also provides a number of constant static fields to return special addresses. For example, the
Loopback address allows a machine to send messages to itself, while the Broadcast address allows
multicasting to the local network.
// The following line will set loopback to “127.0.0.1”.
// the loopback address indicates the local host.
string loopback = IPAddress.Loopback.ToString();
// The following line will set broadcast address to “255.255.255.255”.
// the broadcast address is used to send a message to all machines on
// the local network.
string broadcast = IPAddress.Broadcast.ToString();
IPHostEntry
The IPHostEntry class encapsulates information relating to a particular host computer. This class
makes the host name available via the
HostName property (which returns a string), and the
AddressList property returns an array of IPAddress objects. We are going to use the IPHostEntry
class in the in next example: DNSLookupResolver.
Dns
The Dns class is able to communicate with your default DNS server in order to retrieve IP addresses. The


two important (static) methods are
Resolve(), which uses the DNS server to obtain the details of a host
with a given host name, and
GetHostByAddress(), which also returns details of the host, but this time
using the IP address. Both methods return an
IPHostEntry object.
IPHostEntry wroxHost = Dns.Resolve(“www.wrox.com”);
IPHostEntry wroxHostCopy = Dns.GetHostByAddress(“168.215.86.81”);
In this code both IPHostEntry objects will contain details of the Wrox.com servers.
The
Dns class differs from the IPAddress and IPHostEntry classes since it has the ability to actually
communicate with servers to obtain information. In contrast,
IPAddress and IPHostEntry are more
along the lines of simple data structures with convenient properties to allow access to the underlying
data.
1080
Chapter 31
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1080
Simpo PDF Merge and Split Unregistered Version -
The DnsLookup example
We will illustrate the DNS and IP-related classes with an example that looks up DNS names: DnsLookup
(see Figure 31-7).
Figure 31-7
This sample application simply invites the user to type in a DNS name using the main text box. When
the user clicks the Resolve button, the sample uses the
Dns.Resolve() method to retrieve an
IPHostEntry reference and display the host name and IP addresses. Note how the host name displayed
may be different from the name typed in. This can occur if one DNS name (
www.microsoft.com) sim-
ply acts as a proxy for another DNS name (a562.cd.akamai.net).

The
DnsLookup application is a standard C# Windows application. The controls are added as shown in
Figure 31-7, giving them the names
txtBoxInput, btnResolve, txtBoxHostName, and listBoxIPs
respectively. Then we simply add the following method to the Form1 class as the event handler for the
buttonResolve click event.
void btnResolve_Click (object sender, EventArgs e)
{
try
{
IPHostEntry iphost = Dns.Resolve(txtBoxInput.Text);
foreach (IPAddress ip in iphost.AddressList)
{
string ipaddress = ip.AddressFamily.ToString();
listBoxIPs.Items.Add(ipaddress);
listBoxIPs.Items.Add(“ “ + ip.ToString());
}
txtBoxHostName.Text = iphost.HostName;
}
catch(Exception ex)
{
MessageBox.Show(“Unable to process the request because “ +
1081
Accessing the Internet
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1081
Simpo PDF Merge and Split Unregistered Version -
“the following problem occurred:\n” +
ex.Message, “Exception occurred”);
}
}

Notice that in this code we are careful to trap any exceptions. An exception might occur if the user types
in an invalid DNS name, or if the network is down.
After retrieving the
IPHostEntry instance, we use the AddressList property to obtain an array contain-
ing the IP addresses, which we then iterate through with a
foreach loop. For each entry we display the
IP address as an integer and as a string, using the
IPAddress.AddressFamily.ToString() method.
Lower-Level Protocols
In this section we will briefly discuss some of the .NET classes used to communicate at a lower level.
Network communications work on several different levels. The classes we have covered in this chapter
so far work at the highest level: the level at which specific commands are processed. It is probably easi-
est to understand this concept if you think of file transfer using FTP. Although today’s GUI applications
hide many of the FTP details, it was not so long ago when we executed FTP from a command-line
prompt. In this environment we explicitly typed commands to send to the server for downloading,
uploading, and listing files.
FTP is not the only high-level protocol relying on textual commands. HTTP, SMTP, POP, and other proto-
cols are based on a similar type of behavior. Again, many of the modern graphical tools hide the trans-
mission of commands from the user, so you are generally not aware of them. For example, when you
type a URL into a Web browser, and the Web request goes off to a server, the browser is actually sending
a (plain text) GET command to the server, which fulfills a similar purpose as the FTP
get command. It
can also send a POST command, which indicates that the browser has attached other data to the request.
However, these protocols are not sufficient by themselves to achieve communication between comput-
ers. Even if both the client and the server understand, for example, the HTTP protocol, it will still not be
possible for them to understand each other unless there is also agreement on exactly how to transmit the
characters: what binary format will be used, and getting down to the lowest level, what voltages will be
used to represent 0s and 1s in the binary data? Since there are so many items to configure and agree
upon, developers and hardware engineers in the networking field often refer to a protocol stack. When
you list all of the various protocols and mechanisms required for communication between two hosts,

you create a protocol stack with high-level protocols on the top and low-level protocols on the bottom.
This approach results in a modular and layered approach to achieving efficient communication.
Luckily, for most development work, we don’t need to go far down the stack or work with voltage
levels, but if you are writing code that requires efficient communication between computers, it’s not
unusual to write code that works directly at the level of sending binary data packets between computers.
This is the realm of protocols such as TCP, and Microsoft has supplied a number of classes that allow
you to conveniently work with binary data at this level.
1082
Chapter 31
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1082
Simpo PDF Merge and Split Unregistered Version -
Lower-Level Classes
The System.Net.Sockets namespace contains the relevant classes. These classes, for example, allow
you to directly send out TCP network requests or to listen to TCP network requests on a particular port.
The following table explains the main classes.
Class Purpose
Socket Low-level class that deals with managing connections. Classes such as WebRe-
quest, TcpClient, and UdpClient
use this class internally.
NetworkStream Derived from Stream. Represents a stream of data from the network.
TcpClient Enables you to create and use TCP connections.
TcpListener Enables you to listen for incoming TCP connection requests.
UdpClient Enables you to create connections for UDP clients. (UDP is an alternative pro-
tocol to TCP, but is much less widely used, mostly on local networks.)
Using the TCP classes
The transmission control protocol (TCP) classes offer simple methods for connecting and sending data
between two endpoints. An endpoint is the combination of an IP address and a port number. Existing
protocols have well defined port numbers, for example, HTTP uses port 80, while SMTP uses port 25.
The Internet Assigned Number Authority, IANA, (
assigns port numbers to

these well-known services. Unless you are implementing a well-known service, you will want to select a
port number above 1,024.
TCP traffic makes up the majority of traffic on the Internet today. TCP is often the protocol of choice
because it offers guaranteed delivery, error correction, and buffering. The
TcpClient class encapsulates
a TCP connection and provides a number of properties to regulate the connection, including buffering,
buffer size, and timeouts. Reading and writing is accomplished by requesting a
NetworkStream object
via the
GetStream() method.
The
TcpListener class listens for incoming TCP connections with the Start() method. When a con-
nection request arrives you can use the
AcceptSocket() method to return a socket for communication
with the remote machine, or use the
AcceptTcpClient() method to use a higher-level TcpClient
object for communication. The easiest way to demonstrate the TcpListener and TcpClient classes
working together is to work through an example.
The TcpSend and TcpReceive examples
To demonstrate how these classes work we need to build two applications. Figure 31-8 shows the first
application, TcpSend. This application opens a TCP connection to a server and sends the C# source code
for itself.
1083
Accessing the Internet
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1083
Simpo PDF Merge and Split Unregistered Version -
Figure 31-8
Once again we create a C# Windows application. The form consists of two text boxes (
txtHost and
txtPort) for the host name and port, respectively, as well as a button (btnSend) to click and start a con-

nection. First, we ensure that we include the relevant namespaces:
using System.Net;
using System.Net.Sockets;
using System.IO;
The following code shows the event handler for the button’s click event:
private void btnSend_Click(object sender, System.EventArgs e)
{
TcpClient tcpClient = new TcpClient(txtHost.Text, Int32.Parse(txtPort.Text));
NetworkStream ns = tcpClient.GetStream();
FileStream fs = File.Open(“ \\ \\form1.cs”, FileMode.Open);
int data = fs.ReadByte();
while(data != -1)
{
ns.WriteByte((byte)data);
data = fs.ReadByte();
}
fs.Close();
ns.Close();
tcpClient.Close();
}
This example creates the TcpClient using a host name and a port number. Alternatively, if you have an
instance of the
IPEndPoint class, you can pass the instance to the TcpClient constructor. After retriev-
ing an instance of the
NetworkStream class we open the source code file and begin to read bytes. Like
many of the binary streams, we need to check for the end of the stream by comparing the return value of
the
ReadByte() method to -1. After our loop has read all of the bytes and sent them along to the net-
work stream, we make sure to close all of the open files, connections, and streams.
On the other side of the connection, the TcpReceive application displays the received file after the trans-

mission is finished (see Figure 31-9).
1084
Chapter 31
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1084
Simpo PDF Merge and Split Unregistered Version -
Figure 31-9
The form consists of a single RichTextBox control, named
txtDisplay. The TcpReceive application
uses a
TcpListener to wait for the incoming connection. In order to avoid freezing the application
interface, we use a background thread to wait for and then read from the connection. Thus we need to
include the
System.Threading namespace as well:
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
Inside the form’s constructor we spin up a background thread:
public Form1()
{
InitializeComponent();
Thread thread = new Thread(new ThreadStart(Listen));
thread.Start();
}
The remaining important code is this:
public void Listen()
{
TcpListener tcpListener = new TcpListener(2112);
1085
Accessing the Internet

39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1085
Simpo PDF Merge and Split Unregistered Version -
tcpListener.Start();
TcpClient tcpClient = tcpListener.AcceptTcpClient();
NetworkStream ns = tcpClient.GetStream();
StreamReader sr = new StreamReader(ns);
string result = sr.ReadToEnd();
Invoke(new UpdateDisplayDelegate(UpdateDisplay),
new object[] {result} );
tcpClient.Close();
tcpListener.Stop();
}
public void UpdateDisplay(string text)
{
txtDisplay.Text= text;
}
protected delegate void UpdateDisplayDelegate(string text);
The thread begins execution in the Listen() method and allows us to make the blocking call to
AcceptTcpClient() without halting the interface. Notice that we have hard-coded the port number
2112 into the application, so you will need to enter the same port number from the client application.
We use the
TcpClient object returned by AccepTcpClient() to open a new stream for reading. Similar
to the earlier example, we create a
StreamReader to convert the incoming network data into a string.
Before we close the client and stop the listener, we update the form’s text box. We do not want to access
the text box directly from our background thread, so we use the form’s
Invoke() method with a dele-
gate, and pass the result string as the first element in an array of
object parameters. Invoke() ensures
our call is correctly marshaled into the thread owning the control handles in the user interface.

TCP versus UDP
The other protocol to cover in this section is UDP (user datagram protocol). UDP is a simple protocol
with few features but also little overhead. Developers often use UDP in applications where the speed
and performance requirements outweigh the reliability needs, for example, video streaming. In contrast,
TCP offers a number of features to confirm the delivery of data. TCP provides error correction and re-
transmission in the case of lost or corrupted packets. Last, but hardly least, TCP buffers incoming and
outgoing data and also guarantees a sequence of packets scrambled in transmission are reassembled
before delivery to the application. Even with the extra overhead, TCP is the most widely used protocol
across the Internet because of the higher reliability.
The UDP class
As you might expect, the UdpClient class features a smaller and simpler interface compared to
TcpClient. This reflects the relatively simpler nature of the protocol in comparison to TCP. While
both TCP and UDP classes use a socket underneath the covers, the
UdpClient client does not contain a
method to return a network stream for reading and writing. Instead, the member function
Send()accepts
an array of bytes as a parameter, while the
Receive() function returns an array of bytes. Also, since UDP
1086
Chapter 31
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1086
Simpo PDF Merge and Split Unregistered Version -
is a connectionless protocol, you can wait to specify the endpoint for the communication as a parameter to
the
Send() and Receive() methods, instead of earlier in a constructor or Connect() method. You can
also change the endpoint on each subsequent send or receive.
The following code fragment uses the
UdpClient class to send a message to an echo service. A server
with an echo service running accepts TCP or UDP connections on port 7. The echo service simply echoes
any data sent to the server back to the client. This service is useful for diagnostics and testing, although

many system administrators disable echo services for security reasons.
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
namespace Wrox.ProCSharp.InternetAccess.UdpExample
{
class Class1
{
[STAThread]
static void Main(string[] args)
{
UdpClient udpClient = new UdpClient();
string sendMsg = “Hello Echo Server”;
byte [] sendBytes = Encoding.ASCII.GetBytes(sendMsg);
udpClient.Send(sendBytes, sendBytes.Length, “SomeEchoServer.net”, 7);
IPEndPoint endPoint = new IPEndPoint(0,0);
byte [] rcvBytes = udpClient.Receive(ref endPoint);
string rcvMessage = Encoding.ASCII.GetString(rcvBytes,
0,
rcvBytes.Length);
// should print out “Hello Echo Server”
Console.WriteLine(rcvMessage);
}
}
}
We make heavy use of the Encoding.ASCII class to translate strings into arrays of byte and vice versa.
Also note that we pass an
IPEndPoint by reference into the Receive() method. Since UDP is not a con-
nection-oriented protocol, each call to

Receive() might pick up data from a different endpoint, so
Receive() populates this parameter with the IP address and port of the sending host.
Both
UdpClient and TcpClient offer a layer of abstraction over the lowest of the low-level classes: the
Socket.
1087
Accessing the Internet
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1087
Simpo PDF Merge and Split Unregistered Version -
The Socket class
The Socket class offers the highest level of control in network programming. One of the easiest ways to
demonstrate the class is to rewrite the
TcpReceive application with the Socket class. The updated
Listen() method is listed in this example:
public void Listen()
{
Socket listener = new Socket(AddressFamily.InterNetwork,
SocketType.Stream,
ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Any, 2112));
listener.Listen(0);
Socket socket = listener.Accept();
Stream netStream = new NetworkStream(socket);
StreamReader reader = new StreamReader(netStream);
string result = reader.ReadToEnd();
Invoke(new UpdateDisplayDelegate(UpdateDisplay),
new object[] {result} );
socket.Close();
listener.Close();
}

The Socket class requires a few more lines of code to complete the same task. For starters, the construc-
tor arguments need to specify an IP addressing scheme for a streaming socket with the TCP protocol.
These arguments are just one of the many combinations available to the
Socket class, and the
TcpClient class configured these settings for you. We then bind the listener socket to a port and begin
to listen for incoming connections. When an incoming request arrives we can use the
Accept() method
to create a new socket for handling the connection. We ultimately attach a
StreamReader instance to the
socket to read the incoming data, in much the same fashion as before.
The
Socket class also contains a number of methods for asynchronously accepting, connecting, sending,
and receiving. You can use these methods with callback delegates in the same way we used the asyn-
chronous page requests with the
WebRequest class. If you really need to dig into the internals of the
socket, the
GetSocketOption() and SetSocketOption() methods are available. These methods allow
you to see and configure options, including timeout, time-to-live, and other low-level options.
Summary
In this chapter we have reviewed the .NET Framework classes available in the System.Net namespace
for communication across networks. We have seen some of the .NET base classes that deal with opening
client connections on the network and Internet, and how to send requests to and receive responses from
servers; the most obvious use of this being to receive HTML pages. By taking advantage of COM inter-
operability in .NET, you can easily make use of Internet Explorer from your desktop applications.
1088
Chapter 31
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1088
Simpo PDF Merge and Split Unregistered Version -
As a rule of thumb, when programming with classes in the System.Net namespace, you should
always try to use the most generic class possible. For instance, using the

TCPClient class instead of the
Socket class isolates your code from many of the lower-level socket details. Moving one step higher, the
WebRequest class allows you to take advantage of the pluggable protocol architecture of.NET Framework.
Your code will be ready to take advantage of new application-level protocols as Microsoft and other third-
party developers introduce new functionality.
Finally, we discussed the use of the asynchronous capabilities in the networking classes, which give a
Windows Forms application the professional touch of a responsive user interface.
1089
Accessing the Internet
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1089
Simpo PDF Merge and Split Unregistered Version -
39 557599 Ch31.qxd 4/29/04 11:43 AM Page 1090
Simpo PDF Merge and Split Unregistered Version -
Windows Services
Windows Services are programs that can be started automatically at boot-time without the need of
anyone to log on to the machine After reading this chapter you can modify the server processes we
discuss in Chapters 16 and 31 to be started automatically.
In this chapter we explore:
❑ The architecture of Windows Services; the functionality of a service program, service con-
trol program, and service configuration program.
❑ How to implement a Windows Service with the classes found in the
System
.ServiceProcess
namespace.
❑ Installation programs to configure the Windows Service in the registry.
❑ Writing a program to control the Windows Service using the
ServiceController class.
❑ How to implement event handling.
❑ Adding event logging to other application types.
❑ Implementing performance monitoring for a Windows Service.

First, we’ll define what a Windows Service is. (You can download the code for this chapter from
the Wrox Web site at
www.wrox.com.)
What Is a Windows Service?
Windows Services are applications that can be automatically started when the operating system
boots. They can run without having an interactive user logged on to the system. You can configure
a Windows Service to be run from a specially configured user account; or from the system user
account—a user account that has even more privileges than that of the system administrator.
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1091
Simpo PDF Merge and Split Unregistered Version -
Unless otherwise noted, when we are referring to a service, we are referring to a Windows Service.
Here are few examples of services:
❑ Simple TCP/IP Services is a service program that hosts some small TCP/IP servers: echo, day-
time, quote, and others
❑ World Wide Publishing Service is the service of the Internet Information Server (IIS)
❑ Event Log is a service to log messages to the event log system
❑ Microsoft Search is a service that creates indexes of data on the disk
You can use the Services administration tool, shown in Figure 32-1, to see all of the services on a system.
On a Windows 2000 Server this program can be accessed be selecting Start➪Programs➪Administrative
Tools➪Services; on Windows 2000 Professional and Windows XP the program is accessible through
Settings➪Control Panel➪Administrative Tools➪Services.
Figure 32-1
Windows Services don’t run on Windows 95, 98, or ME; the NT kernel is a require-
ment. Windows Services do run on Windows NT 4, Windows 2000, Windows XP,
and Windows Server 2003.
1092
Chapter 32
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1092
Simpo PDF Merge and Split Unregistered Version -
Windows Services Architecture

Three program types are necessary to operate a Windows Service:
❑ A service program
❑ A service control program
❑ A service configuration program
The service program itself provides the actual functionality we are looking for. With a service control pro-
gram, it’s possible to send control requests to a service, such as start, stop, pause, and continue. With a
service configuration program, a service can be installed; it’s copied to the file system, written into the
registry, and configured as a service. While .NET components can be installed simply with an xcopy
because they don’t need the use of the registry, installation for services does require registry configura-
tion. A service configuration program can also be used to change the configuration of that service at a
later point.
In the following subsections, we discuss these three ingredients of a Windows Service.
Service Program
Before looking at the .NET implementation of a service, let’s look at it from an independent point of
view and discover what the Windows architecture of services looks like, and what the inner functional-
ity of a service is.
The service program implements the functionality of the service. It needs three parts:
❑ A main function
❑ A service-main function
❑ A handler
Before we can discuss these parts, we must introduce the Service Control Manager (SCM) . The SCM plays
an important role for services, sending requests to our service to start and to stop it.
Service Control Manager
The SCM is the part of the operating system that communicates with the service. Figure 32-2 illustrates
how this communication works with a UML sequence diagram.
1093
Windows Services
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1093
Simpo PDF Merge and Split Unregistered Version -
Figure 32-2

Main function, service-main, and handlers
The main function of the service might register more than one service-main function. The service must
register a service-main function for each service it provides. A service program can provide a lot of ser-
vices in a single program; for example, <windows>\system32\services.exe is the service program that
includes Alerter, Application Management, Computer Browser, and DHCP Client, among others.
The SCM now calls the service-main function for each service that should be started. The service-main
function contains the actual functionality of the service. One important task of the service-main function
is to register a handler with the SCM.
The handler function is the third part of service program. The handler must respond to events from the
SCM. Services can be stopped, suspended, and resumed, and the handler must react to these events.
Once a handler has been registered with the SCM, the service control program can post requests to the
SCM to stop, suspend, and resume the service. The service control program is independent of the SCM
and the service itself. We get many service control programs with the operating system; one is the MMC
Services snap-in that we’ve seen earlier. You can also write our own service control program; a good
example of this is the SQL Server Service Manager shown in Figure 32-3.
At boot time, each process for which a service is set to start automatically is started,
and so the main function of this process gets called. The service has the responsibility
to register the service-main function for each of its services. The main function is the
entry point of the service program, and in here, the entry points for the service-main
functions must be registered with the SCM.
SCM
start service process
register service-mains
service-main
register handler
Service
1094
Chapter 32
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1094
Simpo PDF Merge and Split Unregistered Version -

Figure 32-3
Service Control Program
As the name suggests, with a service control program we can control the service. For stopping, suspend-
ing, and resuming the service, you can send control codes to the service, and the handler should react to
these events. It’s also possible to ask the service about the actual status, and to implement a custom han-
dler that responds to custom control codes.
Service Configuration Program
You can’t use xcopy installation with services, since services must be configured in the registry. You can
set the startup type to automatic, manual, or disabled. You have to configure the user of the service pro-
gram, and dependencies of the service—for example, the services that must be started before this one
can start. All these configurations are made within a service configuration program. The installation pro-
gram can use the service configuration program to configure the service, but this program can also be
used at a later time to change service configuration parameters.
System.ServiceProcess Namespace
In.NET Framework, you can find service classes in the System.ServiceProcess namespace that
implement the three parts of a service:
❑ You have to inherit from the
ServiceBase class to implement a service. The ServiceBase class
is used to register the service and to answer start and stop requests.
❑ The
ServiceController class is used to implement a service control program. With this class
you can send requests to services.
❑ The
ServiceProcessInstaller and ServiceInstaller classes are, as their names suggest,
classes to install and configure service programs.
Now we are ready to create a new service.
1095
Windows Services
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1095
Simpo PDF Merge and Split Unregistered Version -

Creating a Windows Service
The service that we create will host a quote server. With every request that is made from a client the
quote server returns a random quote from a quote file. The first part of the solution uses three assem-
blies, one for the client and two for the server. Figure 32-4 shows an overview of the solution. The assem-
bly QuoteServer holds the actual functionality. The service reads the quote file in a memory cache, and
answers requests for quotes with the help of a socket server. The QuoteClient is a Windows Forms rich-
client application. This application creates a client socket to communicate with the QuoteServer. The
third assembly is the actual service. The QuoteService starts and stops the QuoteServer; the service con-
trols the server:
Figure 32-4
Before creating the service part of our program, we create a simple socket server in an extra C# class
library that will be used from our service process.
A Class Library Using Sockets
You could build any functionality in the service such as scanning for files to do a backup or a virus
check, or starting a .NET Remoting server, for example. However, all service programs share some simi-
larities. The program must be able to start (and to return to the caller), stop, and suspend. We will look
at such an implementation using a socket server.
Windows forms Application
and Socket client
Client Server
«assembly»
QuoteClient
communicates
«assembly»
QuoteServer
«assembly»
QuoteService
Socket Server
Windows Service
1096

Chapter 32
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1096
Simpo PDF Merge and Split Unregistered Version -
With Windows 2000 or Windows XP, the Simple TCP/IP Services can be installed as part of the Windows
components. Part of the Simple TCP/IP Services is a “quote of the day,” or qotd, TCP/IP server. This
simple service listens to port 17 and answers every request with a random message from the file
<windir>\system32\drivers\etc\quotes. With the sample service a similar server will be built. The sam-
ple server returns a Unicode string, in contrast to the good old qotd server that returns an ASCII string.
First create a Class Library called
QuoteServer and implement the code for the server. Let’s step
through the source code of our
QuoteServer class in the file QuoteServer.cs:
using System;
using System.IO;
using System.Threading;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Collections.Specialized;
namespace Wrox.ProCSharp.WinServices
{
public class QuoteServer
{
private TcpListener listener;
private int port;
private string filename;
private StringCollection quotes;
private Random random;
private Thread listenerThread;
The constructor QuoteServer() is overloaded, so that a file name and a port can be passed to the call.

The constructor where just the file name is passed uses the default port 7890 for the server. The default
constructor defines the default file name for the quotes as quotes.txt:
public QuoteServer() : this (“quotes.txt”)
{
}
public QuoteServer(string filename) : this(filename, 7890)
{
}
public QuoteServer(string filename, int port)
{
this.filename = filename;
this.port = port;
}
ReadQuotes() is a helper method that reads all the quotes from a file that was specified in the construc-
tor. All the quotes are added to the
StringCollection quotes. In addition, we are creating an instance
of the
Random class that will be used to return random quotes:
protected void ReadQuotes()
{
quotes = new StringCollection();
Stream stream = File.OpenRead(filename);
1097
Windows Services
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1097
Simpo PDF Merge and Split Unregistered Version -
StreamReader streamReader = new StreamReader(stream);
string quote;
while ((quote = streamReader.ReadLine()) != null)
{

quotes.Add(quote);
}
streamReader.Close();
stream.Close();
random = new Random();
}
Another helper method is GetRandomQuoteOfTheDay(). This method returns a random quote from the
StringCollection quotes:
protected string GetRandomQuoteOfTheDay()
{
int index = random.Next(0, quotes.Count);
return quotes[index];
}
In the Start() method, the complete file containing the quotes is read in the StringCollection
quotes by using the helper method ReadQuotes(). After this, a new thread is started, which immedi-
ately calls the
Listener() method—similar to the TcpReceive example in Chapter 31.
Here a thread is used because the
Start() method can not block and wait for a client; it must return
immediately to the caller (SCM). The SCM would assume the start failed if the method didn’t return to
the caller in a timely fashion (30 seconds):
public void Start()
{
ReadQuotes();
listenerThread = new Thread(
new ThreadStart(this.Listener));
listenerThread.Start();
}
The thread function Listener() creates a TcpListener instance. The AcceptSocket() method waits
for a client to connect. As soon as a client connects,

AcceptSocket() returns with a socket associated
with the client. Next
GetRandomQuoteOfTheDay() is called to send the returned random quote to the
client using
socket.Send():
protected void Listener()
{
try
{
IPAddress ipAddress = Dns.Resolve(“localhost”).AddressList[0];
listener = new TcpListener(ipAddress, port);
listener.Start();
while (true)
{
1098
Chapter 32
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1098
Simpo PDF Merge and Split Unregistered Version -
Socket socket = listener.AcceptSocket();
string message = GetRandomQuoteOfTheDay();
UnicodeEncoding encoder = new UnicodeEncoding();
byte[] buffer = encoder.GetBytes(message);
socket.Send(buffer, buffer.Length, 0);
socket.Close();
}
}
catch (SocketException e)
{
Console.WriteLine(e.Message);
}

}
In addition to the Start() method, the following methods are needed to control the service: Stop(),
Suspend(), and Resume():
public void Stop()
{
listener.Stop();
}
public void Suspend()
{
listenerThread.Suspend();
}
public void Resume()
{
listenerThread.Resume();
}
Another method that will be publicly available is RefreshQuotes(). If the file containing the quotes
changes, then the fire is re-read with this method:
public void RefreshQuotes()
{
ReadQuotes();
}
}
}
Before building a service around the server, it is useful to build a test program that just creates an
instance of the
QuoteServer and calls Start(). This way, you can test the functionality without the
need to handle service-specific issues. This test server must be started manually, and you can easily walk
through the code with a debugger.
The test program is a C# console application, TestQuoteServer. You have to reference the assembly of the
QuoteServer class. The file containing the quotes must be copied to the directory c:\ProCSharp\Services

(or you have to change the argument in the constructor to specify where you have copied the file). After
calling the constructor, the
Start() method of the QuoteServer instance is called. Start() returns
immediately after having created a thread, so the console application keeps running until
Return is
pressed:
1099
Windows Services
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1099
Simpo PDF Merge and Split Unregistered Version -
static void Main(string[] args)
{
QuoteServer qs = new QuoteServer(@”c:\ProCSharp\Services\quotes.txt”,
4567);
qs.Start();
Console.WriteLine(“Hit return to exit”);
Console.ReadLine();
qs.Stop();
}
Note that QuoteServer will be running on port 4567 on localhost using this program—you will have to
use these settings in the client later.
TcpClient Example
The client is a simple Windows application where you can enter the host name and the port number of
the server. This application uses the
TcpClient class to connect to the running server, and receives the
returned message, displaying it in a multiline text box. There’s also a status bar at the bottom of the form
(see Figure 32-5).
Figure 32-5
You have to add the following
using directives to our code:

using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Net;
using System.Net.Sockets;
using System.Text;
1100
Chapter 32
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1100
Simpo PDF Merge and Split Unregistered Version -
The remainder of the code is automatically generated by the IDE, so we won’t go into detail here. The
major functionality of the client lies in the handler for the click event of the Get Quote button:
protected void buttonQuote_Click (object sender, System.EventArgs e)
{
statusBar.Text = “”;
string server = textHostname.Text;
try
{
int port = Convert.ToInt32(textPortNumber.Text);
}
catch (FormatException ex)
{
statusBar.Text = ex.Message;
return;
}
TcpClient client = new TcpClient();
try

{
client.Connect(textHostname.Text,
Convert.ToInt32(textPortNumber.Text));
NetworkStream stream = client.GetStream();
byte[] buffer = new Byte[1024];
int received = stream.Read(buffer, 0, 1024);
if (received <= 0)
{
statusBar.Text = “Read failed”;
return;
}
textQuote.Text = Encoding.Unicode.GetString(buffer);
}
catch (SocketException ex)
{
statusBar.Text = ex.Message;
}
finally
{
client.Close();
}
}
After starting the test server and this Windows application client, you can test the functionality. Figure
32-6 shows a successful run of this application.
Next we implement the service functionality in the server. The program is already running, so what
more do we need? Well, the server program should be automatically started at boot-time without any-
one logged on to the system, and we want to control it by using service control programs.
1101
Windows Services
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1101

Simpo PDF Merge and Split Unregistered Version -
Figure 32-6
Windows Service Project
Using the new project wizard for C# Windows Services, you can now start to create a Windows Service.
For the new service use the name QuoteService. Pay careful not to select a Web Service project (see
Figure 32-7).
Figure 32-7
After you press the OK button to create the Windows Service application, you will see the Designer sur-
face (just like with Windows Forms applications). However you can’t insert any Windows Forms com-
ponents, because the application can not directly display anything on the screen. The Designer surface
is used later in this chapter to add other components, such as performance counters and event logging.
Selecting the properties of this service opens up the Properties editor window (see Figure 32-8).
1102
Chapter 32
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1102
Simpo PDF Merge and Split Unregistered Version -
Figure 32-8
With the service properties, you can configure the following values:

AutoLog specifies that events are automatically written to the event log for starting and stop-
ping the service.

CanPauseAndContinue, CanShutdown, and CanStop specify pause, continue, shutdown, and
stop requests.

ServiceName is the name of the service that’s written to the Registry and is used to control the
service.

CanHandlePowerEvent is a very useful option for services running on a laptop. If this option is
enabled, the service can react to low power events, and change the behavior of the service

accordingly.
Changing these properties with the Properties editor sets the values of our
ServiceBase-derived class
in the
InitalizeComponent() method. You already know this method from Windows Forms applica-
tions. With services it’s used in a similar way.
A wizard generates the code, but change the file name to QuoteService.cs, the name of the namespace to
Wrox.ProCSharp.WinServices, and the class name to QuoteService. We discuss the code of the ser-
vice in detail shortly
The default service name is WinService1, regardless of what the project is called.
You can install only one WinService1 service. If you get installation errors during
your testing process, you might already have installed one WinService1 service.
Therefore, make sure that you change the name of the service with the Properties
editor to a more suitable name at the beginning of the service development.
1103
Windows Services
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1103
Simpo PDF Merge and Split Unregistered Version -
The ServiceBase Class
The ServiceBase class is the base class for all .NET services. The class QuoteService derives from
ServiceBase; this class communicates with the SCM using an undocumented helper class, System
.ServiceProcess.NativeMethods
, which is just a wrapper class to the Win32 API calls. The class is
private, so it can not be used in your code.
The sequence diagram in Figure 32-9 shows the interaction of the SCM, the class
QuoteService, and the
classes from the
System.ServiceProcess namespace. In the sequence diagram you can see the life-
lines of objects vertically and the communication going on in the horizontal direction. The communica-
tion is time-ordered from top to bottom.

Figure 32-9
The SCM starts the process of a service that should be started. At startup, the
Main() method is called.
In the
Main() method of the sample service the Run() method of the base class ServiceBase is called.
Run() registers the method ServiceMainCallback() using NativeMethods.StartServiceCtrl
Dispatcher()
in the SCM and writes an entry to the event log.
Next, the SCM calls the registered method
ServiceMainCallback() in the service program. Service
MainCallback()
itself registers the handler in the SCM using NativeMethods.RegisterService
CtrlHandler[Ex]()
and sets the status of the service in the SCM. Then the OnStart() method is
called. In
OnStart() you have to implement the startup code. If OnStart() is successful, the string
“Service started successfully” is written to the event log.
ServiceCommandCallback()
SCM
on a stop request
for the service
Main()
Run()
ServiceMainCallback()
RegisterServiceCtrlHandler[Ex]()
StartServiceCtrlDispatcher()
OnStart()
OnStop()
ServiceCommandCallback()
quoteService : ServiceBase : NativeMethods

1104
Chapter 32
40 557599 Ch32.qxd 4/29/04 11:45 AM Page 1104
Simpo PDF Merge and Split Unregistered Version -

×