第六章:可伸缩的Winsock应用程序

本文详细介绍了如何利用Winsock的高级API(如AcceptEx, TransmitFile等)构建可伸缩的服务器,包括接受连接、数据传输和资源管理策略。通过AcceptEx进行连接接收,TransmitFile提高文件发送效率,以及利用0字节接收缓冲区策略管理内存资源,以实现高并发或高吞吐量的服务器设计。" 50557676,5461531,动态规划解题:矩阵最大奖励路径,"['算法', '动态规划']

1:函数

1:TransmitFile,AcceptEx,ConnectEx,TransmitPackets,DisconnectEx,WSARecvMsg,GetAcceptExSockaddrs他们在MSWSOCK.H中定义

TransmitFile,AcceptEx,GetAcceptExSockaddrs在MSWSOCK.DLL中实现

2:我们可以通过静态连接mswsock.lib来使用这些函数,但这样做实际上每次都会调用WSAIoctl()获得函数指针,我们还不如直接导出函数指针来使用

 

2:详解

1:AcceptEx:

1:基本知识

BOOL AcceptEx(  
			  __in   SOCKET sListenSocket, 
			  __in   SOCKET sAcceptSocket,			
			  __in   PVOID lpOutputBuffer,			//指向一个缓冲区,缓存接收到连接后收到的第一块数据,然后是服务器本机地址
			  //和客户端远程地址,该参数必须制定
			  __in   DWORD dwReceiveDataLength,		//lpOutputBuffer大小,但不包括本机地址和远程地址的大小,所以Windwos会在
			  //lpOutputBuffer之后追加这两个地址,如果此参数设置为0,则此函数会尽快建立连接,而不等待任何数据
			  __in   DWORD dwLocalAddressLength,    //本地地址字节数,大小只能为sizeof(SOCKADDR_IN)+16
			  __in   DWORD dwRemoteAddressLength,   //远程地址字节数,大小只能为sizeof(SOCKADDR_IN)+16
			  __out  LPDWORD lpdwBytesReceived,     //收到的数据大小,只有在同步模式下有意义
			  __in   LPOVERLAPPED lpOverlapped
			  );

//加载AcceptEx实例
GUID			GuidAcceptEx=WSAID_ACCEPTEX;
LPFN_ACCEPTEX   lpfnAcceptEx=NULL;
DWORD			dwBytes=0;
WSAIoctl(
		sListen,										
		SIO_GET_EXTENSION_FUNCTION_POINTER,		//ControlCode
		&GuidAcceptEx,							//lpInBuffer
		sizeof(GuidAcceptEx),					//InBufferSize
		&lpfnAcceptEx,							//lpOutBuffer
		sizeof(lpfnAcceptEx),					//OutBufferSize
		&dwBytes,								//ReturnSize
		NULL,									//LPOVERLAPPED
		NULL,									//LPC例程
		 );

//扩展函数函数指针和对应的GUID为
LPFN_ACCEPTEX				WSAID_ACCEPTEX
LPFN_GETACCEPTEXSOCKADDRS   WSAID_GETACCEPTEXSOCKADDRS
LPFN_CONNECTEX				WSAID_CONNECTEX
LPFN_DISCONNECTEX			WSAID_DISCONNECTEX
LPFN_TRANSMITFILE			WSAID_TRANSMITFILE
LPFN_TRANSMITPACKETS		WSAID_TRANSMITPACKETS

2:AcceptEx使用过程

创建完成端口,创建监听套接字,将监听套接字和完成端口绑定,将监听套接字绑定到本地端口,listen,创建客户端套接字,调用acceptex()

3:AcceptEx其监听套接字的套接字属性不会自动被客户端套接字继承,如果需要继承属性,则可以对客户端套接字setsockopt()并设置SO_UPDATE_ACCEPT_CONTEXT

4:如果AcceptEx设置了接收缓冲区,则重叠操作只有在连接上收到至少一个字节才会完成,为了避免客户机恶意投递连接却又不发送数据,则可以使用SO_CONNECT_TIME

2:GetAcceptExSockaddrs

此函数是AcceptEx的辅助函数,用来将AcceptEx提供的缓冲区本地地址和远程地址解析成SOCKADDR格式

void GetAcceptExSockaddrs(
						  PVOID lpOutputBuffer,
						  DWORD dwReceiveDataLength,
						  DWORD dwLocalAddressLength,
						  DWORD dwRemoteAddressLength,		//前4个参数必须和AcceptEx完全一致
						  LPSOCKADDR* LocalSockaddr,		
						  LPINT LocalSockaddrLength,
						  LPSOCKADDR* RemoteSockaddr,
						  LPINT RemoteSockaddrLength
						  );
3:TransmitFile()

1:发送一个文件,避免了程序员自己打开文件-读文件数据-发送这种操作,使用此函数其读取和发送都在核心模式下,所以效率很高

BOOL TransmitFile(
				  SOCKET hSocket,
				  HANDLE hFile,
				  DWORD nNumberOfBytesToWrite,					//如果为0则表示发送整个文件
				  DWORD nNumberOfBytesPerSend,					//发送操作每个数据块的大小,如果指定为0,则使用默认值
				  //WindowsNT为4KB,Windows服务器上为64KB
				  LPOVERLAPPED lpOverlapped,
				  LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers,	//定义一个缓冲区,它会在文件之前或者之后传送
				  DWORD dwFlags									//相关标志位,具体参考书上
				  );

2:如果指定空的文件句柄和空的缓冲区,flags指定TF_DISCONNECT和TF_REUSE_SOCKET,则此调用不会发送任何数据,而这个套接字能够被AcceptEx重用

