Console.WriteLine"Press a key to shut down the server."; Console.ReadLine; } } Chương trình trên sử dụng file cấu hình app.config để cấu hình các lớp mà nó sẽ hỗ trợ, các cổng mà nó sẽ
Trang 1using System.Runtime.Remoting;
public class Server {
private static void Main() {
// Đăng ký các lớp khả-truy-xuất-từ-xa với NET Remoting
RemotingConfiguration.Configure("Server.exe.config");
// Miễn là ứng dụng này đang chạy, các đối tượng ở xa
// sẽ là khả truy xuất
Console.WriteLine("Press a key to shut down the server.");
Console.ReadLine();
}
}
Chương trình trên sử dụng file cấu hình (app.config) để cấu hình các lớp mà nó sẽ hỗ trợ, các cổng mà nó sẽ hỗ trợ cho giao tiếp mạng, và địa chỉ URI (Uniform Resource Identifier) mà
client sẽ sử dụng để truy xuất đối tượng Dưới đây là một file cấu hình đơn giản đăng ký lớp RemoteObjects.RemoteObject từ RemoteObject.dll với địa chỉ cổng là 9080 thông qua giao
thức TCP/IP Assembly này phải nằm trong GAC (Global Assembly Cache) hoặc trong cùng
thư mục với ứng dụng server File cấu hình cũng cấu hình đối tượng ở xa dùng chế độ kích hoạt gọi-một-lần
<configuration>
<system.runtime.remoting>
<application>
<! Định nghĩa đối tượng khả-truy-xuất-từ-xa >
<service>
<wellknown
mode = "SingleCall"
type="RemoteObject.ProductsDB, RemoteObject"
objectUri="RemoteObject" />
</service>
<! Định nghĩa giao thức dùng cho truy xuất mạng
Bạn có thể sử dụng kênh tcp hay http >
<channels>
Trang 2<channel ref="tcp" port="9080" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
Host không bao giờ tương tác trực tiếp tới các đối tượng ở xa, những gì nó làm chỉ là đăng ký
các kiểu thích hợp với kiến trúc NET Remoting Sau thời điểm đó, ứng dụng client có thể tạo
ra các đối tượng này, và ứng dụng server có thể tiếp tục thực hiện các công việc khác Tuy nhiên, khi host bị đóng, tất cả các đối tượng sẽ bị hủy, và không thể tạo đối tượng được nữa.
Ứng dụng client sử dụng file cấu hình tương tự như trên để định nghĩa địa chỉ URL và kiểu của đối tượng ở xa Địa chỉ URL có định dạng như sau:
[Protocol]://[Server]:[PortNumber]/[ObjectURI]
Dưới đây là file cấu hình phía client:
<configuration>
<system.runtime.remoting>
<application>
<!— Định nghĩa đối tượng mà ứng dụng này
muốn truy xuất từ xa >
<client>
<wellknown type="RemoteObject.ProductsDB, RemoteObject"
url="tcp://localhost:9080/RemoteObject" />
</client>
<! Định nghĩa giao thức dùng cho truy xuất mạng
Giao thức này phải khớp với giao thức được
định nghĩa phía server, nhưng địa chỉ cổng có thể
khác Địa chỉ cổng 0 nghĩa là "lấy bất kỳ
một địa chỉ cổng nào còn trống" >
<channels>
<channel ref="tcp" port="0" />
</channels>
</application>
</system.runtime.remoting>
Trang 3Ứng dụng client sử dụng phương thức RemotingConfiguration.Configure để đăng ký các đối tượng mà nó muốn gọi Sau khi đã đăng ký xong, client có thể tạo đối tượng này giống như tạo đối tượng cục bộ mặc dù nó thật sự nằm trong miền ứng dụng của host Đoạn mã dưới đây trình bày các bước này:
using System;
using System.Runtime.Remoting;
using System.Data;
using RemoteObject;
public class Client {
private static void Main() {
// Đăng ký các lớp sẽ được truy xuất từ xa
RemotingConfiguration.Configure("Client.exe.config");
// Tương tác với đối tượng ở xa thông qua proxy
ProductsDB proxy = new ProductsDB();
// Hiển thị tên miền ứng dụng của host
Console.WriteLine("Object executing in: " +
proxy.GetHostLocation());
// Lấy DataSet và hiển thị nội dung của nó
DataTable dt = proxy.GetProducts();
foreach (DataRow row in dt.Rows) {
Console.WriteLine(row[1]);
}
Console.ReadLine();
}
}
Để tạo một đối tượng ở xa, client cần một tham chiếu đến assembly mà lớp này được định nghĩa trong đó Điều này cần thêm một bước triển khai nữa, bạn có thể tránh đi bằng cách sử dụng một giao diện có định nghĩa các chức năng được hỗ trợ.
Trang 4 Để chuyển dữ liệu đến đối tượng ở xa (hoặc chuyển dữ liệu từ đối tượng ở xa về),
các kiểu dữ liệu dùng cho các tham số và giá trị trả về phải là khả-tuần-tự-hóa
(serializable—tất cả các kiểu cơ bản đều có tính chất này) Nếu muốn sử dụng
các lớp tùy biến để chuyển dữ liệu, bạn phải làm cho các lớp này trở thành khả-tuần-tự-hóa bằng cách áp dụng đặc tính Serializable (xem mục 16.1 để biết thêm chi tiết).
8. Đăng ký t t c các l p kh -truy-xu t-t -xa trong m t assembly Đăng ký t t c các l p kh -truy-xu t-t -xa trong m t assembly ấ ả ấ ả ớ ớ ả ả ấ ừ ấ ừ ộ ộ
Bạn muốn đăng ký tất cả các lớp khả-truy-xuất-từ-xa được định nghĩa trong một
assembly mà không phải chỉ định chúng trong file cấu hình.
Nạp assembly (có chứa các lớp khả-truy-xuất-từ-xa) bằng cơ chế phản chiếu
(reflection) Duyệt qua tất cả các kiểu trong đó và sử dụng phương thức
RemotingConfiguration.RegisterWellKnownServiceType để đăng ký mỗi lớp khả-truy-xuất-từ-xa
.NET cho phép bạn đăng ký các lớp khả-truy-xuất-từ-xa một cách dễ dàng thông qua việc viết
file cấu hình hoặc viết mã chương trình Xét ví dụ trong mục 12.7 Để đăng ký bằng mã chương trình, trước hết bạn phải gỡ bỏ những khai báo lớp trong file cấu hình trên server như sau:
<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel ref="tcp" port="9080" />
</channels>
</application>
</system.runtime.remoting>
</configuration>
Bây giờ, bạn có thể sử dụng kỹ thuật phản chiếu kết hợp với phương thức RegisterWellKnownServiceType để đăng ký tất cả các lớp khả-truy-xuất-từ-xa Tuy nhiên, bạn
cần thêm một tham chiếu đến System.Runtime.Remoting.dll Đoạn mã dưới đây tìm các lớp khả-truy-xuất-từ-xa trong RemoteObject.dll và đăng ký mỗi lớp:
using System;
using System.Runtime.Remoting;
using System.Runtime.Remoting.Channels;
Trang 5using System.Reflection;
public class Server {
private static void Main() {
// Sử dụng file cấu hình để định nghĩa các tùy chọn về mạng
RemotingConfiguration.Configure("Server.exe.config");
// Lấy kênh Remoting đã được đăng ký
TcpChannel channel =
(TcpChannel)ChannelServices.RegisteredChannels[0];
// Nạp RemoteObject.dll
Assembly assembly = Assembly.LoadFrom("RemoteObject.dll");
// Xử lý tất cả các kiểu trong RemoteObject.dll
foreach (Type type in assembly.GetTypes()) {
// Kiểm tra kiểu có phải là khả-truy-xuất-từ-xa hay không
if (type.IsSubclassOf(typeof(MarshalByRefObject))) {
// Đăng ký kiểu (tên kiểu là địa chỉ URI)
Console.WriteLine("Registering " + type.Name);
RemotingConfiguration.RegisterWellKnownServiceType(
type, type.Name, WellKnownObjectMode.SingleCall);
// Xác định địa chỉ URL (kiểu được publish tại đây)
string[] urls = channel.GetUrlsForUri(type.Name);
Console.WriteLine(urls[0]);
}
}
Console.WriteLine("Press a key to shut down the server.");
Console.ReadLine();
}
Trang 69. Qu n lý các đ i t Qu n lý các đ i t ả ả ố ượ ở ố ượ ở ng xa trong IIS ng xa trong IIS
Bạn muốn tạo một đối tượng khả-truy-xuất-từ-xa trong IIS (để có thể sử dụng
SSL hay IIS authentication) thay cho một host chuyên biệt.
Đặt file cấu hình và assembly vào một thư mục ảo, và thay đổi URI sao cho nó
kết thúc bằng rem hay soap.
Thay vì tạo một host chuyên biệt, bạn có thể quản lý một lớp khả-truy-xuất-từ-xa trong IIS (Internet Information Services) Điều này cho phép bạn bảo đảm các lớp khả-truy-xuất-từ-xa
sẽ luôn có hiệu lực, và cho phép bạn sử dụng các tính năng của IIS như SSL Encryption và Integrated Windows authentication.
Để quản lý một lớp khả-truy-xuất-từ-xa trong IIS, trước hết bạn phải tạo một thư mục ảo Thư
mục này chứa hai thứ: file cấu hình dùng để đăng ký các lớp khả-truy-xuất-từ-xa và thư mục
bin dùng để chứa Class Library Assembly tương ứng (hoặc cài đặt assembly vào GAC).
File cấu hình này hoàn toàn tương tự với file cấu hình mà bạn sử dụng cho một host tùy biến Tuy nhiên, bạn phải tuân theo các quy tắc:
• Bạn phải sử dụng kênh HTTP (mặc dù có thể sử dụng Binary formatter đối với các kích
thước thông điệp nhỏ hơn).
• Bạn không thể chỉ cụ thể địa chỉ cổng IIS lắng nghe tất cả các cổng bạn đã cấu hình trong IIS Manager (cổng 80 và 443).
• URI phải kết thúc bằng rem hay soap.
• File cấu hình phải có tên là Web.config, nếu không nó sẽ bị bỏ qua.
File Web.config dưới đây sẽ đăng ký lớp đã được trình bày trong mục 12.7:
<configuration>
<system.runtime.remoting>
<application>
<service>
<wellknown mode="SingleCall"
type="RemoteObject.ProductsDB, RemoteObject"
objectUri="RemoteObject.rem" />
</service>
<channels>
<channel ref="http">
<! Gỡ bỏ chú thích dưới đây để sử dụng Binary formatter
Trang 7
<serverProviders>
<formatter ref="binary"/>
</serverProviders>
>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>
Client có thể sử dụng đối tượng được quản lý trong IIS giống như đối tượng được quản lý trong một host tùy biến Tuy nhiên, tên thư mục ảo sẽ là một phần của URI Ví dụ, nếu file Web.config vừa trình bày ở trên được đặt trong thư mục ảo http://localhost/RemoteObjects thì
URL đầy đủ sẽ là http://localhost/RemoteObjects/RemoteObject.rem
Khi quản lý một đối tượng với IIS, tài khoản được sử dụng để thực thi đối tượng
là tài khoản ASP.NET (được định nghĩa trong file machine.config) Nếu tài
khoản này không có quyền truy xuất cơ sở dữ liệu (là trạng thái mặc định), bạn
sẽ gặp lỗi khi chạy ví dụ này Để giải quyết vấn đề này, bạn hãy xem mục 7.17.
10. Phát sinh s ki n trên kênh truy xu t t xa Phát sinh s ki n trên kênh truy xu t t xa ự ệ ự ệ ấ ừ ấ ừ
Bạn cần tạo một client có thể nhận một sự kiện do đối tượng ở xa phát sinh.
Phải chắc rằng bạn đang sử dụng các kênh hai chiều (bidirectional channel) Tạo
một đối tượng khả-truy-xuất-từ-xa bên client (có thể nhận sự kiện từ server).
Mặc dù cú pháp thụ lý sự kiện không hề thay đổi khi bạn sử dụng NET Remoting, nhưng bạn
cần tạo một client có thể thụ lý sự kiện từ một đối tượng ở xa Dưới đây là các yêu cầu chính:
• Lớp khả-truy-xuất-từ-xa phải sử dụng chế độ được-client-kích-hoạt (client-activated) hay chế độ kích hoạt đơn-nhất (singleton activation) không phải chế độ kích hoạt
gọi-một-lần (single-call activation) Điều này bảo đảm đối tượng vẫn “còn sống” giữa các
lần gọi phương thức, cho phép nó phát sinh sự kiện đến client.
• Client phải sử dụng kênh hai chiều để nó có thể nhận các kết nối do server khởi tạo.
• Đối tượng EventArgs phải là khả-tuần-tự-hóa để nó có thể được chuyển qua các biên miền ứng dụng.
• Client phải sử dụng một đối tượng khả-truy-xuất-từ-xa để nhận sự kiện (được gọi là listener) Theo đó, listener sẽ dựng một sự kiện cục bộ mà client có thể xử lý được Đối
Trang 8tượng ở xa không thể trực tiếp phát sinh sự kiện đến một lớp bình thường vì lớp bình thường không thể được truy xuất từ các miền ứng dụng khác.
• Bạn phải thay đổi các file cấu hình của client và server để cho phép “full serialization”
(điều này không cần thiết với NET 1.0).
Dưới đây là lớp khả-truy-xuất-từ-xa mà bạn có thể sử dụng để phát sinh một sự kiện đến client Lớp này cung cấp phương thức StartTask để khởi chạy một bộ định thời, phát sinh sau một thời gian ngắn (khoảng 10 giây) Khi bộ định thời phát sinh, đối tượng
khả-truy-xuất-từ-xa dựng lên sự kiện TaskComplete.
using System;
using System.Timers;
public delegate void TaskCompleted(object sender,
TaskCompleteEventArgs e);
public class RemoteObject : MarshalByRefObject {
public event TaskCompleted TaskComplete;
private Timer tmr = new Timer();
public void StartTask() {
tmr.Interval = 10000;
tmr.Elapsed += new ElapsedEventHandler(tmrCallback);
tmr.Start();
}
private void tmrCallback(object sender, ElapsedEventArgs e) {
tmr.Enabled = false;
if (TaskComplete != null) {
TaskComplete(this,
new TaskCompleteEventArgs("Task completed on server"));
}
}
public override object InitializeLifetimeService() {
Trang 9}
}
[Serializable()]
public class TaskCompleteEventArgs : EventArgs {
public string Result;
public TaskCompleteEventArgs(string result) {
this.Result = result;
}
}
Bước kế tiếp là định nghĩa một lớp khả-truy-xuất-từ-xa chạy trên client và có thể nhận sự kiện này Theo đó, lớp này có thể tiếp xúc với client Lớp EventListener dưới đây trình bày một ví
dụ như thế—nó chỉ đơn giản dựng lên sự kiện thứ hai, mà client có thể trực tiếp xử lý Cũng như tất cả các đối tượng khả-truy-xuất-từ-xa, nó sẽ chỉ được truy xuất trong 5 phút, trừ khi
bạn thay đổi chính sách “lease” (sẽ được mô tả trong mục 12.11) Có một cách là chép đè
phương thức InitializeLifetimeService để cho phép đối tượng được sống vĩnh viễn:
public class EventListener : MarshalByRefObject {
public event RemoteObject.TaskCompleted TaskComplete;
// Thụ lý sự kiện ở xa
public void OnTaskComplete(object sender,
RemoteObject.TaskCompleteEventArgs e) {
TaskComplete(sender, e);
}
public override object InitializeLifetimeService() {
return null;
}
}
Listener phải được định nghĩa trong một assembly riêng để nó có thể được tham chiếu bởi ứng dụng client và lớp khả-truy-xuất-từ-xa (cả hai đều cần tương tác với nó).
Bây giờ ứng dụng client có thể khởi chạy tác vụ bất đồng bộ thông qua lớp RemoteObject và thụ lý sự kiện thông qua lớp EventListener Đoạn mã dưới đây trình bày một client chỉ đơn giản hiển thị thông báo khi nhận được sự kiện:
Trang 10using System;
using System.Windows.Forms;
using System.Runtime.Remoting;
public class ClientForm : System.Windows.Forms.Form {
private System.Windows.Forms.Button cmdStart;
// (Bỏ qua phần mã designer.)
RemoteObject.RemoteObject remoteObj;
EventListener.EventListener listener;
private void ClientForm_Load(object sender, System.EventArgs e) {
RemotingConfiguration.Configure("Client.exe.config");
remoteObj = new RemoteObject.RemoteObject();
listener = new EventListener.EventListener();
}
private void cmdStart_Click(object sender, System.EventArgs e) {
// Kết nối phương thức thụ lý sự kiện ở xa
remoteObj.TaskComplete += new
RemoteObject.TaskCompleted(listener.OnTaskComplete);
// Kết nối phương thức thụ lý sự kiện cục bộ
listener.TaskComplete +=
new RemoteObject.TaskCompleted(TaskComplete);
remoteObj.StartTask();
MessageBox.Show("Task has been started.");
}
// Định nghĩa phương thức thụ lý sự kiện cục bộ