TCP,UDP不同详解,还有一些其他的
1.TCP三次握手与四次挥手
TCP报文格式
上图中有几个字段需要重点介绍下:
(1)序号:Seq序号,占32位,用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
(2)确认序号:Ack序号,占32位,只有ACK标志位为1时,确认序号字段才有效,Ack=Seq+1。
(3)标志位:共6个,即URG、ACK、PSH、RST、SYN、FIN等,具体含义如下:
(A)URG:紧急指针(urgent pointer)有效。
(B)ACK:确认序号有效。
(C)PSH:接收方应该尽快将这个报文交给应用层。
(D)RST:重置连接。
(E)SYN:发起一个新连接。(表示这个报文使用来建立新连接用的)
(F)FIN:释放一个连接。
需要注意的是:
(A)不要将确认序号Ack与标志位中的ACK搞混了。
(B)确认方Ack=发起方Seq+1,两端配对。
三次握手
TCP服务器进程先创建传输控制模块TCB,准备接受客户进程的连接请求,然后服务器进程就处于LISTEN(监听)状态,等待客户的连接请求
- 第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。(TCP规定,SYN报文段(即SYN=1的报文段)不能携带数据,但要消耗掉一个序号。)
- 第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,确认序号ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。(请注意,这个报文段也不能携带数据,但同样要消耗掉一个序号。)
- 第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态
简化一下不看Seq和ack的话过程如下:
- 客户端发送SYN给服务端
- 服务端返回SYN+ACK给客户端
- 客户端确认,返回ACK给服务端
完成三次握手,客户端与服务器开始传送数据。
为什么客户端还要发送一次确认呢?
这主要是为了防止已失效的连接请求报文段突然又传送到了服务器,因而产生错误。
假定A发出的某一个连接请求报文段在传输的过程中并没有丢失,而是在某个网络节点长时间滞留了,以致延误到连接释放以后的某个时间才到达B。本来这是一个早已失效的报文段。但B收到此失效的连接请求报文段后,就误以为A又发了一次新的连接请求,于是向A发出确认报文段,同意建立连接。假如不采用三次握手,那么只要B发出确认,新的连接就建立了。
由于A并未发出建立连接的请求,因此不会理睬B的确认,也不会向B发送数据。但B却以为新的运输连接已经建立了,并一直等待A发来数据,因此白白浪费了许多资源。
采用TCP三次握手的方法可以防止上述现象发生。例如在刚才的情况下,由于A不会向B的确认发出确认,B由于收不到确认,就知道A并没有要求建立连接。
四次挥手
- 客户端发送一个报文给服务端(没有数据),用来关闭Client到Server的数据传送,其中FIN设置为1,Sequence Number置为u,客户端进入FIN_WAIT_1状态
- 服务端收到来自客户端的请求,发送一个ACK给客户端,Acknowledge置为u+1,同时发送Sequence Number为v,服务端年进入CLOSE_WAIT状态
- 服务端发送一个报文给客户端,用来关闭Server到Client的数据传送,其中FIN设置为1,ACK置为1,Sequence置为w,Acknowledge置为u+1,用来关闭服务端到客户端的数据传送,服务端进入LAST_ACK状态
- 客户端收到FIN后,进入TIME_WAIT状态,接着发送一个ACK给服务端,Acknowledge置为w+1,Sequence Number置为u+1,最后客户端和服务端都进入CLOSED状态
简化一下不看Seq和ack:
- 第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
- 第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
- 第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
- 第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,我们也未必把全部数据都发给了对方,所以我们可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方表示同意关闭连接。因此我们的ACK和FIN一般会分开发送。
之前讨论的是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况,具体流程如下图
2.
3.单工,半双工,全双工
单工
单工数据传输只支持数据在一个方向上传输(在同一时间只有一方能接受或发送信息,不能实现双向通信)
举例:电视,广播。
半双工
半双工数据传输允许数据在两个方向上传输,但是,在某一时刻,只允许数据在一个方向上传输(它实际上是一种切换方向的单工通信;在同一时间只可以有一方接受或发送信息,可以实现双向通信。)
举例:对讲机。
全双工
全双工数据通信允许数据同时在两个方向上传输。(因此,全双工通信是两个单工通信方式的结合,它要求发送设备和接收设备都有独立的接收和发送能力;在同一时间可以同时接受和发送信息,实现双向通信。)
举例:电话通信。
4.unsigned int 和 int相加
int main()
{
int a = -6;
unsigned int b = 4;
if(a+b > 0)
printf("a+b>0\n");//这句话被打印
else
printf("a+b<0\n");
int z = a+b;
if(z > 0)
printf("z>0");
else
printf("z<0");//这句话被打印
}
下面解释一下原因:当int和unsigned in相加时,要将int转化为unsigned int(有人说总是向能表示范围更大的类型转换),而int小于0,所以它的最高位是符号位,为1,所以转化的结果是一个很大的正数,在第一个if语句中,是两个“正数”相加,结果自然就大于0了。而在z = a+b这一句时(相当于把unsigned int转化为int),它把a+b的结果看做一个int类型,而a+b最高位为1,所以z是一个负数,所以打印的是第二个语句。
5.struct结构体大小计算(在32/64位操作系统)
预备知识:基本类型占用字节
32位和64位的位数分别是什么意思?
这个位数指的是CPU 里面的通用寄存器的数据宽度为64位,也就是说一个地址占二进制位数是32/64。所以32位系统的指针占大小是4字节,64位系统就是8字节。(有人说自己是64位系统为什么指针大小打印是4字节,因为编译器是32位的,跟编译器而不是自己的电脑有关)
在32位操作系统和64位操作系统上,基本数据类型分别占多少字节呢?
32位操作系统:
char : 1 int :4 short : 2 unsigned int : 4 long : 4 unsigned long : 4 long long : 8 float : 4 double : 8 指针 : 4
64位操作系统
char : 1 int :4 short : 2 unsigned int : 4 long : 8 unsigned long : 8 long long : 8 float : 4 double : 8 指针 : 8
默认对齐方式
在默认对齐方式下,结构体成员的内存分配满足下面三个条件
- 结构体第一个成员的地址和结构体的首地址相同
- 结构体每个成员地址相对于结构体首地址的偏移量(offset)是该成员大小的整数倍,如果不是则编译器会在成员之间添加填充字节(internal adding)。
- 成员变量是数组时,按照类型长度对齐,而不是数组长度对齐(比如char a[10]就是按1而不是10来对齐)
- 如果成员是struct结构体,根据上一条,并不是按该结构体大小对齐,而是按该结构体中数据类型最大的成员来对齐
- union联合体同上
-
union内存大小需要满足以下2个条件:
-
大于或等于union中最长的成员变量的长度(char c[10]算10字节);
-
能整除其他成员变量长度(char c[10]算1字节)
-
-
- 结构体总的大小要是其成员中最大size(注意是最长的数据类型,而不是最长的变量)的整数倍,如果不是编译器会在其末尾添加填充字节(trailing padding)。
指定对齐方式
可以使用#pragma pack(N)来指定结构体成员的对齐方式
对于指定的对齐方式,其成员的地址偏移以及结构的总的大小也有下面三个约束条件
- 结构体第一个成员的地址和结构体的首地址相同
- 结构体每个成员的地址偏移需要满足:N大于等于该成员的大小,那么该成员的地址偏移需满足默认对齐方式(地址偏移是其成员大小的整数倍);N小于该成员的大小,那么该成员的地址偏移是N的整数倍。
- 结构体总的大小需要时N的整数倍,如果不是需要在结构体的末尾进行填充。
- 如果N大于结构体成员中最大成员的大小,则N不起作用,仍然按照默认方式对齐。
参考:
6.class类大小计算
空类:1
没有虚函数:sizeof(数据成员)的和
有虚函数:sizeof(数据成员)的和(除静态成员外)+sizeof(Virtual表指针)即4+父类成员
若类中包含成员,则类对象的大小只包括其中非静态成员经过对齐所占的空间,对齐方式和结构体相同。