前面讨论的开发网络通信的经典入门采用的是WSAAsyncSelect的异步I/O模型,本文将讨论WSAEventSelect异步I/O模型。
WSAEventSelect模型有点类似WSAAsyncSelect模型,不同的是他不是用消息映射的方式来响应网络事件,而是用等待多重事件的方式来响应网络事件。下面是用WSAEventSelect模型和多线程机制做的一个简单的服务器程序的.cpp和.h文件,应用程序基于MFC的标准对话框。实现接受多个客户端的连接请求,并记录下所有客户端的相关信息,显示在列表框中。
// serverDlg.cpp : implementation file //
#include "stdafx.h" #include "server.h" #include "serverDlg.h"
#ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif
SOCKET Accept; file://用于新的一个连接通信的套接字 WSAEVENT NewEvent; file://对应于新的套接字的新事件 SOCKET Socket[WSA_MAXIMUM_WAIT_EVENTS]; file://存放所有生成的套接字 WSAEVENT Event[WSA_MAXIMUM_WAIT_EVENTS]; file://存放所有生成的事件对象 int EventTotal; file://创建的事件总数 int Index; file://等待多重事件函数的返回值 WSANETWORKEVENTS NetworkEvents; file://用于接收套接字上发生的网络事件类型以及可能出现的错 误代码
///////////////////////////////////////////////////////////////////////////// // CAboutDlg dialog used for App About
class CAboutDlg : public CDialog { public: CAboutDlg();
// Dialog Data file://{{AFX_DATA(CAboutDlg) enum { IDD = IDD_ABOUTBOX }; file://}}AFX_DATA
// ClassWizard generated virtual function overrides file://{{AFX_VIRTUAL(CAboutDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support file://}}AFX_VIRTUAL
// Implementation protected: file://{{AFX_MSG(CAboutDlg) file://}}AFX_MSG DECLARE_MESSAGE_MAP() };
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD) { file://{{AFX_DATA_INIT(CAboutDlg) file://}}AFX_DATA_INIT }
void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); file://{{AFX_DATA_MAP(CAboutDlg) file://}}AFX_DATA_MAP }
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog) file://{{AFX_MSG_MAP(CAboutDlg) // No message handlers file://}}AFX_MSG_MAP END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////////// // CServerDlg dialog
CServerDlg::CServerDlg(CWnd* pParent /*=NULL*/) : CDialog(CServerDlg::IDD, pParent) { file://{{AFX_DATA_INIT(CServerDlg) // NOTE: the ClassWizard will add member initialization here file://}}AFX_DATA_INIT // Note that LoadIcon does not require a subsequent DestroyIcon in Win32 m_Connectnum = 0; m_NetworkID = 0; EventTotal = 0; for(int i = 0; i < MAX_CLIENT_NUM; i++) { ZeroMemory(&m_ClientInfo[i], sizeof(client_info)); }
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
void CServerDlg::DoDataExchange(CDataExchange* pDX) { CDialog::DoDataExchange(pDX); file://{{AFX_DATA_MAP(CServerDlg) // NOTE: the ClassWizard will add DDX and DDV calls here file://}}AFX_DATA_MAP }
BEGIN_MESSAGE_MAP(CServerDlg, CDialog) file://{{AFX_MSG_MAP(CServerDlg) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_TIMER() file://}}AFX_MSG_MAP END_MESSAGE_MAP()
///////////////////////////////////////////////////////////////////////////// // CServerDlg message handlers
BOOL CServerDlg::OnInitDialog() { CDialog::OnInitDialog();
// Add "About..." menu item to system menu.
// IDM_ABOUTBOX must be in the system command range. ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { CString strAboutMenu; strAboutMenu.LoadString(IDS_ABOUTBOX); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } }
// Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here
WSADATA wsaData; int ret;
ret = WSAStartup(MAKEWORD(2,2), &wsaData); if(ret != 0) { MessageBox("初始化套接字失败!"); return FALSE; }
file://创建一个套接字 m_ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(m_ListenSocket == INVALID_SOCKET) { MessageBox("创建套接字失败!"); closesocket(m_ListenSocket); WSACleanup(); return FALSE; }
file://绑定到指定的端口上 sockaddr_in localaddr; localaddr.sin_family = AF_INET; localaddr.sin_port = htons(1688); localaddr.sin_addr.s_addr = 0;
if(bind(m_ListenSocket, (const struct sockaddr*)&localaddr, sizeof(sockaddr)) == SOCKET_ERROR) { MessageBox("绑定地址失败!"); closesocket(m_ListenSocket); WSACleanup(); return FALSE; } NewEvent = WSACreateEvent(); file://创建一个新的事件对象
file://将创建的事件对象与前面创建的套接字关联在一起,并注册网络事件类型 if(WSAEventSelect(m_ListenSocket, NewEvent, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR) { MessageBox("注册网络事件失败!"); closesocket(m_ListenSocket); WSACleanup(); return FALSE; }
file://让创建的套接字处于监听状态 listen(m_ListenSocket, 5);
Event[EventTotal] = NewEvent; Socket[EventTotal] = m_ListenSocket; EventTotal++;
file://设置List控件的图象列表 HICON hIcon;
m_imagelist.Create(16, 16, 0, 4, 4); // 32, 32 for large icons hIcon = AfxGetApp()->LoadIcon(IDI_CLIENT_INFO); m_imagelist.SetBkColor (RGB(248,232,224)); m_imagelist.Add(hIcon);
pList = (CListCtrl*)GetDlgItem(IDC_CLIENT_INFO); pList->SetImageList(&m_imagelist, LVSIL_SMALL); pList->SetBkColor(RGB(248,232,224)); pList->SetTextBkColor(RGB(248,232,224));
pList->InsertColumn(0," 客户名",LVCFMT_CENTER,90, 0); pList->InsertColumn(1,"网络ID",LVCFMT_CENTER,50,1); pList->InsertColumn(2,"IP地址",LVCFMT_CENTER,100,2); pList->InsertColumn (3,"登录时间",LVCFMT_CENTER,120,3); pList->InsertColumn (4,"在线时间",LVCFMT_CENTER,100,4);
SetTimer(1, 1000, NULL);
file://启动核心处理线程 AfxBeginThread(KernelWorkThread,this,THREAD_PRIORITY_NORMAL);
return TRUE; // return TRUE unless you set the focus to a control }
void CServerDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialog::OnSysCommand(nID, lParam); } }
// If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework.
void CServerDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
// Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2;
// Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } }
// The system calls this to obtain the cursor to display while the user drags // the minimized window. HCURSOR CServerDlg::OnQueryDragIcon() { return (HCURSOR) m_hIcon; }
file://核
心处理线程, 响应并处理各种网络事件 UINT KernelWorkThread(LPVOID pParam) { int len = sizeof(sockaddr);
CServerDlg* dlg; dlg = (CServerDlg*)pParam;
while(1) { Index = WSAWaitForMultipleEvents(EventTotal, Event, FALSE, WSA_INFINITE, FALSE);
WSAEnumNetworkEvents(Socket[Index - WSA_WAIT_EVENT_0], Event[Index - WSA_WAIT_EVENT_0], &NetworkEvents); if(NetworkEvents.lNetworkEvents & FD_ACCEPT) file://连接事件 { if(NetworkEvents.iErrorCode[FD_ACCEPT_BIT] != 0) { dlg->MessageBox("接受连接事件失败!"); break; }
Accept = accept(Socket[Index - WSA_WAIT_EVENT_0], (struct sockaddr*)&(dlg->clientaddr), &len); if(Accept == INVALID_SOCKET) { dlg->MessageBox("接受连接失败!"); break; } if(EventTotal > WSA_MAXIMUM_WAIT_EVENTS) { dlg->MessageBox("连接个数溢出,拒绝接受!"); break; }
NewEvent = WSACreateEvent();
if(WSAEventSelect(Accept, NewEvent, FD_READ | FD_WRITE | FD_CLOSE) == SOCKET_ERROR) { dlg->MessageBox("注册网络事件失败!"); closesocket(Accept); break; }
Event[EventTotal] = NewEvent; Socket[EventTotal] = Accept; EventTotal ++; }
if(NetworkEvents.lNetworkEvents & FD_READ) file://读取数据事件 { if(NetworkEvents.iErrorCode[FD_READ_BIT] != 0) { dlg->MessageBox("读事件失败!"); break; }
if(dlg->OnReceive(Socket[Index - WSA_WAIT_EVENT_0]) == FALSE) { dlg->MessageBox("读取数据失败!"); break; } }
if(NetworkEvents.lNetworkEvents & FD_CLOSE) file://关闭套接字事件 { if(NetworkEvents.iErrorCode[FD_CLOSE_BIT] != 0) { dlg->MessageBox("关闭事件失败!"); break; }
if(dlg->OnClose(Socket[Index - WSA_WAIT_EVENT_0]) == FALSE) { dlg->MessageBox("关闭套接字失败!"); break; } } }
return 0; }
BOOL CServerDlg::OnClose(SOCKET pSocket) { int i, exitnum; for(i = 0; i < m_Connectnum; i++) { if(m_ClientInfo[i].Client_Socket == pSocket) { exitnum = i; } } for(i = exitnum; i < m_Connectnum; i++) { memcpy(&m_ClientInfo[i], &m_ClientInfo[i+1], sizeof(client_info)); }
m_Connectnum --;
file://向所有客户端发送在线客户信息的报文 cmd_client_info ClientInfo; ClientInfo.cmd_type = CMD_CLIENT_INFO; ClientInfo.client_num = m_Connectnum; for(i=0; i<=m_Connectnum; i++) { ClientInfo.Networks_ID[i] = m_ClientInfo[i].Network_ID; strcpy(ClientInfo.users_name[i], m_ClientInfo[i].User_Name); strcpy(ClientInfo.clients_ipaddr[i], inet_ntoa(m_ClientInfo[i].Client_Addr.sin_addr)); } for(i=0; i<=m_Connectnum; i++) { send(m_ClientInfo[i].Client_Socket, (char*)&ClientInfo, sizeof(cmd_client_info), NULL); } closesocket(pSocket);
pList->DeleteItem(exitnum); return TRUE; }
BOOL CServerDlg::OnReceive(SOCKET pSocket) { static char rcvbuf[65535]; file://接收缓冲区 int ret; int offset=0; find_type* pFindType; int i = 0; CTime m_current_time=CTime::GetCurrentTime (); CString strTime = m_current_time.Format("%c"); CString networkid; file://列表框的网络ID项
ret = recv(pSocket, rcvbuf, 65535, 0); if(ret == OPERATION_ERROR) return FALSE;
while(offset < ret) { pFindType = (find_type*)(rcvbuf+offset); switch(pFindType->cmd_type) { case CMD_HELLO: cmd_hello Hello; memcpy(&Hello, rcvbuf+offset, sizeof(cmd_hello)); offset+=sizeof(cmd_hello);
cmd_hello_resp HelloResp; m_NetworkID ++; HelloResp.cmd_type = CMD_HELLO_RESP; HelloResp.Network_ID = m_NetworkID; strcpy(HelloResp.user_name, Hello.user_name);
memcpy((struct sockaddr*)&(m_ClientInfo[m_Connectnum].Client_Addr), (const struct sockaddr*)&clientaddr, sizeof(sockaddr)); m_ClientInfo[m_Connectnum].Client_Socket = Accept; strcpy(m_ClientInfo[m_Connectnum].User_Name, HelloResp.user_name); m_ClientInfo[m_Connectnum].Network_ID = m_NetworkID; m_ClientInfo[m_Connectnum].Login_Time = m_current_time; send(pSocket, (char*)&HelloResp, sizeof(cmd_hello_resp), NULL);
file://向登录的客户端发送回应报文 Sleep(200);
cmd_client_info ClientInfo; ClientInfo.cmd_type = CMD_CLIENT_INFO; ClientInfo.client_num = m_Connectnum +1;
for(i=0; i<=m_Connectnum; i++) { ClientInfo.Networks_ID[i] = m_ClientInfo[i].Network_ID; strcpy(ClientInfo.users_name[i], m_ClientInfo[i].User_Name); strcpy(ClientInfo.clients_ipaddr[i], inet_ntoa(m_ClientInfo[i].Client_Addr.sin_addr)); }
file://向所有在线客户端发送在线客户信息报文 for(i=0; i<=m_Connectnum; i++) { send(m_ClientInfo[i].Client_Socket, (char*)&ClientInfo, sizeof(cmd_client_info), NULL); }
file://刷新客户端信息列表 networkid.Format("%d", m_NetworkID);
LVITEM lvinsert; lvinsert.mask=LVIF_TEXT|LVIF_IMAGE|LVIF_PARAM; lvinsert.iItem=m_Connectnum; lvinsert.iSubItem=0; lvinsert.cchTextMax=20; lvinsert.pszText=HelloResp.user_name; lvinsert.iImage = 0; pList->InsertItem (&lvinsert); pList->SetItemText (m_Connectnum,1,networkid); pList->SetItemText(m_Connectnum,2, inet_ntoa(m_ClientInfo[m_Connectnum].Client_Addr.sin_addr)); pList->SetItemText (m_Connectnum,3,strTime);
m_Connectnum ++;
break; case CMD_ASK: cmd_ask Ask; cmd_ask_resp AskResp; memcpy(&Ask,rcvbuf+offset,sizeof(cmd_ask)); offset+=sizeof(cmd_ask); AskResp.cmd_type = CMD_ASK_RESP; AskResp.Network_ID = Ask.Network_ID; for(i=0; i<m_Connectnum; i++) { if(m_ClientInfo[i].Network_ID == Ask.Network_ID) { strcpy(AskResp.pData1,m_ClientInfo[i].User_Name); strcat(AskResp.pData1, ":"); } } strcpy(AskResp.pData2, Ask.pData); for(i=0; i<m_Connectnum; i++) { send(m_ClientInfo[i].Client_Socket, (char*)&AskResp, sizeof(AskResp), 0); }
break; case CMD_GOODBYE: closesocket(pSocket); break; default: break; } }
return TRUE; } BOOL CServerDlg::OnSend(SOCKET pSocket) { return TRUE; }
void CServerDlg::OnOK() { closesocket(m_ListenSocket); WSACleanup(); CDialog::OnOK(); }
void CServerDlg::OnTimer(UINT nIDEvent) { CTime m_current_time = CTime::GetCurrentTime(); CTimeSpan logintimes; CString login_times; CString networkid; file://列表框的网络ID项 for(int i=0; i<m_Connectnum; i++) { logintimes = m_current_time - m_ClientInfo[i].Login_Time; login_times.Format("%d小时%d分%d秒", logintimes.GetHours(), logintimes.GetMinutes(), logintimes.GetSeconds()); pList->SetItemText (i,4,login_times); }
CDialog::OnTimer(nIDEvent); }
// serverDlg.h : header file //
#if !defined(AFX_SERVERDLG_H__B0AA0367_C1F4_11D4_AB1C_0080C8D6FEA5__INCLUDED_) #define AFX_SERVERDLG_H__B0AA0367_C1F4_11D4_AB1C_0080C8D6FEA5__INCLUDED_
#if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000
#include "global.h"
///////////////////////////////////////////////////////////////////////////// // CServerDlg dialog
class CServerDlg : public CDialog { file://全局函数 friend UINT KernelWorkThread(LPVOID pParam); // Construction public: CListCtrl* pList; file://客户端在线信息列表框对象 CImageList m_imagelist;
SOCKET m_ListenSocket; file://用于监听端口的套接字 client_info m_ClientInfo[MAX_CLIENT_NUM]; file://保存在线客户端信息的结构体数组 sockaddr_in clientaddr; file://保存发起连接的客户端地址 int m_Connectnum; file://在线客户端个数 int m_NetworkID; file://返回给客户端的网络ID号
BOOL OnSend(SOCKET pSocket); file://发送数据网络事件的响应函数 BOOL OnReceive(SOCKET pSocket); file://接收数据网络事件的响应函数 BOOL OnClose(SOCKET pSocket); file://关闭套接字网络事件的响应函数
CServerDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data file://{{AFX_DATA(CServerDlg) enum { IDD = IDD_SERVER_DIALOG }; // NOTE: the ClassWizard will add data members here file://}}AFX_DATA
// ClassWizard generated virtual function overrides file://{{AFX_VIRTUAL(CServerDlg) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support file://}}AFX_VIRTUAL
// Implementation protected: HICON m_hIcon;
// Generated message map functions file://{{AFX_MSG(CServerDlg) virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); virtual void OnOK(); afx_msg void OnTimer(UINT nIDEvent); file://}}AFX_MSG DECLARE_MESSAGE_MAP() };
|