在我的一個J2EE項目中,當一個人的某一任務到來時,我們在一個jsp頁面顯示這個到來的任務,提示使用者需要處理該任務。
我們是通過輪詢資料庫來實現對任務的即時監控的。表USER_MESSAGE儲存使用者的任務。表結構如下:
create table USER_MESSAGE
(
MESSAGE_ID NUMBER(9) not null,
TASK_ID VARCHAR2(100),
USER_CODE VARCHAR2(10),
TASK_NAME VARCHAR2(50),
IS_READ VARCHAR2(1),
CREATE_TIME DATE,
TASK_STATE VARCHAR2(10),
CREATE_MAN VARCHAR2(10),
SEND_MAN VARCHAR2(10),
PRE_TASK_NAME VARCHAR2(50),
SERIAL_CODE NUMBER(11),
MESSAGE_TITLE VARCHAR2(200)
)
當IS_READ=‘F’時,則是新任務。
這個方案有以下弊病:
1:必須開啟頁面才可以看到任務;
2:每個用戶端都需要輪詢,當使用者多時資料庫負荷過大;
於是我做了一個輔助的訊息提示工具,當任務到來時發出提示,在系統托盤區發出提示資訊,類似QQ。
方案如下:
利用通訊端(socket)技術,編寫用戶端、服務端程式。服務端作為Message Service器,採用每隔一段時間輪詢資料庫的方式即時監控USER_MESSAGE表。
把新任務訊息發送到用戶端;用戶端識別該訊息是否是“我的”訊息,如果是“我的訊息”,則在系統托盤區閃爍,使用者點擊閃爍表徵圖,則可以看到訊息標題,點擊訊息標題下
的箭頭圖案,則可以開啟頁面進入J2EE系統。
好了,現在就來一步一步的用C++builder做這個小東東吧:
需要準備的知識如下:
一:採用自己的訊息通訊協議
二:socket編程
三:如何?系統托盤
四:資料庫中的一條記錄,如何打包發送到用戶端?
我們一點一點的逐一解決:
一:訊息通訊協議如下
Msg.h
/*
* Create Date: 2004-12-01
* Create By: 李春雷
* purpose: 協議用自訂訊息結構:
* MESSAGEINFO:任務訊息
* MsgType訊息頭:0xA登陸成功,0xB登陸失敗,0xC工作訊息,0xD任務結束標識
* LoginInfo:登陸訊息
*/
//------------------------------------------------------------------------------
struct MESSAGEINFO{ //訊息結構
int MsgType; //訊息頭:0xA登陸成功,0xB登陸失敗,0xC工作訊息,0xD任務結束標識
char MessageID[10]; //任務序號
char UsrCode[10]; //使用者帳號
//char TastName[50]; //任務名稱
char TastName[200]; //任務名稱
char CreatTime[20]; //時間
};
struct LoginInfo{ //登陸資訊
char pwd[20]; //使用者密碼
char userID[30]; //使用者ID
};
//------------------------------------------------------------------------------
服務端發送的訊息只採用的是一個結構體MESSAGEINFO(確認是否登陸成功和發出任務訊息),用戶端只發送一次登陸資訊(LoginInfo)
給服務端,其他時間只是接受服務端發來的訊息。
二:socket編程
如果沒有用過socket編程,沒關係,現在趕快學習:
C++ Builder提供了Internet套件,其中的TClientSocket和TServerSocket組件封裝了Windows的有關API,大大簡化了WinSock編程。
要通過Internet傳輸資料,至少需要一對Socket,一個Socket在用戶端,另一個Socket在伺服器端。其實TClientSocket、TServerSocket組件並不是Socket對象,
其屬性Socket將返回各自的Socket對象。TClientSocket用來處理用戶端到伺服器端之間的socket串連,TServerSocket用來處理由用戶端發來的socket串連,
一旦用戶端和伺服器端都接通了socket,用戶端和伺服器端就可以相互連信了。
我們做一個小例子:
---- 建立一新項目,建立應用程式的使用者介面:
---- 1.將組件頁切換到Internet頁,放一個TServerSocket組件和一個TClientSocket組件到表單上,這樣應用程式既可以是TCP/IP伺服器,也可以是TCP/IP客戶。
將Port屬性都設為同一個值(如1000),確定Socket之間的連線類型為NonBlocking(非阻塞方式)。
---- 2.放兩個TMemo組件到表單上,用來分別顯示雙方的談話內容,將Memo2的ReadOnly屬性設為True。
---- 3.在表單的頂部放上一個Panel組件,在其上放三個按鈕:監聽(btnlisten)、串連(btnconnect)、斷開(btndisconnect),用來啟動相應的操作。
---- 4.在表單底部放一個StatusBar組件,將其SimplePanel屬性設為True,在相應的事件處理常式中改變狀態條資訊,讓使用者隨時瞭解串連狀態。
---- 開啟標頭檔,在表單類的Private段添加兩個私人成員: bool IsServer;String Server。雙方通訊時需同時運行Chat程式,IsServer用來確定哪個
Chat程式處於伺服器端,Server用來存放伺服器的主機名稱。建立表單類的構造器如下:
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
IsServer=false;
Server="localhost";
}
---- 這裡Server被預設設為localhost,這樣程式可以在沒有連入Internet的單機上進行調試。在Windows子目錄下你可以找到hosts.sam檔案中,在該檔案
中已經將本機IP地址127.0.0.1定義了主機名稱:localhost。
void __fastcall TForm1::FormCreate(TObject *Sender)
{
btndisconnect- >Enabled=false;
}
---- 程式運行後,如果使用者按下"監聽"鈕,則將該程式設為伺服器端,這時應將TServerSocket的Active屬性設為True,使伺服器自動進入監聽狀態。
void __fastcall TForm1::btnlistenClick(TObject *Sender)
{
ClientSocket1- >Active=false;
ServerSocket1- >Active=true;
StatusBar1- >SimpleText="正在監聽...";
btnlisten- >Enabled=false;
btnconnect- >Enabled=false;
}
---- 當使用者按下"串連"鈕後,程式會彈出一個詢問框,要求使用者輸入要已連線的服務器的主機名稱,然後建立串連。
void __fastcall TForm1::btnconnectClick(TObject *Sender)
{
if(InputQuery("串連到伺服器","輸入伺服器位址:",Server)){
if(Server.Length() >0){
ClientSocket1- >Host=Server;
ClientSocket1- >Active=true;
btnlisten- >Enabled=false;
btnconnect- >Enabled=false;
btndisconnect- >Enabled=true;
}
}
}
---- 當使用者提出串連請求後,用戶端會觸發OnCreate事件,程式先在狀態條中顯示串連資訊,然後將顯示對方談話內容的Memo2清空,準備開始交談。
void __fastcall TForm1::ClientSocket1Connect(TObject *Sender,
TCustomWinSocket *Socket)
{
StatusBar1- >SimpleText="串連到:"+Server;
Memo2- >Lines- >Clear();
}
---- 在伺服器接受了客戶的請求後會觸發OnAccept事件,在這個事件處理常式中將標誌伺服器端的變數IsServer設為True,並準備開始交談。
void __fastcall TForm1::ServerSocket1Accept(
TObject *Sender,
TCustomWinSocket *Socket)
{
Memo2- >Lines- >Clear();
IsServer=true;
StatusBar1- >SimpleText="串連到:"
+Socket- >RemoteAddress;
}
---- 在建立串連後,雙方就可以在Memo1中輸入談話內容開始進行交談了,按下Enter鍵後,將所在行的文本發送出去。伺服器端的Socket的Connections
屬性返回一個數組,該數組由伺服器當前活動的串連組成。
void __fastcall TForm1::Memo1KeyDown(
TObject *Sender, WORD &Key,
TShiftState Shift)
{
if(Key==VK_RETURN){
if(IsServer)
ServerSocket1- >Socket- >Connections[0]- >SendText(
Memo1- >Lines- >Strings[Memo1- >Lines- >Count-1]);
else
ClientSocket1- >Socket- >SendText(
Memo1- >Lines- >Strings[Memo1- >Lines- >Count-1]);
}
}
---- 在本例中我們採用非阻塞傳輸方式,當其中的一方進行寫操作時,另一方會觸發OnRead事件(用戶端)或OnClientRead事件(伺服器端),這兩個事件的處理常式只是將接收到的內容添加到Memo2的後面。
Memo2- >Lines- >Add(Socket- >ReceiveText());
---- 如果在使用者建立串連後單擊"斷開"鈕,將斷開用戶端與伺服器的串連,伺服器端將觸發OnClientDisconnect事件,而用戶端則會觸發OnDisconnect事件,這時伺服器端應回到監聽狀態,等待使用者的串連;而用戶端將返回到串連前的狀態,等待使用者再次建立串連,如果有不止一個伺服器的話,可以選擇串連到其他的伺服器上。
void __fastcall TForm1::btndisconnectClick(
TObject *Sender)
{
ClientSocket1- >Close();
}
void __fastcall TForm1::ServerSocket1ClientDisconnect(
TObject *Sender,
TCustomWinSocket *Socket)
{
StatusBar1- >SimpleText="正在監聽...";
}
void __fastcall TForm1::ClientSocket1Disconnect(
TObject *Sender, TCustomWinSocket *Socket)
{
btnlisten- >Enabled=true;
btnconnect- >Enabled=true;
btndisconnect- >Enabled=false;
StatusBar1- >SimpleText="";
}
---- 此外在用戶端還應該增加錯誤捕獲機制,當使用者輸入無效的伺服器名或伺服器端沒有處於監聽狀態時能夠及時給使用者反饋資訊。
void __fastcall TForm1::ClientSocke
t1Error(TObject *Sender,
TCustomWinSocket *Socket,
TErrorEvent ErrorEvent, int &ErrorCode)
{
StatusBar1- >SimpleText="無法串連到:
"+Socket- >RemoteHost;
ErrorCode=0;
}
以上步驟完成後,socket就可以run了。是不是沒有想象中的困難?
三:關於托盤,bcb給出了很簡單的實現方法:
下面讓我們來編一個簡單的Tary程式:
1、建立工程,添加一個TrayIcon組件、一個PopupMenu組件和一個ImageList組件。它們的Name屬性都用預設的名字:TrayIcon1、
PopupMenu1、ImageList1。
2、設定TrayIcon1的屬性,如下:
屬性 值
Animate true
AnimateInterva 1000
Hide true
Hint Tary示範程式
IconIndex 0
Icons ImageList1
Name TrayIcon1
PopupMenu PopupMenu1
PopupMenuOn imRightClickUp
RestoreOn imDoubleClick
Visible true
3、雙擊PopupMenu1,快顯功能表設計器,隨意地加入幾個功能表項目。
4、雙擊ImageList1,加入支援的圖片(*.ico、*.bmp)。
到此,不用編寫一句程式碼,一個簡單的Tary程式就做好了。按F9編譯運行,將滑鼠移動到Tary上面就會出現“Tary示範程式”的提示資訊;
在Tary上單擊滑鼠右鍵快顯功能表Popmenu1;按下程式視窗的最小化按鈕,程式最小化後隱藏工作列上的標題列;雙擊Tary將會恢複程式最小化;
而且,Tary表徵圖以1000毫秒(1秒)的速度變換。夠簡單了吧?!
四:資料庫中的一條記錄,如何打包發送到用戶端?
我們把資料庫中的一條記錄,放在一個結構體中,然後把該結構體send出去。
下面是一個簡單的例子:
.h
struct TUSERINFO
{
int NO; //使用者組別
char UsrID[20]; //使用者帳號
};
.cpp
void __fastcall TForm1::Button1Click(TObject *Sender)
{ TUSERINFO *tst = new TUSERINFO[2];
tst[0].NO=1;
tst[1].NO=2;
String tmp = "正常";
memcpy(tst[0].UsrID,tmp.c_str(),tmp.Length()+1);
tmp = "不正常";
memcpy(tst[1].UsrID,tmp.c_str(),tmp.Length()+1);
ClientSocket1->Socket->SendBuf(tst,24);
delete tst;}
void __fastcall TForm1::ServerSocket1ClientRead(TObject *Sender,
TCustomWinSocket *Socket)
{
TUSERINFO *tst = new TUSERINFO[2];
Socket->ReceiveBuf(tst,24);
ShowMessage(tst[0].UsrID);
ShowMessage(tst[1].UsrID);
}
上面的例子很簡單,可以實現struct的發送,但存在一個小問題:
如果要傳2條記錄,必須SendBuf(tst,24*2);那麼接受的時候必須ReceiveBuf(tst,24*2);
而我發的條數是不確定的,時多時少,有什麼辦法可以我發多少,客戶就接多少?
其實也簡單解決:
每次只發一個struct,用for迴圈發出去就可以了。
通過了以上的一二三四的技術準備,訊息提示工具的具體實現就很明朗了,只是工作量的問題了。
在訊息提示工具中,我採用ADO訪問資料庫。
另外,為了到客戶那實施起來方便,我採用了設定檔,程式先讀取設定檔,然後運行。