1.为什么到现在才弄懂这个
不知道这个Socket重叠IO这种模型是不是socket IO完成端口的基础,不过我感觉,学习一下这个再去学习socket IO完成端口是比较有好处的。
这个Scoket重叠IO我以前记得看过好几次,都没看懂。一部分原因是我没能静态心来写代码,还有更重要的原因就是,Socket重叠他们的结构体参数,还有传参数让人很难理解。下面我将对这些数据结构和参数进行一下讲解
2.初识WSARecv 函数
int WSARecv( SOCKET s,//要接收消息的socket LPWSABUF lpBuffers, //一个结构体数组。当接收IO操作完毕后接收内容就在这个里面了 DWORD dwBufferCount, //要接多少个WSABUF LPDWORD lpNumberOfBytesRecvd,//接收了多少个字节 LPDWORD lpFlags, LPWSAOVERLAPPED lpOverlapped,//Overlapped结构体指针 LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine//在本节用不用 );
lpBuffers参数:这是一WSABUF数组,意思是这个函数可以接收不止一个字符缓冲,但是我们一般用一个就够了。 接收多个我还没能测试
dwBufferCount参数:是指上一个参数的数组个数
lpOverlapped参数:这个参数是Overlappad结构体指针,这个指针当IO操作完毕的时候,这里会被系统填充。当IO操作完成时这个结构也可以通过WSAGetOverlappedResult得到
返回值:
0:没有错误发生,IO操作当即完成
SOCKET_ERROR:发生错误
如果是SOCKET_ERROR并且WSAGetLastError() == WSA_IO_PENDING 这时表示操作已经提交。异步操作大部分都是这样的。
3.何时得到收取的消息,然后取出消息
当异常操作完成时,Overlapped的hEvent这个事件会触发。这时Overlapped的InternalHigh表示接受的字节数。Internal表示错误代码。消息的内容即是你当初调用WSARecv时传入的lpBuffers参数。
4.代码组织
以服务端为例
首先传入WSARecv的几个参数必定与一个socket关联。而且这些参数在异步调用完成之后,但是以后还要用(在WaitForMutiObjects时要用到),而且每一个socket得拥有一个不同的Event来标识是哪个客户端来消息了。所以为每一个客户端socket构造一个Overlapped结构。比如我测试的代码中每一个客户端都有这样一个结构体,而且当accept来的时候表示有新的socket连接,就得生成这样一个结构体,当客户端掉线的时候,就得删除这样一个结构体
下面就是这个结构体:
struct CClientInfo { public: CClientInfo() { ZeroMemory(&m_ol,sizeof(m_ol)); ZeroMemory(m_szBuf,256); m_ol.hEvent = WSACreateEvent(); } ~CClientInfo() { WSACloseEvent(m_ol.hEvent); } WSAOVERLAPPED m_ol; SOCKET sSocket; CString strIp; u_short nPort; CString GetShowText(); char m_szBuf[256]; };
下面是两个函数,一个是当客户端连接的时候,一个是当客户端断开的时候
CClientInfo * CServerDlg::OnSocketConnected(SOCKET sClientSocket,sockaddr_in * saClient) { u_short uPort = ntohs(((sockaddr_in *)saClient)->sin_port); CString strIp = CA2T(inet_ntoa(((sockaddr_in *)saClient)->sin_addr)); CClientInfo * pClientInfo = new CClientInfo; pClientInfo->nPort = uPort; pClientInfo->strIp = strIp; pClientInfo->sSocket = sClientSocket; LockClientArray(); m_ClientArray.Add(pClientInfo); int nIndexInserted = m_ClientListBox.AddString(pClientInfo->GetShowText()); m_ClientListBox.SetItemData(nIndexInserted,pClientInfo->sSocket); UnLockClientArray(); return pClientInfo; } void CServerDlg::OnSocketDisconnect(SOCKET aClientSocket) { LockClientArray(); for(int i = 0;i<m_ClientArray.GetCount();i++) { CClientInfo * pClientInfo = m_ClientArray.GetAt(i); if(pClientInfo->sSocket == aClientSocket) { m_ClientListBox.DeleteString(m_ClientListBox.FindString(0,pClientInfo->GetShowText())); delete pClientInfo; m_ClientArray.RemoveAt(i); break; } } UnLockClientArray(); }
5.没有测试的内容和疑问
发送的时候没有用WSASend,以后再学习
6.注意
当使用AcceptEx之后,一般用GetAcceptExSockaddrs这个函数来得到本地或者远程的地址信息。但也可以调用getsockname这个函数,前提是,必须先给接受到socket设置一个SO_UPDATE_ACCEPT_CONTEXT。
7.AcceptEx用法
BOOL AcceptEx(
__in SOCKET sListenSocket,
__in SOCKET sAcceptSocket,
__in PVOID lpOutputBuffer,
__in DWORD dwReceiveDataLength,
__in DWORD dwLocalAddressLength,
__in DWORD dwRemoteAddressLength,
__out LPDWORD lpdwBytesReceived,
__in LPOVERLAPPED lpOverlapped
);
● sListenSocket 参数指定的是一个监听套接字。
● sAcceptSocket 参数指定的是另一个套接字,负责对进入连接请求的“接受”。
AcceptEx 函数和 accept 函数的区别在于,我们必须提供接受的套接字,而不是让函数自动为我们创建。
正是由于要提供套接字,所以要求我们事先调用 socket 或 WSASocket 函数,创建一个套接字,以便通过 sAcceptSocket 参数,将其传递给 AcceptEx。
● lpOutputBuffer 参数指定的是一个特殊的缓冲区,因为它要负责三种数据的接收:服务器的本地地址,客户机的远程地址,以及在新建连接上发送的第一个数据块。
● dwReceiveDataLength参数以字节为单位,指定了在 lpOutputBuffer 缓冲区中,保留多大的空间,用于数据的接收。
如这个参数设为0,那么在连接的接受过程中,不会再一道接收任何数据。
● dwLocalAddressLength 和 dwRemoteAddressLength 参数也是以字节为单位,指定在 lpOutputBuffer 缓冲区中,保留多大的空间,
在一个套接字被接受的时候,用于本地和远程地址信息的保存。
要注意的是,和当前采用的传送协议允许的最大地址长度比较起来,这里指定的缓冲区大小至少应多出16字节。
举个例子来说:假定正在使用的是 TCP/IP 协议,那么这里的大小应设为“SOCKADDRIN 结构的长度+16字节”。
● lpdwBytesReceived 参数用于返回接收到的实际数据量,以字节为单位。
只有在操作以同步方式完成的前提下,才会设置这个参数。假如 AcceptEx 函数返回 ERROR_IO_PENDING,
那么这个参数永远都不会设置,我们必须利用完成事件通知机制,获知实际读取的字节量。
● lpOverlapped 参数对应的是一个 OVERLAPPED 结构,允许 AcceptEx 以一种异步方式工作。
如我们早先所述,只有在一个重叠 I/O 应用中,该函数才需要使用事件对象通知机制,这是由于此时没有一个完成例程参数可供使用。
也就是说 AcceptEx 函数只能由本节课给大家讲的“事件通知”方式获取异步 I/O 请求的结果,而“完成例程”方法无法被使用。
8.源代码
.h文件
// ServerDlg.h : 头文件 // #pragma once #include "afxwin.h" #include <Winsock2.h> #pragma comment(lib,"Ws2_32.lib") #define WM_SOCKET WM_USER+220 enum IO_TYPE { IO_RECV, IO_ACCEPT, IO_UNKNOW }; // CServerDlg 对话框 struct CClientInfo { public: CClientInfo() { ZeroMemory(&m_ol,sizeof(m_ol)); ZeroMemory(m_szBuf,256); sAcceptSocket = INVALID_SOCKET; m_ol.hEvent = WSACreateEvent(); m_IO_type = IO_UNKNOW; } ~CClientInfo() { WSACloseEvent(m_ol.hEvent); } WSAOVERLAPPED m_ol; SOCKET sSocket; SOCKET sAcceptSocket; CString strIp; u_short nPort; CString GetShowText(); char m_szBuf[256]; IO_TYPE m_IO_type; }; class CServerDlg : public CDialogEx { // 构造 public: CServerDlg(CWnd* pParent = NULL); // 标准构造函数 ~CServerDlg(); // 对话框数据 enum { IDD = IDD_SERVER_DIALOG }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 // 实现 protected: HICON m_hIcon; // 生成的消息映射函数 virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: afx_msg void OnBnClickedButton1(); void InitSocket(); SOCKET m_ServerSocket; CClientInfo * OnSocketConnected(SOCKET sClientSocket,sockaddr_in * saClient); void OnSocketDisconnect(SOCKET aClientSocket); CListBox m_ClientListBox; CArray<CClientInfo *> m_ClientArray; CListBox m_RecvMsgListBox; CRITICAL_SECTION m_CS_ClientArray; void LockClientArray(){EnterCriticalSection(&m_CS_ClientArray);} void UnLockClientArray(){LeaveCriticalSection(&m_CS_ClientArray);} afx_msg void OnBnClickedButton3(); void PostRecv(CClientInfo * p); BOOL PostAccept(SOCKET sListenSocket); BOOL PostAccept(CClientInfo * p); void OnError(); };
.cpp文件
// ServerDlg.cpp : 实现文件 // #include "stdafx.h" #include "Server.h" #include "ServerDlg.h" #include "afxdialogex.h" #include <Mswsock.h> #ifdef _DEBUG #define new DEBUG_NEW #endif // 用于应用程序“关于”菜单项的 CAboutDlg 对话框 class CAboutDlg : public CDialogEx { public: CAboutDlg(); // 对话框数据 enum { IDD = IDD_ABOUTBOX }; protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持 // 实现 protected: DECLARE_MESSAGE_MAP() }; CAboutDlg::CAboutDlg() : CDialogEx(CAboutDlg::IDD) { } void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx) END_MESSAGE_MAP() // CServerDlg 对话框 CServerDlg::CServerDlg(CWnd* pParent /*=NULL*/) : CDialogEx(CServerDlg::IDD, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); m_ServerSocket = INVALID_SOCKET; InitializeCriticalSection(&m_CS_ClientArray); } CServerDlg::~CServerDlg() { DeleteCriticalSection(&m_CS_ClientArray); } void CServerDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Control(pDX, IDC_LIST_CLIENT_LIST, m_ClientListBox); DDX_Control(pDX, IDC_LIST_RECV, m_RecvMsgListBox); } BEGIN_MESSAGE_MAP(CServerDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BUTTON1, &CServerDlg::OnBnClickedButton1) ON_BN_CLICKED(IDC_BUTTON3, &CServerDlg::OnBnClickedButton3) END_MESSAGE_MAP() // CServerDlg 消息处理程序 BOOL CServerDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // 将“关于...”菜单项添加到系统菜单中。 // IDM_ABOUTBOX 必须在系统命令范围内。 ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000); CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != NULL) { BOOL bNameValid; CString strAboutMenu; bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX); ASSERT(bNameValid); if (!strAboutMenu.IsEmpty()) { pSysMenu->AppendMenu(MF_SEPARATOR); pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu); } } // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动 // 执行此操作 SetIcon(m_hIcon, TRUE); // 设置大图标 SetIcon(m_hIcon, FALSE); // 设置小图标 // TODO: 在此添加额外的初始化代码 InitSocket(); SetDlgItemInt(IDC_EDIT_PORT,10103); return TRUE; // 除非将焦点设置到控件,否则返回 TRUE } void CServerDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialogEx::OnSysCommand(nID, lParam); } } // 如果向对话框添加最小化按钮,则需要下面的代码 // 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序, // 这将由框架自动完成。 void CServerDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // 用于绘制的设备上下文 SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // 使图标在工作区矩形中居中 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; // 绘制图标 dc.DrawIcon(x, y, m_hIcon); } else { CDialogEx::OnPaint(); } } //当用户拖动最小化窗口时系统调用此函数取得光标 //显示。 HCURSOR CServerDlg::OnQueryDragIcon() { return static_cast<HCURSOR>(m_hIcon); } CString CClientInfo::GetShowText() { CString strItemText; strItemText.Format(_T("%s:%d"),strIp,nPort); return strItemText; } CClientInfo * CServerDlg::OnSocketConnected(SOCKET sClientSocket,sockaddr_in * saClient) { BOOL bDelete = FALSE; if(saClient == NULL) { sockaddr_in * pTempClientAddr = new sockaddr_in; int nLen = sizeof(sockaddr_in); if(SOCKET_ERROR == getpeername(sClientSocket,(sockaddr*)pTempClientAddr,&nLen)) { int nErrorCode = ::WSAGetLastError(); if(SOCKET_ERROR == getpeername(sClientSocket,(sockaddr*)pTempClientAddr,&nLen)) OutputDebugStringA("Failed! "); } saClient = (sockaddr_in*)pTempClientAddr; bDelete = TRUE; } u_short uPort = ntohs(((sockaddr_in *)saClient)->sin_port); CString strIp = CA2T(inet_ntoa(((sockaddr_in *)saClient)->sin_addr)); CClientInfo * pClientInfo = new CClientInfo; pClientInfo->nPort = uPort; pClientInfo->strIp = strIp; pClientInfo->sSocket = sClientSocket; pClientInfo->m_IO_type = IO_RECV; LockClientArray(); m_ClientArray.Add(pClientInfo); int nIndexInserted = m_ClientListBox.AddString(pClientInfo->GetShowText()); m_ClientListBox.SetItemData(nIndexInserted,pClientInfo->sSocket); UnLockClientArray(); if(bDelete) delete saClient; return pClientInfo; } void CServerDlg::OnSocketDisconnect(SOCKET aClientSocket) { LockClientArray(); for(int i = 0;i<m_ClientArray.GetCount();i++) { CClientInfo * pClientInfo = m_ClientArray.GetAt(i); if(pClientInfo->sSocket == aClientSocket) { m_ClientListBox.DeleteString(m_ClientListBox.FindString(0,pClientInfo->GetShowText())); delete pClientInfo; m_ClientArray.RemoveAt(i); break; } } UnLockClientArray(); } BOOL CServerDlg::PostAccept(SOCKET sListenSocket) { // SOCKET sClientSocket = WSASocket(AF_INET, // SOCK_STREAM, // IPPROTO_TCP, // NULL,0,WSA_FLAG_OVERLAPPED); // char szBuf[256] = {0}; // DWORD dwByteReceived = 0; // OVERLAPPED ov; // memset(&ov,0,sizeof(ov)); // AcceptEx(sListenSocket,sClientSocket,szBuf,0,sizeof(sockaddr_in) + 16,sizeof(sockaddr_in) + 16,&dwByteReceived,&ov); OutputDebugStringA("PostAccept! "); CClientInfo * pClientInfo = new CClientInfo; SOCKET sClientSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL,0,WSA_FLAG_OVERLAPPED); pClientInfo->sSocket = sListenSocket; pClientInfo->m_IO_type = IO_ACCEPT; pClientInfo->sAcceptSocket = sClientSocket; DWORD dwByteRecieved = 0; int nRet = AcceptEx(pClientInfo->sSocket,pClientInfo->sAcceptSocket, pClientInfo->m_szBuf,0, sizeof(sockaddr_in) + 16,sizeof(sockaddr_in) + 16,&dwByteRecieved, &pClientInfo->m_ol); if(nRet || ::WSAGetLastError() == ERROR_IO_PENDING) { LockClientArray(); m_ClientArray.Add(pClientInfo); UnLockClientArray(); return TRUE; } else { AfxMessageBox(_T("出错非常严重!PostAccept")); return FALSE; } } BOOL CServerDlg::PostAccept(CClientInfo * pClientInfo) { OutputDebugStringA("PostAccept! "); SOCKET sClientSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL,0,WSA_FLAG_OVERLAPPED); pClientInfo->m_IO_type = IO_ACCEPT; pClientInfo->sAcceptSocket = sClientSocket; DWORD dwByteRecieved = 0; int nRet = AcceptEx(pClientInfo->sSocket,pClientInfo->sAcceptSocket, pClientInfo->m_szBuf,0, sizeof(sockaddr_in) + 16,sizeof(sockaddr_in) + 16,&dwByteRecieved, &pClientInfo->m_ol); if(nRet || ::WSAGetLastError() == ERROR_IO_PENDING) { return TRUE; } else { AfxMessageBox(_T("出错非常严重!PostAccept")); delete pClientInfo; return FALSE; } } void CServerDlg::PostRecv(CClientInfo * pClientInfo) { OutputDebugStringA("PostRecv! "); WSABUF pBuf; pBuf.buf = pClientInfo->m_szBuf; pBuf.len = 256; DWORD cbRecv = 0; DWORD dwFlag = 0; int nRet = WSARecv(pClientInfo->sSocket,&pBuf,1,&cbRecv,&dwFlag,&pClientInfo->m_ol,NULL); if(nRet != 0) { int nError = WSAGetLastError(); } else { m_RecvMsgListBox.AddString((LPCTSTR)pBuf.buf); } } UINT AFX_CDECL AcceptThreadProc(LPVOID p) { CServerDlg * pThis = (CServerDlg*)p; while(true) { sockaddr_in saClient = {0}; int nClientSocketLen = sizeof(saClient); SOCKET sClientSocket = accept(pThis->m_ServerSocket,(sockaddr *)&saClient,&nClientSocketLen); CClientInfo * pClientInfo = pThis->OnSocketConnected(sClientSocket,&saClient); pThis->PostRecv(pClientInfo); Sleep(500); } return 0; } UINT AFX_CDECL WorkThreadProc(LPVOID p) { CServerDlg * pThis = (CServerDlg*)p; while (true) { pThis->LockClientArray(); int nClientCount = pThis->m_ClientArray.GetCount(); WSAEVENT * pEvent = NULL; if(nClientCount > 0) { pEvent = new WSAEVENT[nClientCount]; for(int i = 0;i < nClientCount;i++) pEvent[i] = pThis->m_ClientArray.GetAt(i)->m_ol.hEvent; } pThis->UnLockClientArray(); if(pEvent == NULL) { Sleep(1000); continue; } DWORD dwWaitRet = ::WaitForMultipleObjects(nClientCount,pEvent,FALSE,10000); if(dwWaitRet == WAIT_FAILED) { } else if(dwWaitRet == WAIT_TIMEOUT) { } else { pThis->LockClientArray(); CClientInfo * pClientInfo = pThis->m_ClientArray.GetAt(dwWaitRet - WAIT_OBJECT_0); WSAResetEvent(pClientInfo->m_ol.hEvent); char szDbg[256] = {0}; sprintf_s(szDbg,"OverLapped:Internal:0x%x,InternalHigh:%d,Offset:%d,OffsetHigh:%d ", pClientInfo->m_ol.Internal, pClientInfo->m_ol.InternalHigh,//发了多少字节 pClientInfo->m_ol.Offset, pClientInfo->m_ol.OffsetHigh); OutputDebugStringA(szDbg); switch(pClientInfo->m_IO_type) { case IO_RECV: if(pClientInfo->m_ol.InternalHigh == 0) { //断开连接 pThis->OnSocketDisconnect(pClientInfo->sSocket); } else { pClientInfo->m_szBuf[pClientInfo->m_ol.InternalHigh] = 0; pThis->m_RecvMsgListBox.AddString((LPCTSTR)pClientInfo->m_szBuf); pThis->PostRecv(pClientInfo); } break; case IO_ACCEPT: setsockopt(pClientInfo->sAcceptSocket, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, (char*)&pClientInfo->sAcceptSocket, sizeof(pClientInfo->sAcceptSocket)); CClientInfo * pNewClientInfo = pThis->OnSocketConnected(pClientInfo->sAcceptSocket,NULL); pThis->PostRecv(pNewClientInfo); pThis->PostAccept(pClientInfo); break; } pThis->UnLockClientArray(); } delete [] pEvent; } } void CServerDlg::OnBnClickedButton1() { //启动服务端 int nPort = GetDlgItemInt(IDC_EDIT_PORT); // TODO: 在此添加控件通知处理程序代码 m_ServerSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL,0,WSA_FLAG_OVERLAPPED); if(m_ServerSocket == INVALID_SOCKET) { AfxMessageBox(_T("创建套接字失败")); return ; } sockaddr_in saServer; saServer.sin_family = AF_INET; //地址家族 saServer.sin_port = htons(nPort); //注意转化为网络节序 saServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if(SOCKET_ERROR == bind(m_ServerSocket,(SOCKADDR *)&saServer,sizeof(saServer))) { AfxMessageBox(_T("绑定失败")); return ; } if(SOCKET_ERROR == listen(m_ServerSocket,SOMAXCONN)) { AfxMessageBox(_T("监听失败啊")); return ; } //AfxBeginThread(AcceptThreadProc,this); PostAccept(m_ServerSocket); AfxBeginThread(WorkThreadProc,this); GetDlgItem(IDC_BUTTON1)->EnableWindow(FALSE); } void CServerDlg::InitSocket() { WSADATA wsaData = {0}; if(0 != WSAStartup(MAKEWORD(2,2),&wsaData)) { AfxMessageBox(_T("socket 初始化失败")); return ; } } void CServerDlg::OnBnClickedButton3() { // TODO: 在此添加控件通知处理程序代码 if(m_ClientListBox.GetSelCount() <= 0) { AfxMessageBox(_T("请选中右边的客户端进行发送")); return ; } CString strSend; GetDlgItemText(IDC_EDIT2,strSend); for(int i = 0;i < m_ClientListBox.GetCount();i++) { if(m_ClientListBox.GetSel(i) > 0) { SOCKET aClientSocket = m_ClientListBox.GetItemData(i); if( SOCKET_ERROR == send(aClientSocket,(const char * )strSend.GetBuffer(),strSend.GetLength() * sizeof(TCHAR),0)) { int nError = ::WSAGetLastError(); CString strError; strError.Format(_T("send 失败%d"),nError); AfxMessageBox(strError); } } } }