异步与同步通信相比较,前者是非阻塞模式,后者是阻塞模式。有关两者差异在此博主中有详细讲解,推荐: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))) //创建失败提示,异步通信是在网络事件响应时触发nErrorCoe21 22 {23 AfxMessageBox("连接失败,请您重试!");24 return ;25 }26 else27 {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<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)基本操作要烂熟于心。