Windows Socket五种IO模型——代码全攻略
WinSocket模型探讨——完成端口模型
WinSocket模型的探讨——完成端口模型众所皆知,完成端口是在WINDOWS平台下效率最高,扩展性最好的IO模型,特别针对于WINSOCK的海量连接时,更能显示出其威力。
其实建立一个完成端口的服务器也很简单,只要注意几个函数,了解一下关键的步骤也就行了。
这是篇完成端口入门级的文章,分为以下几步来说明完成端口:函数常见问题以及解答步骤例程1、函数:我们在完成端口模型下会使用到的最重要的两个函数是:CreateIoCompletionPort、GetQueuedCompletionStatusCreateIoCompletionPort 的作用是创建一个完成端口和把一个IO句柄和完成端口关联起来:// 创建完成端口HANDLE CompletionPort = CreateIoCompletionPort(INV ALID_HANDLE_V ALUE, NULL, 0, 0);// 把一个IO句柄和完成端口关联起来,这里的句柄是一个socket 句柄CreateIoCompletionPort((HANDLE)sClient, CompletionPort, (DWORD)PerHandleData, 0);其中第一个参数是句柄,可以是文件句柄、SOCKET句柄。
第二个就是我们上面创建出来的完成端口,这里就把两个东西关联在一起了。
第三个参数很关键,叫做PerHandleData,就是对应于每个句柄的数据块。
我们可以使用这个参数在后面取到与这个SOCKET对应的数据。
最后一个参数给0,意思就是根据CPU的个数,允许尽可能多的线程并发执行。
GetQueuedCompletionStatus 的作用就是取得完成端口的结果:// 从完成端口中取得结果GetQueuedCompletionStatus(CompletionPort, &BytesTransferred, (LPDWORD)&PerHandleData, (LPOVERLAPPED*)&PerIoData, INFINITE)第一个参数是完成端口第二个参数是表明这次的操作传递了多少个字节的数据第三个参数是OUT类型的参数,就是前面CreateIoCompletionPort传进去的单句柄数据,这里就是前面的SOCKET句柄以及与之相对应的数据,这里操作系统给我们返回,让我们不用自己去做列表查询等操作了。
Windows Socket五种IO模型——代码全攻略
Windows Socket五种I/O模型——代码全攻略(1)如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。
Windows操作系统提供了选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型。
每一种模型均适用于一种特定的应用场景。
程序员应该对自己的应用需求非常明确,而且综合考虑到程序的扩展性和可移植性等因素,作出自己的选择。
我会以一个回应反射式服务器(与《Windows网络编程》第八章一样)来介绍这五种I/O模型。
我们假设客户端的代码如下(为代码直观,省去所有错误检查,以下同):#include <WINSOCK2.H>#include <stdio.h>#define SERVER_ADDRESS "137.117.2.148"#define PORT 5150#define MSGSIZE 1024#pragma comment(lib, "ws2_32.lib")int main(){WSADATA wsaData;SOCKET sClient;SOCKADDR_IN server;char szMessage[MSGSIZE];int ret;// Initialize Windows socket libraryWSAStartup(0x0202, &wsaData);// Create client socketsClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// Connect to servermemset(&server, 0, sizeof(SOCKADDR_IN));server.sin_family = AF_INET;server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);server.sin_port = htons(PORT);connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));while (TRUE){printf("Send:");gets(szMessage);// Send messagesend(sClient, szMessage, strlen(szMessage), 0);// Receive messageret = recv(sClient, szMessage, MSGSIZE, 0);szMessage[ret] = '\0';printf("Received [%d bytes]: '%s'\n", ret, szMessage);}// Clean upclosesocket(sClient);WSACleanup();return 0;}客户端所做的事情相当简单,创建套接字,连接服务器,然后不停的发送和接收数据。
第4章 Windows套接字IO模型
FIONREAD
SIOCATMARK
用于确定是否所有的 带外数据都已经被读 入,该命令仅适用于 流式套接字
TCP 首部格式
位0 8 源 端 口 序 号 TCP 首部 确 数据 偏移 保 留
U AP RSF R CS SY I G K H T NN
16
24 目 的 端 口
31
认
号 窗 口 紧 急 指 针
沈阳航空航天大学
简单的阻塞模式示例
SOCKET sock; char buff[256]; int done = 0; …… while(!done) { nBytes = recv(sock,buff,65,0); if (nBytes == SOCKET_ERROR) { printf(“recv failed with error %d\n”,WSAGetLastError()); return; } DoComputationData(buff); } ……
网络程序设计
第四章Windows套接字I/O模型
沈阳航空航天大学
设置套接字工作模式—ioctlsocket() 函数形式 :
int ioctlsocket ( SOCKET s, long cmd, u_long * argp );
功能说明: 套接字默认工作在阻塞模式,此函数设置套接字的工作模 式为非阻塞或阻塞模式。 返回值: 正确调用返回0,否则将返回SOCKET_ERROR ,应用程序
假如没有数据处于“待决”状态, 那么recv函数可能永远都无法返回。 只有从系统的输入缓冲区中读回点 什么东西,才允dows套接字I/O模型
沈阳航空航天大学
1.2 非阻塞模式
非阻塞模式的套接字在使用上稍显困难,但它在功能上 是非常强大的。除具备阻塞套接字已有的各项优点之外, 还进行了扩充,功能更强。 创建一个套接字,并将其置为非阻塞模式的程序示例:
windows—socket函数整理
Select模型Select模型是Windows sockets中最常见的IO模型。
它利用select函数实现IO 管理。
通过对select函数的调用,应用程序可以判断套接字是否存在数据、能否向该套接字写入数据。
如:在调用recv函数之前,先调用select函数,如果系统没有可读数据那么select函数就会阻塞在这里。
当系统存在可读或可写数据时,select函数返回,就可以调用recv函数接收数据了。
可以看出使用select模型,需要两次调用函数。
第一次调用select函数第二次socket API。
使用该模式的好处是:可以等待多个套接字。
select函数中需要三个fd_set结构:一:准备接收数据的套接字集合,即可读性集合。
二:准备发送数据的套接字集合,即可写性集合。
在select函数返回时,会在fd_set结构中,填入相应的套接字。
readfds数组将包括满足以下条件的套接字:1:有数据可读。
此时在此套接字上调用recv,立即收到对方的数据。
2:连接已经关闭、重设或终止。
3:正在请求建立连接的套接字。
此时调用accept函数会成功。
writefds数组包含满足下列条件的套接字:1:有数据可以发出。
此时在此套接字上调用send,可以向对方发送数据。
2:调用connect函数,并连接成功的套接字。
exceptfds数组将包括满足下列条件的套接字:1:调用connection函数,但连接失败的套接字。
2:有带外(out of band)数据可读。
select函数的使用:在调用select函数对套接字进行监视之前,必须将要监视的套接字分配给上述三个数组中的一个。
然后调用select函数,再次判断需要监视的套接字是否还在原来的集合中。
就可以知道该集合是否正在发生IO操作。
例如:应用程序想要判断某个套接字是否存在可读的数据,需要进行如下步骤:1:将该套接字加入到readfds集合。
2:以readfds作为第二个参数调用select函数。
Socket IO模型 六种讲解
本文简单介绍了当前Windows支持的各种Socket I/O模型,如果你发现其中存在什么错误请务必赐教。
一:select模型二:WSAAsyncSelect模型三:WSAEventSelect模型四:Overlapped I/O 事件通知模型五:Overlapped I/O 完成例程模型六:IOCP模型老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。
他们的信会被邮递员投递到他们的信箱里。
这和Socket模型非常类似。
下面我就以老陈接收信件为例讲解Socket I/O模型~~~一:select模型老陈非常想看到女儿的信。
以至于他每隔10分钟就下楼检查信箱,看是否有女儿的信~~~~~在这种情况下,"下楼检查信箱"然后回到楼上耽误了老陈太多的时间,以至于老陈无法做其他工作。
select模型和老陈的这种情况非常相似:周而复始地去检查......如果有数据......接收/发送.......使用线程来select应该是通用的做法:procedure TListenThread.Execute;varaddr : TSockAddrIn;fd_read : TFDSet;timeout : TTimeV al;ASock,MainSock : TSocket;len, i : Integer;beginMainSock := socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );addr.sin_family := AF_INET;addr.sin_port := htons(5678);addr.sin_addr.S_addr := htonl(INADDR_ANY);bind( MainSock, @addr, sizeof(addr) );listen( MainSock, 5 );while (not Terminated) dobeginFD_ZERO( fd_read );FD_SET( MainSock, fd_read );_sec := 0;_usec := 500;if select( 0, @fd_read, nil, nil, @timeout ) > 0 then //至少有1个等待Accept的connection beginif FD_ISSET( MainSock, fd_read ) thenbeginfor i:=0 to fd_read.fd_count-1 do //注意,fd_count <= 64,也就是说select只能同时管理最多64个连接beginlen := sizeof(addr);ASock := accept( MainSock, addr, len );if ASock <> INV ALID_SOCKET then....//为ASock创建一个新的线程,在新的线程中再不停地selectend;end;end;end; //while (not self.Terminated)shutdown( MainSock, SD_BOTH );closesocket( MainSock );end;二:WSAAsyncSelect模型后来,老陈使用了微软公司的新式信箱。
Socket阻塞模式和非阻塞模式
Socket阻塞模式和⾮阻塞模式阻塞I/O模型:简介:进程会⼀直阻塞,直到数据拷贝完成应⽤程序调⽤⼀个IO函数,导致应⽤程序阻塞,等待数据准备好。
如果数据没有准备好,⼀直等待….数据准备好了,从内核拷贝到⽤户空间,IO函数返回成功指⽰。
阻塞I/O模型图:在调⽤recv()/recvfrom()函数时,发⽣在内核中等待数据和复制数据的过程。
当调⽤recv()函数时,系统⾸先查是否有准备好的数据。
如果数据没有准备好,那么系统就处于等待状态。
当数据准备好后,将数据从系统缓冲区复制到⽤户空间,然后该函数返回。
在套接应⽤程序中,当调⽤recv()函数时,未必⽤户空间就已经存在数据,那么此时recv()函数就会处于等待状态。
当使⽤socket()函数和WSASocket()函数创建套接字时,默认的套接字都是阻塞的。
这意味着当调⽤Windows Sockets API不能⽴即完成时,线程处于等待状态,直到操作完成。
并不是所有Windows Sockets API以阻塞套接字为参数调⽤都会发⽣阻塞。
例如,以阻塞模式的套接字为参数调⽤bind()、listen()函数时,函数会⽴即返回。
将可能阻塞套接字的Windows Sockets API调⽤分为以下四种:1.输⼊操作: recv()、recvfrom()、WSARecv()和WSARecvfrom()函数。
以阻塞套接字为参数调⽤该函数接收数据。
如果此时套接字缓冲区内没有数据可读,则调⽤线程在数据到来前⼀直睡眠。
2.输出操作: send()、sendto()、WSASend()和WSASendto()函数。
以阻塞套接字为参数调⽤该函数发送数据。
如果套接字缓冲区没有可⽤空间,线程会⼀直睡眠,直到有空间。
3.接受连接:accept()和WSAAcept()函数。
以阻塞套接字为参数调⽤该函数,等待接受对⽅的连接请求。
如果此时没有连接请求,线程就会进⼊睡眠状态。
4.外出连接:connect()和WSAConnect()函数。
一文读懂网络编程中的5种IO模型
一文读懂网络编程中的5种IO模型关于IO模型,就必须先谈到几个日常接触的几个与IO相关名字:同步,异步,阻塞,非阻塞。
一、名词解释1、同步如果事件A需要等待事件B的完成才能完成,这种串行执行机制可以说是同步的,这是一种可靠的任务序列,要么都成功,要么都失败。
2、异步如果事件A的执行不需要依赖事件B的完成结果,这种并行的执行机制可以说是异步的。
事件A不确定事件B是否真正完成,所以是不可靠的任务序列。
同步异步可以理解为多个事件的执行方式和执行时机如何,是串行等待还是并行执行。
同步中依赖事件等待被依赖事件的完成,然后触发自身开始执行,异步中依赖事件不需要等待被依赖事件,可以和被依赖事件并行执行,被依赖事件执行完成后,可以通过回调、通知等方式告知依赖事件。
3、阻塞对于阻塞,如果一个事件在发起一个调用之后,在调用结果返回之前,该事件会被一直挂起,处于等待状态。
4、非阻塞对于非阻塞,如果一个事件在发起调用以后,无论该调用当前是否得到结果,都会立刻返回,不会阻塞当前事件。
阻塞与非阻塞可以理解为单个事件在发起其他调用以后,自身的状态如何,是苦苦等待还是继续干自己的事情。
非阻塞虽然能提高CPU利用率,但是也带来了系统线程切换的成本,需要在CPU执行时间和系统切换成本之间好好估量一下。
二、IO模型IO模型分为五种,阻塞IO模型、非阻塞IO模型、IO复用模型、信号驱动IO模型、异步IO模型、前4种为同步IO操作、只有异步IO模型是异步IO操作、请仔细阅读IO交互便于理解IO模型。
1、阻塞IO模型网络编程中,读取客户端的数据需要调用recvfrom。
在默认情况下,这个调用会一直阻塞直到数据接收完毕,就是一个同步阻塞的IO方式。
模拟举例:老李去火车站买票,排队三天买到一张退票。
耗费:在车站吃喝拉撒睡 3天,其他事一件没干。
2、非阻塞IO模型当用户进程发出read操作时、如果内核中的数据还没有准备好、那么它并不会阻塞用户进程、而是立刻返回一个error、从用户进程角度讲、它发起一个read操作后、并不需要等待、而是马上就得到了一个结果、用户进程判断结果是一个error时、它就知道数据还没有准备好、于是它可以再次发送read操作、一旦内核中的数据准备好了、并且又再次收到了用户进程的系统调用、那么它马上就将数据拷贝到了用户内存、然后返回。
Winsocket编程基础
一、网络编程基础1.1计算机网络概述计算机网络把分布在不同地点且具有独立功能的多个计算机系统通过通信设备和线路连接起来,在功能完善的软件和协议的管理下实现网络中资源共享。
在工控领域,现场数据的采集、传输及控制信息的发送都依赖于计算机网络来实现。
1.2、网络参考模型为什么要对网络进行分层设计?在计算机通信过程中需要通信协议,但因传输介质的不同、计算机本身的差异以及数据格式的不同等因素,致使网络通信相当复杂,为了降低复杂性,OSI提出了协议分层的参考模型,即OSI七层互联参考模型。
因为OSI的网络模型标准比较严格,另外推出的时间也相对较晚,所以目前还没有完全按照OSI模型实现的网络。
TCP/IP是目前实际应用最广泛的一种网络模型,在这个模型中,不只是划分了功能层,还有具体的实现技术,即协议。
TCP和IP就是这个模型中最重要的两个层次的代表协议。
1.2.1、OSI和TCP/IP参考模型在网络的不同分层中有不同的协议,计算机只能在同一层次上进行通信,如下图:虽然TCP/IP不是完全符合OSI的参考模型,但在层次上也存在着对应关系,如下图:1.2.2、数据流向在网络的层次模型中,每一层与相邻层之间都留有接口,较低层通过接口为上一层提供服务,中间层就像个翻译一样,如下图为经典的中德教师的对话过程:在TCP/IP网络模型中,数据在从应用层向网络接口层(链路层)传递的过程中,每经过一层都要加入该层的相应的协议内容,这样数据在链路层形成了完整的数据包,该数据包到达接收方后,数据包从链路层到应用层进行逐层解析,在接收方应用层解析得到的数据就是发送方在应用层发送的数据,数据进行逐层封装和解析的过程如下图:1.2.3、IP分类IP地址在网络层中定义,长度为32个二进制位,分为4段,每段8位,用于主机在网络中的标识,IP地址有两部分组成,一部分为网络地址,另一部分为主机地址。
IP地址分为A、B、C、D、E 共5类,具体如下:A 类:|0| + 网络号7位+主机号24位0.0.0.0 ------127.255.255.255B 类:|1 0|+网络号14位+主机号16位128.0.0.0 ------191.255.255.255C 类:|1 1 0|+网络号21位+主机号8位192.0.0.0 ------223.255.255.255D 类:|1 1 1 0|+网络号28位多播组号224.0.0.0 ------239.255.255.255E 类:|1 1 1 1 0|+27位留待后用在使用IP地址进行通信时,可分为单播、组播和广播三种通信方式。
WindowsSocket学习笔记
Windows Socket学习笔记Socket(套接字)◆先看定义:typedef unsigned int u_int;typedef u_int SOCKET;◆Socket相当于进行网络通信两端的插座,只要对方的Socket和自己的Socket有通信联接,双方就可以发送和接收数据了。
其定义类似于文件句柄的定义。
◆Socket有五种不同的类型:1、流式套接字(stream socket)定义:#define SOCK_STREAM 1流式套接字提供了双向、有序的、无重复的以及无记录边界的数据流服务,适合处理大量数据。
它是面向联结的,必须建立数据传输链路,同时还必须对传输的数据进行验证,确保数据的准确性。
因此,系统开销较大。
2、数据报套接字(datagram socket)定义:#define SOCK_DGRAM 2数据报套接字也支持双向的数据流,但不保证传输数据的准确性,但保留了记录边界。
由于数据报套接字是无联接的,例如广播时的联接,所以并不保证接收端是否正在侦听。
数据报套接字传输效率比较高。
3、原始套接字(raw-protocol interface)定义:#define SOCK_RAW 3原始套接字保存了数据包中的完整IP头,前面两种套接字只能收到用户数据。
因此可以通过原始套接字对数据进行分析。
其它两种套接字不常用,这里就不介绍了。
◆Socket开发所必须需要的文件(以WinSock V2.0为例):头文件:Winsock2.h库文件:WS2_32.LIB动态库:W32_32.DLL一些重要的定义1、数据类型的基本定义:这个大家一看就懂。
typedef unsigned char u_char;typedef unsigned short u_short;typedef unsigned int u_int;typedef unsigned long u_long;2、网络地址的数据结构,有一个老的和一个新的的,请大家留意,如果想知道为什么,请发邮件给Bill Gate。
网络IO模型(二)
•cEvents 要检测的事件数量,
•lphEvents 事件对象数组首地址
•fWaitAll 是否要等待所有事件均返回 •dwTimeout 超时时间 •fAlertables
WSAEnumNetworkEvents
查询指定的套接字上触发的网络事件,并将 WSAEVENT重新设置为非激活状态
int WSAEnumNetworkEvents( SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents ); •s 代表我们要检测的socket套接字
使用完成端口
注意点: 2、在套接字上使用重叠I/O接受连接的惟一 API就是AcceptEx()函数。通常的同步接受 函数accept()的返回值是一个新的套接字, 而AcceptEx()函数则需要另外一个套接字作 为它的参数之一。这是因为AcceptEx()是一 个重叠操作,所以你需要事先创建一个套接 字(但不要绑定或连接它),并把这个套接字 通过参数传成端口的使用分为两步。首先创建完成端 口,如以下代码所示:
HANDLE hIocp; hIocp = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, (ULONG_PTR)0, 0); if (hIocp == NULL) { // Error }
WSAEventSelect原型
为指定的套接字请求基于Windows消息的 网络事件通知,并自动将该套接字设置为 非阻塞模式
int WSAEventSelect( SOCKET s, WSAEVENT hEventObject, long lNetworkEvents );
WinSocket编程
WinSocket编程默认分类2009-12-07 10:33:56 阅读164 评论0 字号:大中小什么是Socket接触网络编程当然要了解Socket,Socket(套接字)是一种网络编程接口,Socket提供了很多灵活的函数,帮助程序员写出高效的网络应用。
Socket分为BSD UNIX和windows两个版本。
在win32平台上的Winsock编程都要经过下列基本步骤:定义变量——获得Winsock版本——加载Winsock库——初始化——创建套接字——设置套接字——关闭套接字——卸载Winsock 库使用winsock2 API编程,必须包含头文件winsock2.h (链接环境WS2_32.LIB),头文件winsock.h(WSOCK32.LIB)是为了兼容winsock1程序时使用的,另外mswsock.h(MSWSOCK.DLL)是微软的扩展类,用于开发高性能的winsock程序。
准备好后,你就可以着手建立你的第一个网络程序了。
二,Socket编程的基本过程Socket通信分为面向连接的通信(TCP)和面向无连接的通信(UDP),通信流程如下:面向连接的通信无连接的通信1,Winsock初始化和结束每一个winsock应用程序必须首先加载相应的winsock dll版本。
方法是调用:int WSAStartup(WORD wVersionRequested, 库版本,高字节副版本,低字节主版本LPWSADATA lpWSAData 结构指针,函数自动填充该结构。
); 函数调用成功返回0可以用宏MAKEWORD(x, y)用来指定第一个参数的值2,建立套接字套接字是传输提供者的一个句柄。
SOCKET socket (int af,int type,int protocol IPPROTO_TCP,IPPROTO_UDP,0(如果不想指定));第一个参数指定通信协议的协议族,AF_INET(IPv4)或AF_INET6(IPv6)(因为Socket是网络编程接口而不是一个协议,它使用流行的网络协议(TCP/IP,IPX)为应用程序提供的一个编程接口。
WinSock三种选择IO模型
在《套接字socket及C/S通信的基本概念》和《WinSock编程基础》中,我们介绍了套接字的基本概念和WinSock API的基本调用规范。
我们讨论了阻塞模式/非阻塞模式和同步I/O和异步I/O等话题。
从概念的角度,阻塞模式因其简洁易用便于快速原型化,但在应付建立连接的多个套接字或在数据的收发量不均、时间不定时却极难管理。
另一方面,我们需要对非阻塞模式套接字的 WinSock API调用频繁返回的WSAEWOULDBLOCK错误加以判断处理也显得难于管理。
WinSock套接字I/O模型提供了管理I/O 完成通知的方法,帮助应用程序判断套接字何时可供读写。
共有6中类型的套接字I/O模型可让WinSock应用程序对I/O进行管理,它们包括blocking(阻塞)、select(选择)、WSAAsyncSelect(异步选择)、WSAEventSelect(事件选择)、overlapped(重叠)以及completionport(完成端口)。
本文讨论三种选择(都带select)模型。
1.基于套接字集合的select模型(1)select模型概述该模型时最初设计是在不使用UNIX操作系统的计算机上实现的,它们采用的是Berkeley套接字方案。
select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。
之所以称其为“select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理!使用select模型,一般需要调用ioctlsocket 函数将一个套接字从锁定模式切换为非锁定模式。
// 将套接字s设置为非阻塞模式unsigned long nonBlocking = 1;ioctlsocket(s, FIONBIO, (u_long*)&nonBlocking);select模型本质上是一种分类处理思想,预先声明几个FD_SET(fd_set 结构)集合(使用FD_ZERO初始化),例如ReadSet,WriteSet,然后调用宏FD_SET(s,&ReadSet)将关注FD_READ事件的套接字s添加到ReadSet 集合,调用宏FD_SET(s,&WriteSet)将关注FD_WRITE事件的套接字s添加到WriteSet集合。
完整的Socket代码
完整的Socket代码先上图列举⼀个通信协议⽹关发送环境数据此⽹关设备所对应的所有传感器参数,格式如下:⽹关发送:包长度+KEY值+请求类型+发送者+接收者+消息类型+消息内容说明:包长度:short int型(16位),除本字段外,后⾯所跟的所有数据字节数;标识符:int32型,固定值:0x987656789;请求类型:int32型,数据通信:3;发送者:string,终端编号,如:11170303001,格式:长度(int)+发送者字符串的Unicode编码(长度是指发送者的Unicode编码后的字节数);接收者:string,服务器编号,如:server,格式:长度(int)+发送者字符串的Unicode编码(长度是指发送者的Unicode编码后的字节数);消息内型:int32型,当前环境参数:146;消息内容:所有控制⼝开关状态及环境参数,其格式如下输出状态+输⼊状态+传感器数量N+传感器数据 * N说明:输出状态:控制器开关量输出状态,long 型(64位),此变量每⼀位表⽰⼀个开关的状态,1表⽰开,0表⽰关;输⼊状态:控制器开关量输⼊状态,long 型(64位),此变量每⼀位表⽰⼀个输⼊的状态,1表⽰有输⼊,0表⽰⽆输⼊。
最低位⽤于⼿⾃动状态指⽰。
传感器数量:byte型,指的是传感器数量传感器数据:格式为:位置+类型+地址+参数个数+数据说明:位置:byte型,表⽰此组传感器所摆放位置;如:1表⽰放置点为1区;类型:byte地址:byte型,此组传感器的地址,每个终端所管理的传感器中不能有重复地址;参数个数:byte,每个参数对应⼀个Float数据数据: float数组,数组元素个数由参数个数决定服务器应答:包长度+KEY值+应答类型数据包长度:int16,除本字段外其他所有字段的字节总数;KEY值:int32,值=123454321;应答类型:int32,值=100:发送成功,可根据此应答判断客户端是否在线//---------------------------------------------------------------------------------我是华丽的分割线--------------------------------------------------------------------------------------------------核⼼代码public class StaticUdpDal{private static Socket socket = null;private bool IsRun = true, _IsSuccse = false;private int num = 0, recNum;private int _LoopNum = 3;//循环计数器和循环等待次数private bool _IsReadMsgType = true;//是否读取消息类型public int LoopNum{get { return _LoopNum; }set { _LoopNum = value; }}private EndPoint point;///<summary>///是否成功收到消息///</summary>public bool IsSuccse{get { return _IsSuccse; }set { _IsSuccse = value; }}private ProtocolModel pModel;///<summary>///数据发送接收之间需要的数据模型///</summary>public ProtocolModel PModel{get { return pModel; }set { pModel = value; }}private MemoryStream sendStream;///<summary>/// //发送内容///</summary>public MemoryStream SendStream{get { return sendStream; }set { sendStream = value; }}///<summary>///是否读取消息类型,默认读取///</summary>public bool IsReadMsgType{get { return _IsReadMsgType; }set { _IsReadMsgType = value; }}public StaticUdpDal(){//绑定IP端⼝if (socket == null){socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);string UdpSendIP = ConfigurationManager.AppSettings["UdpSendIP"].ToString();string UdpSendPort = ConfigurationManager.AppSettings["UdpSendPort"].ToString();IPEndPoint ipep = new IPEndPoint(string.IsNullOrWhiteSpace(UdpSendIP) ? IPAddress.Any : IPAddress.Parse(UdpSendIP),string.IsNullOrWhiteSpace(UdpSendPort) ? 50090 : int.Parse(UdpSendPort));//本机预使⽤的IP和端⼝socket.Bind(ipep);}}///<summary>/// UDP发送接收主函数///</summary>public BinaryReader UdpMain(){Task<BinaryReader> taskRecive = Task<BinaryReader>.Factory.StartNew(() => ReciveMsg());Task taskSend = Task.Factory.StartNew(() => sendMsg());taskRecive.Wait();return taskRecive.Result;}///<summary>///向特定ip的主机的端⼝发送数据报///</summary>public void sendMsg(){while (true){if (num >= _LoopNum || !IsRun){IsRun = false;return;}num++;try{byte[] buffer = sendStream.ToArray();point = new IPEndPoint(IPAddress.Parse(pModel.DisplayIP), pModel.DisplayPort);socket.SendTo(buffer, buffer.Length, SocketFlags.None, point);//发送}catch (Exception ex){m.ErrHandler.WriteError(ex, "UDP通信异常-发送");IsRun = false; return;}Thread.Sleep(1000);//时间间隔为1秒}}///<summary>///接收发送给本机ip对应端⼝号的数据报///</summary>public BinaryReader ReciveMsg(){byte[] data = new byte[1500];while (true){recNum++;if (recNum > 30 || !IsRun) // 3秒未接收到消息,或标记变为停⽌,停⽌{IsRun = false;return null;}if (socket == null || socket.Available < 1){ Thread.Sleep(200); continue; }try{int len = socket.Receive(data);//接收 socket.Receive(data,SocketFlags.None); //}catch (Exception ex){// 在出现未处理的错误时运⾏的代码m.ErrHandler.WriteError(ex, "UDP通信异常-接收");IsRun = false; return null;}BinaryReader reader = GetReciveData(data);//基础判断if (!_IsSuccse){ Thread.Sleep(200); continue; }//socket.Close();IsRun = false;//修改运⾏标记return reader;}}///<summary>///根据模型获取基础发送内容///</summary>///<returns></returns>public MemoryStream GetBaseContent(ProtocolModel _pModel){pModel = _pModel;sendStream = new MemoryStream();BinaryWriter writer = new BinaryWriter(sendStream);writer.Write(.IPAddress.HostToNetworkOrder((short)0));//长度writer.Write(.IPAddress.HostToNetworkOrder((int)pModel.Key));//keywriter.Write(.IPAddress.HostToNetworkOrder((int)pModel.TalkType));//请求类型writer.Write(.IPAddress.HostToNetworkOrder((int)pModel.Sender.Length * 2));//发送者writer.Write(Encoding.BigEndianUnicode.GetBytes(pModel.Sender));writer.Write(.IPAddress.HostToNetworkOrder((int)pModel.Receiver.Length * 2));//接收者writer.Write(Encoding.BigEndianUnicode.GetBytes(pModel.Receiver));writer.Write(.IPAddress.HostToNetworkOrder((int)pModel.MsgType));//消息类型if (eType == 2){writer.Write(.IPAddress.HostToNetworkOrder((int)pModel.DisplayNo.Length * 2)); //终端编号⽇光温室 writer.Write(Encoding.BigEndianUnicode.GetBytes(pModel.DisplayNo));}return sendStream;}///<summary>///根据当前模型,获取接受信息///</summary>///<param name="data"></param>///<returns></returns>public BinaryReader GetReciveData(byte[] data){MemoryStream stream2 = new MemoryStream(data);BinaryReader reader = new BinaryReader(stream2);short packageLen = workToHostOrder(reader.ReadInt16());//长度int key = workToHostOrder(reader.ReadInt32());//keyif (!((!pModel.IsConServer && key == (int)GhMsgEnum.Key) || (pModel.IsConServer && key == (int)GhMsgEnum.Key1)))//987656789 123454321return reader;//-----------------------------------------不同的地⽅--------------------------------------------------int replyKind = workToHostOrder(reader.ReadInt32());//回复的类型 3if (replyKind != PModel.AnswerType)//3return reader;int senderLen = workToHostOrder(reader.ReadInt32());//发送者的长度byte[] temp = new byte[1024];temp = reader.ReadBytes(senderLen);string No = Encoding.BigEndianUnicode.GetString(temp);int receiveLen = workToHostOrder(reader.ReadInt32());//接收者的长度string receive;if (receiveLen > 0)receive = Encoding.BigEndianUnicode.GetString(reader.ReadBytes(receiveLen));//-----------------------------------------不同的地⽅--------------------------------------------------if (IsReadMsgType){int msgType = workToHostOrder(reader.ReadInt32());//消息类型 160 设备信息if (msgType != PModel.AnswerMsgType)//160,155,156,158return reader;}//获取公⽤参数_IsSuccse = true;return reader;}} 最后列举刚刚的通信协议调⽤部分代码///<summary>///写⼊⼿动控制操作信息⽂档2 ⼿动控制,打开关闭设备;未完成(检查开关状态)///</summary>///<param name="GreenhouseID"></param>///<param name="UserName"></param>///<param name="isDoing">true:打开;false:关闭</param>///<param name="coilStatus"></param>public long WriteDevice(int GreenhouseID, string UserName, bool isDoing, long coilStatus){GreenHouseDal ghDal = new GreenHouseDal();D_GreenhouseInfo ghModel = ghDal.GetModel(new D_GreenhouseInfo() { GreenhouseID = GreenhouseID });//获取终端ProtocolModel pModel = new ProtocolModel(){UseType = 1,//使⽤类型,温室IsConServer = ghModel.ConnectType != 1,//是否服务器转发TalkType = (int)GhMsgEnum.TALK,//请求类型AnswerType = (int)GhMsgEnum.TALK,//应答类型Sender = string.IsNullOrWhiteSpace(UserName) ? "server" : UserName,//发送者Receiver = (ghModel.ConnectType != 1) ? ghModel.DisplayNo : "dev0",//接收者,⽹关编号MsgType = isDoing ? (int)GhMsgEnum.SWITCHCTLON : (int)GhMsgEnum.SWITCHCTLOFF,//消息类型为 512、打开开关;513、关闭开关AnswerMsgType = (int)GhMsgEnum.OutputCoil,//应答类型//DisplayIP ="192.168.101.31",// (ghModel.ConnectType != 1) ? _DisplayIP : ghModel.DisplayIP,//⽬标IP//DisplayPort =8085// (ghModel.ConnectType != 1) ? _DisplayPort : (int)ghModel.DisplayPort,//⽬标端⼝DisplayIP = (ghModel.ConnectType != 1) ? _DisplayIP : ghModel.DisplayIP,//⽬标IPDisplayPort = (ghModel.ConnectType != 1) ? _DisplayPort : (int)ghModel.DisplayPort,//⽬标端⼝};StaticUdpDal bud = new StaticUdpDal();MemoryStream stream = bud.GetBaseContent(pModel);//获取基础发送内容BinaryWriter writer = new BinaryWriter(stream);writer.Write(.IPAddress.HostToNetworkOrder((long)coilStatus));//写⼊消息内容writer.Seek(0, SeekOrigin.Begin);writer.Write(.IPAddress.HostToNetworkOrder((short)(stream.Length - 2)));//写⼊长度bud.SendStream = stream;bud.LoopNum = 1;BinaryReader reader = bud.UdpMain();//开始发送和接收if (reader == null || !bud.IsSuccse) return (long)-1;//---------------------------------?long OutputStatus = workToHostOrder(reader.ReadInt64());return OutputStatus;}百度上有很多讲 socket通信的,都⽐较基础,我也是看着别⼈的代码总结到⾃⼰的项⽬中的,这⾥有个关键点就是声明⼀个静态的socket变量,并且使⽤之后并不关闭,⽽是常驻内存,收到信号就开启就收线程,然后再将信号转发给服务器。
Socket编程过程
Socket编程什么是SocketSocket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。
要学Internet上的TCP/IP网络编程,必须理解Socket接口。
Socket接口设计者最先是将接口放在Unix操作系统里面的。
如果了解Unix系统的输入和输出的话,就很容易了解Socket了。
网络的 Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。
Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
常用的Socket类型有两种:流式Socket (SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。
流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
Socket建立为了建立Socket,程序可以调用Socket函数,该函数返回一个类似于文件描述符的句柄。
socket函数原型为:int socket(int domain, int type, int protocol);domain指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族);type参数指定socket的类型: SOCK_STREAM 或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值"0"。
Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。
Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。
调用Socket 函数时,socket执行体将建立一个Socket,实际上"建立一个Socket"意味着为一个Socket数据结构分配存储空间。
Windows Socket 网络编程
Windows Socket 网络编程(二) ——套接字编程原理作者: 冰点工作室小鹰一、客户机/服务器模式在TCP/IP网络中两个进程间的相互作用的主机模式是客户机/服务器模式(Client/Server model)。
该模式的建立基于以下两点:1、非对等作用;2、通信完全是异步的。
客户机/服务器模式在操作过程中采取的是主动请示方式:首先服务器方要先启动,并根据请示提供相应服务:(过程如下)1、打开一通信通道并告知本地主机,它愿意在某一个公认地址上接收客户请求。
2、等待客户请求到达该端口。
3、接收到重复服务请求,处理该请求并发送应答信号。
4、返回第二步,等待另一客户请求5、关闭服务器。
客户方:1、打开一通信通道,并连接到服务器所在主机的特定端口。
2、向服务器发送服务请求报文,等待并接收应答;继续提出请求……3、请求结束后关闭通信通道并终止。
二、基本套接字为了更好说明套接字编程原理,给出几个基本的套接字,在以后的篇幅中会给出更详细的使用说明。
1、创建套接字——socket()功能:使用前创建一个新的套接字格式:SOCKET PASCAL FAR socket(int af,int type,int procotol);参数:af: 通信发生的区域type: 要建立的套接字类型procotol: 使用的特定协议2、指定本地地址——bind()功能:将套接字地址与所创建的套接字号联系起来。
格式: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_INETu_short sin_port;//16位端口号,网络字节顺序struct in_addr sin_addr;//32位IP地址,网络字节顺序char sin_zero[8];//保留}3、建立套接字连接——connect()和accept()功能:共同完成连接工作格式:int PASCAL FAR connect(SOCKET s,const struct sockaddr FAR * name,int namelen);SOCKET PASCAL FAR accept(SOCKET s,struct sockaddr FAR * name,int FAR * addrlen);参数:同上4、监听连接——listen()功能:用于面向连接服务器,表明它愿意接收连接。
winsocket编程例子
winsocket编程例子Winsock是Windows操作系统提供的一组接口,用于开发网络应用程序。
它是“Windows Socket”的缩写,其作用是使开发者可以基于TCP/IP 协议栈来构建高性能的网络应用程序。
在本文中,我们将介绍Winsock 编程的基础知识,并通过一个实例来演示其使用方法。
第一步是引入头文件和库文件。
在开始编写Winsock程序之前,我们需要引入相应的头文件和库文件,以便在程序中使用Winsock接口。
头文件可以通过以下方式引入:c#include <winsock2.h>#include <ws2tcpip.h>而库文件可以在项目属性中进行设置,或者使用#pragma comment指令自动链接库文件:c#pragma comment(lib, "ws2_32.lib")第二步是初始化Winsock库。
在使用Winsock接口之前,我们需要调用WSAStartup函数来初始化Winsock库。
该函数的原型如下:cint WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData)其中,wVersionRequested参数指定所需的Winsock版本号,一般使用MAKEWORD宏来指定版本号,如:cWORD version = MAKEWORD(2, 2);lpWSAData参数用于接收初始化结果的信息,我们可以将其声明为一个WSADATA结构的指针,如:cWSADATA wsaData;初始化完成后,我们可以通过判断返回值来确定初始化是否成功。
如果成功,函数返回0;否则,返回一个错误码。
第三步是创建套接字。
套接字是进行网络通信的关键对象,我们需要调用socket函数来创建一个套接字。
该函数的原型如下:cSOCKET socket(int af, int type, int protocol)其中,af参数指定地址族,一般使用AF_INET表示IPv4地址族;type 参数指定套接字类型,常用的有SOCK_STREAM(面向连接的可靠传输)和SOCK_DGRAM(无连接的不可靠传输);protocol参数指定协议,一般使用0表示自动选择适合的协议。
Windows SOCKET编程汇总
Windows SOCKET编程第一章序言我写这个专题的目的,一方面是为了通过对网络编程再一次系统的总结,提高自己的网络编程水平,特别是Windows下的网络编程水平。
同时,我也希望,能为众多初学网络编程的人提供一点帮助,因为我开始学习网络编程的时候,能找到的资料就很少。
当然,花钱可以买到翻译版本的书:)首先向大家推荐一本很好的参考书,Network Programming for Microsoft Windows 2nd,初学网络编程的时候我还不知道有这样一本好书,只是上各大论坛把能找到的网络编程方面的文章和代码下载下来,然后自己研究。
后来看到别人推荐这一本书,下载了一个,看了感觉非常好,里面的内容写得很规范,条理也很清楚,英文好的朋友可以直接阅读,不然就只好去弄一本翻译好的来研究了。
我试着从Windows编程的基础开始,一直到探索建立高性能的网络应用程序。
我说过,我并不是以高手的身份写这本书,而是以和大家一起学习的心态学习网络编程,写书只是让自己的思路更清晰,以后还可以翻阅。
所以,我不保证书中所有的内容都是绝对正确和标准的,有不妥的地方,还希望高手批评指正。
这本书是完全免费的,读者可以任意使用书中的代码。
但是如果需要转载,请注明原作者和出处。
如果有商业运作的需求,请直接和我联系。
第二章Windows网络编程基础这本书主要探索Windows网络编程,开发平台是Windows 2000 和Visual C++.NET,从一个合格的C++程序员到网络编程高手,还是需要花不少功夫,至少我认为写一个聊天程序很简单,而要写一个能同时响应成千上万用户的高性能网络程序,的确不容易。
这篇文章所介绍的方法也并不是能直接应用于每一个具体的应用程序,只能作为学习的参考资料。
开发高性能网络游戏恐怕是促使很多程序员研究网络编程的原因(包括我),现在的大型网络游戏对同时在线人数的要求比较高,真正的项目往往采取多个服务器(组)负荷分担的方式工作,我将首先把注意力放到单个服务器的情况。
手把手教你玩转SOCKET模型之重叠IO篇
手把手教你玩转SOCKET模型之重叠I/O篇“身为一个初学者,时常能体味到初学者入门的艰辛,所以总是想抽空作点什么来尽我所能的帮助那些需要帮助的人。
我也希望大家能把自己的所学和他人一起分享,不要去鄙视别人索取时的贪婪,因为最应该被鄙视的是不肯付出时的吝啬。
”----- 题记By PiggyXP(小猪)前言其实我首先应该道歉,因为7月份的时候曾信誓旦旦的说要写一套关于SOCKET所有模型的入门文章以及配套代码,不过没想到后天竟然被美女所迷出去度假了,刚刚回来不久。
-_-b其实那些模型的配套代码我已经基本写完了,只是没写配套文字,不过我想还是先从稍微难一点的模型写起吧,因为其他模型的入门毕竟要简单一些。
不过由于也是初学者,疏漏之处还望不吝指正。
本文凝聚着笔者心血,如要转载,请指明原作者及出处,谢谢!^_^OK, Let’s go ! Have fun!! q^_^p本文配套的示例源码下载地址( 2003编写的多客户端MFC代码,配有详尽注释,只是简单的显示一下客户端发来的字符,稍加改进就是个聊天室了):/PiggyXP/OverlappedModel.rar(unix系统,千万注意链接大小写)非常感谢网络版的limin兄弟为我无偿提供的空间,以及在我学习过程中给我的大力帮助与支持,真的非常感谢他,感激涕零啊~~~~~T_T也欢迎大家光临他的Blog一起讨论网络技术/(本文假设你已经具备用SOCKET简单模型编程的能力,如果对SOCKET一无所知请关注本系列其他文章)目录:1.重叠模型的优点2.重叠模型的基本原理3.关于重叠模型的基础知识4.重叠模型的实现步骤5.多客户端情况的注意事项一.重叠模型的优点1.可以运行在支持Winsock2的所有Windows平台,而不像完成端口只是支持NT系统。
2.比起阻塞、select、WSAAsyncSelect以及WSAEventSelect等模型,重叠I/O(Overlapped I/O)模型使应用程序能达到更佳的系统性能。
学习 Socket 之六种模型
学习 Socket 之六种模型2010年6月9日发表评论阅读评论阻塞模型这个模型是讲解计算机网络时被作为例子介绍的,也是最简单的.其基本原理是:首先建立一个 Socket 连接,然后对其进行操作,比如,从该 Socket 读数据.因为网络传输是要一定的时间的,即使网络通畅的情况下,接受数据的操作也要花费时间.对于一个简单的单线程程序,接收数据的过程是无法处理其他操作的.比如一个窗口程序,当你接收数据时,点击按钮或关闭窗口操作都不会有效.它的缺点显而易见,一个线程你只能处理一个 Socket ,用来教课还行,实际使用效果就不行了.选择模型为了处理多个 Socket 连接,聪明的人们发明了 Select 模型.该模型以集合来管理 Socket 连接,每次去查询集合中的 Socket 状态,从而达到处理多连接的能力,其函数原型是int select(int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout)比如我们判断某个 Socket 是否有数据可读,我们首先将一个 fdread 集合置空,然后将 Socket 加入到该集合,调用select(0,&fdread,NULL,NULL,NULL),之后我们判断 Socket 是否还在 fdread 中,如果还在,则说明有数据可读.数据的读取和阻塞模型相同,调用recv函数.但是每个集合容量都有一个限值,默认情况下是64个,当然你可以重新定义它的大小,但还是有一个最上限,自己设置也不能超过该值,一般情况下是1024.尽管选择模型可以处理多连接,但集合的管理多少让人感到繁琐.异步选择模型熟悉 Windows 操作系统的都知道,其窗口处理是基于消息的.人们又发明了一种新的网络模型 WSAAsyncSelect 模型,即异步选择模型.该模型为每个 Socket绑定一个消息,当 Socket 上出现事先设置的 Socket 事件时,操作系统就会给应用程序发送这个消息,从而对该 Socket 事件进行处理,其函数原型是int WSAAsynSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent) hWnd 指明接收消息的句柄,wMsg 指定消息 ID, lEvent 按位设置感兴趣的网络事件,入 WSAAsyncSelect(s,hwnd,WM_SOCKET, FD_CONNECT | FD_READ |FD_CLOSE)该模型的优点是在系统开销不大的情况下同时处理许多连接,也不需要什么集合管理.缺点很明显,即使你的程序不需要窗口,也要专门为 WSAAsyncSelect模型定义一个窗口.另外,让单个窗口去处理成千上万的 Socket 操作事件,很可能成为性能瓶颈.事件选择模型与 WSAAsynSelect 模型类似,人们还发明了 WSAEventSelect 模型,即事件选择模型.看名字就可以猜测出来,它是基于事件的. WSAAsynSelect 模型在出现感兴趣的 Socket 事件时,系统会发一个相应的消息.而 WSAEventSelect 模型在出现感兴趣的 Socket 事件时,系统会将相应 WSAEVENT 事件设为传信.可能你现在对 Sokect 事件和普通 WSAEVENT 事件还不是很清楚. Socket 事件是与Socket 操作相关的一些事件,如 FD_READ,FD_WRITE,FD_ACCEPT 等.而WSAEVENT 事件是传统的事件,该事件有两种状态,传信 (Signaled) 和未传信(Non-Signaled).所谓传信,就是事件发生了,未传信就是还没有发生.我们每次建立一个连接,都为其绑定一个事件,等到该连接变化时,事件就会变为传信状态.那么,谁去接受这个事件变化呢?我们通过一个 WSAWaitForMultipleEvents() 函数来等待事件发生,传入参数中的事件数组中,只有有一个事件发生,该函数就会返回(也可以设置为所有事件发生才返回,在这里没用),返回值为事件的数组序号,这样我们就知道了哪个事件发生了,也就是该事件对应的 Socket 有了 Socket 操作事件.该模型比起 WSAAsynSelect 模型的优势很明显,不需要窗口.唯一缺点是,该模型每次只能等待64个事件,这一限制使得在处理多 Socket 时,有必要组织一个线程池,伸缩性不如后面要讲的重叠模型.重叠 I/O模型重叠I/O (Overlapped I/O) 模型使应用程序达到更佳的系统性能.重叠模型的基本设计原理是让应用程序使用重叠数据结构,一次投递一个或多个 WinSockI/O请求.重叠模型到底是什么东西呢?可以与WSAEventSelect模型做类比(其实不恰当,后面再说),事件选择模型为每个 Socket 连接绑定了一个事件,而重叠模型为每个 Socket 连接绑定了一个重叠.当连接上发生 Socket 事件时,对应的重叠就会被更新.其实重叠的高明之处在于,它在更新重叠的同时,还把网络数据传到了实现指定的缓存区中.我们知道,前面的网络模型都要用户自己通过recv 函数来接受数据,这样就降低了效率.我们打个比方,WSAEventSelect 模型就像邮局的包裹通知,用户收到通知后要自己去邮局取包裹.而重叠模型就像送货上门,邮递员发给你通知时,也把包裹放到了你事先指定的仓库中.重叠模型又分为事件通知和完成例程两种模式.在分析这两种模式之前,我们还是来看看重叠数据结构:1 typedef struct WSAOVERLAPPED{2 DWORD Internal;3 DWORD InternalHigh;4 DWORD Offset;5 DWORD OffsetHigh;6 WSAEVENT hEvent;7 }WSAOVERLAPPED, FAR * LPWSAOVERLAPPED;该数据结构中,Internal、InternalHigh、Offset、OffsetHigh都是系统使用的,用户不用去管,唯一关注的就是 hEvent.如果使用事件通知模式,那么hEvent就指向相应的事件句柄.如果是完成例程模式,hEvent设为NULL.我们现在来看事件通知模式,首先创建一个事件hEvent,并创建一个重叠结构AcceptOverlapped, 并设置AcceptOverlapped.hEvent = hEvent,DataBuf是我们事先设置的数据缓存区.调用WSARecv(AcceptSocket,&DataBuf,1,&RecvBytes,&Flags,&AcceptOverlapped,N ULL) ,则将 AcceptSocket 与 AcceptOverlapped重叠绑定在了一起.当接收到数据以后,hEvent就会设为传信,而数据就会放到 DataBuf中.我们再通过WSAWaitForMultipleEvents()接收到该事件通知.这里我们要注意,既然是基于事件通知的,那它就有一个事件处理上限,一般为64.完成例程和事件通知模式的区别在于,当相应的socket事件出现时,系统会调用用户事先指定的回调函数,而不是设置事件.其实就是将WSARecv的最后一个参数设为函数指针.该回调函数的原型如下:1 void CALLBACK CompletionROUTINE(2 DWORD dwError,3 DWORD cbTransferred,4 LPWSAOVERLAPPED lpOverlapped,5 DWORD dwFlags6 );其中,cbTransferred表示传输的字节数,lpOverlapped是发生socket事件的重叠指针.我们调用WSARecv(AcceptSocket,&DataBuf,1,&RecvBytes,&Flags,&AcceptOverlapped,W orkerRoutine) 将AcceptSocket与WorkRoutine例程绑定.这里有一点小提示,当我们创建多个socket的连接时,最好把重叠与相应的数据缓存区用一个大的数据结构放到一块,这样,我们在例程中通过lpOverlapped指针就可以直接找到相应的数据缓存区.这里要注意,不能将多个重叠使用同一个数据缓存区,这样在多个重叠都在处理时,就会出现数据混乱.完成端口模型下面我们来介绍专门用于处理为数众多socket连接的网络模型——完成端口.因为需要做出大量的工作以便将socket添加到一个完成端口,而其他方法的初始化步骤则省事多了,所以对新手来说,完成端口模型好像过于复杂了.然而,一旦弄明白是怎么回事,就会发现步骤其实并非那么复杂.所谓完成端口,实际是Windows采用的一种I/O构造机制,除套接字句柄之外,还可以接受其他东西.使用这种模式之前,首先要创建一个I/O完成端口对象,该函数定义如下:1 HANDLE CreateIoCompletionPort(2 HANDLE FileHandle,3 HANDLE ExistingCompletionPort,4 DWORD CompletionKey,5 DWORD NumberOfConcurrentThreads6 );该函数用于两个截然不同的目的: 1)用于创建一个完成端口对象.2)将一个句柄同完成端口关联到一起.通过参数 NumberOfConcurrentThreads,我们可以指定同时运行的线程数.理想状态下,我们希望每个处理器各自负责一个线程的运行,为完成端口提供服务,避免过于频繁的线程任务切换.对于一个socket连接,我们通过CreateIoCompletionPort ((HANDLE)Accept,CompletionPort,(DWORD)PerHandleData,0)将Accept连接与 CompletionPort 完成端口绑定到一起,CompetionPort对应的那些线程不断通过GetQueuedCompletionStatus来查询与其关联的socket连接是否有I/O操作完成,如果有,则做相应的数据处理,然后通过WSARecv将该socket连接再次投递,继续工作.完成端口在性能和伸缩性方面表现都很好,相关联的socket连接数目没有限制.。
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
Windows Socket五种I/O模型——代码全攻略Winsock 的I/O操作:1、两种I/O模式阻塞模式:执行I/O操作完成前会一直进行等待,不会将控制权交给程序。
套接字默认为阻塞模式。
可以通过多线程技术进行处理。
非阻塞模式:执行I/O操作时,Winsock函数会返回并交出控制权。
这种模式使用起来比较复杂,因为函数在没有运行完成就进行返回,会不断地返回WSAEWOULDBLOCK错误。
但功能强大。
为了解决这个问题,提出了进行I/O操作的一些I/O模型,下面介绍最常见的三种:Windows Socket五种I/O模型——代码全攻略如果你想在Windows平台上构建服务器应用,那么I/O模型是你必须考虑的。
Windows操作系统提供了选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)、重叠I/O(Overlapped I/O)和完成端口(Completion Port)共五种I/O模型。
每一种模型均适用于一种特定的应用场景。
程序员应该对自己的应用需求非常明确,而且综合考虑到程序的扩展性和可移植性等因素,作出自己的选择。
我会以一个回应反射式服务器(与《Windows网络编程》第八章一样)来介绍这五种I/O模型。
我们假设客户端的代码如下(为代码直观,省去所有错误检查,以下同):#include <WINSOCK2.H>#include <stdio.h>#define SERVER_ADDRESS "137.117.2.148"#define PORT 5150#define MSGSIZE 1024#pragma comment(lib, "ws2_32.lib")int main(){WSADA TA wsaData;SOCKET sClient;SOCKADDR_IN server;char szMessage[MSGSIZE];int ret;// Initialize Windows socket libraryWSAStartup(0x0202, &wsaData);// Create client socketsClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// Connect to servermemset(&server, 0, sizeof(SOCKADDR_IN));server.sin_family = AF_INET;server.sin_addr.S_un.S_addr = inet_addr(SERVER_ADDRESS);server.sin_port = htons(PORT);connect(sClient, (struct sockaddr *)&server, sizeof(SOCKADDR_IN));while (TRUE){printf("Send:");gets(szMessage);// Send messagesend(sClient, szMessage, strlen(szMessage), 0);// Receive messageret = recv(sClient, szMessage, MSGSIZE, 0);szMessage[ret] = '\0';printf("Received [%d bytes]: '%s'\n", ret, szMessage);}// Clean upclosesocket(sClient);WSACleanup();return 0;}客户端所做的事情相当简单,创建套接字,连接服务器,然后不停的发送和接收数据。
比较容易想到的一种服务器模型就是采用一个主线程,负责监听客户端的连接请求,当接收到某个客户端的连接请求后,创建一个专门用于和该客户端通信的套接字和一个辅助线程。
以后该客户端和服务器的交互都在这个辅助线程内完成。
这种方法比较直观,程序非常简单而且可移植性好,但是不能利用平台相关的特性。
例如,如果连接数增多的时候(成千上万的连接),那么线程数成倍增长,操作系统忙于频繁的线程间切换,而且大部分线程在其生命周期内都是处于非活动状态的,这大大浪费了系统的资源。
所以,如果你已经知道你的代码只会运行在Windows平台上,建议采用Winsock I/O模型。
一.选择模型Select(选择)模型是Winsock中最常见的I/O模型。
之所以称其为“Select模型”,是由于它的“中心思想”便是利用select函数,实现对I/O的管理。
最初设计该模型时,主要面向的是某些使用UNIX操作系统的计算机,它们采用的是Berkeley套接字方案。
Select模型已集成到Winsock 1.1中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。
由于Winsock 1.1向后兼容于Berkeley 套接字实施方案,所以假如有一个Berkeley套接字应用使用了select函数,那么从理论角度讲,毋需对其进行任何修改,便可正常运行。
(节选自《Windows网络编程》第八章)下面的这段程序就是利用选择模型实现的Echo服务器的代码(已经不能再精简了):#include <winsock.h>#include <stdio.h>#define PORT 5150#define MSGSIZE 1024#pragma comment(lib, "ws2_32.lib")int g_iTotalConn = 0;SOCKET g_CliSocketArr[FD_SETSIZE];DWORD WINAPI WorkerThread(LPVOID lpParameter);int main(){WSADA TA wsaData;SOCKET sListen, sClient;SOCKADDR_IN local, client;int iaddrSize = sizeof(SOCKADDR_IN);DWORD dwThreadId;// Initialize Windows socket libraryWSAStartup(0x0202, &wsaData);// Create listening socketsListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// Bindlocal.sin_addr.S_un.S_addr = htonl(INADDR_ANY);local.sin_family = AF_INET;local.sin_port = htons(PORT);bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));// Listenlisten(sListen, 3);// Create worker threadCreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);while (TRUE){// Accept a connectionsClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));// Add socket to g_CliSocketArrg_CliSocketArr[g_iTotalConn++] = sClient;}return 0;}DWORD WINAPI WorkerThread(LPVOID lpParam){int i;fd_set fdread;int ret;struct timeval tv = {1, 0};char szMessage[MSGSIZE];while (TRUE){FD_ZERO(&fdread);for (i = 0; i < g_iTotalConn; i++){FD_SET(g_CliSocketArr, &fdread);}// We only care read eventret = select(0, &fdread, NULL, NULL, &tv);if (ret == 0){// Time expiredcontinue;}for (i = 0; i < g_iTotalConn; i++){if (FD_ISSET(g_CliSocketArr, &fdread)){// A read event happened on g_CliSocketArrret = recv(g_CliSocketArr, szMessage, MSGSIZE, 0);if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)) {// Client socket closedprintf("Client socket %d closed.\n", g_CliSocketArr);closesocket(g_CliSocketArr);if (i < g_iTotalConn - 1){g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];}}else{// We received a message from clientszMessage[ret] = '\0';send(g_CliSocketArr, szMessage, strlen(szMessage), 0);}}}}return 0;}服务器的几个主要动作如下:1.创建监听套接字,绑定,监听;2.创建工作者线程;3.创建一个套接字数组,用来存放当前所有活动的客户端套接字,每accept一个连接就更新一次数组;4.接受客户端的连接。
这里有一点需要注意的,就是我没有重新定义FD_SETSIZE宏,所以服务器最多支持的并发连接数为64。