TCP/IP相关的网络知识整理。一些疑惑的简单理解。
一些名词的解析
- 连接:收包方需要确认收到,就是有连接。TCP就需要ACK确认。
ACK超时没收到,TCP会重发数据包。 - 顺序:收包顺序和发包顺序一致是有序的。TCP这种流式协议是有序的,udp这种数据报是无序的。
- 边界:边界用于确认收到一个完整的包。TCP是流协议,需要应用层区分;UDP 的recv返回值>=0,就是单个包的长度了。
UDP的一种丢包:UDP的recv传入的缓存空间要是小于内核收到的数据报大小,这个数据报将被丢弃。 - MTU:最大传输单元,可以理解成IP层最大的分片大小,超出这个大小,就需要分片了。
这个值一般由链路层限制,以太网:1500,PPPoE:1492,X.25协议:576。UDP常常限制包大小为1400或者512。
可以用ping命令检测这个值,例如:ping -l 1600 -f www.baidu.com
会返回需要分片的信息。
一些疑惑自解
- TCP的ACK包的ack一般都是syn+payload,SYN和FIN的ack为什么要+1,他们的payload是0啊?
答:如果不+1无法确认收到。用FIN就好理解了,如果ack不+1,那么不能区分这个ACK是确认之前的数据全部收到还是确认这个FIN收到了。 - UDP的包长度有限制吗?TCP会自己分片数据送给ip层传输,UDP怎么处理,它的包怎么分片的?
答:UDP报文的长度字段是有两个字节的。UDP直接把包交给ip层,IP层自己根据MTU分配,目标机器的ip层组装。所以最好是限制大小1400,避免被分片。 - IP层分片重组怎么判断重组失败,丢弃整个数据报?用定时器吗?
答:不用定时器?收到分片时,会检查内存消耗,这时清理最旧的分片,相应的数据包也就不能重组成功了。一个blog http://blog.chinaunix.net/uid-22577711-id-3219806.html
- accept函数返回的socket连接和listen的socket是同一个吗?
答:不是同一个。accept成功会生成一个新的socket,这个socket连接与listen的socket的ip和端口一样,不一样是这个socket是已连接的,可以获取到客户端的ip和端口。
怎么确定连接断掉了。
TCP要确认,UDP就不用了。
- recv返回==0,对端关闭连接。
- recv返回<0,检查下errno。如果是
errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN
,连接还是正常的,其他的连接断掉了。 - 使用getsockopt判断连接是否异常。一个简单的判断,只要有错误,就认为连接断掉了。
{
int type = 0;
socklen_t typesize = sizeof(type);
int iCode = getsockopt(socket_id, SOL_SOCKET, SO_ERROR, (char*)&type, &typesize);
return (iCode == 0);
}
有个特殊情况:TCP连接的一端奔溃后,对端是收不到任何信息的。
这个时候需要心跳包了:
- TCP本身提供了keep-alive机制,当TCP连接不使用一段时间后,发keep-alive包检测下。
补充:keep-alive默认是不开的。一般服务器用,避免半连接占用资源。 - 应用层自己收发心跳包,确定连接是否正常。好处:可以同时支持TCP和UDP,可以计算延时啥的。
补充:应用层缓冲区满了,可以直接认为连接不可用,比较方便,毕竟应用层的缓冲区一般是很大的。
PS:在学习样例里SetKeepAlive的函数,兼容windows、mac、linux
为什么要用TCP_NODELAY,TCP的延时问题是怎么样的
很多文章会说到这个。这里简单描述下。
延迟确认与Nagle算法同时起作用时,会造成延时增加。描述起来是这样一个情况:
- 客户端发送第一个小数据包
- 服务器收到,触发延迟确认
- 客户端发送第二个小数据包,但是第一个包还没收到ACK,触发Nagle算法,延迟发送。
问题就来了,第二个小数据包要等待服务器超时确认后才发送。
有选项解决这个问题:
- TCP_NODELAY:关闭Nagle算法,小包立即发送
- TCP_QUICKACK:关闭延迟确认,只生效一次,需要每次recv后重新设置
PS:还有个问题会增加延时,TCP有超时重传的机制,丢包后延时不可避免要增加。这个超时时间由系统决定。
所以对时效性要求高的应用(如:实时对战游戏)会选择UDP,浪费带宽,快速重传。
PS:在学习样例里SetNoBlock的函数,兼容windows、mac、linux
组播是个什么东西。单播和广播比较好理解,组播不清楚具体怎么工作的。
ipv4和ipv6里都有专门的组播地址,标识的是一个组而不是单独终端。
一般编程用不到,大概是路由器层面才用到。
组播地址列表。
- ipv4: 224.0.0.0 ~ 239.255.255.255,也就是1110开头的所有ip
- ipv6: FF00::/8
ipv6没有广播地址255.255.255.255,但是有特殊的多播地址FF02::1
,同样的作用。
奇怪的问题
UDP的bind出错,错误码10022,错误消息“提供了一个无效的参数”。
windows上,udp的socket先connect,再bind出这个错误。bind需要在connect之前调用。-
开发测试时,把本机同时当成客户端和服务器端,wireshark不能抓取数据包。设置路由解决。
例如本地ip是192.168.2.186,网关是192.168.0.1。
添加路由:route add 192.168.2.186 mask 255.255.255.255 192.168.0.1 metric 1
删除路由:route delete 192.168.2.186 mask 255.255.255.255 192.168.0.1 metric 1