4:TransmitPackets()

TransmitPackets与TransmitFile的区别是它可以按任意顺序发送任意数量的文件及其缓冲区

5:ConnectEx()
BOOL PASCAL ConnectEx(
					  SOCKET s,							//必须预先绑定一个地址和端口,端口随意
					  const struct sockaddr* name,		//远程地址
					  int namelen,						//远程地址长度
					  PVOID lpSendBuffer,				//连接成功后发送的第一个缓冲区
					  DWORD dwSendDataLength,			//缓冲区长度
					  LPDWORD lpdwBytesSent,			//如果连接立即建立,返回发送的长度
					  LPOVERLAPPED lpOverlapped
					  );
6:DisconnectEx()

断开一个套接字的连接,使其在AcceptEx()中可被重新使用

BOOL DisconnectEx(
				  SOCKET hSocket,						
				  LPOVERLAPPED lpOverlapped,
				  DWORD dwFlags,			 //0或者TF_REUSE_SOCKET,0则只是简单的断开连接,如果是想重新使用AcceptEx中
				  //的套接字,则必须指定TF_REUSE_SOCKET
				  DWORD reserved			 //必须为0
				  );
//如果此函数被重叠调用时,但套接字上还有挂起的操作,则会返回FALSE,GetLastError()==WSA_IO_PENDING,当所有挂起操作完成,
//则会断开连接
//如果此函数被阻塞调用,则只有挂起的IO都完成,连接断开后,函数才会返回
7:WSARecvMsg()

相当于一个复杂的WSARecv(),不同的是它能够返回接收数据包的是那个接口,对于一个被绑定到多重初始地址计算机的通配符地址上,并需要知道数据包到了那个接口的数据包套接字而言,这一点很重要

上面那句话我没看懂,以后搞定了...哦呵呵

 

3:可伸缩的服务器体系结构

1:接受连接

1:采用AcceptEx接收连接,WindowsNT最大储备值为200,如果投递了15个AcceptEx,当到达50个连接时,这50个连接任然会被处理,让下次投递AcceptEx时,这些链接会立即成功

2:之所以采用AcceptEx是因为创建套接字开销过大

3:AcceptEx可以在连接刚建立时接受数据,为了防止恶意攻击,可以调用getsockopt()并传入SO_CONNECT_TIME,如果没有连接,返回-1,如果连接,返回大于0的值,不应该关闭一个在AcceptEx调用中使用的,未被接受的客户端套接字句柄,这样会导致内存泄露,由于性能原因,在未连接的客户端句柄被关闭时,与AcceptEx调用相关的核心模式结构不会被彻底清除干净,除非建立一个新的客户端连接或关闭套接字

4:应该避免在工作线程中投递AcceptEx操作(也就是当收到一个连接,就去检查连接是否已经达到上限,否则投递AcceptEx),书上讲的原因是创建套接字开销过大,但我们一般都会使用句柄池,所以真正的开销看自己抉择了,书上建议的策略是使用事件发动另外一个线程接收连接

2:数据传输

1:如果接收缓冲区为0,并没有接收IO,则任何数据都只能停留在TCP层缓冲区,TCP驱动程序的缓冲区最多只能接收窗口大小-17KB,也就是说,如果有1000个连接,但服务器没有投递接收操作,他们可能会占用1000*17KB的非分页缓冲池

2:套接字默认接收缓冲区大小为8KB

3:资源管理

1:资源管理面临的两个问题:一是锁定页面的数量,二是非分页内存池的使用,二要严重的多

2:对于每个发送和接受的重叠操作,提交的缓冲区都可能被锁定,操作系统有一个被锁定的内存数量的上限,如果超过这个上限,返回WSAENOBUFS,如果在每个连接上都投递很多重叠接收操作,则这个上限很快就会达到,一种解决方案是投递一个0字节的接收缓冲区,这个缓冲当然不会被锁定,当它返回时,就可以采用非阻塞的方法读取真正的数据,此时如果返回WSAEWOULDBLOCK,则表示没有数据了

3:已经建立连接的套接字会消耗大约2KB的非分页内存池,从accept或AcceptEx中返回的套接字会使用1.5KB的非分页内存池,每个IO操作会使用500字节的非分页池

4:通常非分页内存池是整个物理内存的四分之一,上限为256M,256M一般可以处理5万个连接

5:例子:以5万个连接为例,非分页内存池占用为:5000*(1.5KB+500字节)=100M,因为要为每个连接投递一个接受操作,并且这里采用了0缓冲机制

6:没有一种方法动态监控非分页内存池使用情况,所以编写程序应该做大量测试工作确定连接极限

 

4:服务器策略

1:高吞吐率

1:高吞吐率主要关心大块数据的传输,FTP服务器就是一个例子,服务器把重点放在处理每个连接上,以减少传输数据所需的事件,这种服务器策略要求少的并发连接,因为并发连接越多,每个连接上的数据吞吐率就会越低,FTP服务器因为太繁忙而拒绝连接就是一个例子

2:服务器应该保持足够的接受或发送操作用于投递,以提高数据吞吐率,IO应该被限制在一小组连接之中,一种策略是为一组连接投递IO,当完成后,再为另一组连接投递IO,这是节约资源的一种方法

3:服务器应该监视每个连接上未完成的IO数量,防止客户端恶意的投递请求,但不接受数据,在这种情况下,如果发现服务器未完成操作太多,就应该关掉连接

2:高并发

为了追求最大的并发,服务器不应该为每个连接投递接收IO,因为这样会导致锁定页面数量的暴增,所以应该采用0字节投递策略

 

 

 

 



 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值