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

被折叠的 条评论
为什么被折叠?



