Winsock的事件I/O非同步模型(開發網路通訊程式入門的繼續) (轉)
前面討論的開發通訊的經典採用的是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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 網路I/O模型模型
- socket阻塞與非阻塞,同步與非同步、I/O模型非同步模型
- 談談對不同I/O模型的理解 (阻塞/非阻塞IO,同步/非同步IO)模型非同步
- 網路I/O模型 解讀模型
- 沒搞清楚網路I/O模型?那怎麼入門Netty模型Netty
- electron開發入門(二)程式通訊
- 手動搭建I/O網路通訊框架4:AIO程式設計模型,聊天室終極改造框架AI程式設計模型
- RabbitMQ 入門(一)同步通訊和非同步通訊MQ非同步
- Java入門系列-25-NIO(實現非阻塞網路通訊)Java
- 五種網路I/O模型詳解模型
- 多執行緒、重疊I/O的通訊埠元件 (轉)執行緒元件
- Rust高效率非同步I/O模型Rust非同步模型
- Java非阻塞I/O模型之NIO說明Java模型
- 一文徹底搞定(阻塞/非阻塞/同步/非同步)網路IO、併發程式設計模型、非同步程式設計模型的愛恨情仇非同步程式設計模型
- 從網路I/O模型到Netty,先深入瞭解下I/O多路複用模型Netty
- IO通訊模型(二)同步非阻塞模式NIO(NonBlocking IO)模型模式BloC
- 網路通訊協議基礎(ISIS)——入門協議
- Java I/O 模型的演進Java模型
- 計算機I/O與I/O模型計算機模型
- [轉載]使用非同步 I/O 大大提高應用程式的效能非同步
- node.js的非同步I/O、事件驅動、單執行緒Node.js非同步事件執行緒
- 系統程式設計 - I/O模型程式設計模型
- Java入門學習-理解I/OJava
- 網路通訊——socket(TCP/IP).Http,同步和非同步的區別TCPHTTP非同步
- Java 非同步 I/OJava非同步
- Netty權威指南:Linux網路 I/O 模型簡介NettyLinux模型
- 如何將JBuilder下的程式轉到eclipse下進行繼續開發?UIEclipse
- 《Linux網路開發必學教程》18_網路通訊框架的完善Linux框架
- TNN iOS非影像模型入門iOS模型
- Linux下的5種I/O模型與3組I/O複用Linux模型
- python 非同步 I/OPython非同步
- NodeJs 非同步 I/ONodeJS非同步
- I/O阻塞與同步理解
- java的nio之:淺析I/O模型Java模型
- Nature通訊:馴化發生在網際網路絡上演化過程的非平衡動態
- 一個網路通訊開發庫原始碼原始碼
- 網路通訊程式設計程式設計
- 使用Task實現非阻塞式的I/O操作