当前位置: 首页 > news >正文

粘包、丢包及TCP信息收发

粘包、丢包及TCP信息收发
初涉socket编程的朋友经常有下面一些疑惑: 1. 为什么我发了3次,另一端只收到2次? 2. 我每次发送都成功了,为什么对方收到的信息不完整?
这些疑惑往往是对send和recv这两个函数理解不准确所致。send和recv都提供了一个长度参数。对于send而言,这是你希望发送的字节数,而对于recv而言,则是希望收到的最大字节数。
1。 send
send函数的原型是:int send(SOCKET sd, const char * buffer, int len, int flag). 其中len指出buffer中包含的实际字节数,也是程序员希望发出的最大字节数。而这个函数的返回值是实际发送出去的字节数。在网络程序中,正常情况是这个返回值小于len,也就是说buffer中的内容没有完全被发送出去。
为了确保一个缓冲区内的内容被完全被发送出去,我们需要如下代码:
int res; int pos = 0; //下一次发送需要开始的位置: while(pos < len) { res = send(sd, buffer + pos , len - pos, 0); if(res <=0) goto err_handler; //去错误处理 pos += res; }
这样经过多次send,可以确保buffer内的内容都别发送出去。
为了避免发送线程被阻塞,应该考虑把上述代码放到一个子线程中,并通过队列来缓冲所有收发。
2. recv
recv的原型是:int recv(SOCKET sd, char * buffer, int len, int flag),其各个参数的含义同前面send。需要注意的是,系统并不会等待bufer被填满了再返回,而是一旦有数据被收到,就立刻返回。因此不要期望实际收到的数据长度就等于len。
你可以使用前面send的循环算法确保收到len个字节,也可以使用内容驱动的方法实现分段数据分析。不过这个就超出本文内容,也就不再赘述。
3. 粘包 所谓粘包,是指发送端发送的两个报文,在接收端被拼在一起。由于TCP是面向流的协议,报文与报文之间是没有分界符号的。在接收端,所有的数据都逻辑上拼在一起给你。举例来说,你分10次发送10个长度为10的报文,在接收端,你可能只收到一个长度为100的报文,而不会收到10个消息。
为了解决这个问题,你必须在接收端有能力把这些报文分隔开来。如果消息长度总是固定的,这就比较容易,只要按长度取出即可。如果长度不固定,一般有两种方法解决:
a)使用特征字节。例如:如果是聊天程序,发送的是普通文本,一些字符是绝对不可能出现在正文中的,你可以使用这些字符做分隔符隔离不同消息。我们可以使用'\0'做分隔,一般对于全文本传输是比较安全的。
b) 在发送方发送正文前,先发送一个长度。例如你要发送2345字节的内容,你可以先发送一个2字节的长度给对方,然后再发正文。接收放只要收到这个长度信息,就可以正确的分包。需要注意的,到底用多少字节来发送长度是应该预先约定的,一般2字节就足够,不过你约定4字节也是可以的。还要注意的是,如果接收一次报文后,解包完毕还剩下一部分内容,这些内容应该留给下次报文分包使用,而不能扔掉。
4. 丢包 丢包一般都是由于对上面说的理解不足引起的,因为TCP本身是确保不丢包的。
发表于 2010-01-03 13:33 馨荣家园 阅读(3369) | 评论
(39) | 编辑 收藏

2009年12月30日 #

