Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Visual CSharp 2005 Recipes (2006) [eng]

.pdf
Скачиваний:
49
Добавлен:
16.08.2013
Размер:
4.04 Mб
Скачать

358 C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

Tip The Ping class derives from System.ComponentModel.Component, so you can add it to the Visual Studio 2005 Form Designer Toolbox in order to allow you to easily set the properties or define the event handlers in a Windows Forms–based application.

The Code

The following example pings the computers whose domain names or IP addresses are specified as command-line arguments:

using System;

using System.Net.NetworkInformation;

namespace Apress.VisualCSharpRecipes.Chapter10

{

class Recipe10_09

{

public static void Main(string[] args)

{

// Create an instance of the Ping class. using (Ping ping = new Ping())

{

Console.WriteLine("Pinging:");

foreach (string comp in args)

{

try

{

Console.Write(" {0}...", comp);

// Ping the specified computer with a time-out of 100ms. PingReply reply = ping.Send(comp, 100);

if (reply.Status == IPStatus.Success)

{

Console.WriteLine("Success - IP Address:{0} Time:{1}ms", reply.Address, reply.RoundtripTime);

}

else

{

Console.WriteLine(reply.Status);

}

}

catch (Exception ex)

{

Console.WriteLine("Error ({0})", ex.InnerException.Message);

}

}

}

C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

359

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter"); Console.ReadLine();

}

}

}

Usage

Running the example with the following command line:

recipe10-09 www.apress.com www.google.com localhost somejunk

will produce the following output:

Pinging: www.apress.com...TimedOut

www.google.com...Success - IP Address:216.239.59.104 Time:42ms localhost...Success - IP Address:127.0.0.1 Time:0ms somejunk...Error (No such host is known)

10-10. Communicate Using TCP

Problem

You need to send data between two computers on a network using a TCP/IP connection.

Solution

One computer (the server) must begin listening using the System.Net.Sockets.TcpListener class. Another computer (the client) connects to it using the System.Net.Sockets.TcpClient class. Once a connection is established, both computers can communicate using the System.Net.Sockets. NetworkStream class.

How It Works

TCP is a reliable, connection-oriented protocol that allows two computers to communicate over

a network. It provides built-in flow control, sequencing, and error handling, which makes it reliable and easy to program.

To create a TCP connection, one computer must act as the server and start listening on a specific endpoint. (An endpoint is a combination of an IP address and a port number.) The other computer must act as a client and send a connection request to the endpoint on which the first computer is listening. Once the connection is established, the two computers can take turns exchanging messages.

.NET makes this process easy through its stream abstraction. Both computers simply write to and read from a System.Net.Sockets.NetworkStream to transmit data.

360 C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

Note Even though a TCP connection always requires a server and a client, an individual application could be both. For example, in a peer-to-peer application, one thread is dedicated to listening for incoming requests (acting as a server), and another thread is dedicated to initiating outgoing connections (acting as a client). In the examples provided with this chapter, the client and server are provided as separate applications and are placed in separate subdirectories.

Once a TCP connection is established, the two computers can send any type of data by writing it to the NetworkStream. However, it’s a good idea to begin designing a networked application by defining the application-level protocol that clients and servers will use to communicate. This protocol includes constants that represent the allowable commands, ensuring that your application code doesn’t include hard-coded communication strings.

The Code

In this example, the defined protocol is basic. You would add more constants depending on the type of application. For example, in a file transfer application, you might include a client message for requesting a file. The server might then respond with an acknowledgment and return file details such as the file size. These constants should be compiled into a separate class library assembly, which must be referenced by both the client and server. Here is the code for the shared protocol:

namespace Apress.VisualCSharpRecipes.Chapter10

{

public class Recipe10_10Shared

{

public const string AcknowledgeOK = "OK";

public const string AcknowledgeCancel = "Cancel"; public const string Disconnect = "Bye";

public const string RequestConnect = "Hello";

}

}

