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

Các giải pháp lập trình CSharp- P57 ppt

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 (2.61 MB, 10 trang )

451
Chương 11: Lập trình mạng
// (Bỏ qua phương thức private dùng để tính checksum.)
}
}
public class IcmpPacket {
public byte Type;
public byte SubCode;
public UInt16 CheckSum;
public UInt16 Identifier;
public UInt16 SequenceNumber;
public byte[] Data;
}
Bạn có thể sử dụng phương thức tĩnh
Pinger.GetPingTime
với một địa chỉ IP hay một tên
miền. Phương thức
GetPingTime
trả về lượng mili-giây trôi qua trước khi một đáp ứng được
tiếp nhận. Dưới đây là đoạn mã thử nghiệm trên ba website:
public class PingTest {
private static void Main() {
Console.WriteLine("Milliseconds to contact www.yahoo.com:" +
Pinger.GetPingTime("www.yahoo.com").ToString());
Console.WriteLine("Milliseconds to contact www.seti.org:" +
Pinger.GetPingTime("www.seti.org").ToString());
Console.WriteLine("Milliseconds to contact the local computer:" +
Pinger.GetPingTime("127.0.0.1").ToString());
Console.ReadLine();
}
}


Thử nghiệm “ping” cho phép bạn xác minh các máy tính khác có online hay không. Nó cũng
có thể hữu ích khi ứng dụng của bạn cần đánh giá những máy tính khác nhau (ở xa) nhưng
cho cùng nội dung để xác định máy nào có thời gian giao tiếp mạng thấp nhất.

Một yêu cầu “ping”

có thể không thành công nếu bị firewall ngăn lại. Ví dụ,
nhiều site bỏ qua yêu cầu “ping”

vì sợ bị sa vào một luồng “ping” cùng một lúc
sẽ làm cản trở server (thực chất là một tấn công từ chối dịch vụ).
452
Chương 11: Lập trình mạng
8.
8.
Giao ti p b ng TCPế ằ
Giao ti p b ng TCPế ằ


Bạn cần gửi dữ liệu giữa hai máy tính trên một network bằng kết nối
TCP/IP
.


Một máy tính (server) phải lắng nghe bằng lớp
System.Net.Sockets.TcpListener
.
Mỗi khi một kết nối được thiết lập, cả hai máy tính đều có thể giao tiếp bằng lớp
System.Net.Sockets.TcpListener
.

TCP là một giao thức đáng tin cậy dựa-trên-kết-nối, cho phép hai máy tính giao tiếp thông qua
một network. Để tạo một kết nối TCP, một máy tính phải đóng vai trò là server và bắt đầu
lắng nghe trên một endpoint cụ thể (endpoint được định nghĩa là một địa chỉ IP, cho biết máy
tính và số port). Một máy tính khác phải đóng vai trò là client và gửi một yêu cầu kết nối đến
endpoint mà máy tính thứ nhất đang lắng nghe trên đó. Một khi kết nối được thiết lập, hai máy
tính có thể trao đổi các thông điệp với nhau. Cả hai máy tính chỉ đơn giản đọc/ghi từ một
System.Net.Sockets.NetworkStream
.

Mặc dù một kết nối
TCP
luôn cần có một server và một client, nhưng không lý
do gì một ứng dụng không thể là cả hai. Ví dụ, trong một ứng dụng
peer
-
to
-
peer
,
một tiểu trình được sử dụng lắng nghe các yêu cầu đến (đóng vai trò là một
server) trong khi một tiểu trình khác được sử dụng để khởi tạo các kết nối đi
(đóng vai trò là một client). Trong ví dụ đi kèm mục này, client và server là các
ứng dụng riêng rẽ và được đặt trong các thư mục con riêng.
Một khi kết nối TCP được thiết lập, hai máy tính có thể gửi bất kỳ kiểu dữ liệu nào bằng cách
ghi dữ liệu đó ra
NetworkStream
. Tuy nhiên, ý tưởng hay là bắt đầu thiết kế một ứng dụng
mạng bằng cách định nghĩa giao thức mức-ứng-dụng mà client và server sẽ sử dụng để giao
tiếp. Giao thức này chứa các hằng mô tả các lệnh được phép, bảo đảm mã lệnh của ứng dụng
không chứa các chuỗi giao tiếp được viết cứng.