内存访问越界
1. 原理分析 经常有些新C++程序员问:C++的类的成员个数是不是有限制,为什么我加一个变量后程序就死了?或者说:是不是成员变量的顺序很重要,为什么我两个成员变量顺序换一换程序就不行了?凡此种种之怪现象,往往都是内存访问越界所致。
何谓内存访问越界,简单的说,你向系统申请了一块内存,在使用这块内存的时候,超出了你申请的范围。例如,你明明申请的是100字节的空间,但是你由于某种原因写入了120字节,这就是内存访问越界。内存访问越界的后果是:你的写入破坏了本不属于你的空间。
下面是一个简单的例子: int a; char b[16]="abcd"; int c;
a = 1; c = 2; printf("a=%d,c=%d\n", a,c); memset(b, 0,32); //注意这里访问越界了,你只有16字节空间,却修改了32字节 printf("a=%d,c=%d\n", a,c);
你可以看出,在memset前后,两个printf语句打印出来的值并不一样,因为memset越界后修改了a或者c的值(由于不同编译器对变量在空间中顺序的安排可能有不同策略,因此我用两个变量,希望能抓到越界信息。对于VC,debug模式下系统添加了很多填充字节,你可能需要增加越界的数量才能看到效果)
2. 为什么增加一个变量后程序就崩溃了? 增加一个变量后,内存中变量的布局也发生了变化。如果一个内存越界破坏了一个不含指针的结构,程序虽然逻辑不对,但是不至于崩溃。但是如果增加变量后,内存访问越界破坏了一个指针,则会导致程序崩溃。
例如:
int a; char b[128]; //bool c; char* d=new char[128]; int e;
b[136] = '\0'; b[137] = '\0'; b[138] = '\0'; b[139] = '\0'; strcpy(d, "haha"); 注意, b访问越界了8个字节位置处的4个字节。如果没有c,那么越界破坏了e变量,不会导致程序崩溃。但是加上c之后,破坏的变量可能就是d了,由于指针被破坏后,一旦访问就是内存访问违例,导致程序崩溃。
这也解释了为什么交换顺序会导致程序崩溃。如果上面情况没有变量c,你交换e和d,结构也是类似的,程序也一样要崩溃。
3. 为什么有些情况越界了程序也没错? 这主要是说这个话的人对什么是“错”没有正确的认识。程序不是只有崩溃了才是错!你破坏了别的变量,那个变量总有被使用的时候,尽管那个变量不会导致诸如程序崩溃、报警之类的严重错误,但是其计算结果必然是错误的。你说“程序没错”,是因为你根本没有发现错误而已。这种情况甚至比程序直接崩溃还要恶劣,因为程序一旦崩溃你肯定会去查,可以在导致真正严重的问题之前就把问题解决了。而如果计算错误隐藏到很晚,你的损失就可能很大了。(例如,一颗卫星上天了,你才发现一台仪器由于软件故障无法测量真正的数据,那得多少损失?)
4. 如何解决内存访问越界问题? 老实说没有好的方法。遇到这种问题,首先你得找到哪里有内存访问越界,而一个比较麻烦得问题在于,出现错误得地方往往不是真正内存越界得地方。对于内存访问越界,往往需要进行仔细得代码走查、单步跟踪并观察变量以及在调试环境得帮助下对变量进行写入跟踪(如VC6就有一旦变量被修改就break得机制)。
更重要得是,程序员要养成良好的编程习惯,在修改每个数组时一定要对这个数组有多少空间有清醒的认识,否则一旦出错,找到原因是很痛苦的事情。
发表于 2009-12-30 10:58 馨荣家园 阅读(11919) | 评论 (56) | 编辑 收藏

2007年8月28日 #

概率疑问
考虑这样一个问题:我们把2个红色球和2个白色球放进一个黑盒子里,问取出两个球颜色相同得概率是多少。
很显然,取出两个球的组合有以下四种:RR, RW,WR,WW,因此同颜色的概率是2/4=0.5
按照乘法原理,我们可以分两次取出。 第一次,我们取出白色的可能性为1/2,剩下的3个球再取出白色的可能性为1/3,因此,两次都取出白色的可能性为1/2 * 1/3 = 1/6 同理,都取出红色的可能性为1/6 那么,取出同种颜色的可能性应该为1/6 + 1/6=1/3
为什么会不一样?
发表于 2007-08-28 10:25 馨荣家园 阅读(3566) | 评论 (19) | 编辑 收藏

2007年6月25日 #

