Tính năng Thread cho phép một chương trình xử lý đồng thời nhiều công việc. Chẳng hạn trong chương trình Go!Zilla Ver 3.2 Copyright 1997-98 Gizmonet, một lúc có thể download nhiều file từ cùng hoặc nhiều website khác nhau. Nó còn cho người dùng định độ ưu tiên cho từng thread.
130 trang |
Chia sẻ: vietpd | Lượt xem: 1544 | Lượt tải: 1
Bạn đang xem trước 20 trang tài liệu Đề tài Kinh doanh nhà trên mạng Client/Server, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
Giới thiệu Thread trong Delphi
Thread là gì?
Tính năng Thread cho phép một chương trình xử lý đồng thời nhiều công việc. Chẳng hạn trong chương trình Go!Zilla Ver 3.2 Copyright Ĩ 1997-98 Gizmonet, một lúc có thể download nhiều file từ cùng hoặc nhiều website khác nhau. Nó còn cho người dùng định độ ưu tiên cho từng thread.
Bạn có thể dùng BorlandC, Delphi, Cbuilder, Visual C ... để viết nên những chương trình có tính năng thread. Nhưng dù bạn lập trình bằng ngôn ngữ nào đi chăng nữa, thì cũng phải sử dụng những hàm API của Microsoft cung cấp như CreateThread, SetThreadPriority ... một cách trực tiếp hoặc gián tiếp.
Ứng dụng của Thread
Thread được sử dụng rất nhiều trên các lĩnh vực như đồ họa (xử lý nhiều hình ảnh một lúc), trong trò chơi (làm nền hoặc mỗi nhân vật trong trò chơi là một thread), truyền tin (tín hiệu xuất ra được xử lý tức thời) ...
Thread làm cho chương trình trở nên đơn giản, dễ hiểu khi ta phải viết những chương trình đòi hỏi xử lý đồng thời nhiều công việc.
Sử dụng TThread trong Delphi
Sử dụng TThread của Delphi để viết nên những chương trình có tính năng Thread thì rất dễ. Cấu trúc cơ bản một unit có sử dụng Thread như sau :
unit Unit1;
interface
uses
Classes;
type
TMyThread = class(TThread)
private
{ Private declarations }
protected
procedure Execute; override;
end;
implementation
{ TMyThread }
procedure TMyThread.Execute;
begin
{ phần mã có tính năng Thread đặt ở đây}
end;
end.
Một điều quan trọng cần lưu ý khi viết Thread là những đối tượng hay hàm nào nằm trong thư viện VCL của Delphi phải gọi bằng phương pháp Synchronize.
Ví dụ :
Synchronize(UpdateCaption);
Với UpdateCaption :
procedure TMyThread.UpdateCaption;
begin
Form1.Caption := 'Updated in a thread';
end;
Các method đáng chú ý khi viết Thread
* Constructor Create(CreateSuspended: Boolean);
Constructor Create để khởi tạo một đối tượng Thread.
Nếu CreateSuspended = False, Execute được gọi ngay lập tức
Nều CreateSuspended = True, Execute không được gọi cho đến khi Resume được gọi.
* Destructor Destroy; override;
Destructor Destroy hủy đối tượng thread và giải phóng bộ nhớ nó chiếm giữ.
* Procedure DoTerminate; virtual;
DoTerminate làm cho thread gọi sự kiện OnTerminate.
* Procedure Execute; virtual; abstract;
Procedure Execute là một method abstract phải được override bởi lớp con. Nó chứa mã khi bắt đầu Thread.
* Procedure Resume;
Procedure Resume tiếp tục gọi một thread tạm ngưng.
* Procedure Suspend;
Procedure Suspend tạm ngưng một thread đang chạy.
* Procedure Synchronize(Method: TThreadMethod);
Procedure Synchronize thực hiện method dùng thư viện VCL.
* Procedure Terminate;
Procedure Terminate ra hiệu cho thread dừng bằng cách gán property Terminated là True.
Các property đáng chú ý khi viết Thread
* property FreeOnTerminate: Boolean;
property FreeOnTerminate định cho đối tượng thread tự động giải phóng nhớ nếu FreeOnTerminate = True.
* property Handle: Thandle;
property Handle là handle của thread.
* property Priority: TThreadPriority;
property Priority xác định độ ưu tiên giữa các thread khác trong tiến trình.
TThreadPriority = (tpIdle, tpLowest, tpLower, tpNormal, tpHigher, tpHighest,
tpTimeCritical);
+ tpIdle : Chỉ thực hiện khi hệ thống Windows rảnh rỗi, nó không ngắt ngang đối tượng thread khác để chạy.
+ tpLowest : Mức độ ưu tiên dưới 2 điểm so với bình thường.
+ tpLower : Mức độ ưu tiên dưới 1 điểm so với bình thường.
+ tpNormal: Mức độ ưu tiên bình thường.
+ tpHigher : Mức độ ưu tiên trên 1 điểm so với bình thường.
+ tpHigher : Mức độ ưu tiên trên 1 điểm so với bình thường.
+ tpHighest : Mức độ ưu tiên trên 2 điểm so với bình thường.
+ tpTimeCritical : Mức độ ưu tiên cao nhất.
* property Suspended: Boolean;
property Suspended chỉ định thread tạm ngưng nếu bằng true.
* property Terminated: Boolean;
property Terminated chỉ định rằng thread yêu cầu dừng nếu bằng true.
* property ThreadID: THandle;
property ThreadID nhận biết duy nhất cho đối tượng thread xuyên suốt hệ thống. ThreadID thì khác property Handle.
Tranh chấp dữ liệu khi viết multi-thread
Khi bạn viết một chương trình multi-threaded, có trường hợp 2 thread cùng truy cập đến một vùng dữ liệu, chẳng hạn như là một file, điều này có thể làm cho file bị hư hoặc có thể máy của bạn sẽ bị treo. Để giải quyết vấn đề này, bạn sẽ cho các thread khác không thể truy cập đến vùng nhớ mà thread hiện hành đang sửa đổi dữ liệu cho đến khi dữ liệu được xử lý xong.
Critical sections được thiết kế mà một phần resource được dùng riêng biệt và tách rời khỏi các tiến trình khác. Bạn hãy tưởng tượng rằng có hai người cùng đi song song trên một con đường, bỗng nhiên xuất hiện một cây cầu hẹp, chỉ đủ để một người đi qua. Bình thường một người sẽ nhường đường cho người kia đi trước. Khi mà một người qua cầu rồi thì người kia sẽ tiếp tục đi qua cầu và họ lại tiếp tục đi song song với nhau.
Critical sections cũng tương tự như việc đi qua cầu. Bạn hãy nhìn hình ở trên đây, hai thread (mũi tên màu cyan) đang chạy trên tiến trình đơn (vùng màu đen), khi các thread chạy đến critical sections (vùng màu xanh dương), một thread sẽ nhường cho thread kia đi qua. Trong thực tế, một thread sẽ đi đến critical sections, nó có thể tạo ra một cờ ra hiệu cho các thread khác không được liên lạc với data.
Để thực hiện critacal sections trong mã chương trình viết bằng Borland Delphi, bạn hãy làm dấu khối trên mỗi thread mà truy cập đến vùng nhớ chung bằng các hàm EnterCriticalSection và LeaveCriticalSection. Điều đầu tiên, trong chương trình, bạn phải định nghĩa một critical trên bộ nhớ. Điều này thực hiện được bằng cách gọi hàm InitializeCriticalSection. Hàm này được khai báo như sau:
procedure InitializeCriticalSection(var lpCriticalSection: TRTLCriticalSection); stdcall;
InitializeCriticalSection lấy một tham số kiểu TRTLCriticalSection, mà trường của nó lưu trữ thông tin về critical section. Bạn không cần quan tâm về cấu trúc của nó, bởi bì chẳng cần thao tác trên đó. Hãy khai báo một biến toàn cục kiểu TRTLCriticalSection.
Sau khi làm critical xong, hãy hủy nó bằng hàm DeleteCriticalSection. Cũng giống như InitializeCriticalSection, hàm này có một tham số kiểu TRTLCriticalSeciont. Trong trường hợp này, hai tham số biến trong DeleteCriticalSection và InitializeCriticalSection là một. Trong chương trình bạn hãy gõ vào các lệnh sau:
unit myCritical;
Interface
var
CritSect : TRTLCriticalSection;
…
Implementation
…
initialization
InitializeCriticalSection(CritSect);
finalization
DeleteCriticalSection(CritSect);
end.
Sử Dụng Critical Section trong chương trình
Đặt critical section để dùng trong thread thì rất dễ. Bạn chỉ cần xem một đoạn mã ví dụ dưới đây thì sẽ biết ngay:
procedure TMyThread.ChangeData;
begin
...
EnterCriticalSection(CritSect);
...Các thao tác sửa đổi dữ liệu dùng chung đặt ở đây
LeaveCriticalSection(CritSect);
end;
Giao tiếp giữa các máy mô hình Client/Server
Trong đề tài thực tập này, ta dùng ngôn ngữ lập trình Delphi để giao tiếp giữa các máy qua mô hình Client/Server, cụ thể là sử dụng các control như : TClientSocket, ClientWinSocket, TServerSocket, TServerWinSocket. Hình dưới đây liệt kê các lớp của socket control:
TClientSocket
TClientSocket được dùng để kết nối đến một Server Socket. Để sử dụng control này,
bạn nên làm các thao tác sau đây:
Viết mã cho OnRead Event
Định Address property đến Server address.
Định Port là giá trị Port Property của TServerSocket muốn kết nối.
Định ClientType property đến ctNonBlocking.
Gọi Open method để kết nối đến remote Server.
Ta nói thêm về ClientType property.
Nếu ClientType = ctNonBlocking, khi chương trình đọc hay ghi dữ liệu đến socket, chương trình sẽ thực hiện lệnh I/O này rồi tiếp tục thực hiện chương trình ngay cả thao tác I/O này chưa thực hiện xong.
Nếu ClientType = ctBlocking, chương trình sẽ tạm không chạy tiếp mà chờ cho đến khi thao tác I/O thực hiện xong.
Trong chương trình này, ta sẽ cho ClientType = ctNonBlocking.
Ghi dữ liệu đến server
Khi bạn muốn gửi dữ liệu đi, dùng lệnh tương tự như sau:
MyClientSocket.Socket.SendText("this is a test");
TClientSocket TClientWinSocket
Hoặc dùng lệnh:
BytesWritten := MyClientSocket.Socket.SendBuf(buf, count);
Đọc dữ liệu từ server
Bạn có thể đọc dữ liệu từ Server bằng cách dùng lệnh ReceiveText hay ReceiveBuf đặt trong OnRead Event.
procedure TChatForm.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
StringData := Socket.ReceiveText;
end;
TClientWinSocket
Ta sẽ mô tả một số hàm quan trọng trong TClientWinSocket.
function ReceiveBuf(var Buf; Count: Integer): Integer;
ReceiveBuf đọc dữ liệu từ server socket, số byte đọc do người dùng định. Dữ liệu đọc được sẽ chứa trong Buf, giá trị trả về là số byte đã thực sự đọc được.
function ReceiveText: string;
Dùng ReceiveText để đọc một chuỗi từ server socket.
function SendBuf(var Buf; Count: Integer): Integer;
Dùng SendBuf để truyền dữ liệu trong Buf đến server socket. Hàm này trả về số byte thực sự đã truyền.
procedure SendText(const S: string);
Dùng SendText để truyền một chuỗi đến server socket.
function SendStream(AStream: TStream): Boolean;
Dùng SendStream để truyền tất cả thông tin trong AStream đến server socket. Hàm này trả về true nếu dữ liệu đã thực sự truyền đi. Bạn không cần phải giải phóng bộ nhớ cho AStream, windows socket sẽ giải phóng nó cho bạn.
function SendStreamThenDrop(AStream: TStream): Boolean;
SendStreamThenDrop làm việc giống như SendStream, nhưng khi thi hành xong lệnh này, nó sẽ tự động terminates.
procedure Lock;
Gọi Lock trước khi bắt đầu đoạn mã không thread-safe. Bạn đừng dùng Lock khi thực hiện đoạn mã như đọc và ghi lên blocking connection bởi vì nếu dùng sẽ thừa. Với ClientType là ctBlocking, chương trình đã tự động tạm ngưng cho đến khi các thao tác I/O thực hiện xong.
procedure UnLock;
Gọi UnLock ở cuối đoạn mã không thread-safe mà trước đó đã dùng lệnh Lock, khi ấy các thread khác lại tiếp tục thi hành tiếp.
TserverSocket
TServerSocket điều khiển server socket connections trong TCP/IP server. TServerSocket sẽ thiết lập kết nối với một máy khác khi máy này ra yêu cầu. Server socket có thể kết nối đến một số client và bạn có thể giao tiếp với các client này.
Truyền dữ liệu đến một Client
MyServer.Socket.Connections[2].SendText("this is a test");
TServerSocket
TServerWinSocket
TCustomWinSocket[array]
Câu lệnh ở trên gửi dữ liệu đến client thứ 3.
Đọc dữ liệu từ một client
Để đọc dữ liệu từ một Client, bạn hãy đón sự kiện OnClientRead. Bạn có thể ghi lệnh trong event OnClientRead như sau:
procedure TChatForm.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
Data : string;
begin
Data := Socket.Receive;
ShowMessage(‘Data sent : ‘ + Data);
end;
Bạn có thể biết chính xác Client thứ mấy đã gửi dữ liệu đến server socket
Đoạn mã sau đây sẽ cho biết client nào đã gửi dữ liệu đến server socket:
procedure TChatForm.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
Data : string;
Client, Total : integer;
begin
Total := MyServer.Socket.ActiveConnections;
for Client := 0 to Total do
if (MyServer.Socket.Connections[Client] = Socket then
Break;
Data := Socket.Receive;
ShowMessage(‘Data sent from client[‘ + IntToStr(Client) + ‘] : ‘ + Data);
end;
TServerWinSocket
Hai nhiệm vụ chính của TServerWinSocket là:
Lắng nghe yêu cầu kết nối của Client
TServerWinsocket luôn lắng nghe yêu cầu kết nối từ client. Khi có một yêu cầu kết nối gửi tới, TServerWinSocket sẽ tạo ra một socket control mới (kiểu TServerWinSocket) để liên lạc với client đó. socket control mới này sẽ được thêm vào một danh sách được mô tả dưới đây. Công việc này là hoàn toàn tự động, người lập trình không cần quan tâm.
Quản lý dãy các client socket đã kết nối với server socket
Để liên lạc với một client, bạn phải chọn một socket trong danh sách các client, sau đó bạn có thể thực hiện các thao tác đọc ghi cho client này.
Bạn có thể dùng ActiveConnections property để giao tiếp với tất cả các client.
var
TotalClients, x : integer;
begin
TotalClients := MyServer.Socket.ActiveConnections;
for x := 0 to TotalClients do
MyServer.Socket.Connections[x].SendText("testing...");
end;
TServerClientWinSocket
TServerClientWinSocket giống như TClientWinSocket, cả hai đều là con của TCustomWinSocket. Sự khác nhau giữa TClientWinSocket và TServerClientWinSocket là TClientWinSocket liên lạc với Server socket còn TServerClientWinSocket liên lạc với một client nào đó.
Giới thiệu chương trình
Nội dung và ý nghĩa chương trình
Chương trình được xây dựng trên mô hình client/server cho phép nhiều client cùng truy cập đến dữ liệu nằm trên máy server. Chương trình được dùng bởi 3 nhóm đối tượng là khách hàng, người cập nhật dữ liệu về nhà cửa trên máy server và người quản lý.
Nếu là máy server, trên máy bạn phải có tất cả các tập tin dữ liệu của chương trình thì mới được.
Nếu là khách hàng hoặc người cập nhật dữ liệu, bạn chỉ cần một chương trình thực thi là đủ. Có thể bạn cũng chẳng cần phải chép chương trình về máy mình làm gì mà thực thi nó bằng cách attach đến máy server để thực thi chương trình.
Bất kỳ người dùng nào muốn truy cập đến chương trình cần phải được đăng ký một username, password và quyền sử dụng.
Khi chương trình được thực thi, một khung trao đổi hiện ra như sau:
* Nếu là khách hàng, bạn phải nhập các tham số sau:
+ HostName : Tên Computer của máy server.
+ User Name: Tên đăng ký.
+ Password : Mật mã đã đăng ký.
+ Level : Chọn Khách Hàng
Khi khách hàng yêu cầu một việc chẳng hạn đăng ký thuê một căn nhà, chương trình (client) sẽ gửi một thông điệp thuê nhà đến máy server. Máy server sẽ nhận được yêu cầu này và tạo ra một thread mới để giải quyết công việc đăng ký thuê nhà cho client. Tất nhiên việc tranh chấp dữ liệu phải được đề cập khi thực hiện thao tác trên. Sau khi thực hiện xong, chương trình server sẽ gửi trả kết quả về cho máy client.
Người khách hàng có thể thực hiện các công việc như sau:
+ Xem thông tin về một căn nhà
+ Liệt kê các căn nhà thỏa một số điều kiện do khách hàng định như: Nhà là thuê hay bán; Nhà nội hay ngoại thành; Giá tiền nhỏ nhất và lớn nhất trong khoảng nào; Diện tích nhà nhỏ nhất và lớn nhất là bao nhiêu.
+ Đăng ký thuê một căn nhà.
+ Đăng ký mua một căn nhà.
* Nếu là người cập nhật từ điển dữ liệu nhà, bạn phải nhập các tham số sau:
+ HostName : Tên Computer của máy server.
+ User Name: Tên đăng ký.
+ Password : Mật mã đã đăng ký.
+ Level : Chọn Người Cập Nhật Thông Tin
Khi người cập nhật dữ liệu yêu cầu một việc chẳng hạn thêm một thông tin nhà mới, chương trình (client) sẽ gửi một thông điệp thêm thông tin nhà mới đến máy server. Chương trình server sẽ nhận được yêu cầu này và tạo ra một thread để giải quyết công việc thêm thông tin nhà mới cho client. Tất nhiên việc tranh chấp dữ liệu phải được đề cập khi thực hiện thao tác trên. Sau khi thực hiện xong, nó sẽ gửi trả kết quả về cho máy client.
Người cập nhật dữ liệu có thể làm các thao tác sau:
+ Xem thông tin về một căn nhà
+ Liệt kê các căn nhà thỏa một số điều kiện do khách hàng định như: Nhà là thuê hay bán; Nhà nội hay ngoại thành; Giá tiền nhỏ nhất và lớn nhất trong khoảng nào; Diện tích nhà nhỏ nhất và lớn nhất là bao nhiêu.
+ Thêm thông tin về một căn nhà mới.
+ Sửa đổi thông tin một căn nhà đã có rồi.
+ Bớt thông tin về một căn nhà khỏi từ điển.
* Nếu là người quản lý (server), bạn phải nhập các tham số sau:
+ User Name: Tên đăng ký.
+ Password : Mật mã đã đăng ký.
+ Level : Chọn Người Quản Lý
Các tập tin dữ liệu đều nằm trên máy server. Các client không thể truy cập trực tiếp đến dữ liệu được mà chỉ gửi yêu cầu đến cho server thực hiện.
Khi Server nhận được một yêu cầu nào đó từ client, nó sẽ tạo ra một thread mới để giải quyết cho vấn đề này. Nếu dữ liệu bị thay đổi, chương trình sẽ phải cập nhật thông tin ngay trên màn hình của toàn bộ các máy client.
Người quản lý có thể làm các thao tác sau:
+ Xem thông tin về một căn nhà
+ Liệt kê các căn nhà thỏa một số điều kiện do khách hàng định như: Nhà là thuê hay bán; Nhà nội hay ngoại thành; Giá tiền nhỏ nhất và lớn nhất trong khoảng nào; Diện tích nhà nhỏ nhất và lớn nhất là bao nhiêu.
+ Thêm thông tin về một căn nhà mới.
+ Sửa đổi thông tin một căn nhà đã có rồi.
+ Bớt thông tin về một căn nhà khỏi từ điển.
+ Thống kê các căn nhà đã cho thuê. Chương trình liệt kê danh sách các nhà đã cho thuê kèm theo mọi thông tin về các nhà đã thuê này.
+ Người quản lý có thể xem nhà nào đã hết hạn cho thuê để đưa nhà này trở về danh sách nhà có thể cho thuê.
+ Thống kê các căn nhà đã bán. Người quản lý có thể xem danh sách tất cả các nhà đã bán và cả mọi thông tin về nó nữa.
Ý nghĩa chính trong chương trình này là nhằm giải quyết ba vấn đề chính sau đây:
Dùng thread để phân chia thời gian thực hiện công việc của từng client.
Ví dụ một client nào đó yêu cầu đến server một tác vụ mà đòi hỏi một thời gian lâu mới giải quyết xong. Nếu không phân chia thời gian thì trong lúc thực hiện tác vụ này cho client đó, các client khác không thể ra lệnh được cho máy server, chương trình chạy không tốt.
(2) Giải quyết tranh chấp để bảo toàn dữ liệu. Nếu cùng một lúc, hai hay nhiều client cùng đòi quyền sửa chữa thông tin về nhà cửa, nếu không tính đến việc này, dữ liệu có nguy cơ bị hư hỏng.
Đây là một chương trình ứng dụng thực sự vì nó có hầu hết các tính năng của một chương trình kinh doanh nhà đất trên mạng client/server.
Phần tiếp theo ta sẽ nói chi tiết về tổ chức dữ liệu của chương trình, thông tin về nhà cửa, cách giao tiếp giữa các máy, tranh chấp dữ liệu, phân chia CPU cho client…
Các vấn đề chính cần giải quyết
Tổ chức dữ liệu trong chương trình
Thông tin về nhà cửa gồm có:
THomeInfo = packed record
TenNha : string[30]; // Tên nhà
CapNha : byte; // Cấp nhà
ChieuDai : real // Chiều dài nhà
ChieuRong : real; // Chiều rộng nhà
TangLau : byte; // Số lầu
PhongKhach : byte; // Số phòng khách
PhongNgu : byte; // Số phòng ngủ
PhongTam : byte; // Số phòn