异步与同步通信相比较,前者是非阻塞模式,后者是阻塞模式。有关两者差异在此博主中有详细讲解,推荐:https://www.cnblogs.com/wzsblogs/p/4671559.html。
采用同步socket,同时可与CArchive、CSocketFile 配合使用(这两者能否与异步socket配合使用呢?还待验证)。两者的运行机制基本相同,但是在同步机制中OnConnect与OnSend永远不会被系统调用。(为啥?CSocket在Connect()返回WSAEWOULDBLOCK错误时,不是在OnConnect(),OnReceive()这些事件终端函数里去等待。它马上调用一个用于提取消息的函数PumpMessage(...),就是从当前线程的消息队列里取关心的消息。原文:https://www.cnblogs.com/yuanzfy/archive/2011/08/26/2155189.html)
如果不使用CArchive/CSocketFile,则同步与异步最大的区别在于没有调用系统通知的OnConnect与OnSend。下例采用串行化进行说明。
1、创建基于对话框的MFC项目,包含Windows套接字。在工程中创建基于CSocket的类MySocket用于通信。
1)客户端:在MySocket类中新增函数pGetDlg用户快速获取主窗口指针,并声明一个Dlg类的指针用于绑定,在CXXXDlg.h中声明指针对象m_ClientSocket;
2)服务端:在MySocket类中新增函数pGetDl用户快速获取主窗口指针,并声明一个Dlg类的指针用于实现函数快速获取指针。在CXXXDlg.h中声明指针对象m_ListenSocket/m_ServerSocket。
Tips:相比异步通信,新增了三个指针对象,分别用于收发和缓冲。
1 // XXXSocke.h中 2 3 class CXXXDlg; //类声明,创建指针对象 4 class XXXSocket : public CSocket 5 { 6 public: 7 CXXXXDlg *m_dlg; 8 void pGetDlg(CXXXXDlg*dlg); 9 10 CArchive *m_archiveIn; 11 CArchive *m_archiveOut; 12 CSocketFile *m_socketFile; 13 ...... 14 } 15 16 void CxxxxSocket::pGetDlg(CxxxxDlg* dlg) 17 { 18 m_dlg=dlg; 19 }
2、在Dlg类中对指针对象初始化,并声明通信处理函数
因指定为指针型,在Dlg.cpp的初始化InitInstance函数中中进行指针初始化(=NULL),并新增一个CString变量用于接收信息.
1 class CXXXXDlg : public CDialog 2 { 3 public: 4 CXXXXSocket * m_xxxxsocket; //客户端一个,服务器端两个,一个用于监听,一个用于服务 5 CArchive *m_archiveIn; 6 CArchive *m_archiveOut; 7 CSocketFile *m_socketFile;
CString recvfile; //用于临时接收文件 8 void OnReceive(); 9 void OnClose(); 10 // void OnConnect();不需要 11 void Reset(); //用于释放套接字对象 12 ...... 13 } 14 //在Dlg.cpp中实现Reset函数,即删除套接字对象,并将指针赋空 15 void CXXXDlg::Reset() 16 {
m_XXXXXsocket->Close(); //如果不关闭的话,直接点击中断会引发程序崩溃
m_ArchiveIn->Close();
m_ArchiveOut->Close();
m_socketFile->Close(); 17 if(m_xxxxSocket!=NULL) //注意用于监听的套接字不能释放,因为监听处于打开状态,与连接是并列关系 18 { 19 delete m_xxxxSocket; 20 m_xxxxSocket=NULL; 21 } 22 if(m_archiveIn!=NULL) 23 { 24 delete m_archiveIn; 25 m_archiveIn=NULL; 26 } 27 if(m_archiveOut!=NULL) 28 { 29 delete m_archiveOut; 30 m_archiveOut=NULL; 31 } 32 if(m_socketFile!=NULL) 33 { 34 delete m_socketFile; 35 m_socketFile=NULL; 36 37 } 38 39 } 40 41 void CXXXXDlg::OnBnClickedCancel() //采用指针机制,在退出时需确保指针释放 42 { 43 // TODO: 在此添加控件通知处理程序代码 44 Reset(); 45 OnCancel(); 46 }
3、实例化套接字对象,并更新Dlg.cpp中的函数
1)客户端:在连接时实例化一个Socket对象,并绑定指针到主窗口,创建串行化对象用于接发写;
1 void Ccase005Dlg::OnBnClickedBnConnect() 2 { 3 // TODO: 在此添加控件通知处理程序代码 4 if(!AfxSocketInit()) //套接字初始化失败提示 5 { 6 MessageBox("windowsocket initial failed ","Receive",MB_ICONSTOP); 7 return; 8 } 9 m_clientsocket=new CMySocket; 10 m_clientsocket->pGetDlg(this); 11 m_clientsocket->Create(); 12 13 BYTE nFild[4]; 14 CString strIP; 15 UpdateData(); 16 17 m_edit_ip.GetAddress(nFild[0],nFild[1],nFild[2],nFild[3]); 18 strIP.Format("%d.%d.%d.%d",nFild[0],nFild[1],nFild[2],nFild[3]); 19 20 if(!m_clientsocket->Connect(strIP,atoi(m_str_port))) //创建失败提示,异步通信是在网络事件响应时触发nErrorCoe 21 22 { 23 AfxMessageBox("连接失败,请您重试!"); 24 return ; 25 } 26 else 27 { 28 m_listbox.AddString("连接成功!"); 29 // m_listbox.SetTopIndex(m_listbox.GetCount()-1); 30 m_socketFile=new CSocketFile(m_clientsocket); 31 m_ArchiveIn=new CArchive(m_socketFile,CArchive::load); 32 m_ArchiveOut=new CArchive(m_socketFile,CArchive::store); //用于发送写 33 34 m_edit_ip.EnableWindow(FALSE); 35 m_edit_port.EnableWindow(FALSE); 36 m_bn_connect.EnableWindow(FALSE); 37 m_bn_disconnect.EnableWindow(TRUE); 38 m_bn_clear.EnableWindow(TRUE); 39 m_bn_send.EnableWindow(TRUE); 40 m_bn_rewrite.EnableWindow(TRUE); 41 m_editbox.EnableWindow(TRUE); 42 } 43 } 44 45 void Ccase005Dlg::OnBnClickedBnDisconnect() 46 { 47 // TODO: 在此添加控件通知处理程序代码 48 m_listbox.AddString("断开连接!"); 49 Reset(); 50 51 m_edit_ip.EnableWindow(TRUE); 52 m_edit_port.EnableWindow(TRUE); 53 m_bn_connect.EnableWindow(TRUE); 54 m_bn_disconnect.EnableWindow(FALSE); 55 m_bn_clear.EnableWindow(TRUE); 56 m_bn_send.EnableWindow(FALSE); 57 m_bn_rewrite.EnableWindow(FALSE); 58 m_editbox.EnableWindow(FALSE); 59 }
2)服务器端:监听在获取IP地址后,调用Create创建套接字,并侦听连接请求。
1 void Ccase006Dlg::OnBnClickedBnListen() 2 { 3 // TODO: 在此添加控件通知处理程序代码 4 if(!AfxSocketInit()) 5 { 6 MessageBox("Windowsocket initial failed!","Send",MB_ICONSTOP); 7 return ; 8 } 9 m_listensocket=new CMySocket; //创建套接字对象 10 m_listensocket->pGetDlg(this); 11 BYTE nFild[4]; 12 CString strIP; 13 UpdateData(); //更新获取数据 14 m_edit_ip.GetAddress(nFild[0],nFild[1],nFild[2],nFild[3]); 15 strIP.Format("%d.%d.%d.%d",nFild[0],nFild[1],nFild[2],nFild[3]); 16 m_listensocket->Create(atoi(m_str_port),1,strIP); //此处的Create是三参数 17 m_listensocket->Listen(1); 18 m_listbox.AddString("监听开始"); 19 m_listbox.AddString("地址"+strIP+" 端口"+m_str_port); 20 m_listbox.AddString("等待客户端连接...."); 21 22 } 23 24 void Ccase006Dlg::OnBnClickedBnStoplisten() 25 { 26 // TODO: 在此添加控件通知处理程序代码 27 m_listensocket->Close(); 28 if(m_listensocket!=NULL) 29 { 30 delete m_listensocket; 31 m_listensocket=NULL; 32 } 33 m_listbox.AddString("停止监听"); 34 }
完成OnAccept函数,并调用AsyncSelect准备随时接收信息。
1 void Ccase006Dlg::OnAccept(void) 2 { 3 m_serversocket=new CMySocket; 4 m_serversocket->pGetDlg(this); 5 m_listensocket->Accept(*m_serversocket); 6 m_serversocket->AsyncSelect(FD_READ|FD_CLOSE); 7 8 m_socketfile=new CSocketFile(m_serversocket); 9 m_archiveIn=new CArchive(m_socketfile,CArchive::load); 10 m_archiveOut=new CArchive(m_socketfile,CArchive::store); 11 m_listbox.AddString("接收到连接请求"); 12 m_listbox.SetTopIndex(m_listbox.GetCount()-1); 13 }
3)完成其他对应的功能模块:发送信息、接收信息、断开连接、清空列表、重新输入、OnClose、OnReceive。可以通用。
1 void Ccase005Dlg::OnBnClickedBnSend() 2 { 3 // TODO: 在此添加控件通知处理程序代码 4 UpdateData(); 5 *m_ArchiveOut<<m_str_words; 6 m_ArchiveOut->Flush(); 7 m_listbox.AddString("发送: "+m_str_words); 8 m_listbox.SetTopIndex(m_listbox.GetCount()-1); 9 10 m_editbox.SetWindowText(""); //注意发送后清空输入内容 11 m_editbox.SetFocus(); //发送后焦点指定在编辑栏 12 } 13 void Ccase005Dlg::OnBnClickedBnRewrite() 14 { 15 // TODO: 在此添加控件通知处理程序代码 16 m_editbox.SetWindowText(""); 17 m_editbox.SetFocus(); 18 } 19 20 void Ccase005Dlg::OnBnClickedBnClear() 21 { 22 // TODO: 在此添加控件通知处理程序代码 23 m_listbox.ResetContent(); 24 } 25 26 27 void Ccase005Dlg::OnReceive(void) 28 { 29 *m_ArchiveIn>>recvfile; 30 m_ArchiveIn->Flush(); 31 m_listbox.AddString("收到: "+recvfile); 32 m_listbox.SetTopIndex(m_listbox.GetCount()-1); 33 } 34 35 void Ccase005Dlg::OnClose(void) 36 { 37 Reset(); 38 m_listbox.AddString("服务器断开了"); 39 // m_listbox.SetTopIndex(m_listbox.GetCount()-1); 40 41 m_edit_ip.EnableWindow(TRUE); 42 m_edit_port.EnableWindow(TRUE); 43 m_bn_connect.EnableWindow(TRUE); 44 m_bn_disconnect.EnableWindow(FALSE); 45 m_bn_clear.EnableWindow(TRUE); 46 m_bn_send.EnableWindow(FALSE); 47 m_bn_rewrite.EnableWindow(FALSE); 48 m_editbox.EnableWindow(FALSE); 49 }
4 、实现网络事件响应函数
在执行相应按钮操作后,系统会根据程序运行自动触发响应。因采用指针调用机制。所有处理事件的实现已经在主程序体中完成, 使用指针调回主程序接口即可.
1 void CMySocket::OnClose(int nErrorCode) 2 { 3 // TODO: 在此添加专用代码和/或调用基类 4 m_dlg->OnClose(); 5 CSocket::OnClose(nErrorCode); 6 } 7 8 void CMySocket::OnReceive(int nErrorCode) 9 { 10 // TODO: 在此添加专用代码和/或调用基类 11 m_dlg->OnReceive(); 12 AsyncSelect(FD_READ|FD_CLOSE|FD_WRITE); //需使用此条用于随时接收信息 13 CSocket::OnReceive(nErrorCode); 14 }
5、大功告成。
小结:
1) 同步通信与异步通信各有千秋,理解其机制运行;
2)多保存,以免网页程序崩溃;
3)基本操作要烂熟于心。