内存泄漏问题分析
大家经常听到一个名词叫内存泄漏。到底怎样才会遇到内存泄漏,内存泄漏到底该怎么定位,大家却都很糊涂。实际上我对这个问题也很头疼,下面就是我关于这个问题的一些小看法:
1. 什么叫内存泄漏? 内存泄漏是指你分配了内存,使用完毕后没有正确释放它。这样这个内存就不能再被使用。
例如: void test() {     char * p = new char[MAX_PATH];     GetModuleFileName(NULL,p, MAX_PATH);     strcat(p+strlen(p) - 3, "txt");     CStdioFile file(p);     file.WriteString("text");     file.Close(); }
注意:上面的指针p函数退出后就无法再释放,new char[MAX_PATH]分配的内存就泄漏了。
2. 何时我的程序泄漏了? 从外部看,我们很难100%准确的方法去判断是否有内存泄漏。实际上,一块内存是否被泄漏,从外部看只有当进程结束才能知道。因为你完全可以从开始分配一块内存,直到结束才去释放它。
基本上,下面行为不能说明内存泄漏: a) 任务管理器上显示内存使用增加了1MB b) 内存使用很多(我见过最大内存使用超过1.5GB的程序)
下面行为可能意味着有内存泄漏: a) 一个进程的内存使用量按照一个固定的速度稳定的增长(例如每小时增加20MB)
如果可以在VC IDE中运行程序,当程序退出时,系统会报一堆泄漏错误。
3. 如何定位 从编译器角度讲,在分配内存后,任何时候释放都是合适的,因此系统不会知道你何时适合去释放内存。在进程运行过程中,你只能估计是否有内存泄漏,而不能确定一定有内存泄漏。一般来说,我们可以用以下一些办法定位:
a) 使用性能监视器跟踪进程的内存使用情况,如果它在不断增长,且增长速度趋于稳定,一般说明在某个循环性的操作中有内存泄漏 b) 在编译器中按照Debug模式运行程序,运行一定时间后(例如2天),使用正常的方式停止进程 正常情况下,你不会收到任何和内存有关的异常。如果你程序有内存泄漏,你会收到如下错误信息:
Detected memory leaks! Dumping objects -> D:\projects\EnumWnd\EnumWndDlg.cpp(185) : {78} normal block at 0x00421330, 100 bytes long. Data: <                > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD Object dump complete.
注意,这里EnumWndDlg.cpp(185)应该是分配这块内存的地方。这是因为没有所谓的内存泄漏点,只有在某点分配的内存被泄漏了。
4. 如何分析内存泄漏 内存泄漏分析一般是通过分析内存生命周期来进行的。你必须对你的内存的可能使用情况有所了解。在此基础上,你应该了解在那个地方内存应该被释放。如果有内存泄漏,说明程序没有走到那个地方,或者释放代码没有正确执行。因此你可能需要跟踪你代码的执行情况,通过分析为什么代码没有按照预期走到内存释放处,了解为什么内存泄漏了。
常见的原因:
a) 忘记释放 b) 释放的方法错误 例如:把指针加入诸如CArray之类的集合对象中,释放时应该首先通过delete操作删除指针指向的对象,然后才能去调用诸如RemoveAll函数去释放集合中的指针。单纯析构诸如CArray之类的对象,不会自动释放它所包含的指针所指向的对象。
c) 析构函数错误:如果你对象中包含指针,那么你析构函数需要正确的释放这些指针。如果你析构函数不做这些,就会有内存泄漏 d) 线程问题:如果线程被你通过TerminateThread中止,那么它所分配的对象一般不会有机会被释放。这是那些喜欢在线程外中止线程的人经常遇到的问题。
发表于 2007-06-25 17:19 馨荣家园 阅读(13591) | 评论 (271) | 编辑 收藏

2007年4月29日 #

基类定义了operator new,派生类有什么需求

一般而言,如果基类定义了operator new,那么派生类也必须对应定义。 考虑下面的两个类 char * pAddress; class CBase { public: static void* operator new(size_t size){return pAddress;}; static void operator delete(void * p){}; };

