Đ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 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 ");
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.
1.1 Lấy địa chỉ IP của client từ kết nối socket
V
V
Ứ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 và 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 {
// Đợ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();
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();
}
}
1.2 Thiết lập các tùy chọn socket
V
V
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 và 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àiliệ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 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.)
1.3 Tạo một TCP-server hỗ-trợ-đa-tiểu-trình
V
V
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;
using System.Threading;
using 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.");
}
// Đó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
// một tiểu trình khác.
Thread 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.
. 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. Console.WriteLine("Connection accepted.");
// Tạo network stream.
NetworkStream stream = new NetworkStream(socket);
// Tạo BinaryWriter để ghi