The following code is a template for a basic TCP server. It listens on a fixed port, accepts the first incoming connection, and then waits for the client to request a disconnect. At this point, the server could call the TcpListener.AcceptTcpClient method again to wait for the next client, but instead it simply shuts down.

using System; using System.IO; using System.Net;

using System.Net.Sockets;

namespace Apress.VisualCSharpRecipes.Chapter10

{

public class Recipe10_10Server

{

public static void Main()

{

// Create a new listener on port 8000. TcpListener listener =

new TcpListener(IPAddress.Parse("127.0.0.1"), 8000);

Console.WriteLine("About to initialize port."); listener.Start();

Console.WriteLine("Listening for a connection...");

C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

361

try

{

//Wait for a connection request, and return a TcpClient

//initialized for communication.

using (TcpClient client = listener.AcceptTcpClient())

{

Console.WriteLine("Connection accepted.");

//Retrieve the network stream. NetworkStream stream = client.GetStream();

//Create a BinaryWriter for writing to the stream. using (BinaryWriter w = new BinaryWriter(stream))

{

//Create a BinaryReader for reading from the stream. using (BinaryReader r = new BinaryReader(stream))

{

if (r.ReadString() == Recipe10_10Shared.RequestConnect)

{

w.Write(Recipe10_10Shared.AcknowledgeOK); Console.WriteLine("Connection completed.");

while (r.ReadString() != Recipe10_10Shared.Disconnect) { }

Console.WriteLine(Environment.NewLine); Console.WriteLine("Disconnect request received.");

}

else

{

Console.WriteLine("Can't complete connection.");

}

}

}

}

Console.WriteLine("Connection closed.");

}

catch (Exception ex)

{

Console.WriteLine(ex.ToString());

}

finally

{

// Close the underlying socket (stop listening for new requests). listener.Stop();

Console.WriteLine("Listener stopped.");

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter"); Console.ReadLine();

}

}

}

362 C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

The following code is a template for a basic TCP client. It contacts the server at the specified IP address and port. In this example, the loopback address (127.0.0.1) is used, which always points to the local computer. Keep in mind that a TCP connection requires two ports: one at the server end and one at the client end. However, only the server port to connect to needs to be specified. The outgoing client port can be chosen dynamically at runtime from the available ports, which is what the TcpClient class will do by default.

using System; using System.IO; using System.Net;

using System.Net.Sockets;

namespace Apress.VisualCSharpRecipes.Chapter10

{

public class Recipe10_10Client

{

public static void Main()

{

TcpClient client = new TcpClient();

try

{

Console.WriteLine("Attempting to connect to the server ", "on port 8000.");

client.Connect(IPAddress.Parse("127.0.0.1"), 8000); Console.WriteLine("Connection established.");

//Retrieve the network stream. NetworkStream stream = client.GetStream();

//Create a BinaryWriter for writing to the stream. using (BinaryWriter w = new BinaryWriter(stream))

{

//Create a BinaryReader for reading from the stream. using (BinaryReader r = new BinaryReader(stream))

{

//Start a dialogue.

w.Write(Recipe10_10Shared.RequestConnect);

if (r.ReadString() == Recipe10_10Shared.AcknowledgeOK)

{

Console.WriteLine("Connected."); Console.WriteLine("Press Enter to disconnect."); Console.ReadLine(); Console.WriteLine("Disconnecting..."); w.Write(Recipe10_10Shared.Disconnect);

}

else

{

Console.WriteLine("Connection not completed.");

}

}

}

}

catch (Exception err)

{

Console.WriteLine(err.ToString());

C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

363

}

finally

{

// Close the connection socket. client.Close(); Console.WriteLine("Port closed.");

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Main method complete. Press Enter"); Console.ReadLine();

}

}

}

Usage

Here’s a sample connection transcript on the server side:

About to initialize port.

Listening for a connection...

Connection accepted.

Connection completed.

Disconnect request received.

Connection closed.

Listener stopped.

And here’s a sample connection transcript on the client side:

Attempting to connect to the server on port 8000.

Connection established.

Connected.

Press Enter to disconnect.

Disconnecting...

Port closed.

10-11. Create a Multithreaded TCP Server That

Supports Asynchronous Communications

Problem

You need to handle multiple network requests concurrently or perform a network data transfer as a background task while your program continues with other processing.

Solution

Use the method AcceptTcpClient of the System.Net.Sockets.TcpListener class to accept connections. Every time a new client connects, start a new thread to handle the connection. Alternatively, use the TcpListener.BeginAcceptTcpClient to accept a new client connection on a thread-pool thread using the asynchronous execution pattern (discussed in recipe 4-2).

364 C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

To start a background task to handle the asynchronous sending of data, you can use the

BeginWrite method of the System.Net.Sockets.NetworkStream class and supply a callback method— each time the callback is triggered, send more data.

How It Works

A single TCP endpoint (IP address and port) can serve multiple connections. In fact, the operating system takes care of most of the work for you. All you need to do is create a worker object on the server that will handle each connection on a separate thread. The TcpListener.AcceptTcpClient method returns a TcpClient when a connection is established. This should be passed off to a threaded worker object so that the worker can communicate with the remote client.

Alternatively, call the TcpListener.BeginAcceptTcpClient method to start an asynchronous operation using a thread-pool thread that waits in the background for a client to connect. BeginAcceptTcpClient follows the asynchronous execution pattern, allowing you to wait for the operation to complete or specify a callback that the .NET runtime will call when a client connects. (See recipe 4-2 for details on the options available.) Whichever mechanism you use, once

BeginAcceptTcpClient has completed, call EndAcceptTcpClient to obtain the newly created TcpClient object.

To exchange network data asynchronously, you can use the NetworkStream class, which includes basic support for asynchronous communication through the BeginRead and BeginWrite methods. Using these methods, you can send or receive a block of data on one of the threads provided by the thread pool, without blocking your code. When sending data asynchronously, you must send raw binary data (an array of bytes). It’s up to you to choose the amount you want to send or receive at a time.

One advantage of this approach when sending files is that the entire content of the file does not have to be held in memory at once. Instead, it is retrieved just before a new block is sent. Another advantage is that the server can abort the transfer operation easily at any time.

The Code

The following example demonstrates various techniques for handling network connections and communications asynchronously. The server (Recipe10-11Server) starts a thread-pool thread listening for new connections using the TcpListener.BeginAcceptTcpClient method and specifying a callback method to handle the new connections. Every time a client connects to the server, the callback method obtains the new TcpClient object and passes it to a new threaded ClientHandler object to handle client communications.

The ClientHandler object waits for the client to request data and then sends a large amount of data (read from a file) to the client. This data is sent asynchronously, which means ClientHandler could continue to perform other tasks. In this example, it simply monitors the network stream for messages sent from the client. The client reads only a third of the data before sending a disconnect message to the server, which terminates the remainder of the file transfer and drops the client connection.

Here is the code for the shared protocol:

namespace Apress.VisualCSharpRecipes.Chapter10

{

public class Recipe10_11Shared

{

public const string AcknowledgeOK = "OK";

public const string AcknowledgeCancel = "Cancel"; public const string Disconnect = "Bye";

public const string RequestConnect = "Hello"; public const string RequestData = "Data";

C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

365

}

}

Here is the server code:

using System; using System.IO; using System.Net;

using System.Threading; using System.Net.Sockets;

namespace Apress.VisualCSharpRecipes.Chapter10

{

public class Recipe10_11Server

{

//A flag used to indicate whether the server is shutting down. private static bool terminate;

public static bool Terminate { get { return terminate; } }

//A variable to track the identity of each client connection. private static int ClientNumber = 0;

//A single TcpListener will accept all incoming client connections. private static TcpListener listener;

public static void Main()

{

//Create a 100Kb test file for use in the example. This file will be

//sent to clients that connect.

using (FileStream fs = new FileStream("test.bin", FileMode.Create))

{

fs.SetLength(100000);

}

try

{

//Create a TcpListener that will accept incoming client

//connections on port 8000 of the local machine.

listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8000);

Console.WriteLine("Starting TcpListener...");

//Start the TcpListener accepting connections. terminate = false;

listener.Start();

//Begin asynchronously listening for client connections. When a

//new connection is established, call the ConnectionHandler

//method to process the new connection. listener.BeginAcceptTcpClient(ConnectionHandler, null);

//Keep the server active until the user presses Enter. Console.WriteLine("Server awaiting connections. " +

"Press Enter to stop server."); Console.ReadLine();

}

finally

366 C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

{

//Shut down the TcpListener. This will cause any outstanding

//asynchronous requests to stop and throw an exception in

//the ConnectionHandler when EndAcceptTcpClient is called.

//More robust termination synchronization may be desired here,

//but for the purpose of this example ClientHandler threads are

//all background threads and will terminate automatically when

//the main thread terminates. This is suitable for our needs. Console.WriteLine("Server stopping...");

terminate = true;

if (listener != null) listener.Stop();

}

// Wait to continue. Console.WriteLine(Environment.NewLine); Console.WriteLine("Server stopped. Press Enter"); Console.ReadLine();

}

//A method to handle the callback when a connection is established

//from a client. This is a simple way to implement a dispatcher

//but lacks the control and scalability required when implementing

//full-blown asynchronous server applications.

private static void ConnectionHandler(IAsyncResult result)

{

TcpClient client = null;

// Always end the asynchronous operation to avoid leaks. try

{

// Get the TcpClient that represents the new client connection. client = listener.EndAcceptTcpClient(result);

}

catch (ObjectDisposedException)

{

//Server is shutting down and the outstanding asynchronous

//request calls the completion method with this exception.

//The exception is thrown when EndAcceptTcpClient is called.

//Do nothing and return.

return;

}

Console.WriteLine("Dispatcher: New connection accepted.");

//Begin asynchronously listening for the next client

//connection. listener.BeginAcceptTcpClient(ConnectionHandler, null);

if (client != null)

{

// Determine the identifier for the new client connection. Interlocked.Increment(ref ClientNumber);

string clientName = "Client " + ClientNumber.ToString();

Console.WriteLine("Dispatcher: Creating client handler ({0})." , clientName);

C H A P T E R 1 0 N E T W O R K I N G A N D R E M OT I N G

367

// Create a new ClientHandler to handle this connection. new ClientHandler(client, clientName);

}

}

}

// A class that encapsulates the logic to handle a client connection. public class ClientHandler

{

//The TcpClient that represents the connection to the client. private TcpClient client;

//An ID that uniquely identifies this ClientHandler.

private string ID;

//The amount of data that will be written in one block (2 KB). private int bufferSize = 2048;

//The buffer that holds the data to write.

private byte[] buffer;

//Used to read data from the local file. private FileStream fileStream;

//A signal to stop sending data to the client. private bool stopDataTransfer;

internal ClientHandler(TcpClient client, string ID)

{

this.buffer = new byte[bufferSize]; this.client = client;

this.ID = ID;

//Create a new background thread to handle the client connection

//so that we do not consume a thread-pool thread for a long time

//and also so that it will be terminated when the main thread ends. Thread thread = new Thread(ProcessConnection);

thread.IsBackground = true; thread.Start();

}

private void ProcessConnection()

{

using (client)

{

//Create a BinaryReader to receive messages from the client. At

//the end of the using block, it will close both the BinaryReader

//and the underlying NetworkStream.

using (BinaryReader reader = new BinaryReader(client.GetStream()))

{

if (reader.ReadString() == Recipe10_11Shared.RequestConnect)

{

//Create a BinaryWriter to send messages to the client.

//At the end of the using block, it will close both the

//BinaryWriter and the underlying NetworkStream.

using (BinaryWriter writer =

new BinaryWriter(client.GetStream()))

Соседние файлы в предмете Программирование на C++