class CDerive:public CBase { char buffer[1024]; public: CDerive() {   for(int i=0; i < 1024; i ++)   {    buffer[i] = i %26 + 'a';   } } }; 当调用CDerive * p = new CDerive时,编译器首先尝试匹配该类自己的new,由于没有,编译器就尝试匹配在其祖先链上的new,于是调用CBase::operator new 但是基类其实不知道派生类任何信息,它仅仅根据CBase处理,因此构造了一个错误的类对象。

下面是我的测试代码,你可以发现在delete时程序报告p2指针被破坏,这就是因为CDerive得不到自己的1024字节内容,因此覆盖了后面的内容

void test() { char *p1, * p2; p1 = new char[10]; memset(p1,0,10); pAddress = new char[sizeof(CBase)]; p2 = new char[10]; memset(p2,0,10); CDerive *pDerive = new CDerive; TRACE(_T("%p\n"),pDerive); delete p1; delete p2; delete pAddress; }

发表于 2007-04-29 22:33 馨荣家园 阅读(4842) | 评论 (29) | 编辑 收藏

2007年4月11日 #

我的面试经历 - 给一些初学者

前一阵子,我申请部门内部调动,被其他部门的人面试了一次,面试官让我写一段代码来对一个整形数组排序,我写了下面一段代码 #define SWAP(a,b) do {\     a = a +b;\     b = a - b;\     a = a - b;\ }while(0) void sort(int number, int vData[]) {        int ii, jj;       for(ii=0; ii < number ; ii++)      {              for(jj=ii+1; jj < number ; jj ++)              {                  if(vData[ii] > vData[jj])                  {                   SWAP(vData[ii], vData[jj]);                  }              }      } }
面试官让我优化一下代码,我又写出下面代码: #define SWAP(a,b) do {\     a = a +b;\     b = a - b;\     a = a - b;\ }while(0) void sort(int number, int vData[]) {        int ii, jj;        int min;       for(ii=0; ii < number ; ii++)      {            min = ii;              for(jj=ii+1; jj < number ; jj ++)              {                  if(vData[min] > vData[jj])                  {                    min = jj;                  }              }                           if(min != ii)              {              SWAP(vData[min],vData[ii]);              }      } }

面试官说能不能再优化一下.其实那时候我已经一脑糨糊,啥快速排序算法啊都不会,只好对面试官说不行了. 面试官指着我那段宏说:你如果用一个中间变量进行交换,会减少很多计算,对于大的排序可以优化很多.
感言:看起来神奇而水平高的算法不一定实用,要考虑算法的实用性.

发表于 2007-04-11 19:12 馨荣家园 阅读(4774) | 评论 (15) | 编辑 收藏

2006年9月23日 #

15天看好大三阳 - 甘肃电视台坐诊医生是否该发诺贝尔奖了?
今天下午五点无聊的拿遥控器换台,突然发现甘肃电视台正在有名家坐诊用“五联疗法”治疗大三阳。我听了几分钟,大意如下:
黄主任:以前治好大三阳需要2-3个月就已经很快了,没想到技术发展的这么快,现在15天就可以治愈了。......
曾主任:我们医院是卫生部....的医院,医生......,护士.....,所以患者放心前来就诊。
在我记忆中,大三阳是很难治的,怎么这位黄医生2-3个月治好还嫌慢?现在半个月就好了?我感冒还得一星期才好,怎么肝病原来这么容易治啊?
到网上一查大三阳,第一个帖子就是说“ 中国中医研究院专家提醒患者:可以使大三阳和小三阳都转阴就是一个骗局”,看来这个黄主任看来也是属于这个范畴得。老实说,我不怎么懂医学,但是大体我比较相信说黄某是骗子是肯定错不了。这篇文章似乎有介绍这是骗局得:http://www.zhongke.com/liver/006.htm。
查查五联疗法: 下面网站提到这种疗法,同一页面上说乙肝3、5年内才可能提治疗:http://www.renai.cn/zhongyi/ganbing/ 下面网站介绍是仁爱医院提出这种方法,乙肝转阴不是梦,但是目前没有有效治疗方法,只能使病毒DNA转阴(和肝病转阴有关否?)http://www.ccgs120.com/yfbj/news.jsp?info_id=10068
看来:15天治好,只能如那位黄主任的话解释“一般15天能治好,多的也要2、3个月。当然有些人可能需要更长的时间,这就是所谓个体差异,有的人按时吃药,体质比较好,当然就快一些。有些人你问他,‘哦,昨天我去蹦的了,忘吃药了’,这样今天吃明天忘后天再吃,当然就慢一些(注意,不是不好,只是慢一点)”。我恐怕这种个体差异是100个中有0.00000001个治好,其他都差异了。真奇怪甘肃怎么还允许这种片子上电视台反复的造!
PS:留的三个联系电话,2个北京,一个上海。本地不敢做广告,跑甘肃去,反正你被骗了也不能花很大代价到北京上海找我。
发表于 2006-09-23 23:50 馨荣家园 阅读(3608) | 评论 (13) | 编辑 收藏

