Winsock的事件I/O非同步模型(開發網路通訊程式入門的繼續) (轉)

worldblog發表於2007-12-11
Winsock的事件I/O非同步模型(開發網路通訊程式入門的繼續) (轉)[@more@]

    前面討論的開發通訊的經典採用的是WSAAsync的非同步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 _DE
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

SOCKET Accept; 於新的一個連線通訊的套接字
WSAEVENT NewEvent; 應於新的套接字的新事件
SOCKET Socket[WSA_MAXIMUM_WAIT_EVENTS];  放所有生成的套接字
WSAEVENT Event[WSA_MAXIMUM_WAIT_EVENTS]; 放所有生成的事件
int EventTotal; 建的事件總數
int Index;  待多重事件的返回值
WSWORKEVENTS NetworkEvents; 於接收套接字上發生的網路事件型別以及可能出現的錯
誤程式碼

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
 CAboutDlg();

// Dialog Data
 AFX_DATA(CAboutDlg)
 enum { IDD = IDD_ABOUTBOX };
 AFX_DATA

 // ClassWizard generated virtual function overrs
 AFX_VIRTUAL(CAboutDlg)
 protected:
 virtual void DoData(CDataExchange* pDX);  // DDX/DDV support
 AFX_VIRTUAL

// Implementation
protected:
 AFX_MSG(CAboutDlg)
 AFX_MSG
 DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
 AFX_DATA_INIT(CAboutDlg)
 AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialog::DoDataExchange(pDX);
 AFX_DATA_MAP(CAboutDlg)
 AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
 AFX_MSG_MAP(CAboutDlg)
 // No message handlers
 AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CServerDlg dialog

CServerDlg::CServerDlg(CWnd* pParent /*=NULL*/)
 : CDialog(CServerDlg::IDD, pParent)
{
 AFX_DATA_INIT(CServerDlg)
 // NOTE: the ClassWizard will add member initialization here
 AFX_DATA_INIT
 // Note that LoadIcon does not require a subsequent DestroyIcon in
 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);
 AFX_DATA_MAP(CServerDlg)
 // NOTE: the ClassWizard will add DDX and DDV calls here
 AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CServerDlg, CDialog)
 AFX_MSG_MAP(CServerDlg)
 ON_WM_SYMMAND()
 ON_WM_PAINT()
 ON_WM_QUERYDRAGICON()
 ON_WM_TIMER()
 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 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 = Wtartup(MAKE(2,2), &wsaData);
 if(ret != 0)
 {
 MessageBox("初始化套接字失敗!");
 return FALSE;
 }

 建一個套接字
 m_ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  if(m_ListenSocket == INVALID_SOCKET)
 {
 MessageBox("建立套接字失敗!");
 closesocket(m_ListenSocket);
 WSACleanup();
 return FALSE;
 }

 定到指定的埠上
 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(); 建一個新的事件物件

 建立的事件物件與前面建立的套接字關聯在一起,並註冊網路事件型別
  if(WSAEventSelect(m_ListenSocket, NewEvent, FD_ACCEPT | FD_CLOSE) == SOCKET_ERROR)
 {
 MessageBox("註冊網路事件失敗!");
 closesocket(m_ListenSocket);
 WSACleanup();
 return FALSE;
 }

 建立的套接字處於狀態
 listen(m_ListenSocket, 5);

 Event[EventTotal] = NewEvent;
 Socket[EventTotal] = m_ListenSocket;
 EventTotal++;

  置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,"",LVCFMT_CENTER,100,2);
 pList->InsertColumn (3,"登入時間",LVCFMT_CENTER,120,3);
 pList->InsertColumn (4,"線上時間",LVCFMT_CENTER,100,4);

 SetTimer(1, 1000, NULL);

 動核心處理執行緒
 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.odal();
 }
 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;
}

心處理執行緒, 響應並處理各種網路事件
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)
 接事件
 {
 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)
 取資料事件
 {
 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)
 閉套接字事件
 {
 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 --;

 所有客戶端傳送線上客戶資訊的報文
 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],_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];  收緩衝區
  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; 表框的網路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);

 登入的客戶端傳送回應報文
 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));
 }

 所有線上客戶端傳送線上客戶資訊報文
 for(i=0; i<=m_Connectnum; i++)
 {
 send(m_ClientInfo[i].Client_Socket, (char*)&ClientInfo,
  sizeof(cmd_client_info), NULL);
 }

 新客戶端資訊列表
 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 {
 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 {
 send(m_ClientInfo[i].Client_Socket, (char*)&AskResp, sizeof(AskResp), 0);
 }

 break;
 case CMD_GOOYE:
 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; 表框的網路ID項
 
 for(int i=0; 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
{
局函式
  friend UINT KernelWorkThread(LPVOID pParam);
// Construction
public:
 CListCtrl* pList; 戶端線上資訊列表框物件
 CImageList m_imagelist;

 SOCKET m_ListenSocket; 於監聽埠的套接字
 client_info m_ClientInfo[MAX_CLIENT_NUM]; 存線上客戶端資訊的結構體陣列
 sockaddr_in clientaddr; 存發起連線的客戶端地址
 int m_Connectnum; 線客戶端個數
 int m_NetworkID;  回給客戶端的網路ID號

 BOOL OnSend(SOCKET pSocket);  送資料網路事件的響應函式
 BOOL OnReceive(SOCKET pSocket); 收資料網路事件的響應函式 
 BOOL OnClose(SOCKET pSocket);  閉套接字網路事件的響應函式

 CServerDlg(CWnd* pParent = NULL); // standard constructor

// Dialog Data
 AFX_DATA(CServerDlg)
 enum { IDD = IDD_SERVER_DIALOG };
 // NOTE: the ClassWizard will add data members here
 AFX_DATA

 // ClassWizard generated virtual function overrides
 AFX_VIRTUAL(CServerDlg)
 protected:
 virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support
 AFX_VIRTUAL

// Implementation
protected:
 HICON m_hIcon;

 // Generated message map functions
 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);
 AFX_MSG
 DECLARE_MESSAGE_MAP()
};

AFX_INSERT_LOCATION}}
// Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_SERVERDLG_H__B0AA0367_C1F4_11D4_AB1C_0080C8D6FEA5__INCLUDED_)


 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-991413/,如需轉載,請註明出處,否則將追究法律責任。

相關文章