接口函数名称 | 接口函数描述 | 接口函数 |
---|---|---|
socket | 创建套接字 | int socket(int domain, int type, int protocol); |
connect | 连接一个服务器地址 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); |
send | 发送数据 | ssize_t send(int sockfd, const void *buf, size_t len, int flags); |
recv | 收取数据 | ssize_t recv(int sockfd, void *buf, size_t len, int flags); |
accept | 接收连接 | int accept4(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags); |
shutdown | 关闭收发链路 | int shutdown(int sockfd, int how); |
close | 关闭套接字 | int close(int fd); |
setsockopt | 设置套接字选项 | int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); |
注意: 这里以bekeley提供的标准为例,不包括特定操作系统上特有的接口函数(如Windows平台的WSASend,linux的accept4),也不包括实际与网络数据来往不相关的函数(如select、linux的epoll),这里只讨论与tcp相关的接口函数,像与udp相关的函数sendto/recvfrom等函数与此类似。
下面讨论一下以上函数的一些使用注意事项:
阻塞模式下,connect函数如果不能立刻连上服务器,会导致执行流阻塞在那里一会儿,直到connect连接成功或失败或网络超时;
而非阻塞模式下,无论是否连接成功connect将立即返回,此时如果未连接成功,返回值将是-1,错误码是EINPROGRESS,表示连接操作仍然在进行中。Linux平台后续可以通过使用select/poll等函数检测该socket是否可写来判断连接是否成功。
阻塞套接字模式下,send函数如果由于对端tcp窗口太小,不足以将全部数据发送出去,将阻塞执行流,直到出错或超时或者全部发送出去为止;
同理recv函数如果当前协议栈系统缓冲区中无数据可读,也会阻塞执行流,直到出错或者超时或者读取到数据。
send和recv函数的超时时间可以参考下文关于常用socket选项的介绍。
非阻塞套接字模式下,如果由于对端tcp窗口太小,不足以将数据发出去,它将立刻返回,不会阻塞执行流,此时返回值为-1,错误码是EAGAIN或EWOULDBLOCK,表示当前数据发不出去,希望你下次再试。但是返回值如果是-1,也可能是真正的出错了,也可能得到错误码EINTR,表示被linux信号中断了,这点需要注意一下。
recv函数与send函数情形一样。
(1)由于套接字实现是收发全双工的,收和发通道相互独立,不会相互影响,shutdown函数是用来选择关闭socket收发通道中某一路(当然,也可以两路都关闭);
其how参数取值一般有三个:SHUT_RD/SHUT_WR/SHUT_RDWR:
(2)但是这里有个问题,有时候我们需要等待缓冲区中数据发送完后再关闭连接怎么办?
这里就要用到套接字选项LINGER,关于这个选项请参考下文常见的套接字选项介绍。
(3)通过上面的分析,我们得出结论,shutdown函数并不会要求操作系统底层回收套接字等资源,真正会回收资源是close函数,这个函数会要求操作系统回收相关套接字资源,并释放对ip地址与端口号二元组的占用,但是由于tcp四次挥手最后一个阶段有个TIME_WAIT状态(关于这个状态下文介绍tcp三次握手和四次回收时会详细介绍),导致与该socket相关的端口号资源不会被立即释放,有时候为了达到释放端口用来复用,我们会设置套接字选项SOL_REUSEPORT(关于这个选项,下文会介绍)。
综合起来,我们关闭一个套接字,一般会先调用shutdown函数再调用close函数,这就是所谓的优雅关闭:
严格意义上说套接字选项是有不同层级的(level),如socket级别、TCP级别、IP级别,这里我们不区分具体的级别。
(1)SO_SNDTIMEO与SO_RCVTIMEO
(2)TCP_NODELAY
(3)SO_LINGER
(4)SO_REUSEADDR/SO_REUSEPORT
(5)SO_KEEPALIVE
(1)
(2)windows打开telnet
由于我们使用的开发机器以windows居多,默认情况下,windows系统的telnet命令是没有打开的,我们可以在【控制面板】- 【程序】- 【程序和功能】- 【打开或关闭Windows功能】中打开telnet功能。
常见的选项有:
若netstat显示的进程PID为-,则使用:lsof -i:端口号
执行netstat -anop后得到如上结果,有些端口占用没有进程的pid和Program name,怎么才能找出来呢。用root执行。
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 172.23.154.139:56000 0.0.0.0:* LISTEN 131/sshd
tcp 0 0 172.23.154.139:36000 0.0.0.0:* LISTEN 125/sshd
tcp 0 0 0.0.0.0:3306 0.0.0.0:* LISTEN 32547/mysqld
tcp 0 0 0.0.0.0:41069 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN -
tcp 0 0 0.0.0.0:31101 0.0.0.0:* LISTEN -
lsof,即list opened filedescriptor,即列出当前操作系统中打开的所有文件描述符,socket也是一种file descriptor,常见的选项是:
严格意义上来说,这个不算网络排查故障和调试命令,但是我们可以利用这个命令来查看某个进程的线程数量和线程调用堆栈是否运行正常。
即netcat命令,这个工具在排查网络故障时非常有用,因而被业绩称为网络界的“瑞士军刀”。
常见的用法如下:
这个是linux系统自带的抓包工具,功能非常强大,默认需要开启root权限才能使用。
在这里插入图片描述
其常见的选项有:
常用的过滤条件有如下形式:
tcpdump –i any ‘port 8888’
tcpdump –i any ‘tcp port 8888’
tcpdump –i any ‘tcp src port 8888’
tcpdump –i any ‘tcp src port 8888 and udp dst port 9999’
tcpdump -i any 'src host 127.0.0.1 and tcp src port 12345' -XX -nn -vv
服务端:
nc -l 127.0.0.1 5555
nc -l 服务端ip 服务端port
客户端:
nc -p 65534 127.0.0.1 5555
nc -p 客户端port 服务端ip 服务端port
服务端最好是:
tcpdump -i any 'host 127.0.0.1 and port 5555' -XX -nn -vv
客户端最好是:
tcpdump -i any 'host 127.0.0.1 and port 12345' -XX -nn -vv
udp
服务端:nc -lu 10.10.10.10 123
客户端:nc -u 10.10.10.10 123
注意:要把除了eth1外的网口down掉
服务端ip:192.169.10.8
tcpdump -i eth1 icmp
客户端
ping 192.169.10.8 -i 1 -s 8192
-i:表示每隔1s
-s:表示发8k的包
关于tcpdump命令接下来将会以对tcp三次握手和四次挥手的包数据进行抓包来分析。
熟练地掌握tcp三次握手和四次挥手过程的每一个细节是我们排查网络问题的基础。
下面我们来通过tcpdump抓包能实战一下三次握手的过程:
假如我们连接的服务器ip地址存在,但监听端口号并不存在
这个时候客户端发送SYN,服务器应答ACK+RST:
这个应答包会导致客户端的connect连接失败。
还有一种情况就是客户端访问一个很遥远的ip,或者网络繁忙,服务器对客户端发送的网络SYN报文没有应答,会出现什么情况呢?
连接不上,一共重试了5次,重试的时间间隔是1秒,2秒,4秒,8秒,16秒,最后返回失败。这个重试次数在/proc/sys/net/ipv4/tcp_syn_retries 内核参数中设置,默认为6。
netstat -n | awk '/^tcp/ {++S[$NF]} END{for (a in S) print a, S[a]}'
服务端收到建立连接的SYN没有收到ACK包的时候处在SYN_RECV状态。有两个相关系统配置:
net.ipv4.tcp_synack_retries,整形,默认值是5
对于远端的连接请求SYN,内核会发送SYN + ACK数据报,以确认收到上一个 SYN连接请求包。这是三次握手机制的第二个步骤。这里决定内核在放弃连接之前所送出的 SYN+ACK 数目。不应该大于255,默认值是5,对应于180秒左右时间。
通常我们不对这个值进行修改,因为我们希望TCP连接不要因为偶尔的丢包而无法建立。
net.ipv4.tcp_syncookies
一般服务器都会设置net.ipv4.tcp_syncookies=1来防止SYN Flood攻击。
假设一个用户向服务器发送了SYN报文后突然死机或掉线,那么服务器在发出SYN+ACK应答报文后是无法收到客户端的ACK报文的(第三次握手无法完成),这种情况下服务器端一般会重试(再次发送SYN+ACK给客户端)并等待一段时间后丢弃这个未完成的连接,这段时间的长度我们称为SYN Timeout,一般来说这个时间是分钟的数量级(大约为30秒-2分钟)。
这些处在SYNC_RECV的TCP连接称为半连接, 并存储在内核的半连接队列中,在内核收到对端发送的ack包时会查找半连接队列,并将符合的requst_sock信息存储到完成三次握手的连接的队列中,然后删除此半连接。大量SYNC_RECV的TCP连接会导致半连接队列溢出,这样后续的连接建立请求会被内核直接丢弃,这就是SYN Flood攻击。
能够有效防范SYN Flood攻击的手段之一,就是SYN Cookie。SYN Cookie原理由D. J. Bernstain和 Eric Schenk发明。SYN Cookie是对TCP服务器端的三次握手协议作一些修改,专门用来防范SYN Flood攻击的一种手段。 它的原理是,在TCP服务器收到SYN包并返回SYN+ACK包时,不分配一个专门的数据区,而是根据这个SYN包计算出一个cookie值。在收到ACK包时,TCP服务器在根据那个cookie值检查这个TCP ACK包的合法性。如果合法,再分配专门的数据区进行处理未来的TCP连接。
观测服务上SYN_RECV连接个数为:7314,对于一个高并发连接的通讯服务器,这个数字比较正常。
发起TCP连接关闭的一方称为client,被动关闭的一方称为server。被动关闭的server收到FIN后,但未发出ACK的TCP状态是CLOSE_WAIT。
出现这种状况一般都是由于server端代码的问题,如果你的服务器上出现大量CLOSE_WAIT,应该要考虑检查代码。
如何在Java语言中去解析C++的网络数据包,如何在C++中解析Java的网络数据包,对于很多人来说是一件很困难的事情,所以只能变着法子使用第三方的库。其实使用tcpdump工具可以很容易解决与分析。
首先,我们需要明确字节序列这样一个概念,即我们说的大端编码(big endian)和小端编码(little endian),x86和x64系列的cpu使用小端编码,而数据在网络上传输,以及Java语言中,使用的是大端编码。那么这是什么意思呢?
我们举个例子,看一个x64机器上的32位数值在内存中的存储方式:
i在内存中的地址序列是0x003CF7C4~ 0x003CF7C8,值为40 e2 01 00。
所以,如果我们一个C++程序的int32值123456不作转换地传给Java程序,那么Java按照大端编码的形式读出来的值是:十六进制40E20100 = 十进制1088553216。
所以,我们要么在发送方将数据转换成网络字节序(大端编码),要么在接收端再进行转换。
下面看一下如果C++端传送一个如下数据结构,Java端该如何解析(由于Java中是没有指针的,也无法操作内存地址,导致很多人无从下手),下面利用tcpdump来解决这个问题的思路。
1.CentOS配置信息存储位置
上文中涉及到的配置信息位于/etc/sysctl.conf,修改后执行以下命令让配置生效:
/sbin/sysctl -p
2.涉及到的配置信息
3.当客户端C连接服务器S成功后,如果服务器先关闭,客户端C不关闭,服务器S将处于FIN_WAIT_2状态,客户端C处于CLOSE_WAIT状态, 服务器的FIN_WAIT_2状态将在net.ipv4.tcp_fin_timeout后被回收,默认30秒,在这个期间不会被复用;
客户端C处于CLOSE_WAIT状态将一直持续到进程结束或者操作系统重启,否则操作系统不会回收CLOSE_WAIT状态的连接,因为这个错误是可以避免的,其根本原因就是客户端没关闭连接导致,应该去检查你的代码。
同样的道理,如果是客户端C先关闭,服务器S未关闭,则客户端C处于FIN_WAIT_2状态,服务器器端处于CLOSE_WAIT状态,与上面的情况类似。
但是,我这里需要强调一点是:如果两个处于相互连接状态的端较远,当中间的链路出现故障(如路由器断电),且该链路是两端的必经之路,那么除非发送数据监测,否则两端的tcp协议栈本身是监测不到这个连接断开的问题,这个时候,我们需要使用类似于“保活”机制的心跳包来监测,并及时发现这种“死链”,关闭套接字或者重连。
4 .每一路连接以(源地址,源端口号,目标地址,目标端口号)这样一个四元组唯一确定,假设目标地址和目标端口号确定的情况下,由源地址+源端口号确定,源地址一般可以认为是固定的,
所以现在连接数量由可用端口号数量来确定,这个参数由net.ipv4.ip_local_port_range确定,默认值32768~61000,大约28000个左右。
5 .当发生网络故障时,我们需要除了需要关注机器的内存、磁盘、线程栈等状态外,还需要关注一下,服务上的连接状态,确认是否存在不正常的tcp三次握手或者四次挥手的中间状态(如CLOSE_WAIT和TIME_WAIT)状态,另外就是查看下临近的防火墙上来往的数据是否正常。
在CentOS 7上我们可以使用iptables等命令查看和修改相关防火墙规则。
版权说明:如非注明,本站文章均为 扬州驻场服务-网络设备调试-监控维修-南京泽同信息科技有限公司 原创,转载请注明出处和附带本文链接。
请在这里放置你的在线分享代码