2006年9月17日 #

汇款失败也收手续费 - 记我在招商银行的一次失败的维权经历
日前,我从我招行帐户向外地汇出一笔钱。两三天以后,对方仍然没有受到钱。由于招行声称最多5天到帐,倒也没有当回事情。但是一周过去后,才发现对方根本没有收到钱。连到招行专业版,发现我的存款记录中多了一条“退转帐汇款本金”,时间是汇款后的第四天。
从这个记录中,我注意到招商银行专业版一来没有注明我的钱为什么没汇成功,二来没有把我汇款的手续费也退回来。我于是拨95555去问。95555在经过复杂的自动应答后,终于把我电话转到人工台。可是那天我等了3分钟,也没有人接电话。我以为周末他们不值班,就给他们发了封邮件,希望他们解释这两个问题。后来问了一下,第一:他们24小时有人值班,但是不知道为什么那天我的电话没人接。第二,目前他们还没有回我邮件,看来也没把我的问题当回事情。
我以为我帐户写错了,特意打电话到对方,让他们把帐户用短信再发一遍。可是我对着我的汇款记录检查了好几遍,依然是帐户没错啊?
我选工作时间再拨95555,询问两个问题:
1。汇款为什么没汇出 招商银行的回答是:对方银行退款原因是无效帐户。
2。为什么手续费没有退 我本来以为这只是系统一个失误,问之后退给我就好了。对方的回答让我很惊讶:因为我们提供了服务,所以要收费啊。
我就问:失败的服务也收费?(后面具体话是不记得了,但是大意不会错,后面问代表是我的问话,答是银行服务员答复) 答:这是你账号填错了,又不是我们银行的错误啊? 问:但是服务没有成功,服务费当然应该退啊? 答:在跨行汇款时,我们中有人工参与,做了事情当然应该收钱 问:不是电子系统处理么? 答:在汇款之前,首先要有人工验核,然后再发到人行,人行转到x行,这手续费是三家分的,不是招行一家收钱 问:我不管谁分,只知道是你收的,不管怎么说,你的服务不成功,你就应该给我退钱。 答:但是这是你填错帐户,不是银行的过错 问:要是我查询后发现帐户没错,你是不是就退钱? 答:如果你查出没错,你可以找我,我工号是xxxx(我想,我本来就是查过后才问你的,我用什么方法证明我帐户没错啊?难道让对方把他存折复印给我?)
后面还有一堆对话,不过似乎我和她都很激动,我就让他们经理给我打回电,她说没法保证时间,我说ok,反正有电话打过来就行。顺便问一句为什么我邮件没人回:她说电话和邮件答复系统不是一家,她不知道。
一天以后,招行打我手机,再和我讨论: 招行:我是招行的 我:我对这个收费不退不理解,希望你们退回手续费 招行:不理解没有办法,我这只能给你解释 我:服务不成功为什么收费 招行:因为你帐户错了 我:但是我查过了,肯定对的 招行:你是怎么填的? 我:我就填四川省成都市工商银行xxxx帐户及户名啊 招行:成都市工商银行写的太泛,这样没法转帐 后面我们就各自找例子证明自己的话有理,最终由于她只解释不处理,实在谈不下去,而且我的手机费+电话费估计也超过能获得的退款了,只好挂机了事。
事后想想,觉得银行实在是座大山,咱们实在撼她不动。其实她有明显的错误,罗列如下: 1。她说我帐户错误是因为我没有详细到具体开户行(估计要到某某营业所了) 这种说辞是站不住脚的。其实帐户号码+用户名已经足够定位到具体帐户。就如同我们打招行电话,她也只要我报帐户和密码,从来没有说在哪个营业点挂的号。
2。按照一般人的理解,我的填法应该就对了。如果你银行有特殊要求,你必须事先说明,但是我查遍了专业版所有页面,也没看到谁提示我必须具体到哪个开户营业网点。
3。招行没商量 我们手机收费很快就不能没商量了,但是招商银行客服第一句就是只能给你解释,不能退钱。不知道什么时候“不能没商量”
4。遇到银行收错钱,如果不是错的离谱,还是别维权了。一来你是弱势群体,维权也是白搭,二来,你维权的费用估计也足以抵消你能收回的钱了
5。汇钱前要注意问清具体要求,别象我一样这么白送手续费了。
唉,真是
服务收费本应当 失败退钱更合情 只想收钱不退钱 储户心里很受伤
发表于 2006-09-17 00:34 馨荣家园 阅读(4890) | 评论 (30) | 编辑 收藏

