??xml version="1.0" encoding="utf-8" standalone="yes"?>
前言Q串操作是编E中最常用也最基本的操作之一?做ؓVCE序员,无论是菜鸟或高手都曾用过Cstring。而且好像实际~程中很隄得开它(虽然它不是标准E++中的库)。因为MFC中提供的q个cd我们操作字串实在太方便了QCString不仅提供各种丰富的操作函数、操作符重蝲Q我们使用起串h更象basic中那L观;而且它还提供了动态内存分配,使我们减了多少字符串数l越界的隐患。但是,我们在用过E中也体会到CString直太Ҏ出错了,而且有的不可捉摸。所以有许多高h站过来,抛弃它?
在此Q我个h认ؓQCString装得确实很完美Q它有许多优点,?#8220;Ҏ使用 Q功能强Q动态分配内存,大量q行拯时它很能节省内存资源q且执行效率高,与标准E完全兼容Q同时支持多字节与宽字节Q由于有异常机制所以用它安全方便” 其实Q用过E中之所以容易出错,那是因ؓ我们对它了解得还不够Q特别是它的实现机制。因为我们中的大多数人,在工作中q不那么爱深入地ȝ关于它的文档Q何况它q是英文的?
׃前几天我在工作中遇到了一个本不是问题但却特别手、特别难解决而且莫名惊诧的问题。好来最后发现是׃CString引发的。所以没办法Q我把整个CString的实现全部看了一遍,才慌然大悟,q彻底弄清了问题的原?q个问题Q我已在csdn上开?。在此,我想把我的一些关于CString的知识ȝ一番,以供他(她)人借鉴Q也许其中有我理解上的错误,望发现者能通知我,不胜感谢?/p>
1Q?CString实现的机?
CString是通过“引用”来管理串的,“引用”q个词我怿大家q不陌生Q象Window内核对象、COM对象{都是通过引用来实现的。而CString也是通过q样的机制来理分配的内存块。实际上CString对象只有一个指针成员变?所以Q何CString实例的长度只?字节.
? int len = sizeof(CString);//len{于4
q个指针指向一个相关的引用内存块,如图: CString str("abcd");
‘A’
‘B’
‘C’
‘D’
0
0x04040404 head部,为引用内存块相关信息
str 0x40404040
正因为如此,一个这L内存块可被多个CString所引用Q例如下列代码:
CString str("abcd");
CString a = str;
CString b(str);
CString c;
c = b;
上面代码的结果是Q上面四个对?str,a,b,c)中的成员变量指针有相同的|都ؓ0x40404040.而这块内存块怎么知道有多个CString引用它呢Q同P它也会记录一些信息。如被引用数Q串长度Q分配内存长度?/p>
q块引用内存块的l构定义如下Q?/p>
struct CStringData
{
long nRefs; //表示有多个CString 引用? 4
int nDataLength; //串实际长? 4
int nAllocLength; //d分配的内存长度(不计q头部的12字节Q? 4
};
׃有了q些信息QCStringp正确地分配、管理、释攑ּ用内存块?/p>
如果你想在调试程序的时候获得这些信息。可以在WatchH口键入下列表达式:
(CStringData*)((CStringData*)(this->m_pchData)-1)?/p>
(CStringData*)((CStringData*)(str.m_pchData)-1)//str为指CString实例
正因为采用了q样的好机制Q得CString在大量拷贝时Q不仅效率高Q而且分配内存?/p>
2QLPCTSTR ?GetBuffer(int nMinBufLength)
q两个函数提供了与标准C的兼容{换。在实际中用频率很高,但却是最Ҏ出错的地斏V这两个函数实际上返回的都是指针Q但它们有何区别呢?以及调用它们后,q后是做了怎样的处理过E呢Q?/p>
(1) LPCTSTR 它的执行q程其实很简单,只是q回引用内存块的串地址?它是作ؓ操作W重载提供的Q所以在代码中有时可以隐式{换,而有时却需强制转制。如Q?/p>
CString str;
const char* p = (LPCTSTR)str;
//假设有这L一个函敎ͼTest(const char* p)Q?你就可以q样调用
Test(str);//q里会隐式{换ؓLPCTSTR
(2) GetBuffer(int nMinBufLength) 它类|也会q回一个指针,不过它有点差?q回的是LPTSTR
(3) q两者到底有何不同呢Q我惛_诉大Ӟ其本质上完全不一P一般说LPCTSTR转换后只应该当常量用,或者做函数的入参;而GetBuffer(...)取出指针后,可以通过q个指针来修攚w面的内容Q或者做函数的出参。ؓ什么呢Q也许经常有q样的代码:
CString str("abcd");
char* p = (char*)(const char*)str;
p[2] = 'z';
其实Q也许有q样的代码后Q你的程序ƈ没有错,而且E序也运行得挺好。但它却是非常危险的。再?/p>
CString str("abcd");
CString test = str;
....
char* p = (char*)(const char*)str;
p[2] = 'z';
strcpy(p, "akfjaksjfakfakfakj");//q下完蛋?
你知道此Ӟtest中的值是多少吗?{案?abzd"。它也跟着改变了,q不是你所期望发生的。但Z么会q样呢?你稍微想惛_会明白,前面说过Q因为CString是指向引用块的,str与test指向同一块地?当你p[2]='z'后,当然test也会随着改变。所以用它做LPCTSTR做{换后Q你只能去读q块数据Q千万别L变它的内宏V?/p>
假如我想直接通过指针MҎ据的话,那怎样办呢Q就是用GetBuffer(...).看下qC码:
CString str("abcd");
CString test = str;
....
char* p = str.GetBuffer(20);
p[2] = 'z'; // 执行到此Q现在test中值却仍是"abcd"
strcpy(p, "akfjaksjfakfakfakj"); // 执行到此Q现在test中D?abcd"
Z么会q样Q其实GetBuffer(20)调用Ӟ它实际上另外建立了一块新内块存,q分?0字节长度的bufferQ而原来的内存块引用计C相应?. 所以执行代码后str与test是指向了两块不同的地方,所以相安无事?/p>
(4) 不过q里q有一Ҏ意事:是str.GetBuffer(20)后,str的分配长度ؓ20Q即指针p它所指向的buffer只有20字节长,l它赋值时Q切不可过Q否则灾隄你不q了Q如果指定长度小于原来串长度Q如GetBuffer(1),实际上它会分?个字节长度(卛_来串长度Q;另外Q当调用GetBuffer(...)后ƈ改变其内容,一定要记得调用ReleaseBuffer(),q个函数会根据串内容来更新引用内存块的头部信息?/p>
(5) 最后还有一注意事项Q看下述代码Q?/p>
char* p = NULL;
const char* q = NULL;
{
CString str = "abcd";
q = (LPCTSTR)str;
p = str.GetBuffer(20);
AfxMessageBox(q);// 合法?/p>
strcpy(p, "this is test");//合法的,
}
AfxMessageBox(q);// 非法的,可能完蛋
strcpy(p, "this is test");//非法的,可能完蛋
q里要说的就是,当返回这些指针后Q?如果CString对象生命l束Q这些指针也相应无效?/p>
3Q拷?& 赋?& "引用内存? 什么时候释放?
下面演示一D代码执行过E?/p>
void Test()
{
CString str("abcd");
//str指向一引用内存块(引用内存块的引用计数?,长度?,分配长度?Q?/p>
CString a;
//a指向一初始数据状态,
a = str;
//a与str指向同一引用内存块(引用内存块的引用计数?,长度?,分配长度?Q?/p>
CString b(a);
//a、b与str指向同一引用内存块(引用内存块的引用计数?,长度?,分配长度?Q?/p>
{
LPCTSTR temp = (LPCTSTR)a;
//temp指向引用内存块的串首地址。(引用内存块的引用计数?,长度?,分配长度?Q?/p>
CString d = a;
//a、b、d与str指向同一引用内存块(引用内存块的引用计数?, 长度?,分配长度?Q?/p>
b = "testa";
//q条语句实际是调用CString::operator=(CString&)函数?b指向一新分配的引用内存块。(新分配的引用内存块的 引用计数?, 长度?, 分配长度?Q?/p>
//同时原引用内存块引用计数?. a、d与str仍指向原 引用内存块(引用内存块的引用计数?,长度?,分配长度?Q?
}
//׃d生命l束Q调用析构函敎ͼD引用计数?Q引用内存块的引用计Cؓ2,长度?,分配长度?Q?/p>
LPTSTR temp = a.GetBuffer(10);
//此语句也会导致重新分配新内存块。temp指向新分配引用内存块的串首地址Q新 分配的引用内存块的引用计Cؓ1,长度?,分配长度?0Q?/p>
//同时原引用内存块引用计数?. 只有str?指向原引用内存块 Q引用内存块的引用计Cؓ1, 长度?, 分配长度?Q?
strcpy(temp, "temp");
//a指向的引用内存块的引用计Cؓ1,长度?,分配长度?0 a.ReleaseBuffer();//注意:a指向的引用内存块的引用计Cؓ1,长度?,分配长度?0
}
//执行到此Q所有的局部变量生命周期都已结束。对象str a b 各自调用自己的析构构
//函数Q所指向的引用内存块也相应减1
//注意Qstr a b 所分别指向的引用内存块的计数均?,q导致所分配的内存块释放
通过观察上面执行q程Q我们会发现CString虽然可以多个对象指向同一引用内块存,但是它们在进行各U拷贝、赋值及改变串内ҎQ它的处理是很智能ƈ且非常安全的Q完全做C互不q涉、互不媄响。当然必要求你的代码用正恰当,特别是实际用中会有更复杂的情况Q如做函数参数、引用、及有时需保存到CStringList当中Q如果哪怕有一块地方使用不当Q其l果也会D发生不可预知的错?/p>
5 FreeExtra()的作?/p>
看这D代?/p>
(1) CString str("test");
(2) LPTSTR temp = str.GetBuffer(50);
(3) strcpy(temp, "there are 22 character");
(4) str.ReleaseBuffer();
(5) str.FreeExtra();
上面代码执行到第(4)行时Q大安知道str指向的引用内存块计数?,长度?2,分配长度?0. 那么执行str.FreeExtra()Ӟ它会释放所分配的多余的内存?引用内存块计Cؓ1,长度?2,分配长度?2)
6 Format(...) ?FormatV(...)
q条语句在用中是最Ҏ出错的。因为它最富有技巧性,也相当灵zR在q里Q我没打对它细l分析,实际上sprintf(...)怎么用,它就怎么用。我只提醒用时需注意一点:是它的参数的特D性,׃~译器在~译时ƈ不能L验格式串参数与对应的变元的类型及长度。所以你必须要注意,两者一定要对应上,
否则׃出错。如Q?/p>
CString str;
int a = 12;
str.Format("first:%l, second: %s", a, "error");//result?试试
7 LockBuffer() ?UnlockBuffer()
思议Q这两个函数的作用就是对引用内存块进行加锁及解锁。但使用它有什么作用及执行q它后对CString串有什么实质上的媄响。其实挺单,看下面代?
(1) CString str("test");
(2) str.LockBuffer();
(3) CString temp = str;
(4) str.UnlockBuffer();
(5) str.LockBuffer();
(6) str = "error";
(7) str.ReleaseBuffer();
执行?3)后,与通常情况下不同,temp与strq不指向同一引用内存块。你可以在watchH口用这个表辑ּ(CStringData*)((CStringData*)(str.m_pchData)-1)看看?/p>
其实在msdn中有说明Q?/p>
While in a locked state, the string is protected in two ways:
No other string can get a reference to the data in the locked string, even if that string is assigned to the locked string.
The locked string will never reference another string, even if that other string is copied to the locked string.
8 CString 只是处理串吗Q?/p>
不对QCString不只是能操作Ԍ而且q能处理内存块数据。功能完善吧Q看q段代码
char p[20];
for(int loop=0; loop<sizeof(p); loop++)
{
p[loop] = 10-loop;
}
CString str((LPCTSTR)p, 20);
char temp[20];
memcpy(temp, str, str.GetLength());
str完全能够转蝲内存块p到内存块temp中。所以能用CString来处理二q制数据
8 AllocSysString()与SetSysString(BSTR*)
q两个函数提供了串与BSTR的{换。用时L意一点:当调用AllocSysString()后,调用它SysFreeString(...)
9 参数的安全检?/p>
在MFC中提供了多个宏来q行参数的安全检查,如:ASSERT. 其中在CString中也不例外,有许多这L参数验,其实q也说明了代码的安全性高Q可有时我们会发现这很烦Q也DDebug与Release版本不一P如有时程序Debug通正常,而Release则程序崩溃;而有时恰相反QDebug不行QRelease行。其实我个h认ؓQ我们对CString的用过E中Q应力求代码质量高,不能在Debug版本中出CQ何断a框,哪怕releaseq行g看v来一切正常。但很不安全。如下代码:
(1) CString str("test");
(2) str.LockBuffer();
(3) LPTSTR temp = str.GetBuffer(10);
(4) strcpy(temp, "error");
(5) str.ReleaseBuffer();
(6) str.ReleaseBuffer();//执行到此ӞDebug版本会弹出错?/p>
10 CString的异常处?/p>
我只惛_调一点:只有分配内存Ӟ才有可能D抛出CMemoryException.
同样Q在msdn中的函数声明中,注有throw( CMemoryException)的函数都有重新分配或调整内存的可能?/p>
11 跨模块时的Cstring。即一个DLL的接口函C的参CؓCString&Ӟ它会发生怎样的现象。解{我遇到的问题。我的问题原来已l发_地址为:
http://www.csdn.net/expert/topic/741/741921.xml?temp=.2283136
构造一个这样CString对象Ӟ如CString strQ你可知道此时的str所指向的引用内存块吗?也许你会认ؓ它指向NULL。其实不对,如果q样的话QCString所采用的引用机制管理内存块׃有麻烦了Q所以CString在构造一个空串的对象Ӟ它会指向一个固定的初始化地址Q这块数据的声明如下Q?/p>
AFX_STATIC_DATA int _afxInitData[] = {-1,0,0,0};
要描q概括一下:当某个CString对象串置I的话,如Empty(),CString a{,它的成员变量m_pchData׃指向_afxInitDataq个变量的地址。当q个CString对象生命周期l束Ӟ正常情况下它会去Ҏ指向的引用内存块计数?Q如果引用计Cؓ0(x有Q何CString引用它时)Q则释放q块引用内存。而现在的情况是如果CString所指向的引用内存块是初始化内存块时Q则不会释放M内存?/p>
说了q么多,q与我遇到的问题有什么关pdQ其实关pd着呢?其真正原因就是如果exe模块与dll模块有一个是static~译q接的话。那么这个CString初始化数据在exe模块与dll模块中有不同的地址Q因为staticq接则会在本模块中有一份源代码的拷贝。另外一U情况,如果两个模块都是shareq接的,CString的实C码则在另一个单独的dll中实玎ͼ而AFX_STATIC_DATA指定变量只装一ơ,所以两个模块中_afxInitData有相同的地址?/p>
现在问题完全明白了吧Q你可以自己LCZ下?/p>
__declspec (dllexport) void test(CString& str)
{
str = "abdefakdfj";//如果是staticq接Qƈ且传入的str为空串的话,q里出错?/p>
}
最后一Ҏ法:写得q里Q其实CString中还有许多技巧性的好东东,我ƈ没去解释。如很多重蝲的操作符、查扄。我认ؓq是详细看看msdnQ这样也怼比我讲的好多了。我只侧重那些可能会出错的情c当Ӟ如我上面叙述中有错误Q敬请高手指点,不胜感谢Q?/p>
msdnQ?a >http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vcmfc98/html/_mfc_cstring_class_members.asp
int setsockopt(
SOCKET s,
int level,
int optname,
const char* optval,
int optlen
);
s(套接?: 指向一个打开的套接口描述?
level:(U别)Q?#160;指定选项代码的类型?
SOL_SOCKET: 基本套接?#160;
IPPROTO_IP: IPv4套接?#160;
IPPROTO_IPV6: IPv6套接?#160;
IPPROTO_TCP: TCP套接?#160;
optname(选项?Q?#160;选项名称
optval(选项?: 是一个指向变量的指针 cdQ整形,套接口结构, 其他l构cd:linger{}, timeval{ }
optlen(选项长度) Qoptval 的大?#160;
q回|标志打开或关闭某个特征的二进刉项
[/code:1:59df4ce128]
========================================================================
SOL_SOCKET ------------------------------------------------------------------------ SO_BROADCAST 允许发送广播数?#160;int 适用?#160;UDP socket。其意义是允?#160;UDP socket 「广播」(broadcastQ讯息到|\上? SO_DEBUG 允许调试 int SO_DONTROUTE 不查找\?#160;int SO_ERROR 获得套接字错?#160;int SO_KEEPALIVE 保持q接 int 对方主机是否崩溃,避免Q服务器Q永q阻塞于TCPq接的输入?#160;讄该选项后,如果2时内在此套接口的Q一方向都没有数据交换,TCPp动给Ҏ 发一个保持存zL分?keepalive probe)。这是一个对方必d应的TCP分节.它会D以下三种情况Q?#160;Ҏ接收一切正常:以期望的ACK响应?时后,TCP发出另一个探分节?#160;Ҏ已崩溃且已重新启动:以RST响应。套接口的待处理错误被置为ECONNRESETQ套?#160;口本w则被关闭?#160;Ҏ无Q何响应:源自berkeley的TCP发送另?个探分节,盔R75U一个,试图得到 一个响应。在发出W一个探分?1分钟15U后若仍无响应就攑ּ。套接口的待处理?#160;误被|ؓETIMEOUTQ套接口本n则被关闭。如ICMP错误?#8220;host unreachable(L?#160;可达)”Q说明对方主机ƈ没有崩溃Q但是不可达Q这U情况下待处理错误被|ؓ EHOSTUNREACH?#160; SO_DONTLINGER 若ؓ真,则SO_LINGER选项被禁止? SO_LINGER 延迟关闭q接 struct linger 上面q两个选项影响close行ؓ 选项 间隔 关闭方式 {待关闭与否 SO_DONTLINGER 不关?#160;优雅 ? SO_LINGER ?#160;强制 ? SO_LINGER 非零 优雅 ? 若设|了SO_LINGERQ亦即lingerl构中的l_onoff域设为非Ӟ参见2.4Q?.1.7?.1.21各节Q,q设|了零超旉隔,则closesocket()不被d立即执行Q不论是否有排队数据未发送或未被认。这U关闭方式称?#8220;强制”?#8220;失效”关闭Q因为套接口的虚电\立即被复位,且丢׃未发送的数据。在q端的recv()调用以WSAECONNRESET出错? 若设|了SO_LINGERq确定了非零的超旉隔,则closesocket()调用dq程Q直到所剩数据发送完毕或时。这U关闭称?#8220;优雅?#8221;关闭。请注意如果套接口置为非d且SO_LINGER设ؓ非零时Q则closesocket()调用以WSAEWOULDBLOCK错误q回? 若在一个流cd接口上设|了SO_DONTLINGERQ也是说将lingerl构的l_onoff域设为零Q参?.4Q?.1.7Q?.1.21节)Q则closesocket()调用立即q回。但是,如果可能Q排队的数据在套接口关闭前发送。请注意Q在q种情况下WINDOWS套接口实现将在一D不定的时间内保留套接口以及其他资源,q对于想用所以套接口的应用程序来说有一定媄响? SO_OOBINLINE 带外数据攑օ正常数据?在普通数据流中接收带外数?#160;int SO_RCVBUF 接收~冲区大?#160;int 讄接收~冲区的保留大小 ?#160;SO_MAX_MSG_SIZE 或TCP滑动H口无关Q如果一般发送的包很大很频繁Q那么用这个选项 SO_SNDBUF 发送缓冲区大小 int 讄发送缓冲区的保留大? ?#160;SO_MAX_MSG_SIZE 或TCP滑动H口无关Q如果一般发送的包很大很频繁Q那么用这个选项 每个套接口都有一个发送缓冲区和一个接收缓冲区?#160;接收~冲TCP和UDP用来接收到的数据一直保存到由应用进E来诅R?#160;TCPQTCP通告另一端的H口大小?#160;TCP套接口接收缓冲区不可能溢出,因ؓҎ不允许发q所通告H口大小的数据?#160;q就是TCP的流量控Ӟ如果Ҏ无视H口大小而发Z过宙口大小的数据,则接 收方TCP丢弃它?#160;UDPQ当接收到的数据报装不进套接口接收缓冲区Ӟ此数据报p丢弃。UDP是没?#160;量控制的;快的发送者可以很Ҏ地就Ҏ慢的接收者,D接收方的UDP丢弃数据报? SO_RCVLOWAT 接收~冲Z?#160;int SO_SNDLOWAT 发送缓冲区下限 int 每个套接口都有一个接收低潮限度和一个发送低潮限度。它们是函数selectt使用的, 接收低潮限度是让selectq回“可读”而在套接口接收缓冲区中必L的数据总量?#160;——对于一个TCP或UDP套接口,此值缺省ؓ1。发送低潮限度是让selectq回“可写” 而在套接口发送缓冲区中必L的可用空间。对于TCP套接口,此值常~省?048?#160;对于UDP使用低潮限度Q?#160;׃其发送缓冲区中可用空间的字节数是从不变化的,只要 UDP套接口发送缓冲区大小大于套接口的低潮限度Q这LUDP套接口就L可写的?#160;UDP没有发送缓冲区Q只有发送缓冲区的大? SO_RCVTIMEO 接收时 struct timeval SO_SNDTIMEO 发送超?#160;struct timeval SO_REUSERADDR 允许重用本地地址和端?#160;int 充许l定已被使用的地址Q或端口PQ可以参考bind的man SO_EXCLUSIVEADDRUSE 独占模式使用端口,是不充许和其它E序使用SO_REUSEADDR׃n的用某一端口? 在确定多重绑定用谁的时候,Ҏ一条原则是谁的指定最明确则将包递交l谁Q而且没有权限之分Q也是说低U权限的用户是可以重l定在高U权限如服务启动的端口上?q是非常重大的一个安全隐? 如果不想让自q序被监听Q那么用这个选项 SO_TYPE 获得套接字类?#160;int SO_BSDCOMPAT 与BSDpȝ兼容 int ========================================================================== IPPROTO_IP -------------------------------------------------------------------------- IP_HDRINCL 在数据包中包含IP首部 int q个选项常用于黑客技术中Q隐藏自qIP地址 IP_OPTINOS IP首部选项 int IP_TOS 服务cd IP_TTL 生存旉 int 以下IPV4选项用于l播 IPv4 选项 数据cd ?#160;q? IP_ADD_MEMBERSHIP struct ip_mreq 加入到组播组? IP_ROP_MEMBERSHIP struct ip_mreq 从组播组中退? IP_MULTICAST_IF struct ip_mreq 指定提交l播报文的接? IP_MULTICAST_TTL u_char 指定提交l播报文的TTL IP_MULTICAST_LOOP u_char 使组播报文环路有效或无效 在头文g中定义了ip_mreql构Q? [code:1:63724de67f] struct ip_mreq { struct in_addr imr_multiaddr; /* IP multicast address of group */ struct in_addr imr_interface; /* local IP address of interface */ }; [/code:1:63724de67f] 若进E要加入C个组播组中,用soket的setsockopt()函数发送该选项。该选项cd是ip_mreql构Q它的第一个字Dimr_multiaddr指定了组播组的地址Q第二个字段imr_interface指定了接口的IPv4地址? IP_DROP_MEMBERSHIP 该选项用来从某个组播组中退出。数据结构ip_mreq的用方法与上面相同? IP_MULTICAST_IF 该选项可以修改|络接口Q在l构ip_mreq中定义新的接口? IP_MULTICAST_TTL 讄l播报文的数据包的TTLQ生存时_。默认值是1Q表C数据包只能在本地的子网中传送? IP_MULTICAST_LOOP l播l中的成员自׃会收到它向本l发送的报文。这个选项用于选择是否Ȁz这U状态?br> |
无双 回复于:2003-05-08 21:21:52 |
IPPRO_TCP -------------------------------------------------------------------------- TCP_MAXSEG TCP最大数据段的大?#160;int 获取或设|TCPq接的最大分节大?MSS)。返回值是我们的TCP发送给另一端的最?#160;数据量,它常常就是由另一端用SYN分节通告的MSSQ除非我们的TCP选择使用一个比 Ҏ通告的MSS些的倹{如果此值在套接口连接之前取得,则返回gؓ未从?#183;—端 收到Mss选项的情况下所用的~省倹{小于此q回值的信可能真正用在连接上Q因 如说使用旉戳选项的话Q它在每个分节上占用12字节的TCP选项定w。我们的TcP?#160;发送的每个分节的最大数据量也可在连接存zL内改变,但前提是TCP要支持\径MTU 发现功能。如果到Ҏ的\径改变了Q此值可上下调整? TCP_NODELAY 不用Nagle法 int 指定TCP开始发送保持存zL分节前以秒为单位的q接I闲旉。缺省D必Mؓ7200U,?时。此选项仅在SO_KEPALIVEE套接口选项打开时才有效? TCP_NODELAY ?#160;TCP_CORKQ? q两个选项都对|络q接的行为具有重要的作用。许多UNIXpȝ都实CTCP_NODELAY选项Q但是,TCP_CORK则是Linuxpȝ所独有的而且相对较新Q它首先在内核版?.4上得以实现。此外,其他UNIXpȝ版本也有功能cM的选项Q值得注意的是Q在某种由BSDz的系l上的TCP_NOPUSH选项其实是TCP_CORK的一部分具体实现? TCP_NODELAY和TCP_CORK基本上控制了包的“Nagle?#8221;QNagle化在q里的含义是采用Nagle法把较的包组装ؓ更大的。John Nagle是Nagle法的发明hQ后者就是用他的名字来命名的Q他?984q首ơ用q种Ҏ来尝试解决福Ҏ车公司的|络拥塞问题Q欲了解详情请参看IETF RFC 896Q。他解决的问题就是所谓的silly window syndrome Q中文称“愚蠢H口症候群”Q具体含义是Q因为普遍终端应用程序每产生一ơ击键操作就会发送一个包Q而典型情况下一个包会拥有一个字节的数据载荷以及40个字节长的包_于是产生4000%的过载,很轻易地p令网l发生拥??#160;Nagle化后来成了一U标准ƈ且立卛_因特|上得以实现。它现在已经成ؓ~省配置了,但在我们看来Q有些场合下把这一选项x也是合乎需要的? 现在让我们假设某个应用程序发Z一个请求,希望发送小块数据。我们可以选择立即发送数据或者等待生更多的数据然后再一ơ发送两U策略。如果我们马上发送数据,那么交互性的以及客户/服务器型的应用程序将极大地受益。例如,当我们正在发送一个较短的hq且{候较大的响应Ӟ相关q蝲与传输的数据总量相比׃比较低,而且Q如果请求立卛_出那么响应时间也会快一些。以上操作可以通过讄套接字的TCP_NODELAY选项来完成,q样q用了Nagle法? 另外一U情况则需要我们等到数据量辑ֈ最大时才通过|络一ơ发送全部数据,q种数据传输方式有益于大量数据的通信性能Q典型的应用是文g服务器。应用Nagle法在这U情况下׃产生问题。但是,如果你正在发送大量数据,你可以设|TCP_CORK选项用Nagle化,其方式正好同TCP_NODELAY相反QTCP_CORK ?#160;TCP_NODELAY 是互相排斥的Q。下面就让我们仔l分析下其工作原理? 假设应用E序使用sendfile()函数来{Ud量数据。应用协议通常要求发送某些信息来预先解释数据Q这些信息其实就是报头内宏V典型情况下报头很小Q而且套接字上讄了TCP_NODELAY。有报头的包被立即传输Q在某些情况下(取决于内部的包计数器Q,因ؓq个包成功地被对Ҏ到后需要请求对方确认。这P大量数据的传输就会被推迟而且产生了不必要的网l流量交换? 但是Q如果我们在套接字上讄了TCP_CORKQ可以比Mؓ在管道上插入“塞子”Q选项Q具有报头的包就会填补大量的数据Q所有的数据都根据大自动地通过包传输出厅R当数据传输完成Ӟ最好取消TCP_CORK 选项讄l连?#8220;拔去塞子”以便M部分的都能发送出厅R这?#8220;塞住”|络q接同等重要? 总而言之,如果你肯定能一起发送多个数据集合(例如HTTP响应的头和正文)Q那么我们徏议你讄TCP_CORK选项Q这样在q些数据之间不存在gq。能极大地有益于WWW、FTP以及文g服务器的性能Q同时也化了你的工作。示例代码如下: intfd, on = 1; … /* 此处是创建套接字{操作,Z幅的考虑省略*/ … setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* cork */ write (fd, …); fprintf (fd, …); sendfile (fd, …); write (fd, …); sendfile (fd, …); … on = 0; setsockopt (fd, SOL_TCP, TCP_CORK, &on, sizeof (on)); /* 拔去塞子 */ 不幸的是Q许多常用的E序q没有考虑C上问题。例如,Eric Allman~写的sendmail没有对其套接字讄M选项? Apache HTTPD是因特网上最行的Web服务器,它的所有套接字都讄了TCP_NODELAY选项Q而且其性能也深受大多数用户的满意。这是ؓ什么呢Q答案就在于实现的差别之上。由BSD衍生的TCP/IP协议栈(值得注意的是FreeBSDQ在q种状况下的操作׃同。当在TCP_NODELAY 模式下提交大量小数据块传输时Q大量信息将按照一ơwrite()函数调用发送一块数据的方式发送出厅R然而,因ؓ负责h交付认的记数器是面向字节而非面向包(在Linux上)的,所以引入gq的概率降低了很多。结果仅仅和全部数据的大有关系。?#160;Linux 在第一包到达之后就要求认QFreeBSD则在q行如此操作之前会等待好几百个包? 在Linuxpȝ上,TCP_NODELAY的效果同习惯于BSD TCP/IP协议栈的开发者所期望的效果有很大不同Q而且在Linux上的Apache性能表现也会更差些。其他在Linux上频J采用TCP_NODELAY的应用程序也有同L问题? TCP_DEFER_ACCEPT 我们首先考虑的第1个选项是TCP_DEFER_ACCEPTQ这是Linuxpȝ上的叫法Q其他一些操作系l上也有同样的选项但用不同的名字Q。ؓ了理解TCP_DEFER_ACCEPT选项的具体思想Q我们有必要大致阐述一下典型的HTTP客户/服务器交互过E。请回想下TCP是如何与传输数据的目标徏立连接的。在|络上,在分ȝ单元之间传输的信息称为IP包(或IP 数据报)。一个包L一个携带服务信息的包头Q包头用于内部协议的处理Qƈ且它也可以携带数据负载。服务信息的典型例子是一套所谓的标志Q它把包标记代表TCP/IP协议栈内的特D含义,例如收到包的成功认{等。通常Q在l过“标记”的包里携带负载是完全可能的,但有Ӟ内部逻辑qTCP/IP协议栈发出只有包头的IP包。这些包l常会引发讨厌的|络延迟而且q增加了pȝ的负载,l果D|络性能在整体上降低? 现在服务器创Z一个套接字同时{待q接。TCP/IP式的q接q程是所?#8220;3ơ握?#8221;。首先,客户E序发送一个设|SYN标志而且不带数据负蝲的TCP包(一个SYN包)。服务器则以发出带SYN/ACK标志的数据包Q一个SYN/ACK包)作ؓ刚才收到包的认响应。客户随后发送一个ACK包确认收CW?个包从而结束连接过E。在收到客户发来的这个SYN/ACK包之后,服务器会唤醒一个接收进E等待数据到达。当3ơ握手完成后Q客L序即开始把“有用?#8221;的数据发送给服务器。通常Q一个HTTPh的量是很的而且完全可以装到一个包里。但是,在以上的情况下,臛_?个包用来进行双向传输,q样增加了可观的gq时间。此外,你还得注意到Q在“有用?#8221;数据被发送之前,接收方已l开始在{待信息了? Z减轻q些问题所带来的媄响,LinuxQ以及其他的一些操作系l)在其TCP实现中包括了TCP_DEFER_ACCEPT选项。它们设|在侦听套接字的服务器方Q该选项命o内核不等待最后的ACK包而且在第1个真正有数据的包到达才初始化侦听q程。在发送SYN/ACK包之后,服务器就会等待客L序发送含数据的IP包。现在,只需要在|络上传?个包了,而且q显著降低了q接建立的gq,对HTTP通信而言其如此? q一选项在好些操作系l上都有相应的对{物。例如,在FreeBSD上,同样的行为可以用以下代码实现Q? /* 为明晰v见,此处略去无关代码 */ struct accept_filter_arg af = { "dataready", "" }; setsockopt(s, SOL_SOCKET, SO_ACCEPTFILTER, &af, sizeof(af)); q个特征在FreeBSD上叫?#8220;接受qo?#8221;Q而且h多种用法。不q,在几乎所有的情况下其效果与TCP_DEFER_ACCEPT是一LQ服务器不等待最后的ACK包而仅仅等待携带数据负载的包。要了解该选项及其寚w性能Web服务器的重要意义的更多信息请参考Apache文档上的有关内容? HTTP客户/服务器交互而言Q有可能需要改变客L序的行ؓ。客L序ؓ什么要发送这U?#8220;无用?#8221;ACK包呢Q这是因为,TCP协议栈无法知道ACK包的状态。如果采用FTP而非HTTPQ那么客L序直到接收了FTP服务器提C的数据包之后才发送数据。在q种情况下,延迟的ACK导致客?服务器交互出现gq。ؓ了确定ACK是否必要Q客L序必ȝ道应用程序协议及其当前状态。这P修改客户行ؓ成为必要了? 对Linux客户E序来说Q我们还可以采用另一个选项Q它也被叫做TCP_DEFER_ACCEPT。我们知道,套接字分成两U类型,侦听套接字和q接套接字,所以它们也各自h相应的TCP选项集合。因此,l常同时采用的这两类选项却具有同L名字也是完全可能的。在q接套接字上讄该选项以后Q客户在收到一个SYN/ACK包之后就不再发送ACK包,而是{待用户E序的下一个发送数据请求;因此Q服务器发送的包也q应减了? TCP_QUICKACK L因发送无用包而引发gq的另一个方法是使用TCP_QUICKACK选项。这一选项?#160;TCP_DEFER_ACCEPT不同Q它不但能用作管理连接徏立过E而且在正常数据传输过E期间也可以使用。另外,它能在客?服务器连接的M一方设|。如果知道数据不久即发送,那么推迟ACK包的发送就会派上用场,而且最好在那个携带数据的数据包上设|ACK 标志以便把网l负载减到最。当发送方肯定数据被立即发送(多个包)ӞTCP_QUICKACK选项可以讄?。对处于“q接”状态下的套接字该选项的缺省值是1Q首ơ用以后内核将把该选项立即复位?Q这是个一ơ性的选项Q? 在某些情形下Q发出ACK包则非常有用。ACK包将认数据块的接收Q而且Q当下一块被处理时不至于引入延迟。这U数据传输模式对交互q程是相当典型的Q因为此cL况下用户的输入时L法预。在Linuxpȝ上这是~省的套接字行ؓ? 在上q情况下Q客L序在向服务器发送HTTPhQ而预先就知道h包很短所以在q接建立之后应该立卛_送,q可谓HTTP的典型工作方式。既然没有必要发送一个纯_的ACK包,所以设|TCP_QUICKACK?以提高性能是完全可能的。在服务器方Q这两种选项都只能在侦听套接字上讄一ơ。所有的套接字,也就是被接受呼叫间接创徏的套接字则会l承原有套接字的所有选项? 通过TCP_CORK、TCP_DEFER_ACCEPT和TCP_QUICKACK选项的组合,参与每一HTTP交互的数据包数量被降低到最的可接受水qIҎTCP协议的要求和安全斚w的考虑Q。结果不仅是获得更快的数据传输和h处理速度而且q客户/服务器双向gq实C最化? |