?? 前面討論的開發網絡通信的經典入門采用的是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() };
|