2006年5月24日 #

如何创建资源dll,让自己程序能适应不同语言环境
http://spaces.msn.com/ronaldyan/blog/cns!FA58BC446FBB14B9!107.entry
发表于 2006-05-24 22:00 馨荣家园 阅读(4647) | 评论 (10) | 编辑 收藏

2005年12月7日 #

调试之编程准备

对于一个程序员而言,学习一种语言和一种算法是非常容易的(不包括那些上学花很多时间玩,上班说学习没时间的人)。但是,任何程序都可能是有瑕疵的,尤其有过团队协作编程经验的人,对这个感触尤为深刻。

 

在我前面的述及调试的文章里,我侧重于VC集成环境中的一些设置信息和调试所需要的一些基本技巧。但是,仅仅知道这些是不够的。一个成功的调试的开端是编程中的准备。

 

分离错误

很多程序员喜欢写下面这样的式子:

   CLeftView* pView =
     ((CFrameWnd*)AfxGetApp()->m_pMainWnd)->m_wndSplitterWnd.GetPane(0,0);

如果一切顺利,这样的式子当然是没什么问题。但是作为一个程序员,你应该时刻记得任何一个调用在某些特殊的情况下都可能失败,一旦上面某个式子失败,那么整个级联式就会出问题,而你很难弄清楚到底哪儿出错了。这样的式子的结果往往是:省了2分钟编码的时间,多了几星期的调试时间。

对于上面的式子,应该尽可能的把式子分解成独立的函数调用,这样我们可以随时确定是哪个函数调用出问题,进口缩小需要检查的范围。

检查返回值

检查返回值对于许多编程者来说似乎是一个很麻烦的事情。但是如果你能在每个可能出错的函数调用处都检查返回值,就可以立刻知道出错的函数。

有些人已经意识到检查返回值的重要性,但是要记住,只检查函数是否失败是不够的,我们需要知道函数失败的确切原因。例如下面的代码:

if(connect(sock, (const sockaddr*)&addr,sizeof(addr)) == SOCKET_ERROR)
{
         AfxMessageBox("connect failed");
}

尽管这里已经检查了返回值,实际上没有多少帮助。正如很多在vckbase上提问的人一样,大概这时候只能喊“为什么连接失败啊?”。这种情况下,其实只能猜测失败的原因,即使高手,也无法准确说出失败的原因。

增加诊断信息

