Sử dụng các phương thức này, bạn có thể gửi hay nhận một khối dữ liệu trên một trong các tiểu trình do thread-pool của bộ thực thi .NET cung cấp, mà không block mã lệnh của bạn.. Dữ liệ
Trang 1using SharedComponent;
public class ClientHandler {
private TcpClient client;
private string ID;
public ClientHandler(TcpClient client, string ID) {
this.client = client;
this.ID = ID;
}
public void Start() {
// Thu lấy network stream
NetworkStream stream = client.GetStream();
// Tạo BinaryWriter để ghi ra stream
BinaryWriter w = new BinaryWriter(stream);
// Tạo BinaryReader để đọc từ stream
BinaryReader r = new BinaryReader(stream);
if (r.ReadString() == ClientMessages.RequestConnect) {
w.Write(ServerMessages.AcknowledgeOK);
Console.WriteLine(ID + ": Connection completed.");
while (r.ReadString() != ClientMessages.Disconnect) {}
Console.WriteLine(ID + ": Disconnect request received.");
w.Write(ServerMessages.Disconnect);
}else {
Console.WriteLine(ID + ": Could not complete connection.");
}
Trang 2// Đóng socket.
client.Close();
Console.WriteLine(ID + ": Client connection closed.");
Console.ReadLine();
}
}
Kế tiếp, thay đổi mã lệnh của server sao cho nó lặp liên tục, tạo ra các thể hiện ClientHandler mới khi cần và chạy chúng trong các tiểu trình mới Dưới đây là mã lệnh đã được sửa đổi: public class TcpServerTest {
private static void Main() {
TcpListener listener =
new TcpListener(IPAddress.Parse("127.0.0.1"), 8000);
Console.WriteLine("Server: About to initialize port.");
listener.Start();
Console.WriteLine("Server: Listening for a connection ");
int clientNum = 0;
while (true) {
try {
// Đợi yêu cầu kết nối, và trả về một TcpClient
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine("Server: Connection accepted.");
// Tạo một đối tượng mới để xử lý kết nối này
clientNum++;
ClientHandler handler =
new ClientHandler(client, "Client " +
clientNum.ToString());
// Khởi động đối tượng này làm việc trong
Trang 3Thread handlerThread =
new Thread(new ThreadStart(handler.Start));
handlerThread.IsBackground = true;
handlerThread.Start();
// (Bạn cũng có thể thêm Handler và HandlerThread vào
// một tập hợp để theo dõi các phiên client.)
}catch (Exception err) {
Console.WriteLine(err.ToString());
}
}
}
}
Dưới đây là transcript phía server của một phiên làm việc với hai client:
Server: About to initialize port
Server: Listening for a connection
Server: Connection accepted
Client 1: Connection completed
Server: Connection accepted
Client 2: Connection completed
Client 2: Disconnect request received
Client 2: Client connection closed
Client 1: Disconnect request received
Client 1: Client connection closed
Bạn có thể thêm mã lệnh vào server để nó theo vết các đối tượng thợ hiện hành trong một tập hợp Làm như thế sẽ cho phép server hủy bỏ các tác vụ này nếu nó cần phải đóng và chỉ được phép một số tối đa client cùng một lúc.
12. S d ng TCP m t cách b t đ ng b S d ng TCP m t cách b t đ ng b ử ụ ử ụ ộ ộ ấ ồ ấ ồ ộ ộ
Bạn cần ghi dữ liệu ra network-stream từng khối một, mà không phải block
phần mã lệnh còn lại Kỹ thuật này có thể được sử dụng nếu bạn muốn “stream” một file lớn trên mạng.
Tạo một lớp riêng để xử lý kỹ thuật streaming bất đồng bộ Bạn có thể bắt đầu
“stream” một khối dữ liệu bằng phương thức NetworkStream.BeginWrite và cung cấp một phương thức callback Khi callback được kích hoạt thì gửi khối kế tiếp.
Trang 4Lớp NetworkStream hỗ trợ việc sử dụng bất đồng bộ thông qua phương thức BeginRead và BeginWrite Sử dụng các phương thức này, bạn có thể gửi hay nhận một khối dữ liệu trên một
trong các tiểu trình do thread-pool của bộ thực thi NET cung cấp, mà không block mã lệnh
của bạn Mục này trình bày kỹ thuật ghi bất đồng bộ.
Khi gửi dữ liệu một cách bất đồng bộ, bạn phải gửi dữ liệu nhị phân thô (một mảng byte) Và
bạn cần chọn kích thước mỗi lần gửi hay nhận Ví dụ dưới đây viết lại server từ mục 11.11
sao cho mỗi lớp ClientHandler gửi một lượng lớn dữ liệu được đọc từ một file Dữ liệu này được gửi một cách bất đồng bộ, nghĩa là ClientHandler có thể tiếp tục thực hiện các tác vụ khác (trong ví dụ này, nó chỉ việc lấy các thông điệp được gửi từ client).
Một thuận lợi của cách tiếp cận này là toàn bộ nội dung của file chẳng bao giờ nằm trong bộ nhớ một lượt Thay vào đó, nó được thu lấy ngay trước khi một khối mới được gửi Một thuận lợi khác nữa là server có thể hủy bỏ thao tác vào bất cứ lúc nào Ví dụ, nếu client chỉ đọc đến khối dữ liệu thứ ba thì ngắt kết nối, server sẽ thiết lập một biến thành viên luận lý có tên là fileStop để báo cho callback không gửi dữ liệu nữa
Dưới đây là lớp ClientHandler đã được sửa đổi (lớp TcpServerTest không cần thay đổi gì): using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using SharedComponent;
public class ClientHandler {
private TcpClient client;
private string ID;
// Kích thước một khối dữ liệu (2 KB)
private int bufferSize = 2048;
// Bộ đệm dùng để chứa dữ liệu
private byte[] buffer;
// Dùng để đọc dữ liệu từ một file
private FileStream fileStream;
// Dùng để giao tiếp với client
private NetworkStream networkStream;
// Dấu hiệu ngừng gửi dữ liệu
private bool fileStop = false;
Trang 5public ClientHandler(TcpClient client, string ID) {
this.buffer = new byte[bufferSize];
this.client = client;
this.ID = ID;
}
public void Start() {
// Thu lấy network stream
networkStream = client.GetStream();
// Tạo các đối tượng dùng để gửi và nhận text
BinaryWriter w = new BinaryWriter(networkStream);
BinaryReader r = new BinaryReader(networkStream);
if (r.ReadString() == ClientMessages.RequestConnect) {
w.Write(ServerMessages.AcknowledgeOK);
Console.WriteLine(ID + ": Connection completed.");
string message = "";
while (message != ClientMessages.Disconnect) {
message = r.ReadString();
if (message == ClientMessages.RequestData) {
// Tên file có thể do client cung cấp, nhưng
// trong ví dụ này, file thử nghiệm là mã cứng
fileStream =
new FileStream("test.bin", FileMode.Open);
// Gửi kích thước file
w.Write(fileStream.Length.ToString());
// Khởi chạy thao tác bất đồng bộ
StreamData(null);
}
Trang 6}
fileStop = true;
Console.WriteLine(ID + ": Disconnect request received."); } else {
Console.WriteLine(ID + ": Could not complete connection."); }
// Đóng kết nối
client.Close();
Console.WriteLine(ID + ": Client connection closed.");
Console.ReadLine();
}
private void StreamData(IAsyncResult asyncResult) {
// Hủy bỏ nếu client ngừng kết nối
if (fileStop == true) {
fileStop = false;
return;
}
if (asyncResult != null) {
// Một khối đã được ghi một cách bất đồng bộ
networkStream.EndWrite(asyncResult);
}
// Lấy khối kế tiếp từ file
int bytesRead = fileStream.Read(buffer, 0, buffer.Length);
// Nếu không đọc được byte nào, stream đã đến cuối file
if (bytesRead > 0) {
Console.WriteLine("Streaming new block.");
// Ghi khối kế tiếp ra network stream
networkStream.BeginWrite(buffer, 0, buffer.Length,
Trang 7} else {
// Kết thúc thao tác
Console.WriteLine("File streaming complete.");
fileStream.Close();
}
}
}
Bạn có thể sử dụng một mẫu tương tự để đọc dữ liệu một cách bất đồng bộ phía client.
13. Giao ti p b ng UDP Giao ti p b ng UDP ế ằ ế ằ
Bạn cần gửi dữ liệu giữa hai máy tính trên một network bằng User Datagram
Protocol (UDP) stream.
Sử dụng lớp System.Net.Sockets.UdpClient, và sử dụng hai tiểu trình: một để gửi
dữ liệu và một để nhận dữ liệu.
UDP là một giao thức phi kết nối, không có bất kỳ điều khiển dòng chảy hay kiểm tra lỗi nào
Khác với TCP, UDP sẽ không được sử dụng ở những nơi cần đến giao tiếp đáng tin cậy Tuy nhiên, vì chi phí thấp hơn, UDP thường được sử dụng cho các ứng dụng "chatty", tại đó chấp
nhận mất một vài thông điệp Ví dụ, giả sử bạn muốn tạo một network mà trong đó, các client gửi thông tin về nhiệt độ hiện thời tại vị trí của chúng đến một server mỗi vài phút Bạn có thể
sử dụng UDP trong trường hợp này vì tần số giao tiếp cao và thiệt hại do mất packet là không
đáng kể (vì server có thể tiếp tục sử dụng nhiệt độ nhận được cuối cùng).
Ứng dụng dưới đây sử dụng hai tiểu trình: một để nhận thông điệp và một để gửi thông điệp
Để thử nghiệm ứng dụng này, hãy nạp hai thể hiện cùng một lúc Trên máy tính A, cho biết địa chỉ IP của máy tính B Trên máy tính B, cho biết địa chỉ IP của máy tính A Theo đó, bạn
có thể gửi qua lại thông điệp dạng text (bạn có thể mô phỏng thử nghiệm này trên một máy đơn bằng cách sử dụng hai port khác nhau và địa chỉ loopback).
using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
public class UdpTest {
private static int localPort;
private static void Main() {
Trang 8// Định nghĩa endpoint (thông điệp được gửi tại đây).
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);
// Định nghĩa endpoint cục bộ (thông điệp được nhận tại đây) Console.Write("Local port for listening: ");
localPort = Int32.Parse(Console.ReadLine());
Console.WriteLine();
// Tạo một tiểu trình mới để nhận thông điệp đến
Thread receiveThread = new Thread(
new ThreadStart(ReceiveData));
receiveThread.IsBackground = true;
receiveThread.Start();
UdpClient client = new UdpClient();
try {
string text;
do {
text = Console.ReadLine();
if (text != "") {
// Mã hóa dữ liệu thành dạng nhị phân
// bằng phép mã hóa UTF8
byte[] data = Encoding.UTF8.GetBytes(text);
// Gửi text đến client ở xa
client.Send(data, data.Length, remoteEndPoint);
Trang 9} while (text != "");
} catch (Exception err) {
Console.WriteLine(err.ToString());
}
Console.ReadLine();
}
private static void ReceiveData() {
UdpClient client = new UdpClient(localPort);
while (true) {
try {
// Nhận dữ liệu (byte)
IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);
byte[] data = client.Receive(ref anyIP);
// Chuyển byte thành text bằng phép mã hóa UTF8
string text = Encoding.UTF8.GetString(data);
// Hiển thị text thu được
Console.WriteLine(">> " + text);
} catch (Exception err) {
Console.WriteLine(err.ToString());
}
}
}
}
Chú ý rằng, các ứng dụng UDP không thể sử dụng NetworkStream như các ứng dụng TCP
Thay vào đó, chúng phải chuyển tất cả dữ liệu thành một stream bằng một lớp mã hóa, như đã được mô tả trong mục 2.2.
Bạn có thể thử nghiệm ứng dụng này với các client trên máy cục bộ bằng cách sử dụng hai
port khác nhau và địa chỉ loopback Ví dụ, giả sử có hai UDP-client: client A và client B Dưới đây là transcript đối với client A:
Connect to IP: 127.0.0.1
Connect to port: 8001
Local port for listening: 8080
Trang 10Hi there!
Và đây là transcript tương ứng đối với client B (cùng với thông điệp nhận được):
Connect to IP: 127.0.0.1
Connect to port: 8080
Local port for listening: 8001
>> Hi there!
14. G i e-mail thông qua SMTP G i e-mail thông qua SMTP ử ử
Bạn cần gửi e-mail đến một địa chỉ e-mail bằng một SMTP-server (Simple Mail
Transfer Protocol server).
Sử dụng lớp SmtpMail và MailMessage thuộc không gian tên System.Web.Mail.
Các lớp trong không gian tên System.Web.Mail cung cấp một vỏ bọc cho thành phần
Collaboration Data Objects for Windows 2000 (CDOSYS) Chúng cho phép bạn soạn và gửi
thông điệp e-mail bằng SMTP.
Dễ dàng sử dụng các kiểu này Bạn chỉ cần tạo một đối tượng MailMessage, cho biết địa chỉ e-mail của người gửi và người nhận, và đặt nội dung của thông điệp trong thuộc tính Body MailMessage myMessage = new MailMessage();
myMessage.To = "someone@somewhere.com";
myMessage.From = "me@somewhere.com";
myMessage.Subject = "Hello";
myMessage.Priority = MailPriority.High;
myMessage.Body = "This is the message!";
Nếu muốn, bạn có thể gửi một thông điệp HTML bằng cách thay đổi định dạng của thông điệp
và sử dụng các thẻ HTML.
myMessage.BodyFormat = MailFormat.Html;
myMessage.Body = @"<HTML><HEAD></HEAD>" +
@"<BODY>This is the message!</BODY></HTML>";
Bạn có thể thêm file đính kèm bằng tập hợp MailMessage.Attachments và lớp MailAttachment
MailAttachment myAttachment = new MailAttachment("c:\\mypic.gif");
myMessage.Attachments.Add(myAttachment);
Để gửi thông điệp, bạn chỉ cần cho biết tên của SMTP-server và gọi phương thức
SmptMail.Send