《2022年网络编程 .pdf》由会员分享,可在线阅读,更多相关《2022年网络编程 .pdf(16页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、MFC socket 编程2007-10-29 13:35 福州大学王骏- 随着计算机网络化的深入,计算机网络编程在程序设计的过程中变得日益重要。由于 C+ 语言对底层操作的优越性,许多文章都曾经介绍过用VC+ 进行Socket 编程的方法。但由于都是直接利用动态连接库wsock32.dll进行操作,实现比较繁琐。其实, VC+ 的 MFC 类库中提供了 CAsyncSocket这样一个套接字类,用他来实现 Socket 编程,是非常方便的。- 本文将用一个 Echo例程来介绍 CAsyncSocket类的用法。- 一 客户端- 1 创建一个 Dialog Based项目: CSockClie
2、nt 。在 Socket 项上面打上勾- 2 设计对话框- 去掉 Ok和 Cancle 两个按钮,增加 ID_Connect(连接)、ID_Send (发送)、ID_Exit(关闭) 按钮, 增加 ListBox 控件 IDC_LISTMSG 和 Edit 控件 IDC_EDITMSG,并按下表在 ClassWizard 中为 CCSockClientDlg 类添加变量。Control ID Type Member IDC_EDITMSG CEdit m_MSG IDC_LISTMSG ClistBox m_MSGS - 3 CAsyncSocket 类用 DoCallBack 函数处理 MF
3、C 消息,当一个网络事件发生时, DoCallBack 函数按网络事件类型:FD_READ、FD_WRITE 、FD_ACCEPT、FD_CONNECT分别调用 OnReceive、 OnSend 、 OnAccept、OnConnect函数。由于 MFC把这些事件处理函数定义为虚函数,所以要生成一个新的C+ 类,以重载这些函数,做法如下:- 以 Public方式继承 CAsyncSocket 类,生成新类 MySock ;- 为 MySock类添加虚函数 OnReceive、OnConnect 、OnSend - 4 在 MySock.ccp 中添加以下代码#include SockClie
4、nt.h #include SockClientDlg.h - 5 在 MySock.h中添加以下代码名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 1 页,共 16 页 - - - - - - - - - public: BOOL m_bConnected; /是否连接 UINT m_nLength; /消息长度 char m_szBuffer4096; /消息缓冲区- 6 在 MySock.ccp 中重载各函数MySock:MySock() m_nLength=0; memset
5、(m_szBuffer,0,sizeof(m_szBuffer); m_bConnected=FALSE; MySock:MySock() / 关闭套接字if(m_hSocket!=INVALID_SOCKET) Close(); void MySock:OnReceive(int nErrorCode) m_nLength=Receive(m_szBuffer,sizeof(m_szBuffer),0); / 下面两行代码用来获取对话框指针CCSockClientApp* pApp=(CCSockClientApp*)AfxGetApp(); CCSockClientDlg* pDlg=(C
6、CSockClientDlg*)pApp- m_pMainWnd; pDlg- m_MSGS.InsertString(0,m_szBuffer); memset(m_szBuffer,0,sizeof(m_szBuffer); CAsyncSocket:OnReceive(nErrorCode); void MySock:OnSend(int nErrorCode) Send(m_szBuffer,m_nLength,0); m_nLength=0; memset(m_szBuffer,0,sizeof(m_szBuffer); / 继续提请一个“读”的网络事件,接收Server 消息Asy
7、ncSelect(FD_READ); CAsyncSocket:OnSend(nErrorCode); void MySock:OnConnect(int nErrorCode) 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 2 页,共 16 页 - - - - - - - - - if (nErrorCode=0) m_bConnected=TRUE; CCSockClientApp* pApp=(CCSockClientApp*)AfxGetApp(); CCSockClien
8、tDlg* pDlg=(CCSockClientDlg*)pApp- m_pMainWnd; memcpy(m_szBuffer,Connected to ,13); strncat(m_szBuffer,pDlg- m_szServerAdr, sizeof(pDlg- m_szServerAdr); pDlg- m_MSGS.InsertString(0,m_szBuffer); AsyncSelect(FD_READ); /提请一个“读”的网络事件,准备接收 CAsyncSocket:OnConnect(nErrorCode); - 7 新建对话框 IDD_Addr,用来输入 IP 地址
9、和 Port ;生成新类 CAddrDlg。增加两个 Edit 控件:IDC_Addr、IDC_Port 按下表在 ClassWizard 中为 CAddrDlg类添加变量。Control ID Type Member IDC_Addr CString m_Addr IDC_Port Int m_Port - 8 在 CSockClientDlg.ccp中添加代码#include AddrDlg.h protected: int TryCount; MySock m_clientSocket; UINT m_szPort; public: char m_szServerAdr256; - 9
10、双击 IDD_CSOCKCLIENT_DIALOG对话框中的“连接”按钮,添加以下代码void CCSockClientDlg:OnConnect() m_clientSocket.ShutDown(2); m_clientSocket.m_hSocket=INVALID_SOCKET; m_clientSocket.m_bConnected=FALSE; CAddrDlg m_Dlg; / 默认端口 1088 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 3 页,共 16 页 -
11、 - - - - - - - - m_Dlg.m_Port=1088; if (m_Dlg.DoModal()=IDOK & !m_Dlg.m_Addr.IsEmpty() memcpy(m_szServerAdr,m_Dlg.m_Addr,sizeof(m_szServerAdr); m_szPort=m_Dlg.m_Port; /建立计时器,每 1 秒尝试连接一次,直到连上或TryCount10 SetTimer(1,1000,NULL); TryCount=0; - 10 添加 Windows消息 WM_TIMER响应函数 OnTimer void CCSockClientDlg:OnT
12、imer(UINT nIDEvent) if (m_clientSocket.m_hSocket=INVALID_SOCKET) BOOL bFlag=m_clientSocket.Create(0,SOCK_STREAM,FD_CONNECT); if(!bFlag) AfxMessageBox(Socket Error!); m_clientSocket.Close(); PostQuitMessage(0); return; m_clientSocket.Connect(m_szServerAdr,m_szPort); TryCount+; if (TryCount =10 | m_cl
13、ientSocket.m_bConnected) KillTimer(1); if (TryCount =10) AfxMessageBox(Connect Failed!); return; CDialog:OnTimer(nIDEvent); - 11 双击 IDD_CSOCKCLIENT_DIALOG对话框中的“发送”按钮,添加以下代码void CCSockClientDlg:OnSend() if (m_clientSocket.m_bConnected) 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 -
14、 - - - - - - 第 4 页,共 16 页 - - - - - - - - - m_clientSocket.m_nLength=m_MSG.GetWindowText (m_clientSocket.m_szBuffer, sizeof(m_clientSocket.m_szBuffer); m_clientSocket.AsyncSelect(FD_WRITE); m_MSG.SetWindowText(); - 12 双击 IDD_CSOCKCLIENT_DIALOG对话框中的“关闭”按钮,添加以下代码void CCSockClientDlg:OnExit() / 关闭 Sock
15、et m_clientSocket.ShutDown(2); / 关闭对话框EndDialog(0); - 12运行此项目,连接时输入主机名或IP 均可, CAsyncSocket 类会自动处理。- 二 服务端- Server 端的编程与 Client端的类似, 下面主要介绍他的Listen及 Accept 函数- 1 建立一个 CNewSocket类,重载 CAsyncSocket 类的 OnReceive、 OnSend函数,如何进行信息的显示和发送可以参考Client程序。本例中采用将收到信息原封不动发回的方法来实现Echo功能,代码如下CNewSocket :OnReceive(int
16、 nErrorCOde ) m_nLength=Receive(m_szBuffer ,sizeof (m_szBuffer ),0);/ 直接转发消息AsyncSelect (FD_WRITE); CNewSocket :OnSend (int nErrorCode) Send(m_szBuffer ,m_nLength,0); - 2 建立一个 CMyServerSocket 类,重载 CAsyncSocket类的 OnAccept 函数代码如下名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - -
17、 - - 第 5 页,共 16 页 - - - - - - - - - - 在 MyServerSocket.h 中声明变量public::CNewSocket* m_pSocket ;void CMyServerSocket :OnAccept(int nErrorCode) / 侦听到连接请求,调用Accept 函数CNewSocket* pSocket = new CNewSocket ();if (Accept(*pSocket ) pSocket- AsyncSelect(FD_READ);m_pSocket=pSocket; else delete pSocket; - 3 为对话
18、框添加一个“侦听”按钮,添加如下代码- 在 CsockServerDlg.ccp中声明变量public :CMyServerSocket m_srvrSocket; void CCSockServerDlg :OnListen () if (m_srvrSocket.m_hSocket=INVALID_SOCKET ) BOOL bFlag=m_srvrSocket.Create (UserPort,SOCK_STREAM,FD_ACCEPT); if (!bFlag) AfxMessageBox(“Socket Error!”); M_srvrSocket.Close(); PostQuit
19、Message(0); Return; / “侦听”成功,等待连接请求if (!m_srvrSocket 。Listen (1) int nErrorCode = m_srvrSocket.GetLastError(); if (nError !=WSAEWOULDBLOCK) 名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 6 页,共 16 页 - - - - - - - - - AfxMessageBox(“Socket Error!”); M_srvrSocket.Close(
20、); PostQuitMessage(0); Return; - 4 目前程序只能实现Echo功能,将信息原封不动的转发, 若能将 Accept 中由CNewSocket* pSocket = new CNewSocket ();得到的 Socket 指针存入一个CList 或一个数组中,便像Client端那样,对所有的连接进行读写控制。- 三 总结- CAsyncSocket类为我们使用 Socket 提供了极大方便。建立 Socket 的 WSAStartup过程和 bind 过程被简化成为 Create 过程,IP 地址类型转换、主机名和IP 地址转换的过程中许多复杂的变量类型都被简化成
21、字符串和整数操作,特别是CAsyncSocket类的异步特点, 完全可以替代繁琐的线程操作。MFC 提供了大量的类库,我们若能灵活的使用他们,便会大大提高编程的效率。一、TCP/IP 体系结构与特点1、TCP/IP 体系结构TCP/IP 协议实际上就是在物理网上的一组完整的网络协议。其中TCP是提供传输层服务,而 IP 则是提供网络层服务。 TCP/IP 包括以下协议: (结构如图1.1 )名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 7 页,共 16 页 - - - - - -
22、- - - ( 图 1.1) IP: 网间协议 (Internet Protocol) 负责主机间数据的路由和网络上数据的存储。同时为 ICMP ,TCP ,UDP 提供分组发送服务。用户进程通常不需要涉及这一层。ARP : 地址解析协议 (Address Resolution Protocol) 此协议将网络地址映射到硬件地址。RARP : 反向地址解析协议 (Reverse Address Resolution Protocol) 此协议将硬件地址映射到网络地址ICMP : 网间报文控制协议 (Internet Control Message Protocol) 此协议处理信关和主机的差错
23、和传送控制。TCP : 传送控制协议 (Transmission Control Protocol) 这是一种提供给用户进程的可靠的全双工字节流面向连接的协议。它要为用户进程提供虚电路服务, 并为数据可靠传输建立检查。 (注:大多数网络用户程序使用 TCP )UDP : 用户数据报协议 (User Datagram Protocol) 这是提供给用户进程的无连接协议,用于传送数据而不执行正确性检查。FTP : 文件传输协议 (File Transfer Protocol) 允许用户以文件操作的方式(文件的增、删、改、查、传送等)与另一主名师资料总结 - - -精品资料欢迎下载 - - - -
24、- - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 8 页,共 16 页 - - - - - - - - - 机相互通信。SMTP : 简单邮件传送协议 (Simple Mail Transfer Protocol) SMTP 协议为系统之间传送电子邮件。TELNET :终端协议 (Telnet Terminal Procotol) 允许用户以虚终端方式访问远程主机HTTP : 超文本传输协议 (Hypertext Transfer Procotol) TFTP: 简单文件传输协议 (Trivial File Transfer Protocol
25、) 2、TCP/IP 特点TCP/IP 协议的核心部分是传输层协议(TCP、UDP) ,网络层协议 (IP) 和物理接口层,这三层通常是在操作系统内核中实现。因此用户一般不涉及。编程时,编程界面有两种形式: 一、是由内核心直接提供的系统调用;二、使用以库函数方式提供的各种函数。 前者为核内实现, 后者为核外实现。 用户服务要通过核外的应用程序才能实现,所以要使用套接字(socket) 来实现。图 1.2 是 TCP/IP 协议核心与应用程序关系图。( 图 1.2) 二、专用术语1、套接字套接字是网络的基本构件。 它是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连听进程
26、。套接字存在通信区域 (通信区域名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 9 页,共 16 页 - - - - - - - - - 又称地址簇) 中。套接字只与同一区域中的套接字交换数据(跨区域时,需要执行某和转换进程才能实现) 。WINDOWS 中的套接字只支持一个域网际域。套接字具有类型。WINDOWS SOCKET 1.1 版本支持两种套接字:流套接字(SOCK_STREAM)和数据报套接字 (SOCK_DGRAM) 2、WINDOWS SOCKETS 实现一个 WIND
27、OWS SOCKETS 实现是指实现了WINDOWS SOCKETS规范所描述的全部功能的一套软件。一般通过DLL文件来实现3、阻塞处理例程阻塞处理例程 (blocking hook, 阻塞钩子 )是 WINDOWS SOCKETS 实现为了支持阻塞套接字函数调用而提供的一种机制。4、多址广播( multicast,多点传送或组播)是一种一对多的传输方式, 传输发起者通过一次传输就将信息传送到一组接收者,与单点传送(unicast)和广播 (Broadcast) 相对应。一、客户机 / 服务器模式在 TCP/IP 网络中两个进程间的相互作用的主机模式是客户机/ 服务器模式(Client/Ser
28、ver model)。该模式的建立基于以下两点:1、非对等作用; 2、通信完全是异步的。客户机/ 服务器模式在操作过程中采取的是主动请示方式:首先服务器方要先启动,并根据请示提供相应服务:(过程如下)1、打开一通信通道并告知本地主机,它愿意在某一个公认地址上接收客户请求。2、等待客户请求到达该端口。3、接收到重复服务请求,处理该请求并发送应答信号。4、返回第二步,等待另一客户请求5、关闭服务器。客户方:名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 10 页,共 16 页 - - -
29、 - - - - - - 1、打开一通信通道,并连接到服务器所在主机的特定端口。2、向服务器发送服务请求报文,等待并接收应答;继续提出请求,3、请求结束后关闭通信通道并终止。二、基本套接字为了更好说明套接字编程原理,给出几个基本的套接字, 在以后的篇幅中会给出更详细的使用说明。1、创建套接字 socket() 功能:使用前创建一个新的套接字格式: SOCKET PASCAL FAR socket(int af,int type,int procotol); 参数: af: 通信发生的区域type: 要建立的套接字类型procotol: 使用的特定协议2、指定本地地址 bind() 功能:将套接
30、字地址与所创建的套接字号联系起来。格式: int PASCAL FAR bind(SOCKET s,const struct sockaddr FAR * name,int namelen); 参数:s: 是由 socket() 调用返回的并且未作连接的套接字描述符(套接字号)。其它:没有错误, bind() 返回 0,否则 SOCKET_ERROR 地址结构说明:struct sockaddr_in short sin_family;/AF_INET u_short sin_port;/16位端口号,网络字节顺序struct in_addr sin_addr;/32位 IP 地址,网络字节顺
31、序char sin_zero8;/保留名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 11 页,共 16 页 - - - - - - - - - 3、建立套接字连接 connect() 和 accept() 功能:共同完成连接工作格式: int PASCAL FAR connect(SOCKET s,const struct sockaddr FAR * name,int namelen); SOCKET PASCAL FAR accept(SOCKET s,struct socka
32、ddr FAR * name,int FAR * addrlen); 参数:同上4、监听连接 listen() 功能:用于面向连接服务器,表明它愿意接收连接。格式: int PASCAL FAR listen(SOCKET s, int backlog); 5、数据传输 send() 与 recv() 功能:数据的发送与接收格式: int PASCAL FAR send(SOCKET s,const char FAR * buf,int len,int flags); int PASCAL FAR recv(SOCKET s,const char FAR * buf,int len,int f
33、lags); 参数: buf: 指向存有传输数据的缓冲区的指针。6、多路复用 select() 功能:用来检测一个或多个套接字状态。格式: int PASCAL FAR select(int nfds,fd_set FAR * readfds,fd_set FAR * writefds, fd_set FAR * exceptfds,const struct timeval FAR * timeout); 参数: readfds: 指向要做读检测的指针writefds:指向要做写检测的指针exceptfds: 指向要检测是否出错的指针名师资料总结 - - -精品资料欢迎下载 - - - - -
34、 - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 12 页,共 16 页 - - - - - - - - - timeout: 最大等待时间7、关闭套接字 closesocket() 功能:关闭套接字s 格式: BOOL PASCAL FAR closesocket(SOCKET s); 三、典型过程图2.1 面向连接的套接字的系统调用时序图2.2 无连接协议的套接字调用时序图名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 13 页,
35、共 16 页 - - - - - - - - - 2.3 面向连接的应用程序流程图Windows Socket1.1 程序设计一、简介Windows Sockets 是从 Berkeley Sockets 扩展而来的,其在继承 Berkeley Sockets 的基础上,又进行了新的扩充。这些扩充主要是提供了一些异步函数,并增加了符合 WINDOWS消息驱动特性的网络事件异步选择机制。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 14 页,共 16 页 - - - - - - -
36、- - Windows Sockets 由两部分组成:开发组件和运行组件。开发组件: Windows Sockets 实现文档、应用程序接口 (API) 引入库和一些头文件。运行组件: Windows Sockets 应用程序接口的动态链接库(WINSOCK.DLL) 。二、主要扩充说明1、异步选择机制:Windows Sockets 的异步选择函数提供了消息机制的网络事件选择,当使用它登记网络事件发生时, 应用程序相应窗口函数将收到一个消息,消息中指示了发生的网络事件,以及与事件相关的一些信息。Windows Sockets 提供了一个异步选择函数 WSAAsyncSelect() ,用它来
37、注册应用程序感兴趣的网络事件,当这些事件发生时, 应用程序相应的窗口函数将收到一个消息。函数结构如下:int PASCAL FAR WSAAsyncSelect(SOCKET s,HWND hWnd,unsigned int wMsg,long lEvent); 参数说明:hWnd :窗口句柄wMsg :需要发送的消息lEvent :事件(以下为事件的内容)值:含义:FD_READ 期望在套接字上收到数据(即读准备好)时接到通知FD_WRITE 期望在套接字上可发送数据(即写准备好)时接到通知FD_OOB 期望在套接字上有带外数据到达时接到通知FD_ACCEPT 期望在套接字上有外来连接时接到
38、通知FD_CONNECT 期望在套接字连接建立完成时接到通知FD_CLOSE 期望在套接字关闭时接到通知名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 15 页,共 16 页 - - - - - - - - - 例如:我们要在套接字读准备好或写准备好时接到通知,语句如下:rc=WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE); 如果我们需要注销对套接字网络事件的消息发送,只要将 lEvent 设置为 0 2、异步请求函数在 Berkeley S
39、ockets 中请求服务是阻塞的, WINDOWS SICKETS 除了支持这一类函数外,还增加了相应的异步请求函数(WSAAsyncGetXByY();)。3、阻塞处理方法Windows Sockets 为了实现当一个应用程序的套接字调用处于阻塞时,能够放弃 CPU让其它应用程序运行, 它在调用处于阻塞时便进入一个叫“HOOK”的例程,此例程负责接收和分配WINDOWS消息,使得其它应用程序仍然能够接收到自己的消息并取得控制权。WINDOWS 是非抢先的多任务环境, 即若一个程序不主动放弃其控制权,别的程序就不能执行。因此在设计Windows Sockets 程序时,尽管系统支持阻塞操作,但
40、还是反对程序员使用该操作。 但由于 SUN 公司下的 Berkeley Sockets 的套接字默认操作是阻塞的,WINDOWS 作为移植的 SOCKETS 也不可避免对这个操作支持。在 Windows Sockets 实现中,对于不能立即完成的阻塞操作做如下处理:DLL初始化循环操作。在循环中,它发送任何 WINDOWS 消息,并检查这个Windows Sockets 调用是否完成,在必要时,它可以放弃CPU 让其它应用程序执行(当然使用超线程的CPU 就不会有这个麻烦了 _)。我们可以调用WSACancelBlockingCall() 函数取消此阻塞操作。在 Windows Sockets
41、 中,有一个默认的阻塞处理例程 BlockingHook() 简单地获取并发送 WINDOWS 消息。如果要对复杂程序进行处理,Windows Sockets 中还有 WSASetBlockingHook() 提供用户安装自己的阻塞处理例程能力;与该函数相对应的则是 SWAUnhookBlockingHook(), 它用于删除先前安装的任何阻塞处理例程,并重新安装默认的处理例程。请注意,设计自己的阻塞处理例程时,除了函数 WSACancelBlockingHook() 之外,它不能使用其它的 Windows Sockets API 函数。 在处理例程中调用 WSACancelBlockingH
42、ook() 函数将取消处于阻塞的操作,它将结束阻塞循环。4、出错处理Windows Sockets 为了和以后多线程环境( WINDOWS/UNIX)兼容,它提供了两个出错处理函数来获取和设置当前线程的最近错误号。(WSAGetLastEror()和 WSASetLastError() )5、启动与终止使用函数 WSAStartup() 和 WSACleanup() 启动和终止套接字。名师资料总结 - - -精品资料欢迎下载 - - - - - - - - - - - - - - - - - - 名师精心整理 - - - - - - - 第 16 页,共 16 页 - - - - - - - - -