在知道错误的情况下,应该尽可能的告诉测试、使用者更多的信息,这样才能了解导致失败的原因。如果程序员能提供如下错误信息,对于诊断错误是非常有帮助的:

  1. 出错的文件:我们可以借助宏THIS_FILE和__FILE__。注意THIS_FILE是在cpp文件手工定义的,而__FILE__是编译器定义的。当记录错误的函数定义在.h中时,有时候用THIS_FILE更好,因为他能说明在哪个cpp中调用并导致失败的。
  2. 出错的行:我们可以借助宏__LINE__
  3. 出错的函数:如果设计的好,有以上两项已经足够。当然我们可以直接打印出出错的函数或者表达式,这样在大堆代码中搜索(尤其是不支持go to line的编辑器中)还是很有用的。大家可以参见我的文章http://blog.vckbase.com/arong/archive/2005/11/10/14704.html中的方式进行处理,也许是一个基本的开端。
  4. 出错的原因:出错的原因很多只能由程序自己给出。如果出错只会问别人,那么你永远不可能成为一个合格的程序设计人员。很多函数失败时都会设置errno。我们可以用GetLastError获得错误码,并通过FormatMessage打印出具体错误的文字描述。

终了

给初学者一个忠告:编程时麻烦10分钟,调试时省却数小时,要想省时间,还是要从代码的可重用性和可维护性上下功夫,而不是两个代码上节省。

转载于:https://www.cnblogs.com/lzhitian/archive/2012/01/25/2329310.html

相关文章:

  • 超前引用
  • Java中的volatile关键字-转载
  • jsp---EL存取器
  • 如何可以导入注册表文件时不提示?
  • 第八章异常和断言
  • VC++播放音频文件和音频数据的方法
  • ubuntu ftp服务器配置(转)
  • shell算数运算
  • 集线器、路由器、交换机、第三层交换技术
  • Slideshow ad
  • 13.6.1 新添加一个界面(Adding One Interface)
  • asp.net 机试题1
  • 第二节 1面向对像简介
  • basic4android 开发教程翻译(八)使用ListView
  • 虚拟机安装Linux怎么使桌面铺满虚拟机
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • Docker容器管理
  • IIS 10 PHP CGI 设置 PHP_INI_SCAN_DIR
  • jquery cookie
  • Linux gpio口使用方法
  • react 代码优化(一) ——事件处理
  • vue学习系列(二)vue-cli
  • Webpack 4x 之路 ( 四 )
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 给新手的新浪微博 SDK 集成教程【一】
  • 扑朔迷离的属性和特性【彻底弄清】
  • 前言-如何学习区块链
  • 我有几个粽子,和一个故事
  • 一个普通的 5 年iOS开发者的自我总结,以及5年开发经历和感想!
  • 【云吞铺子】性能抖动剖析(二)
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • 阿里云ACE认证学习知识点梳理
  • 移动端高清、多屏适配方案
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • # C++之functional库用法整理
  • #周末课堂# 【Linux + JVM + Mysql高级性能优化班】(火热报名中~~~)
  • (10)ATF MMU转换表
  • (4) PIVOT 和 UPIVOT 的使用
  • (9)YOLO-Pose:使用对象关键点相似性损失增强多人姿态估计的增强版YOLO
  • (delphi11最新学习资料) Object Pascal 学习笔记---第8章第2节(共同的基类)
  • (超详细)2-YOLOV5改进-添加SimAM注意力机制
  • (转)c++ std::pair 与 std::make
  • (转)Unity3DUnity3D在android下调试
  • .gitignore
  • .NET Core 中插件式开发实现
  • .NET设计模式(11):组合模式(Composite Pattern)
  • @font-face 用字体画图标
  • []常用AT命令解释()
  • [1525]字符统计2 (哈希)SDUT
  • [20170728]oracle保留字.txt
  • [C#]winform使用引导APSF和梯度自适应卷积增强夜间雾图像的可见性算法实现夜间雾霾图像的可见度增强
  • [c]统计数字
  • [emacs] CUA的矩形块操作很给力啊
  • [GYCTF2020]Ez_Express
  • [LeetCode 687]最长同值路径