Tải bản đầy đủ - 0 (trang)
10-11. Create a Multithreaded TCP Server That Supports Asynchronous Communications

10-11. Create a Multithreaded TCP Server That Supports Asynchronous Communications

Tải bản đầy đủ - 0trang

CHAPTER 10 ■ NETWORKING



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";

}

}

Here is the server code:

using

using

using

using

using



System;

System.IO;

System.Net;

System.Threading;

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()

{



516



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



// 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

{

// 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



517



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



// 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);

// 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;



518



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



// The amount of data that will be written in one block (2KB).

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()))

{

writer.Write(Recipe10_11Shared.AcknowledgeOK);

Console.WriteLine(ID + ": Connection established.");

string message = "";



519



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



while (message != Recipe10_11Shared.Disconnect)

{

try

{

// Read the message from the client.

message = reader.ReadString();

}

catch

{

// For the purpose of the example, any

// exception should be taken as a

// client disconnect.

message = Recipe10_11Shared.Disconnect;

}

if (message == Recipe10_11Shared.RequestData)

{

Console.WriteLine(ID + ": Requested data. ",

"Sending...");

// The filename could be supplied by the

// client, but in this example a test file

// is hard-coded.

fileStream = new FileStream("test.bin",

FileMode.Open, FileAccess.Read);

// Send the file size--this is how the client

// knows how much to read.

writer.Write(fileStream.Length.ToString());

// Start an asynchronous send operation.

stopDataTransfer = false;

StreamData(null);

}

else if (message == Recipe10_11Shared.Disconnect)

{

Console.WriteLine(ID +

": Client disconnecting...");

stopDataTransfer = true;

}

else

{

Console.WriteLine(ID + ": Unknown command.");

}

}

}

}



520



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



else

{

Console.WriteLine(ID +

": Could not establish connection.");

}

}

}

Console.WriteLine(ID + ": Client connection closed.");

}

private void StreamData(IAsyncResult asyncResult)

{

// Always complete outstanding asynchronous operations to avoid leaks.

if (asyncResult != null)

{

try

{

client.GetStream().EndWrite(asyncResult);

}

catch

{

// For the purpose of the example, any exception obtaining

// or writing to the network should just terminate the

// download.

fileStream.Close();

return;

}

}

if (!stopDataTransfer && !Recipe10_11Server.Terminate)

{

// Read the next block from the file.

int bytesRead = fileStream.Read(buffer, 0, buffer.Length);

// If no bytes are read, the stream is at the end of the file.

if (bytesRead > 0)

{

Console.WriteLine(ID + ": Streaming next block.");

// Write the next block to the network stream.

client.GetStream().BeginWrite(buffer, 0, buffer.Length,

StreamData, null);

}

else

{

// End the operation.

Console.WriteLine(ID + ": File streaming complete.");

fileStream.Close();

}

}



521



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



else

{

// Client disconnected.

Console.WriteLine(ID + ": Client disconnected.");

fileStream.Close();

}

}

}

}

And here is the client code:

using

using

using

using



System;

System.Net;

System.Net.Sockets;

System.IO;



namespace Apress.VisualCSharpRecipes.Chapter10

{

public class Recipe10_11Client

{

private static void Main()

{

using (TcpClient client = new TcpClient())

{

Console.WriteLine("Attempting to connect to the server ",

"on port 8000.");

// Connect to the server.

client.Connect(IPAddress.Parse("127.0.0.1"), 8000);

// Retrieve the network stream from the TcpClient.

using (NetworkStream networkStream = client.GetStream())

{

// Create a BinaryWriter for writing to the stream.

using (BinaryWriter writer = new BinaryWriter(networkStream))

{

// Start a dialog.

writer.Write(Recipe10_11Shared.RequestConnect);

// Create a BinaryReader for reading from the stream.

using (BinaryReader reader =

new BinaryReader(networkStream))

{

if (reader.ReadString() ==

Recipe10_11Shared.AcknowledgeOK)

{

Console.WriteLine("Connection established." +

"Press Enter to download data.");

Console.ReadLine();



522



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



// Send message requesting data to server.

writer.Write(Recipe10_11Shared.RequestData);

// The server should respond with the size of

// the data it will send. Assume it does.

int fileSize = int.Parse(reader.ReadString());

// Only get part of the data, then carry out a

// premature disconnect.

for (int i = 0; i < fileSize / 3; i++)

{

Console.Write(networkStream.ReadByte());

}

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Press Enter to disconnect.");

Console.ReadLine();

Console.WriteLine("Disconnecting...");

writer.Write(Recipe10_11Shared.Disconnect);

}

else

{

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

}

}

}

}

}

// Wait to continue.

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Connection closed. Press Enter");

Console.ReadLine();

}

}

}



