




已阅读5页,还剩8页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
添加为友情链接 风铃 内容 管理 数据加载中. WinSock网络编程实用宝典-3 2007-05-28 01:00:13 大 中 小 3、Windows Sockets API实现网络异步通讯摘要:本文对如何使用面向连接的流式套接字实现对网卡的编程以及如何实现异步网络通讯等问题进行了讨论与阐述。一、 引言在80年代初,美国加利福尼亚大学伯克利分校的研究人员为TCP/IP网络通信开发了一个专门用于网络通讯开发的API。这个API就是Socket接口(套接字)-当今在TCP/IP网络最为通用的一种API,也是在互联网上进行应用开发最为通用的一种API。在微软联合其它几家公司共同制定了一套Windows下的网络编程接口Windows Sockets规范后,由于在其规范中引入了一些异步函数,增加了对网络事件异步选择机制,因此更加符合Windows的消息驱动特性,使网络开发人员可以更加方便的进行高性能网络通讯程序的设计。本文接下来就针对Windows Sockets API进行面向连接的流式套接字编程以及对异步网络通讯的编程实现等问题展开讨论。二、 面向连接的流式套接字编程模型的设计本文在方案选择上采用了在网络编程中最常用的一种模型-客户机/服务器模型。这种客户/服务器模型是一种非对称式编程模式。该模式的基本思想是把集中在一起的应用划分成为功能不同的两个部分,分别在不同的计算机上运行,通过它们之间的分工合作来实现一个完整的功能。对于这种模式而言其中一部分需要作为服务器,用来响应并为客户提供固定的服务;另一部分则作为客户机程序用来向服务器提出请求或要求某种服务。本文选取了基于TCP/IP的客户机/服务器模型和面向连接的流式套接字。其通信原理为:服务器端和客户端都必须建立通信套接字,而且服务器端应先进入监听状态,然后客户端套接字发出连接请求,服务器端收到请求后,建立另一个套接字进行通信,原来负责监听的套接字仍进行监听,如果有其它客户发来连接请求,则再建立一个套接字。默认状态下最多可同时接收5个客户的连接请求,并与之建立通信关系。因此本程序的设计流程应当由服务器首先启动,然后在某一时刻启动客户机并使其与服务器建立连接。服务器与客户机开始都必须调用Windows Sockets API函数socket()建立一个套接字sockets,然后服务器方调用bind()将套接字与一个本地网络地址捆扎在一起,再调用listen()使套接字处于一种被动的准备接收状态,同时规定它的请求队列长度。在此之后服务器就可以通过调用accept()来接收客户机的连接。相对于服务器,客户端的工作就显得比较简单了,当客户端打开套接字之后,便可通过调用connect()和服务器建立连接。连接建立之后,客户和服务器之间就可以通过连接发送和接收资料。最后资料传送结束,双方调用closesocket()关闭套接字来结束这次通讯。整个通讯过程的具体流程框图可大致用下面的流程图来表示:面向连接的流式套接字编程流程示意图三、软件设计要点以及异步通讯的实现根据前面设计的程序流程,可将程序划分为两部分:服务器端和客户端。而且整个实现过程可以大致用以下几个非常关键的Windows Sockets API函数将其惯穿下来:服务器方:socket()-bind()-listen-accept()-recv()/send()-closesocket()客户机方:socket()-connect()-send()/recv()-closesocket()有鉴于以上几个函数在整个网络编程中的重要性,有必要结合程序实例对其做较深入的剖析。服务器端应用程序在使用套接字之前,首先必须拥有一个Socket,系统调用socket()函数向应用程序提供创建套接字的手段。该套接字实际上是在计算机中提供了一个通信埠,可以通过这个埠与任何一个具有套接字接口的计算机通信。应用程序在网络上传输、接收的信息都通过这个套接字接口来实现的。在应用开发中如同使用文件句柄一样,可以对套接字句柄进行读写操作:sock=socket(AF_INET,SOCK_STREAM,0);函数的第一个参数用于指定地址族,在Windows下仅支持AF_INET(TCP/IP地址);第二个参数用于描述套接字的类型,对于流式套接字提供有SOCK_STREAM;最后一个参数指定套接字使用的协议,一般为0。该函数的返回值保存了新套接字的句柄,在程序退出前可以用 closesocket(sock);函数来将其释放。服务器方一旦获取了一个新的套接字后应通过bind()将该套接字与本机上的一个端口相关联:sockin.sin_family=AF_INET;sockin.sin_addr.s_addr=0;sockin.sin_port=htons(USERPORT);bind(sock,(LPSOCKADDR)&sockin,sizeof(sockin);该函数的第二个参数是一个指向包含有本机IP地址和端口信息的sockaddr_in结构类型的指针,其成员描述了本地端口号和本地主机地址,经过bind()将服务器进程在网络上标识出来。需要注意的是由于1024以内的埠号都是保留的埠号因此如无特别需要一般不能将sockin.sin_port的埠号设置为1024以内的值。然后调用listen()函数开始侦听,再通过accept()调用等待接收连接以完成连接的建立:/连接请求队列长度为1,即只允许有一个请求,若有多个请求,/则出现错误,给出错误代码WSAECONNREFUSED。listen(sock,1);/开启线程避免主程序的阻塞AfxBeginThread(Server,NULL);UINT Server(LPVOID lpVoid)int nLen=sizeof(SOCKADDR);pView-newskt=accept(pView-sock,(LPSOCKADDR)& pView-sockin,(LPINT)& nLen);WSAAsyncSelect(pView-newskt,pView-m_hWnd,WM_SOCKET_MSG,FD_READ|FD_CLOSE);return 1;这里之所以把accept()放到一个线程中去是因为在执行到该函数时如没有客户连接服务器的请求到来,服务器就会停在accept语句上等待连接请求的到来,这势必会引起程序的阻塞,虽然也可以通过设置套接字为非阻塞方式使在没有客户等待时可以使accept()函数调用立即返回,但这种轮询套接字的方式会使CPU处于忙等待方式,从而降低程序的运行效率大大浪费系统资源。考虑到这种情况,将套接字设置为阻塞工作方式,并为其单独开辟一个子线程,将其阻塞控制在子线程范围内而不会造成整个应用程序的阻塞。对于网络事件的响应显然要采取异步选择机制,只有采取这种方式才可以在由网络对方所引起的不可预知的网络事件发生时能马上在进程中做出及时的响应处理,而在没有网络事件到达时则可以处理其他事件,这种效率是很高的,而且完全符合Windows所标榜的消息触发原则。前面那段代码中的WSAAsyncSelect()函数便是实现网络事件异步选择的核心函数。通过第四个参数注册应用程序感兴取的网络事件,在这里通过FD_READ|FD_CLOSE指定了网络读和网络断开两种事件,当这种事件发生时变会发出由第三个参数指定的自定义消息WM_SOCKET_MSG,接收该消息的窗口通过第二个参数指定其句柄。在消息处理函数中可以通过对消息参数低字节进行判断而区别出发生的是何种网络事件:void CNetServerView:OnSocket(WPARAM wParam,LPARAM lParam)int iReadLen=0;int message=lParam & 0x0000FFFF;switch(message)case FD_READ:/读事件发生。此时有字符到达,需要进行接收处理char cDataBufferMTU*10;/通过套接字接收信息iReadLen = recv(newskt,cDataBuffer,MTU*10,0);/将信息保存到文件if(!file.Open(ServerFile.txt,CFile:modeReadWrite)file.Open(E:ServerFile.txt,CFile:modeCreate|CFile:modeReadWrite);file.SeekToEnd();file.Write(cDataBuffer,iReadLen);file.Close();break;case FD_CLOSE:/网络断开事件发生。此时客户机关闭或退出。/进行相应的处理break;default:break;在这里需要实现对自定义消息WM_SOCKET_MSG的响应,需要在头文件和实现文件中分别添加其消息映射关系:头文件:/AFX_MSG(CNetServerView)/AFX_MSGvoid OnSocket(WPARAM wParam,LPARAM lParam);DECLARE_MESSAGE_MAP()实现文件:BEGIN_MESSAGE_MAP(CNetServerView, CView)/AFX_MSG_MAP(CNetServerView)/AFX_MSG_MAPON_MESSAGE(WM_SOCKET_MSG,OnSocket)END_MESSAGE_MAP()在进行异步选择使用WSAAsyncSelect()函数时,有以下几点需要引起特别的注意:1连续使用两次WSAAsyncSelect()函数时,只有第二次设置的事件有效,如:WSAAsyncSelect(s,hwnd,wMsg1,FD_READ);WSAAsyncSelect(s,hwnd,wMsg2,FD_CLOSE);这样只有当FD_CLOSE事件发生时才会发送wMsg2消息。2可以在设置过异步选择后通过再次调用WSAAsyncSelect(s,hwnd,0,0);的形式取消在套接字上所设置的异步事件。3Windows Sockets DLL在一个网络事件发生后,通常只会给相应的应用程序发送一个消息,而不能发送多个消息。但通过使用一些函数隐式地允许重发此事件的消息,这样就可能再次接收到相应的消息。4在调用过closesocket()函数关闭套接字之后不会再发生FD_CLOSE事件。以上基本完成了服务器方的程序设计,下面对于客户端的实现则要简单多了,在用socket()创建完套接字之后只需通过调用connect()完成同服务器的连接即可,剩下的工作同服务器完全一样:用send()/recv()发送/接收收据,用closesocket()关闭套接字:sockin.sin_family=AF_INET; /地址族sockin.sin_addr.S_un.S_addr=IPaddr; /指定服务器的IP地址sockin.sin_port=m_Port; /指定连接的端口号int nConnect=connect(sock,(LPSOCKADDR)&sockin,sizeof(sockin);本文采取的是可靠的面向连接的流式套接字。在数据发送上有write()、writev()和send()等三个函数可供选择,其中前两种分别用于缓冲发送和集中发送,而send()则为可控缓冲发送,并且还可以指定传输控制标志为MSG_OOB进行带外数据的发送或是为MSG_DONTROUTE寻径控制选项。在信宿地址的网络号部分指定数据发送需要经过的网络接口,使其可以不经过本地寻径机制直接发送出去。这也是其同write()函数的真正区别所在。由于接收数据系统调用和发送数据系统调用是一一对应的,因此对于数据的接收,在此不再赘述,相应的三个接收函数分别为:read()、readv()和recv()。由于后者功能上的全面,本文在实现上选择了send()-recv()函数对,在具体编程中应当视具体情况的不同灵活选择适当的发送-接收函数对。小结:TCP/IP协议是目前各网络操作系统主要的通讯协议,也是 Internet的通讯协议,本文通过Windows Sockets API实现了对基于TCP/IP协议的面向连接的流式套接字网络通讯程序的设计,并通过异步通讯和多线程等手段提高了程序的运行效率,避免了阻塞的发生。4、用VC+6.0的SocketsAPI实现一个聊天室程序1.VC+网络编程及Windows Sockets API简介VC+对网络编程的支持有socket支持,WinInet支持,MAPI和ISAPI支持等。其中,Windows Sockets API是TCP/IP网络环境里,也是Internet上进行开发最为通用的API。最早美国加州大学Berkeley分校在UNIX下为TCP/IP协议开发了一个API,这个API就是著名的Berkeley Socket接口(套接字)。在桌面操作系统进入Windows时代后,仍然继承了Socket方法。在TCP/IP网络通信环境下,Socket数据传输是一种特殊的I/O,它也相当于一种文件描述符,具有一个类似于打开文件的函数调用-socket()。可以这样理解:Socket实际上是一个通信端点,通过它,用户的Socket程序可以通过网络和其他的Socket应用程序通信。Socket存在于一个通信域(为描述一般的线程如何通过Socket进行通信而引入的一种抽象概念)里,并且与另一个域的Socket交换数据。Socket有三类。第一种是SOCK_STREAM(流式),提供面向连接的可靠的通信服务,比如telnet,http。第二种是SOCK_DGRAM(数据报),提供无连接不可靠的通信,比如UDP。第三种是SOCK_RAW(原始),主要用于协议的开发和测试,支持通信底层操作,比如对IP和ICMP的直接访问。2.Windows Socket机制分析2.1一些基本的Socket系统调用主要的系统调用包括:socket()-创建Socket;bind()-将创建的Socket与本地端口绑定;connect()与accept()-建立Socket连接;listen()-服务器监听是否有连接请求;send()-数据的可控缓冲发送;recv()-可控缓冲接收;closesocket()-关闭Socket。2.2Windows Socket的启动与终止启动函数WSAStartup()建立与Windows Sockets DLL的连接,终止函数WSAClearup()终止使用该DLL,这两个函数必须成对使用。2.3异步选择机制Windows是一个非抢占式的操作系统,而不采取UNIX的阻塞机制。当一个通信事件产生时,操作系统要根据设置选择是否对该事件加以处理,WSAAsyncSelect()函数就是用来选择系统所要处理的相应事件。当Socket收到设定的网络事件中的一个时,会给程序窗口一个消息,这个消息里会指定产生网络事件的Socket,发生的事件类型和错误码。2.4异步数据传输机制WSAAsyncSelect()设定了Socket上的须响应通信事件后,每发生一个这样的事件就会产生一个WM_SOCKET消息传给窗口。而在窗口的回调函数中就应该添加相应的数据传输处理代码。3.聊天室程序的设计说明3.1实现思想在Internet上的聊天室程序一般都是以服务器提供服务端连接响应,使用者通过客户端程序登录到服务器,就可以与登录在同一服务器上的用户交谈,这是一个面向连接的通信过程。因此,程序要在TCP/IP环境下,实现服务器端和客户端两部分程序。3.2服务器端工作流程服务器端通过socket()系统调用创建一个Socket数组后(即设定了接受连接客户的最大数目),与指定的本地端口绑定bind(),就可以在端口进行侦听listen()。如果有客户端连接请求,则在数组中选择一个空Socket,将客户端地址赋给这个Socket。然后登录成功的客户就可以在服务器上聊天了。3.3客户端工作流程客户端程序相对简单,只需要建立一个Socket与服务器端连接,成功后通过这个Socket来发送和接收数据就可以了。4.核心代码分析限于篇幅,这里仅给出与网络编程相关的核心代码,其他的诸如聊天文字的服务器和客户端显示读者可以自行添加。4.1服务器端代码开启服务器功能:?/Pvoid OnServerOpen() /开启服务器功能WSADATA wsaData;int iErrorCode;char chInfo64;if (WSAStartup(WINSOCK_VERSION, &wsaData) /调用Windows Sockets DLL MessageBeep(MB_ICONSTOP);MessageBox(Winsock无法初始化!, AfxGetAppName(), MB_OK|MB_ICONSTOP);WSACleanup();return; elseWSACleanup();if (gethostname(chInfo, sizeof(chInfo) ReportWinsockErr(n无法获取主机!n );return; CString csWinsockID = n=服务器功能开启在端口:No. ;csWinsockID += itoa(m_pDoc-m_nServerPort, chInfo, 10);csWinsockID += n;PrintString(csWinsockID); /在程序视图显示提示信息的函数,读者可自行创建m_pDoc-m_hServerSocket=socket(PF_INET, SOCK_STREAM, DEFAULT_PROTOCOL);/创建服务器端Socket,类型为SOCK_STREAM,面向连接的通信if (m_pDoc-m_hServerSocket = INVALID_SOCKET) ReportWinsockErr(无法创建服务器socket!);return;m_pDoc-m_sockServerAddr.sin_family = AF_INET;m_pDoc-m_sockServerAddr.sin_addr.s_addr = INADDR_ANY;m_pDoc-m_sockServerAddr.sin_port = htons(m_pDoc-m_nServerPort);if (bind(m_pDoc-m_hServerSocket, (LPSOCKADDR)&m_pDoc-m_sockServerAddr, sizeof(m_pDoc-m_sockServerAddr) = SOCKET_ERROR) /与选定的端口绑定ReportWinsockErr(无法绑定服务器socket!);return;iErrorCode=WSAAsyncSelect(m_pDoc-m_hServerSocket,m_hWnd,WM_SERVER_ACCEPT, FD_ACCEPT);/设定服务器相应的网络事件为FD_ACCEPT,即连接请求,/ 产生相应传递给窗口的消息为WM_SERVER_ACCEPTif (iErrorCode = SOCKET_ERROR) ReportWinsockErr(WSAAsyncSelect设定失败!);return;if (listen(m_pDoc-m_hServerSocket, QUEUE_SIZE) = SOCKET_ERROR) /开始监听客户连接请求ReportWinsockErr(服务器socket监听失败!);m_pParentMenu-EnableMenuItem(ID_SERVER_OPEN, MF_ENABLED);return;m_bServerIsOpen = TRUE; /监视服务器是否打开的变量return;响应客户发送聊天文字到服务器:ON_MESSAGE(WM_CLIENT_READ, OnClientRead)?/PLRESULT OnClientRead(WPARAM wParam, LPARAM lParam)int iRead;int iBufferLength;int iEnd;int iRemainSpace;char chInBuffer1024;int i;for(i=0;(i/MAXClient是服务器可响应连接的最大数目if(i=MAXClient) return 0L;iBufferLength = iRemainSpace = sizeof(chInBuffer);iEnd = 0;iRemainSpace -= iEnd;iBytesRead = recv(m_aClientSocketi, (LPSTR)(chInBuffer+iEnd), iSpaceRemaining, NO_FLAGS); /用可控缓冲接收函数recv()来接收字符iEnd+=iRead;if (iBytesRead = SOCKET_ERROR)ReportWinsockErr(recv出错!);chInBufferiEnd = 0;if (lstrlen(chInBuffer) != 0)PrintString(chInBuffer); /服务器端文字显示OnServerBroadcast(chInBuffer); /自己编写的函数,向所有连接的客户广播这个客户的聊天文字return(0L);对于客户断开连接,会产生一个FD_CLOSE消息,只须相应地用closesocket()关闭相应的Socket即可,这个处理比较简单。4.2客户端代码连接到服务器:?/Pvoid OnSocketConnect() WSADATA wsaData;DWORD dwIPAddr;SOCKADDR_IN sockAddr;if(WSAStartup(WINSOCK_VERSION,&wsaData) /调用Windows Sockets DLLMessageBox(Winsock无法初始化!,NULL,MB_OK);return;m_hSocket=socket(PF_INET,SOCK_STREAM,0); /创建面向连接的socketsockAddr.sin_family=AF_INET; /使用TCP/IP协议sockAddr.sin_port=m_iPort; /客户端指定的IP地址sockAddr.sin_addr.S_un.S_addr=dwIPAddr;int nConnect=connect(m_hSocket,(LPSOCKADDR)&sockAddr,sizeof(sockAddr); /请求连接if(nConnect)ReportWinsockErr(连接失败!);elseMessageBox(连接成功!,NULL,MB_OK);int iErrorCode=WSAAsyncSelect(m_hSocket,m_hWnd,WM_SOCKET_READ,FD_READ);/指定响应的事件,为服务器发送来字符if(iErrorCode=SOCKET_ERROR)MessageBox(WSAAsyncSelect设定失败!);接收服务器端发送的字符也使用可控缓冲接收函数recv(),客户端聊天的字符发送使用数据可控缓冲发送函数send(),这两个过程比较简单,在此就不加赘述了。5.小结通过聊天室程序的编写,可以基本了解Windows Sockets API编程的基本过程和精要之处。本程序在VC+6.0下编译通过,在使用windows 98/NT的局域网里运行良好。5、用VC+制作一个简单的局域网消息发送工程本工程类似于oicq的消息发送机制,不过他只能够发送简单的字符串。虽然简单,但他也是一个很好的VC网络学习例子。本例通过VC带的SOCKET类,重载了他的一个接受类mysock类,此类可以吧接收到的信息显示在客户区理。以下是实现过程:建立一个MFC 单文档工程,工程名为oicq,在第四步选取WINDOWS SOCKetS支持,其它取默认设置即可。为了简单,这里直接把about对话框作些改变,作为发送信息界面。这里通过失去对话框来得到发送的字符串、获得焦点时把字符串发送出去。创建oicq类的窗口,获得VIEW类指针,进而可以把接收到的信息显示出来。?/Pextern CString bb;void CAboutDlg:OnKillFocus(CWnd* pNewWnd)/ TODO: Add your message handler code hereCDialog:OnKillFocus(pNewWnd);bb=m_edit;对于OICQVIEW类char aa100;CString mm;CDC* pdc;class mysock:public CSocket /派生mysock类,此类既有接受功能public:
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 抗击疫情救援申请书
- 余杭电工培训申请书
- 天津市他项权登记申请书
- 贫困申请书格式
- 财产申请抵押申请书
- 潜水望远镜课件
- 2025水果定购的合同范本
- 2025年厂房水电安装合同范本
- 2025合同样例股权激励分配协议范本
- 安全检查培训评价课件
- 公司兼职人员劳务合同4篇
- 制造过程质量追溯系统设计
- 2025年能源行业新能源产业风险管理可行性研究报告
- 会计毕业论文烟草专业
- 数字文旅概论 课件全套 第1-9章 数字文旅内涵与发展背景 - 数字营销
- 井盖安全常识培训课件
- 年产5万吨电熔锆刚玉新材料扩建项目环境影响报告表
- 2025社会工作员考试(社会工作基础知识)综合能力测试题及答案
- 卫生院支委会补选书记会议记录范文
- 酸洗作业安全知识培训
- 沥青混凝土面层和沥青碎砾石面层分项工程质量检验评定表新城
评论
0/150
提交评论