??xml version="1.0" encoding="utf-8" standalone="yes"?>
2 bool GetProxyIP( std::wstring& strIP )
3 {
4 strIP.clear();
5 LPCWSTR pszIP = GetEditBoxText( IDC_DLG_LOGIN_SETTING_EDIT_ADDRESS );
6 if( pszIP == NULL || !wcscmp( pszIP, TEXT("") ) )
7 return false;
8
9 std::wstring strTemp = pszIP;
10 std::wstring::size_type nPos1, nPos2;
11 nPos1 = nPos2 = 0;
12 int nIP[4] = {0};
13 std::wstring::size_type i = 0;
14 for( ; (i < 4) && (nPos2 != std::wstring::npos ); ++i )
15 {
16 if( nPos2 == 0 )
17 nPos1 = nPos2;
18 else
19 nPos1 = nPos2 + 1;
20
21 nPos2 = strTemp.find( TEXT('.'), nPos1);
22 std::wstring::size_type nCount = std::wstring::npos;
23 if( nPos2 != std::wstring::npos )
24 nCount = nPos2 - nPos1;
25
26 std::wstring strSub = strTemp.substr(nPos1, nCount);
27 if( strSub == TEXT("") )
28 return false;
29
30 int nValue = _wtoi( strSub.c_str() );
31 if( nValue < 0 || nValue > 255 )
32 return false;
33 nIP[i] = nValue;
34 }
35
36 if( i < 4 )
37 return false;
38 wchar_t szIP[100] = {0};
39 wsprintf( szIP, TEXT("%d.%d.%d.%d"), nIP[0], nIP[1], nIP[2], nIP[3] );
40 strIP = szIP;
41 return true;
42 }
]]>
作者: Chandrasekhar Vuppalapati
译Qeastvc
Character range(decimal) | Type | Values | Safe/Unsafe |
0-31 | ASCII Control Characters | These characters are not printable | Unsafe |
32-47 | Reserved Characters | '' ''!?#$%&''()*+,-./ | Unsafe |
48-57 | ASCII Characters and Numbers | 0-9 | Safe |
58-64 | Reserved Characters | :;<=>?@ | Unsafe |
65-90 | ASCII Characters | A-Z | Safe |
91-96 | Reserved Characters | [\]^_` | Unsafe |
97-122 | ASCII Characters | a-z | Safe |
123-126 | Reserved Characters | {|}~ | Unsafe |
127 | Control Characters | '' '' | Unsafe |
128-255 | Non-ASCII Characters | '' '' | Unsafe |
所有不安全的ASCII字符都需要编码,例如Q范?32-47, 58-64, 91-96, 123-126)?br />下表描述了这些字Wؓ什么不安全?
Character | Unsafe Reason | Character Encode |
"<" | Delimiters around URLs in free text | %3C |
> | Delimiters around URLs in free text | %3E |
. | Delimits URLs in some systems | %22 |
# | It is used in the World Wide Web and in other systems to delimit a URL from a fragment/anchor identifier that might follow it. | %23 |
{ | Gateways and other transport agents are known to sometimes modify such characters | %7B |
} | Gateways and other transport agents are known to sometimes modify such characters | %7D |
| | Gateways and other transport agents are known to sometimes modify such characters | %7C |
\ | Gateways and other transport agents are known to sometimes modify such characters | %5C |
^ | Gateways and other transport agents are known to sometimes modify such characters | %5E |
~ | Gateways and other transport agents are known to sometimes modify such characters | %7E |
[ | Gateways and other transport agents are known to sometimes modify such characters | %5B |
] | Gateways and other transport agents are known to sometimes modify such characters | %5D |
` | Gateways and other transport agents are known to sometimes modify such characters | %60 |
+ | Indicates a space (spaces cannot be used in a URL) | %20 |
/ | Separates directories and subdirectories | %2F |
? | Separates the actual URL and the parameters | %3F |
& | Separator between parameters specified in the URL | %26 |
如何实现
字符的URL~码是将字符转换??6q制q在前面加上''%''前缀。例如,US-ASCII字符集中I格?0q制
?2?6q制?0Q因此,URL~码?20?/p>
URLEncode: URLEncode是一个C++c,来实现字W串的URL~码。CURLEncodecd含如下函敎ͼ
isUnsafeString
decToHex
convert
URLEncode
URLEncode()函数完成~码q程QURLEncode查每个字W,看是否安全。如果不安全用%16q制D行{换ƈd
到原始字W串中?/p>
代码片断 :
class CURLEncode { private: static CString csUnsafeString; CString (char num, int radix); bool isUnsafe(char compareChar); CString convert(char val); public: CURLEncode() { }; virtual ~CURLEncode() { }; CString (CString vData); }; bool CURLEncode::isUnsafe(char compareChar) { bool bcharfound = false; char tmpsafeChar; int m_strLen = 0; m_strLen = csUnsafeString.GetLength(); for(int ichar_pos = 0; ichar_pos < m_strLen ;ichar_pos++) { tmpsafeChar = csUnsafeString.GetAt(ichar_pos); if(tmpsafeChar == compareChar) { bcharfound = true; break; } } int char_ascii_value = 0; //char_ascii_value = __toascii(compareChar); char_ascii_value = (int) compareChar; if(bcharfound == false && char_ascii_value > 32 && char_ascii_value < 123) { return false; } // found no unsafe chars, return false else { return true; } return true; } CString CURLEncode::decToHex(char num, int radix) { int temp=0; CString csTmp; int num_char; num_char = (int) num; if (num_char < 0) num_char = 256 + num_char; while (num_char >= radix) { temp = num_char % radix; num_char = (int)floor(num_char / radix); csTmp = hexVals[temp]; } csTmp += hexVals[num_char]; if(csTmp.GetLength() < 2) { csTmp += ''0''; } CString strdecToHex(csTmp); // Reverse the String strdecToHex.MakeReverse(); return strdecToHex; } CString CURLEncode::convert(char val) { CString csRet; csRet += "%"; csRet += decToHex(val, 16); return csRet; }
原文出处Q?a target="_blank">http://msdn.microsoft.com/msdnmag/issues/1000/Winsock/
通常要开发网l应用程序ƈ不是一件轻杄事情Q不q,实际上只要掌握几个关键的原则也就可以了——创建和q接一个套接字Q尝试进行连接,然后收发数据。真正难的是要写Z个可以接U_则一个,多则数千个连接的|络应用E序。本文将讨论如何通过Winsock2在Windows NT ?Windows 2000上开发高扩展能力的Winsock应用E序。文章主要的焦点在客h/服务器模型的服务器这一方,当然Q其中的许多要点Ҏ型的双方都适用?
API与响应规?/strong>
通过Win32的重叠I/O机制Q应用程序可以提请一I/O操作Q重叠的操作h在后台完成,而同一旉提请操作的线E去做其他的事情。等重叠操作完成后线E收到有关的通知。这U机制对那些耗时的操作而言特别有用。不q,像Windows 3.1上的WSAAsyncSelect()及Unix下的select()那样的函数虽然易于用,但是它们不能满响应规模的需要。而完成端口机制是针对操作pȝ内部q行了优化,在Windows NT ?Windows 2000上,使用了完成端口的重叠I/O机制才能够真正扩大系l的响应规模?/p>
完成端口
一个完成端口其实就是一个通知队列Q由操作pȝ把已l完成的重叠I/Oh的通知攑օ其中。当某项I/O操作一旦完成,某个可以对该操作l果q行处理的工作者线E就会收C则通知。而套接字在被创徏后,可以在Q何时候与某个完成端口q行兌?/p>
通常情况下,我们会在应用E序中创Z定数量的工作者线E来处理q些通知。线E数量取决于应用E序的特定需要。理想的情况是,U程数量{于处理器的数量Q不q这也要求Q何线E都不应该执行诸如同步读写、等待事仉知{阻塞型的操作,以免U程d。每个线E都分C定的CPU旉Q在此期间该U程可以q行Q然后另一个线E将分到一个时间片q开始执行。如果某个线E执行了d型的操作Q操作系l将剥夺其未使用的剩余时间片q让其它U程开始执行。也是_前一个线E没有充分用其旉片,当发生这L情况Ӟ应用E序应该准备其它U程来充分利用这些时间片?/p>
完成端口的用分Z步。首先创建完成端口,如以下代码所C:
HANDLE hIocp; hIocp = CreateIoCompletionPort( INVALID_HANDLE_VALUE, NULL, (ULONG_PTR)0, 0); if (hIocp == NULL) { // Error }完成端口创徏后,要把用该完成端口的套接字与之兌h。方法是再次调用CreateIoCompletionPort ()函数Q第一个参数FileHandle设ؓ套接字的句柄Q第二个参数ExistingCompletionPort 设ؓ刚刚创徏的那个完成端口的句柄?br />以下代码创徏了一个套接字Qƈ把它和前面创建的完成端口兌hQ?
SOCKET s; s = socket(AF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { // Error if (CreateIoCompletionPort((HANDLE)s, hIocp, (ULONG_PTR)0, 0) == NULL) { // Error } ... }
在创Z完成端口、将一个或多个套接字与之相兌之后Q我们就要创q个U程来处理完成通知。这些线E不断@环调用GetQueuedCompletionStatus ()函数q返回完成通知?/p>
下面Q我们先来看看应用程序如何跟t这些重叠操作。当应用E序调用一个重叠操作函数时Q要把指向一个overlappedl构的指针包括在其参C。当操作完成后,我们可以通过GetQueuedCompletionStatus()函数中拿回这个指针。不q,单是Ҏq个指针所指向的overlapped l构Q应用程序ƈ不能分LI竟完成的是哪个操作。要实现Ҏ作的跟踪Q你可以自己定义一个OVERLAPPEDl构Q在其中加入所需的跟t信息?/p>
无论何时调用重叠操作函数ӞL会通过其lpOverlapped参数传递一个OVERLAPPEDPLUSl构(例如WSASend?WSARecv{函?。这允怽为每一个重叠调用操作设|某些操作状态信息,当操作结束后Q你可以通过 GetQueuedCompletionStatus()函数获得你自定义l构的指针。注意OVERLAPPED字段不要求一定是q个扩展后的l构的第一个字Dc当得到了指向OVERLAPPEDl构的指针以后,可以用CONTAINING_RECORD宏取出其中指向扩展结构的指针?/p>
OVERLAPPED l构的定义如下:
typedef struct _OVERLAPPEDPLUS { OVERLAPPED ol; SOCKET s, sclient; int OpCode; WSABUF wbuf; DWORD dwBytes, dwFlags; // 其它有用的信? } OVERLAPPEDPLUS; #define OP_READ 0 #define OP_WRITE 1 #define OP_ACCEPT 2下面让我们来看看工作者线E的情况?br />
DWORD WINAPI WorkerThread(LPVOID lpParam) { ULONG_PTR *PerHandleKey; OVERLAPPED *Overlap; OVERLAPPEDPLUS *OverlapPlus, *newolp; DWORD dwBytesXfered; while (1) { ret = GetQueuedCompletionStatus( hIocp, &dwBytesXfered, (PULONG_PTR)&PerHandleKey, &Overlap, INFINITE); if (ret == 0) { // Operation failed continue; } OverlapPlus = CONTAINING_RECORD(Overlap, OVERLAPPEDPLUS, ol); switch (OverlapPlus->OpCode) { case OP_ACCEPT: // Client socket is contained in OverlapPlus.sclient // Add client to completion port CreateIoCompletionPort( (HANDLE)OverlapPlus->sclient, hIocp, (ULONG_PTR)0, 0); // Need a new OVERLAPPEDPLUS structure // for the newly accepted socket. Perhaps // keep a look aside list of free structures. newolp = AllocateOverlappedPlus(); if (!newolp) { // Error } newolp->s = OverlapPlus->sclient; newolp->OpCode = OP_READ; // This function prepares the data to be sent PrepareSendBuffer(&newolp->wbuf); ret = WSASend( newolp->s, &newolp->wbuf, 1, &newolp->dwBytes, 0, &newolp.ol, NULL); if (ret == SOCKET_ERROR) { if (WSAGetLastError() != WSA_IO_PENDING) { // Error } } // Put structure in look aside list for later use FreeOverlappedPlus(OverlapPlus); // Signal accept thread to issue another AcceptEx SetEvent(hAcceptThread); break; case OP_READ: // Process the data read // ... // Repost the read if necessary, reusing the same // receive buffer as before memset(&OverlapPlus->ol, 0, sizeof(OVERLAPPED)); ret = WSARecv( OverlapPlus->s, &OverlapPlus->wbuf, 1, &OverlapPlus->dwBytes, &OverlapPlus->dwFlags, &OverlapPlus->ol, NULL); if (ret == SOCKET_ERROR) { if (WSAGetLastError() != WSA_IO_PENDING) { // Error } } break; case OP_WRITE: // Process the data sent, etc. break; } // switch } // while } // WorkerThread其中每句柄键(PerHandleKey)变量的内容,是在把完成端口与套接字进行关联时所讄的完成键参数QOverlap参数q回的是一个指向发出重叠操作时所使用的那个OVERLAPPEDPLUSl构的指针?
要记住,如果重叠操作调用p|?也就是说Q返回值是SOCKET_ERRORQƈ且错误原因不是WSA_IO_PENDING)Q那么完成端口将不会收到M完成通知。如果重叠操作调用成功,或者发生原因是WSA_IO_PENDING的错误时Q完成端口将L能够收到完成通知?br />
Windows NT和Windows 2000的套接字架构
对于开发大响应规模的Winsock应用E序而言Q对Windows NT和Windows 2000的套接字架构有基本的了解是很有帮助的。下图是Windows 2000中的Winsock架构Q?br />
与其它类型操作系l不同,Windows NT和Windows 2000的传输协议没有一U风格像套接字那L、可以和应用E序直接交谈的界面,而是采用了一U更为底层的APIQ叫做传输驱动程序界?Transport Driver Interface,TDI)。Winsock的核心模式驱动程序负责连接和~冲区管理,以便向应用程序提供套接字仿真(在AFD.SYS文g中实?Q同时负责与底层传输驱动E序对话?/p>
谁来负责理~冲区?
正如上面所说的Q应用程序通过Winsock来和传输协议驱动E序交谈Q而AFD.SYS负责为应用程序进行缓冲区理。也是_当应用程序调?send()或WSASend()函数来发送数据时QAFD.SYS把数据拯q它自己的内部缓冲区(取决于SO_SNDBUF讑֮?Q然后send ()或WSASend()函数立即q回。也可以q么_AFD.SYS在后台负责把数据发送出厅R不q,如果应用E序要求发出的数据超q了 SO_SNDBUF讑֮的缓冲区大小Q那么WSASend()函数会阻塞,直至所有数据发送完毕?/p>
从远E客L接收数据的情况也cM。只要不用从应用E序那里接收大量的数据,而且没有出SO_RCVBUF讑֮的|AFD.SYS把数据先拷贝到其内部缓冲区中。当应用E序调用recv()或WSARecv()函数Ӟ数据从内部~冲拯到应用程序提供的~冲区?/p>
多数情况下,q样的架构运行良好,特别在是应用E序采用传统的套接字下非重叠的send()和receive()模式~写的时候。不q程序员要小心的是,管可以通过setsockopt()q个API来把SO_SNDBUF和SO_RCVBUF选项D?(关闭内部~冲?Q但是程序员必须十分清楚?AFD.SYS的内部缓冲区x会造成什么后果,避免收发数据时有关的~冲区拷贝可能引Lpȝ崩溃?/p>
举例来说Q一个应用程序通过讑֮SO_SNDBUF?把缓冲区关闭Q然后发Z个阻塞send()调用。在q样的情况下Q系l内怼把应用程序的~冲区锁定,直到接收方确认收C整个~冲区后send()调用才返回。似乎这是一U判定你的数据是否已lؓҎ全部收到的简z的ҎQ实际上却ƈ非如此。想想看Q即使远端TCP 通知数据已经收到Q其实也Ҏ不代表数据已l成功送给客户端应用程序,比如Ҏ可能发生资源不的情况,DAFD.SYS不能把数据拷贝给应用E序。另一个更要紧的问题是Q在每个U程中每ơ只能进行一ơ发送调用,效率极其低下?/p>
把SO_RCVBUF设ؓ0Q关闭AFD.SYS的接收缓冲区也不能让性能得到提升Q这只会q接收到的数据在比Winsock更低的层ơ进行缓Ԍ当你发出receive调用Ӟ同样要进行缓冲区拯Q因此你本来想避免缓冲区拯的阴谋不会得逞?/p>
现在我们应该清楚了,关闭~冲区对于多数应用程序而言q不是什么好L。只要要应用E序注意随时在某个连接上保持几个WSARecvs重叠调用Q那么通常没有必要关闭接收~冲区。如果AFD.SYSL有由应用E序提供的缓冲区可用Q那么它没有必要用内部缓冲区?/p>
高性能的服务器应用E序可以关闭发送缓冲区Q同时不会损失性能。不q,q样的应用程序必d分小心,保证它L发出多个重叠发送调用,而不是等待某个重叠发送结束了才发Z一个。如果应用程序是按一个发完再发下一个的序来操作,那浪Ҏ两次发送中间的I档旉QM是要保证传输驱动E序在发送完一个缓冲区后,立刻可以转向另一个缓冲区?/p>
资源的限制条?/strong>
在设计Q何服务器应用E序Ӟ其强健性是主要的目标。也是_
你的应用E序要能够应对Q何突发的问题Q例如ƈ发客戯求数辑ֈ峰倹{可用内存时出C뀁以及其它短旉的现象。这p求程序的设计者注意Windows NT?000pȝ下的资源限制条g的问题,从容地处理突发性事件?/p>
你可以直接控制的、最基本的资源就是网l带宽。通常Q用用h据报协议(UDP)的应用程序都可能会比较注意带宽方面的限制Q以最大限度地减少包的丢失。然而,在用TCPq接Ӟ服务器必d分小心地控制好,防止|络带宽q蝲过一定的旉Q否则将需要重发大量的包或造成大量q接中断。关于带宽管理的Ҏ应根据不同的应用E序而定Q这出了本文讨论的范围?/p>
虚拟内存的用也必须很小心地理。通过谨慎地申请和释放内存Q或者应用lookaside lists(一U高速缓?技术来重新使用已分配的内存Q将有助于控制服务器应用E序的内存开销(原文为“让服务器应用程序留下的脚印一点?Q避免操作系l频J地应用程序申L物理内存交换到虚拟内存中(原文为“让操作pȝ能够L把更多的应用E序地址I间更多C留在内存中?。你也可以通过 SetWorkingSetSize()q个Win32 API让操作系l分配给你的应用E序更多的物理内存?/p>
在用Winsock时还可能到另外两个非直接的资源不情况。一个是被锁定的内存面的极限。如果你把AFD.SYS的缓冲关闭,当应用程序收发数据时Q应用程序缓冲区的所有页面将被锁定到物理内存中。这是因为内栔R动程序需要访问这些内存,在此期间q些面不能交换出去。如果操作系l需要给其它应用E序分配一些可分页的物理内存,而又没有_的内存时׃发生问题。我们的目标是要防止写出一个病态的、锁定所有物理内存、让pȝ崩溃的程序。也是_你的E序锁定内存Ӟ不要出pȝ规定的内存分|限?/p>
在Windows NT?000pȝ上,所有应用程序d可以锁定的内存大U是物理内存?/8(不过q只是一个大概的估计Q不是你计算内存的依?。如果你的应用程序不注意q一点,当你的发出太多的重叠收发调用Q而且I/O没来得及完成Ӟ可能偶发生ERROR_INSUFFICIENT_RESOURCES的错误。在q种情况下你要避免过度锁定内存。同时要注意Q系l会锁定包含你的~冲区所在的整个内存面Q因此缓冲区靠近边界时是有代h?译者理解,~冲区如果正好超q页面边界,那怕是1个字节,出的这个字节所在的面也会被锁??/p>
另外一个限制是你的E序可能会遇到系l未分页池资源不的情况。所谓未分页池是一块永q不被交换出ȝ内存区域Q这块内存用来存储一些供各种内核lg讉K的数据,其中有的内核lg是不能访问那些被交换出去的页面空间的。Windows NT?000的驱动程序能够从q个特定的未分页池分配内存?/p>
当应用程序创Z个套接字(或者是cM的打开某个文g)Ӟ内核会从未分|中分配一定数量的内存Q而且在绑定、连接套接字Ӟ内核又会从未分页池中再分配一些内存。当你注意观察这U行为时你将发现Q如果你发出某些I/Oh?例如收发数据)Q你会从未分|里再分配多一些内?比如要追t某个待决的 I/O操作Q你可能需要给q个操作d一个自定义l构Q如前文所提及?。最后这可能会造成一定的问题Q操作系l会限制未分内存的用量?/p>
在Windows NT?000q两U操作系l上Q给每个q接分配的未分页内存的具体数量是不同的,未来版本的Windows很可能也不同。ؓ了应用E序的生命期更长Q你׃应该计算Ҏ分页池内存的具体需求量?/p>
你的E序必须防止消耗到未分|的极限。当pȝ中未分页池剩余空间太时Q某些与你的应用E序毫无关系的内栔R动就会发疯,甚至造成pȝ崩溃Q特别是当系l中有第三方讑֤或驱动程序时Q更Ҏ发生q样的惨?而且无法预测)。同时你q要CQ同一台电脑上q可能运行有其它同样消耗未分页池的其它应用E序Q因此在设计你的应用E序Ӟ对资源量的预估要特别保守和}慎?/p>
处理资源不的问题是十分复杂的,因ؓ发生上述情况时你不会收到特别的错误代码,通常你只能收C般性的WSAENOBUFS或者ERROR_INSUFFICIENT_RESOURCES 错误。要处理q些错误Q首先,把你的应用程序工作配|调整到合理的最大?译者注Q所谓工作配|,是指应用E序各部分运行中所需的内存用量,请参?http://msdn.microsoft.com/msdnmag/issues/1000/Bugslayer/Bugslayer1000.asp Q关于内存优化,译者另有译?Q如果错误l出玎ͼ那么注意查是否是|络带宽不的问题。之后,L认你没有同时发出太多的收发调用。最后,如果q是收到资源不的错误,那就很可能是遇到了未分页内存池不的问题了。要释放未分内存池I间Q请关闭应用E序中相当部分的q接Q等待系l自行渡q和修正q个瞬时的错误?br />
接受q接h
服务器要做的最普通的事情之一是接受来自客户端的q接h。在套接字上使用重叠I/O接受q接的惟一API是AcceptEx()函数。有的是,通常的同步接受函数accept()的返回值是一个新的套接字Q而AcceptEx()函数则需要另外一个套接字作ؓ它的参数之一。这是因?AcceptEx()是一个重叠操作,所以你需要事先创Z个套接字(但不要绑定或q接?Qƈ把这个套接字通过参数传给AcceptEx()。以下是一段典型的用AcceptEx()的伪代码Q?
do { -{待上一?AcceptEx 完成 -创徏一个新套接字ƈ与完成端口进行关? -讄背景l构{等 -发出一?AcceptEx h }while(TRUE);作ؓ一个高响应能力的服务器Q它必须发出_的AcceptEx调用Q守候着Q一旦出现客Lq接hqd应。至于发出多个AcceptEx才够Q就取决于你的服务器E序所期待的通信交通类型。比如,如果q入q接率高的情?因ؓq接持箋旉较短Q或者出C通高?Q那么所需要守候的 AcceptEx当然要比那些偶尔q入的客Lq接的情况要多。聪明的做法是,由应用程序来分析交通状况,q调整AcceptEx守候的数量Q而不是固定在某个数量上?
对于Windows2000QWinsock提供了一些机Ӟ帮助你判定AcceptEx的数量是否够。这是Q在创徏监听套接字时创徏一个事Ӟ通过WSAEventSelect()q个APIq注册FD_ACCEPT事g通知来把套接字和q个事g兌h。一旦系l收C个连接请求,如果pȝ中没有AcceptEx()正在{待接受q接Q那么上面的事g收C个信受通过q个事gQ你可以判断你有没有发够的 AcceptEx()Q或者检出一个非正常的客戯?下文q?。这U机制对Windows NT 4.0不适用?/p>
使用AcceptEx()的一大好处是Q你可以通过一ơ调用就完成接受客户端连接请求和接受数据(通过传送lpOutputBuffer参数)两g事情。也是_如果客户端在发出q接的同时传输数据,你的AcceptEx()调用在连接创建ƈ接收了客L数据后就可以立刻q回。这样可能是很有用的Q但是也可能会引发问题,因ؓAcceptEx()必须{全部客L数据都收C才返回。具体来_如果你在发出AcceptEx()调用的同时传递了lpOutputBuffer参数Q那么AcceptEx()不再是一原子型的操作,而是分成了两步:接受客户q接Q等待接收数据。当~少一U机制来通知你的应用E序所发生的这U情况:“连接已l徏立了Q正在等待客L数据”,q将意味着有可能出现客L只发接请求,但是不发送数据。如果你的服务器收到太多q种cd的连接时Q它拒l连接更多的合法客户端请求。这是黑客q行“拒l服务”攻ȝ常见手法?/p>
要预防此cL击,接受q接的线E应该不时地通过调用getsockopt()函数(选项参数?SO_CONNECT_TIME)来检查AcceptEx()里守候的套接字。getsockopt()函数的选项值将被设|ؓ套接字被q接的时_或者设|ؓ-1(代表套接字尚未徏立连?。这ӞWSAEventSelect()的特性就可以很好地利用来做这U检查。如果发现连接已l徏立,但是很久都没有收到数据的情况Q那么就应该l止q接Q方法就是关闭作为参数提供给AcceptEx()的那个套接字。注意,在多数非紧急情况下Q如果套接字已经传递给AcceptEx()q开始守候,但还未徏立连接,那么你的应用E序不应该关闭它们。这是因为即使关闭了q些套接字,Z提高pȝ性能的考虑Q在q接q入之前Q或者监听套接字自n被关闭之前,相应的内核模式的数据l构也不会被q净地清除?/p>
发出AcceptEx()调用的线E,g与那个进行完成端口关联操作、处理其它I/O完成通知的线E是同一个,但是Q别忘记U程里应该尽力避免执行阻塞型的操作。Winsock2分层l构的一个副作用是调用socket()或WSASocket() API的上层架构可能很重要(译者不太明白原文意思,抱歉)。每个AcceptEx()调用都需要创Z个新套接字,所以最好有一个独立的U程专门调用AcceptEx()Q而不参与其它I/O处理。你也可以利用这个线E来执行其它dQ比如事件记录?/p>
有关AcceptEx()的最后一个注意事:要实现这些APIQƈ不需要其它提供商提供的Winsock2实现。这一点对微YҎ的其它API也同样适用Q比如TransmitFile()和GetAcceptExSockAddrs()Q以及其它可能会被加入到新版Windows的API. 在Windows NT?000上,q些API是在微Y的底层提供者DLL(mswsock.dll)中实现的Q可通过与mswsock.lib~译q接q行调用Q或者通过WSAIoctl() (选项参数为SIO_GET_EXTENSION_FUNCTION_POINTER)动态获得函数的指针?/p>
如果在没有事先获得函数指针的情况下直接调用函?也就是说Q编译时静态连接mswsock.libQ在E序中直接调用函?Q那么性能很受媄响。因?AcceptEx()被置于Winsock2架构之外Q每ơ调用时它都被迫通过WSAIoctl()取得函数指针。要避免q种性能损失Q需要用这?API的应用程序应该通过调用WSAIoctl()直接从底层的提供者那里取得函数的指针?br />
参见下图套接字架构:
TransmitFile ?TransmitPackets
Winsock 提供两个专门为文件和内存数据传输q行了优化的函数。其中TransmitFile()q个API函数在Windows NT 4.0 ?Windows 2000上都可以使用Q而TransmitPackets()则将在未来版本的Windows中实现?/p>
TransmitFile ()用来把文件内定w过Winsockq行传输。通常发送文件的做法是,先调用CreateFile()打开一个文Ӟ然后不断循环调用ReadFile () 和WSASend ()直至数据发送完毕。但是这U方法很没有效率Q因为每ơ调用ReadFile() ?WSASend ()都会涉及一ơ从用户模式到内核模式的转换。如果换成TransmitFile()Q那么只需要给它一个已打开文g的句柄和要发送的字节敎ͼ而所涉及的模式{换操作将只在调用CreateFile()打开文g时发生一ơ,然后TransmitFile()时再发生一ơ。这h率就高多了?/p>
TransmitPackets()比TransmitFile()更进一步,它允许用户只调用一ơ就可以发送指定的多个文g和内存缓冲区。函数原型如下:
BOOL TransmitPackets( SOCKET hSocket, LPTRANSMIT_PACKET_ELEMENT lpPacketArray, DWORD nElementCount, DWORD nSendSize, LPOVERLAPPED lpOverlapped, DWORD dwFlags );其中QlpPacketArray是一个结构的数组Q其中的每个元素既可以是一个文件句柄或者内存缓冲区Q该l构定义如下Q?br />
typedef struct _TRANSMIT_PACKETS_ELEMENT { DWORD dwElFlags; DWORD cLength; union { struct { LARGE_INTEGER nFileOffset; HANDLE hFile; }; PVOID pBuffer; }; } TRANSMIT_FILE_BUFFERS;其中各字D|自描q型?self explanatory)?br />dwElFlags字段Q指定当前元素是一个文件句柄还是内存缓冲区(分别通过帔RTF_ELEMENT_FILE 和TF_ELEMENT_MEMORY指定)Q?br />cLength字段Q指定将从数据源发送的字节?如果是文Ӟq个字段gؓ0表示发送整个文?Q?br />l构中的无名联合体:包含文g句柄的内存缓冲区(以及可能的偏U量)?
使用q两个API的另一个好处,是可以通过指定TF_REUSE_SOCKET和TF_DISCONNECT标志来重用套接字句柄。每当API完成数据的传输工作后Q就会在传输层别断开q接Q这栯个套接字又可以重新提供lAcceptEx()使用。采用这U优化的Ҏ~程Q将减轻那个专门做接受操作的U程创徏套接字的压力(前文q及)?/p>
q两个API也都有一个共同的qQWindows NT Workstation ?Windows 2000 专业版中Q函数每ơ只能处理两个调用请求,只有在Windows NT、Windows 2000服务器版、Windows 2000高服务器版?Windows 2000 Data Center中才获得完全支持?/p>
攑֜一L?/strong>
以上各节中,我们讨论了开发高性能的、大响应规模的应用程序所需的函数、方法和可能遇到的资源瓶颈问题。这些对你意味着什么呢Q其实,q取决于你如何构造你的服务器和客L。当你能够在服务器和客户端设计上q行更好地控制时Q那么你能够避开瓉问题?/p>
来看一个示范的环境。我们要设计一个服务器来响应客L的连接、发送请求、接收数据以及断开q接。那么,服务器将需要创Z个监听套接字Q把它与某个完成端口q行兌Qؓ每颗CPU创徏一个工作线E。再创徏一个线E专门用来发出AcceptEx()。我们知道客L会在发出q接h后立M送数据,所以如果我们准备好接收~冲Z使事情变得更为容易。当Ӟ不要忘记不时地轮询AcceptEx()调用中用的套接?使用SO_CONNECT_TIME选项参数)来确保没有恶意超时的q接?/p>
该设计中有一个重要的问题要考虑Q我们应该允许多个AcceptEx()q行守候。这是因为,每发Z个AcceptEx()时我们都同时需要ؓ它提供一个接收缓冲区Q那么内存中会出现很多被锁定的面(前文说过了,每个重叠操作都会消耗一部分未分页内存池,同时q会锁定所有涉及的~冲?。这个问题很隑֛{,没有一个确切的{案。最好的Ҏ是把q个值做成可以调整的Q通过反复做性能试Q你可以得出在典型应用环境中最佳的倹{?/p>
好了Q当你测清楚后Q下面就是发送数据的问题了,考虑的重Ҏ你希望服务器同时处理多少个ƈ发的q接。通常情况下,服务器应该限制ƈ发连接的数量以及{候处理的发送调用。因为ƈ发连接数量越多,所消耗的未分内存池也越多;{候处理的发送调用越多,被锁定的内存面也越?心别超q了极限)。这同样也需要反复测试才知道{案?/p>
对于上述环境Q通常不需要关闭单个套接字的缓冲区Q因为只在AcceptEx()中有一ơ接收数据的操作Q而要保证l每个到来的q接提供接收~冲区ƈ不是太难的事情。但是,如果客户Z服务器交互的方式变一变,客户机在发送了一ơ数据之后,q需要发送更多的数据Q在q种情况下关闭接收缓冲就不太妙了Q除非你惛_法保证在每个q接上都发出了重叠接收调用来接收更多的数据?/p>
l论
开发大响应规模的Winsock服务器ƈ不是很可怕,其实也就是设|一个监听套接字、接受连接请求和q行重叠收发调用。通过讄合理的进行守候的重叠调用的数量,防止出现未分内存池被耗尽Q这才是最主要的挑战。按照我们前面讨论的一些原则,你就可以开发出大响应规模的服务器应用程序?/p>
#include <stdio.h>
#include <winsock2.h>
#define TCP
#pragma comment(lib,"ws2_32.lib")
int main( int argc ,char* argv[])
{
WSAData wsaData;
WSAStartup(WINSOCK_VERSION,&wsaData);
DWORD begin,finish;
struct timeval tv;
tv.tv_sec = 5000;
tv.tv_usec = 0;
int optlen = sizeof(struct timeval);
//
SOCKET sock = socket(PF_INET,SOCK_STREAM,0);
struct sockaddr_in to;
int len = sizeof(struct sockaddr_in);
memset(&to,0,len);
to.sin_addr.s_addr = inet_addr("202.108.9.39");;
to.sin_port = htons(80);
to.sin_family = AF_INET;
if ( connect(sock,(struct sockaddr*)&to,len) == SOCKET_ERROR )
{
closesocket(sock);
return 0;
}
//讄时gؓtv
if ( setsockopt(sock,SOL_SOCKET,SO_RCVTIMEO,(char*)&tv,optlen) ==
SOCKET_ERROR)
{
closesocket(sock);
return 0;
}
char buf[100];
if ( recv(sock,buf,100,0) == SOCKET_ERROR )
{
printf("d");
}
return 0;
}
DWORD dwType;
const int nTimeOut = 2000;
session.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, nTimeOut); //重试之间的等待g?br /> session.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1); //重试ơ数
char* pszBuffer = NULL;
try
{
AfxParseURL(strFileURLInServer, dwType, strServer, strObject, wPort);
pHttpConnection = session.GetHttpConnection(strServer, wPort);
pHttpFile = pHttpConnection->OpenRequest(CHttpConnection::HTTP_VERB_GET, strObject);
if(pHttpFile->SendRequest() == FALSE)
return false;
DWORD dwStateCode;
pHttpFile->QueryInfoStatusCode(dwStateCode);
if(dwStateCode == HTTP_STATUS_OK)
{
HANDLE hFile = CreateFile(strFileLocalFullPath, GENERIC_WRITE,
FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
NULL); //创徏本地文g
if(hFile == INVALID_HANDLE_VALUE)
{
pHttpFile->Close();
pHttpConnection->Close();
session.Close();
return false;
}
char szInfoBuffer[1000]; //q回消息
DWORD dwFileSize = 0; //文g长度
DWORD dwInfoBufferSize = sizeof(szInfoBuffer);
BOOL bResult = FALSE;
bResult = pHttpFile->QueryInfo(HTTP_QUERY_CONTENT_LENGTH,
(void*)szInfoBuffer,&dwInfoBufferSize,NULL);
dwFileSize = atoi(szInfoBuffer);
const int BUFFER_LENGTH = 1024 * 10;
pszBuffer = new char[BUFFER_LENGTH]; //d文g的缓?br /> DWORD dwWrite, dwTotalWrite;
dwWrite = dwTotalWrite = 0;
UINT nRead = pHttpFile->Read(pszBuffer, BUFFER_LENGTH); //d服务器上数据
while(nRead > 0)
{
WriteFile(hFile, pszBuffer, nRead, &dwWrite, NULL); //写到本地文g
dwTotalWrite += dwWrite;
nRead = pHttpFile->Read(pszBuffer, BUFFER_LENGTH);
}
delete[]pszBuffer;
pszBuffer = NULL;
CloseHandle(hFile);
}
else
{
delete[]pszBuffer;
pszBuffer = NULL;
if(pHttpFile != NULL)
{
pHttpFile->Close();
delete pHttpFile;
pHttpFile = NULL;
}
if(pHttpConnection != NULL)
{
pHttpConnection->Close();
delete pHttpConnection;
pHttpConnection = NULL;
}
session.Close();
return false;
}
}
catch(...)
{
delete[]pszBuffer;
pszBuffer = NULL;
if(pHttpFile != NULL)
{
pHttpFile->Close();
delete pHttpFile;
pHttpFile = NULL;
}
if(pHttpConnection != NULL)
{
pHttpConnection->Close();
delete pHttpConnection;
pHttpConnection = NULL;
}
session.Close();
return false;
}
if(pHttpFile != NULL)
pHttpFile->Close();
if(pHttpConnection != NULL)
pHttpConnection->Close();
session.Close();
return true;
}
2、上传文?br />UploadFile(LPCTSTR strURL, //负责接收上传操作的页面的URL
LPCTSTR strLocalFileName) //待上传的本地文g路径
{
ASSERT(strURL != NULL && strLocalFileName != NULL);
BOOL bResult = FALSE;
DWORD dwType = 0;
CString strServer;
CString strObject;
INTERNET_PORT wPort = 0;
DWORD dwFileLength = 0;
char * pFileBuff = NULL;
CHttpConnection * pHC = NULL;
CHttpFile * pHF = NULL;
CInternetSession cis;
bResult = AfxParseURL(strURL, dwType, strServer, strObject, wPort);
if(!bResult)
return FALSE;
CFile file;
try
{
if(!file.Open(strLocalFileName, CFile::shareDenyNone | CFile::modeRead))
return FALSE;
dwFileLength = file.GetLength();
if(dwFileLength <= 0)
return FALSE;
pFileBuff = new char[dwFileLength];
memset(pFileBuff, 0, sizeof(char) * dwFileLength);
file.Read(pFileBuff, dwFileLength);
const int nTimeOut = 5000;
cis.SetOption(INTERNET_OPTION_CONNECT_TIMEOUT, nTimeOut); //联接时讄
cis.SetOption(INTERNET_OPTION_CONNECT_RETRIES, 1); //重试1?br /> pHC = cis.GetHttpConnection(strServer, wPort); //取得一个Http联接
pHF = pHC->OpenRequest(CHttpConnection::HTTP_VERB_POST, strObject);
if(!pHF->SendRequest(NULL, 0, pFileBuff, dwFileLength))
{
delete[]pFileBuff;
pFileBuff = NULL;
pHF->Close();
pHC->Close();
cis.Close();
return FALSE;
}
DWORD dwStateCode = 0;
pHF->QueryInfoStatusCode(dwStateCode);
if(dwStateCode == HTTP_STATUS_OK)
bResult = TRUE;
}
catch(CInternetException * pEx)
{
char sz[256] = "";
pEx->GetErrorMessage(sz, 25);
CString str;
str.Format("InternetException occur!\r\n%s", sz);
AfxMessageBox(str);
}
catch(CFileException& fe)
{
CString str;
str.Format("FileException occur!\r\n%d", fe.m_lOsError);
AfxMessageBox(str);
}
catch(...)
{
DWORD dwError = GetLastError();
CString str;
str.Format("Unknow Exception occur!\r\n%d", dwError);
AfxMessageBox(str);
}
delete[]pFileBuff;
pFileBuff = NULL;
file.Close();
pHF->Close();
pHC->Close();
cis.Close();
return bResult;
}