10-12. Communicate Using UDP

Problem

You need to send data between two computers on a network using a UDP stream.



Solution

Use the System.Net.Sockets.UdpClient class and use two threads: one to send data and the other to

receive it.



523



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



How It Works

UDP is a connectionless protocol that doesn’t include any flow control or error checking. Unlike TCP,

UDP shouldn’t be used where reliable communication is required. However, because of its lower

overhead, UDP is often used for “chatty” applications where it is acceptable to lose some messages. For

example, imagine you want to create a network in which individual clients send information about the

current temperature at their locations to a server every few seconds. You might use UDP in this case

because the communication frequency is high and the damage caused by losing a packet is trivial

(because the server can just continue to use the last received temperature reading).



The Code

The application shown in the following code uses two threads: one to receive messages and one to send

them. The application stops when the user presses the Enter key without any text to send. Notice that

UDP applications cannot use the NetworkStream abstraction that TCP applications can. Instead, they

must convert all data to a stream of bytes using an encoding class, as described in recipe 2-2.

using

using

using

using

using



System;

System.Text;

System.Net;

System.Net.Sockets;

System.Threading;



namespace Apress.VisualCSharpRecipes.Chapter10

{

class Recipe10_12

{

private static int localPort;

private static void Main()

{

// Define endpoint where messages are sent.

Console.Write("Connect to IP: ");

string IP = Console.ReadLine();

Console.Write("Connect to port: ");

int port = Int32.Parse(Console.ReadLine());

IPEndPoint remoteEndPoint =

new IPEndPoint(IPAddress.Parse(IP), port);

// Define local endpoint (where messages are received).

Console.Write("Local port for listening: ");

localPort = Int32.Parse(Console.ReadLine());

// Create a new thread for receiving incoming messages.

Thread receiveThread = new Thread(ReceiveData);

receiveThread.IsBackground = true;

receiveThread.Start();



524



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



UdpClient client = new UdpClient();

Console.WriteLine("Type message and press Enter to send:");

try

{

string text;

do

{

text = Console.ReadLine();

// Send the text to the remote client.

if (text.Length != 0)

{

// Encode the data to binary using UTF8 encoding.

byte[] data = Encoding.UTF8.GetBytes(text);

// Send the text to the remote client.

client.Send(data, data.Length, remoteEndPoint);

}

} while (text.Length != 0);

}

catch (Exception err)

{

Console.WriteLine(err.ToString());

}

finally

{

client.Close();

}

// Wait to continue.

Console.WriteLine(Environment.NewLine);

Console.WriteLine("Main method complete. Press Enter");

Console.ReadLine();

}

private static void ReceiveData()

{

UdpClient client = new UdpClient(localPort);

while (true)

{

try

{

// Receive bytes.

IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);

byte[] data = client.Receive(ref anyIP);



525



www.it-ebooks.info



CHAPTER 10 ■ NETWORKING



// Convert bytes to text using UTF8 encoding.

string text = Encoding.UTF8.GetString(data);

// Display the retrieved text.

Console.WriteLine(">> " + text);

}

catch (Exception err)

{

Console.WriteLine(err.ToString());

}

}

}

}

}

To test this application, load two instances at the same time. On computer A, specify the IP address

for computer B. On computer B, specify the address for computer A. You can then send text messages

back and forth at will. You can test this application with clients on the local computer using the loopback

alias 127.0.0.1, provided you use different listening ports. For example, imagine a situation with two UDP

clients, client A and client B. Here’s a sample transcript for client A:

Connect to IP: 127.0.0.1

Connect to port: 8001

Local port for listening: 8080

Hi there!

And here’s the corresponding transcript for client B (with the received message):

Connect to IP: 127.0.0.1

Connect to port: 8080

Local port for listening: 8001

>> Hi there!



10-13. Create a SOAP-Based Web Service

Problem

You need to expose functionality as a SOAP-based web service so that it can be accessed across the

Internet.



Solution

Declare an interface that contains the methods you want your web service to expose. Identify this

interface as a Windows Communication Foundation (WCF) service contract by applying the

ServiceContractAttribute attribute to the interface and the OperationContractAttribute attribute to



526



www.it-ebooks.info



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

10-11. Create a Multithreaded TCP Server That Supports Asynchronous Communications

Tải bản đầy đủ ngay(0 tr)

×