namespace SharedComponent {
public class ServerMessages {
public const string AcknowledgeOK = "OK";
public const string AcknowledgeCancel = "Cancel";
public const string Disconnect = "Bye";
}
public class ClientMessages {
public const string RequestConnect = "Hello";
public const string Disconnect = "Bye";
}
}
453
Chương 11: Lập trình mạng
Trong ví dụ này, bảng từ vựng được định nghĩa sẵn chỉ là cơ bản. Bạn có thể thêm nhiều hằng
hơn nữa tùy thuộc vào kiểu ứng dụng. Ví dụ, trong một ứng dụng truyền file, client có thể gửi
một thông điệp để yêu cầu một file. Sau đó, server có thể đáp lại bằng một acknowledgment
(ACK) và trả về các chi tiết của file (kích thước file chẳng hạn). Những hằng này sẽ được biên
dịch thành một Class Library Assembly riêng, và cả client và server đều phải tham chiếu đến
assembly này.
Đoạn mã dưới đây là một khuôn dạng cho một TCP-server cơ bản. Nó lắng nghe trên một port
cố định, nhận kết nối đến đầu tiên và rồi đợi client yêu cầu ngừng kết nối. Tại thời điểm này,
server có thể gọi phương thức
TcpListener.AcceptTcpClient
lần nữa để đợi client kế tiếp.
Nhưng thay vào đó, nó sẽ đóng lại.
using System;
using System.Net;
using System.Net.Sockets;
using System.IO;
using SharedComponent;

public class TcpServerTest {
private static void Main() {
// Tạo listener trên 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 ");

try {
// Đợi yêu cầu kết nối, và trả về TcpClient.
TcpClient client = listener.AcceptTcpClient();
Console.WriteLine("Connection accepted.");

// Thu lấy network stream.
NetworkStream stream = client.GetStream();
// Tạo BinaryWriter để ghi ra stream.
BinaryWriter w = new BinaryWriter(stream);
454
Chương 11: Lập trình mạng

// Tạo BinaryReader để đọc từ stream.
BinaryReader r = new BinaryReader(stream);

if (r.ReadString() == ClientMessages.RequestConnect) {
w.Write(ServerMessages.AcknowledgeOK);
Console.WriteLine("Connection completed.");

while (r.ReadString() != ClientMessages.Disconnect)

{}
Console.WriteLine();
Console.WriteLine("Disconnect request received.");
w.Write(ServerMessages.Disconnect);
} else {
Console.WriteLine("Could not complete connection.");
}
// Đóng socket.
client.Close();
Console.WriteLine("Connection closed.");
// Đóng socket nằm dưới (ngừng lắng nghe yêu cầu mới).
listener.Stop();
Console.WriteLine("Listener stopped.");
} catch (Exception err) {
Console.WriteLine(err.ToString());
}

Console.ReadLine();
}
}
Đoạn mã dưới đây là một khuôn dạng cho một TCP-client cơ bản. Nó tiếp xúc với server tại
địa chỉ IP và port được chỉ định. Trong ví dụ này, địa chỉ loopback (
127.0.0.1
—chỉ đến máy
tính hiện hành) được sử dụng. Nhớ rằng kết nối TCP yêu cần hai port: một tại server và một
455
Chương 11: Lập trình mạng
tại client. Tuy nhiên, chỉ cần chỉ định port tại server, còn port tại client có thể được chọn động
lúc thực thi từ các port có sẵn.
using System;

using System.Net;
using System.Net.Sockets;
using System.IO;
using SharedComponent;
public class TcpClientTest {
private 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.");
// 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);

w.Write(ClientMessages.RequestConnect);
if (r.ReadString() == ServerMessages.AcknowledgeOK) {
Console.WriteLine("Connected.");
Console.WriteLine("Press Enter to disconnect.");
Console.ReadLine();
Console.WriteLine("Disconnecting ");
456
Chương 11: Lập trình mạng
w.Write(ClientMessages.Disconnect);
} else {

Console.WriteLine("Connection not completed.");
}
// Đóng connection socket.
client.Close();
Console.WriteLine("Port closed.");
} catch (Exception err) {
Console.WriteLine(err.ToString());
}

