作者:陈明
Email:qmroom@126.com
Blog: http://blog.csdn.net/qmroom
(转载请注明出处)
环境:VC8+SERVER2003/XP
关键字:C/S、HTML、网络
摘要:
纯B/S的软件系统在系统实时性、桌面功能拓展性上有很大的不足;纯C/S的软件系统虽然实时性强、功能拓展强,但在界面美观和开发效率上远不如B/S软件。有没有一种更完美的解决方案呢,既能解决界面美观性、开发高效性,又有很强的实时性和很好的拓展性?解决方案当然有好多种,如Web+控件、C/S +界面控件+其他基础控件库、C/S +Flash+控件库……,我们今天要讨论的是另一种实现模式:C/S + HTML。
C/S + HTML的实现思路(我们以VC为例):服务器端用VC实现服务程序。客户端在VC中嵌入IE控件,以VC为框架HTML为交换界面。这样做有诸多优点:页面开发高效、界面修改方便、实时性高、功能可拓展性强、可在网页里嵌入其他控件、利用js控制MS OFFICE行为实现vba等。而VC部分只需要关注数据的处理,便于功能划分。
下面我们以竞价系统为例说明:
页面展示:
中间是网页,利用js接口或页面元素和vc通信。
VC从服务器端取得数据,绘制折线图。
利用js技术显示word文档,可以打开并操作word文档。
实现:
新建一个vc对话框工程,在对话框中加入LABEL,修改id为:IDC_BROWSER
定义浏览器类:
class CBrowserView : public CHtmlView {
DECLARE_DYNCREATE(CBrowserView)
DECLARE_MESSAGE_MAP()
DECLARE_DISPATCH_MAP() // 构建dispatch映射表以暴露方法或属性
…
public:
// 当在一个窗体上创建时(当作窗体的一个控件)可以指定一个控件ID按指定的控件大小位置
BOOL CreateFromStatic(UINT nID, CWnd* pParent);
// 调用JS函数
BOOL CallJScript(LPCTSTR strFunc, const CStringArray& paramArray, _variant_t* pVarResult = NULL);
// 修改网页元素的内容
BOOL PutElementHtml(LPCTSTR lpElemID, LPCTSTR lpHtml);
// 取表单元素的值
BOOL GetElementValue(LPCTSTR lpElemID, CString &Value);
// 设置元素的值
BOOL PutElementValue(LPCTSTR lpElemID, LPCTSTR lpValue);
…
}
在对话框中定义浏览器对象
CBrowserView m_Browser;
在对话框初始化时,创建浏览器 Browser.CreateFromStatic(IDC_BROWSER, this);
指定其实页Browser.Navigate2(“webs/index.html”);
JS接口部分:
在类CBrowserView中实现js接口:
BEGIN_DISPATCH_MAP(CBrowserView, CBrowserView)
DISP_FUNCTION(CBrowserView, "CloseWin", CloseWin, VT_EMPTY, VTS_NONE)
DISP_FUNCTION(CBrowserView, "ShowWin", ShowWin, VT_EMPTY, VTS_NONE)
DISP_FUNCTION(CBrowserView, "HideWin", HideWin, VT_EMPTY, VTS_NONE)
…
END_DISPATCH_MAP()
BrowserView.h头文件声明:
//常用操作
void CloseWin(); //关闭窗口
void ShowWin(); //显示窗口
void HideWin(); //隐藏窗口
BrowserView.cpp实现:
void CBrowserView::CloseWin()
{
GetMainDlg()->PostMessage(WM_CLOSE);
}
void CBrowserView::ShowWin()
{
GetMainDlg()->ShowWindow(SW_SHOW);
GetMainDlg()->UpdateWindow();
}
void CBrowserView::HideWin()
{
GetMainDlg()->ShowWindow(SW_HIDE);
GetMainDlg()->UpdateWindow();
}
同时可以触发JS事件:
//JS事件函数,由C++框架调用
function GetMessage(type)
{
do
{
if('1' == type) //最高价更新了
{
//你需要更新页面显示
//同时,若在延时阶段,你需要重置延时结束时间为GetProjYs()秒,更新页面倒计时钟
alert("最高价更新了");
break;
}
if('2' == type) //开始竞价
{
alert("开始竞价");
break;
}
if('3' == type) //正常竞价结束,进入延时竞价
{
alert("正常竞价结束,进入延时竞价");
break;
}
if('4' == type) //竞价结束
{
alert("竞价结束");
break;
}
…
if('7' == type) //报价成功【服务器接受了报价】
{
alert("报价成功");
break;
}
if('8' == type) //报价失败【服务器没有接受报价】,失败原因可以通过GetLastErrMsg()获取
{
alert("报价失败:" + external.GetLastErrMsg());
break;
}
}while(false);
}
网络通信实现部分,可采用异步多线程通信:
监听
bool CNet::Listen(int port)
{
if(IsListening())
StopListen();
sockaddr_in addr;
SOCKET sock;
sock = socket(AF_INET, SOCK_STREAM, 0); // Create socket
addr.sin_family = AF_INET; // Address family Internet
addr.sin_port = htons(port); // Assign port 'port' to this socket
addr.sin_addr.s_addr = htonl(INADDR_ANY); // No destination
if(bind(sock,(LPSOCKADDR) &addr, sizeof(addr)) == SOCKET_ERROR)
{
closesocket(sock);
long err = WSAGetLastError();
CNet::SetLastError(err);
return false;
}
if(listen(sock, BACKLOG) == SOCKET_ERROR)
{
closesocket(sock);
long err = WSAGetLastError();
CNet::SetLastError(err);
return false;
}
m_sync.Enter();
m_socket = sock;
m_addr = addr;
m_sync.Leave();
hAcceptThread = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE) AcceptThread,
this,
0,
(LPDWORD) &dwAcceptThreadID);
if(!hAcceptThread)
{
StopListen();
return false;
}
return true;
}
int CNet::AcceptWait()
{
int addr_size = sizeof(sockaddr_in);
SOCKET lsock;
SOCKET csock = INVALID_SOCKET;
sockaddr_in caddr = { 0 };
m_sync.Enter();
lsock = m_socket;
m_sync.Leave();
while(IsListening())
{
m_sync.Enter();
bool bRightSock =(lsock == m_socket);
m_sync.Leave();
if(!bRightSock) return 1;
csock = accept(lsock,(LPSOCKADDR) &caddr, &addr_size); // accept a connection
if(csock == INVALID_SOCKET)
{
int aErr = WSAGetLastError();
if(aErr == WSAEINTR)
{
StopListen();
return 1;
}
else
{
// error: could not accept
long err = WSAGetLastError();
CNet::SetLastError(err);
StopListen();
return 1;
}
}
else
{
CConnection* cAccepted = new CConnection();
cAccepted->m_socket = csock;
cAccepted->m_addr = caddr;
cAccepted->hRecvThread = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE) cAccepted->RecvThread,
cAccepted,
0,
(LPDWORD) &cAccepted->dwRecvThreadID);
if(cAccepted->hRecvThread == NULL)
{
cAccepted->Disconnect();
CNet::SetLastError(1002);
}
else
{
m_sync.Enter();
m_accepted.Add(cAccepted);
HANDLE hEvent = hAcceptEvent;
CALLBACKFUNC fncCallback = hAcceptFunc;
m_sync.Leave();
if(hEvent) SetEvent(hEvent); // if an event was given, pulse the event
if(fncCallback)(fncCallback)((DWORD) this); // if a function ptr was given, call the function
}
}
}
return 0;
}
int CNet::AcceptThread(void* pThis)
{
return((CNet*)(pThis))->AcceptWait();
}
连接:
bool CCon::Connect(const char* host, unsigned short port)
{
Disconnect();
sockaddr_in addr;
SOCKET sock;
sock = socket(AF_INET, SOCK_STREAM, 0); // create socket
addr.sin_family = AF_INET; // address family Internet
addr.sin_port = htons(port); // set server抯port number
addr.sin_addr.s_addr = inet_addr(host); // set server抯IP
if(connect(sock, (LPSOCKADDR)&addr, sizeof(addr)) == SOCKET_ERROR)
{
long err = WSAGetLastError();
CConnection::SetLastError(err);
return false;
}
linger lg;
lg.l_onoff = 1;
lg.l_linger = 10;
setsockopt(sock, SOL_SOCKET, SO_LINGER, reinterpret_cast <const char*>(&lg), sizeof(linger));
hRecvThread = CreateThread(NULL
, 0
, (LPTHREAD_START_ROUTINE) RecvThread
, this
, 0
, (LPDWORD) &dwRecvThreadID);
if(hRecvThread == NULL)
{
CConnection::SetLastError(1002);
closesocket(sock);
return false;
}
m_sync.Enter();
m_socket = sock;
m_addr = addr;
m_sync.Leave();
return true;
}
至此,C/S + HTML的框架已经搭建完成,剩下的就你们自由发挥了。J
对于这个框架,当然也有缺点,缺点就是需要在客户端安装一个程序。
由于本人水平有限,本文难免有错误疏漏之处,欢迎来信交流指正:qmroom@126.com