Console.ReadLine();
}
}
Dưới đây là transcript phía server:
About to initialize port.
Listening for a connection
Connection accepted.
Connection completed.
Disconnect request received.
Connection closed.
Listener stopped.
Và dưới đây là transcript phía client:
Attempting to connect to the server on port 8000.
Connection established.
Connected.
Press Enter to disconnect.
Disconnecting
Port closed.
457
Chương 11: Lập trình mạng
9.

9.
L y đ a ch IP c a client t k t n i socketấ ị ỉ ủ ừ ế ố
L y đ a ch IP c a client t k t n i socketấ ị ỉ ủ ừ ế ố


Ứng dụng server cần xác định địa chỉ
IP
của client sau khi nó chấp nhận một kết
nối.


Sử dụng phương thức
AcceptSocket
của lớp
TcpListener
để lấy lớp mức-thấp là
System.Net.Sockets.Socket
thay vì là
TcpClient
. Sử dụng thuộc tính
Socket.RemoteEndPoint
để lấy địa chỉ
IP
của client.
Lớp
TcpClient
không cho phép bạn thu lấy socket nằm dưới hay bất cứ thông tin nào về port
và địa chỉ IP của client. Lớp này có cung cấp thuộc tính
Socket
, nhưng thuộc tính này là

được-bảo-vệ (
protected
) và do đó không thể truy xuất được từ các lớp phi dẫn xuất. Để truy
xuất socket nằm dưới, bạn có hai tùy chọn:

Tạo một lớp tùy biến dẫn xuất từ
TcpClient
. Lớp này có thể truy xuất thuộc tính được-
bảo-vệ
Socket
và trưng nó ra thông qua một thuộc tính mới. Sau đó, bạn phải sử dụng
lớp tùy biến này thay cho
TcpClient
.

Bỏ qua lớp
TcpClient
bằng cách sử dụng phương thức
TcpListener.AcceptSocket
. Bạn
vẫn có thể sử dụng các lớp mức-cao là
BinaryReader

BinaryWriter
để đọc/ghi dữ
liệu, nhưng bạn cần phải tạo
NetworkStream
trước (sử dụng socket).
Mục này sử dụng cách thứ hai. Dưới đây là phiên bản sửa đổi của server trong mục 11.8:
using System;

using System.Net;
using System.Net.Sockets;
using System.IO;
using SharedComponent;
public class TcpServerTest {
private static void Main() {
// Tạo listener trên 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 ");

try {
458
Chương 11: Lập trình mạng
// Đợi yêu cầu kết nối, và trả về một Socket.
Socket socket = listener.AcceptSocket();
Console.WriteLine("Connection accepted.");

// Tạo network stream.
NetworkStream stream = new NetworkStream(socket);
// 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("Connection completed.");

// Lấy địa chỉ IP của client.
Console.WriteLine("The client is from IP address: " +
((IPEndPoint)socket.RemoteEndPoint).Address.ToString());
Console.Write("The client uses local port: " +
((IPEndPoint)socket.RemoteEndPoint).Port.ToString());

while (r.ReadString() != ClientMessages.Disconnect)
{}
Console.WriteLine();
Console.WriteLine("Disconnect request received.");
w.Write(ServerMessages.Disconnect);
} else {
Console.WriteLine("Could not complete connection.");
}
// Đóng socket.
socket.Close();
459
Chương 11: Lập trình mạng
Console.WriteLine("Connection closed.");
// Đóng socket nằm dưới (ngừng lắng nghe yêu cầu mới).
listener.Stop();
Console.WriteLine("Listener stopped.");
} catch (Exception err) {
Console.WriteLine(err.ToString());
}

Console.ReadLine();

}
}
10.
10.
Thi t l p các tùy ch n socketế ậ ọ
Thi t l p các tùy ch n socketế ậ ọ


Bạn cần thiết lập các tùy chọn socket mức-thấp, chẳng hạn các tùy chọn cho biết
send timeout

receive timeout
.


Sử dụng phương thức
Socket.SetSocketOption
. Bạn có thể thiết lập các thuộc
tính của socket được sử dụng để lắng nghe các yêu cầu hoặc các thuộc tính của
socket được sử dụng cho một phiên client

cụ thể.
Bạn có thể sử dụng phương thức
Socket.SetSocketOption
để thiết lập một số thuộc tính
socket mức-thấp. Khi gọi phương thức này, bạn cần cung cấp ba đối số sau đây:

Một giá trị thuộc kiểu liệt kê
SocketOptionLevel
, cho biết kiểu socket mà thiết lập này

sẽ áp dụng cho nó (bao gồm
IP
,
IPv6
,
Socket
,
Tcp
,
Udp
).

Một giá trị thuộc kiểu liệt kê
SocketOptionName
, cho biết thiết lập socket mà bạn muốn
thay đổi (xem danh sách các giá trị của
SocketOptionName
trong tài liệu .NET
Framework).

Một giá trị mô tả thiết lập mới. Giá trị này thường là một số nguyên, nhưng cũng có thể
là một mảng byte hay một kiểu đối tượng.
Ví dụ dưới đây sẽ thiết lập send-timeout của socket:
// Thao tác gửi sẽ hết hiệu lực nếu không nhận được
// thông tin xác nhận trong vòng 1000 mili-giây.
socket.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.SendTimeout, 1000);
Chú ý rằng, để truy xuất socket mô tả một kết nối client/server, bạn phải sử dụng phương thức
TcpListener.AcceptSocket
thay cho phương thức

TcpListener.AcceptTcpClient
(đã được
thảo luận trong mục 11.9).
Bạn cũng có thể thiết lập các tùy chọn cho socket được sử dụng bởi
TcpListener
để theo dõi
các yêu cầu kết nối. Tuy nhiên, bạn phải thực hiện thêm một vài bước nữa. Lớp
TcpListener
460
Chương 11: Lập trình mạng
cung cấp thuộc tính
Socket
, nhưng khả năng truy xuất của nó là
protected
, nghĩa là bạn
không thể truy xuất nó một cách trực tiếp. Thay vào đó, bạn phải dẫn xuất một lớp mới từ
TcpListener
:
public class CustomTcpListener : TcpListener {
public Socket Socket {
get {return base.Server;}
}
public CustomTcpListener(IPAddress ip, int port) : base(ip, port) {}
}
Bây giờ, bạn có thể sử dụng lớp này khi tạo một
TcpListener
. Ví dụ dưới đây sử dụng cách
tiếp cận này để thiết lập một tùy chọn socket:
CustomTcpListener listener =
new CustomTcpListener(IPAddress.Parse("127.0.0.1"), 8000);

listener.Socket.SetSocketOption(SocketOptionLevel.Socket,
SocketOptionName.ReceiveTimeout, 1000);
// (Sử dụng CustomTcpListener giống như đã sử dụng TcpListener.)
11.
11.
T o m t TCP-server h -tr -đa-ti u-trìnhạ ộ ỗ ợ ể
T o m t TCP-server h -tr -đa-ti u-trìnhạ ộ ỗ ợ ể


Bạn muốn tạo một
TCP-
server

có thể cùng lúc xử lý nhiều
TCP-
client.


Sử dụng phương thức
AcceptTcpClient
của lớp
TcpListener
. Mỗi khi có một
client mới kết nối đến, khởi chạy một tiểu trình mới để xử lý yêu cầu và gọi
TcpListener.AcceptTcpClient
lần nữa.
Một endpoint TCP (địa chỉ IP và port) có thể phục vụ nhiều kết nối. Thực ra, hệ điều hành
đảm đương phần lớn công việc giùm bạn. Những gì bạn cần làm là tạo một đối tượng thợ
(worker object) trên server để xử lý mỗi kết nối trong một tiểu trình riêng.
Xét lớp TCP-client và TCP-server đã được trình bày trong mục 11.8. Bạn có thể dễ dàng

chuyển server này thành một server hỗ-trợ-đa-tiểu-trình để thực hiện nhiều kết nối cùng một
lúc. Trước hết, tạo một lớp để tương tác với một client:
using System;
using System.Net;
using System.Net.Sockets;
using System.IO;

×