在Unix下进行网络编程时,由于网络并非完全可靠,会遇到各种协议主流程外发生的各种错误。
而健壮的程序必须考虑到这些错误并正确处理,因此这里总结网络编程中可能发生的常见错误。
TCP异常流程
总体
应答超时
在握手,挥手以及消息传递的状态下,若当前发送的报文期待一个应答报文。在规定时间应答报文没有到达,发送方会重发两次报文(报文重发间隔可设置)。若重发三次后依旧没有收到应答,则向应用返回ETIMEOUT
目的不可达
若报文在传送的过程中,因为找不到路由路径,报文无法到达等引发了ICMP错误,发送方会按照上述的方式重发次报文。若重发结束后依旧没有收到应答,则向应用返回EHOSTUNREACH或者ENETUNREACH
禁止分片
在IPV4中,报文超过了MTU长度会导致分片,而其存在DF(Don’t Fragment)标识,表示禁止分片。同时IPV6禁止路由器分片,因此在传送的过程中隐含DF位。在传送过程中设置了DF位而超过了MTU,则会向应用返回EMSGSIZE
阻塞时中断
在系统执行慢系统调用(可能被永远阻塞的系统调用)时阻塞,此时捕获到某个信号并进行了处理(系统对某些信号有默认处理方式),在没有设置自动重启的情况下,会向应用返回EINTR。
一般应对EINTR的方式是简单的重新调用,但在connect返回EINTR时不能这么做,因为connect涉及三次握手的过程,需要使用getsockopt获取连接状态。
读写时RST
在调用read等阻塞时接收到对端RST信号时,会返回ECONNREST。同时对发送方断开的套接字写时也会返回ECONNRESET
写RST套接字
当进程向收到RST的套接字执行写操作的时候,内核向该进程发送一个SIGPIPE信号,该信号的默认行为是终止进程,因此进程必须捕获它以免不情愿的被终止
不论进程是捕捉了该信号并从信号处理函数中返回,还是简单忽略该信号,写操作都讲返回EPIPE错误
握手部分
首次握手服务端RST

在客户端第一次握手时,若服务端返回RST报文,立即向应用返回ECONNREFUSED
握手结束客户端RST

在较为繁忙的服务器中,可能出现上图客户端刚经历三次握手后随机发送RST报文的情况,posix指出这种情况errno设置为ECONNABORTED,只需要再次调用accept即可。
而在Berkeley的实现中,返回EPROTO错误,代表协议错误,是一种致命错误,由内核把该连接从已完成连接套接字队列中释放,若再次调用accept,则不会处理到本次请求,可能导致阻塞。
数据传送部分
服务端主机崩溃
由于客户端无法收到服务端的任何回应,会重发处理,最终向应用返回的情况可能为应答超时或目的不可达。
若想尽快的检测出主机崩溃,不主动发送数据也可做到,即套接字选项的SO_KEEPALIVE(类似心跳机制)
挥手部分

服务端进程终止或关机
服务端由于进程崩溃或者手动kill后,进程终止关闭所有打开的描述符。这导致了其向客户端发送了一个FIN,客户端则响应了一个ack,TCP挥手的前半部分完成,服务端不在发送数据。
但是此时客户端并不知道服务器端已经终止了。当客户端向服务器写数据的时候,由于服务器进程终止,所以响应了RST。
这种情况下可以由select或者poll检测到服务端的终止。
- 如果对端TCP发送数据,套接字可读,并且read返回一个大于0的值(读入字节数)
- 如果对端TCP发送了FIN(对端进程终止),套接字可读,并且read返回0(EOF)
- 如果对端TCP发送RST(对端崩溃并重启),套接字可读,并且read返回-1,errno中含有确切错误码
服务端终止后重启
由于重启后已经丢失了套接字连接信息,服务端对客户端的报文响应RST。若此时客户端读,则会返回ECONNRESET
套接字api异常情况
socket
| errno | 含义 | 可能情况 | 致命 | 解决 |
|---|---|---|---|---|
| EACCES | 权限不足 | √ | ||
| EAFNOSUPPORT | 地址族不支持 | 参数错误 | √ | |
| EINVAL | 参数错误 | 未知协议或协议族不可用 | √ | |
| EMFILE | 打开的文件过多 | 进程级打开的fd达上限 | ulimit -n 调整或等待资源释放 | |
| ENFILE | 打开的文件过多 | 系统级打开的fd达上限 | 等待资源释放 | |
| ENOBUFS/ENOMEM | 内存不足 | Buffer或文件表无法创建 | 等待资源释放 |
bind
| errno | 含义 | 可能情况 | 致命 | 解决 |
|---|---|---|---|---|
| EACCES | 权限不足 | 申请保留端口且非root运行 | √ | |
| EADDRINUSE | 地址已被使用 | 绑定已使用地址或申请临时端口时已占满 | 临时端口已满时重试 | |
| EBADF | fd不可用 | fd已关闭或参数非法 | √ | |
| EINVAL | 参数错误 | 套接字重复绑定或参数错误 | √ | |
| ENOTSOCK | 非套接字 | fd参数错误 | √ |
listen
| errno | 含义 | 可能情况 | 致命 | 解决 |
|---|---|---|---|---|
| EADDRINUSE | 地址已被使用 | 监听已监听端口或未bind至确定端口而申请临时端口时已占满 | 临时端口已满时重试 | |
| EBADF | fd不可用 | fd已关闭或参数非法 | √ | |
| ENOTSOCK | 非套接字 | fd参数错误 | √ | |
| EOPNOTSUPP | 操作不支持 | 只支持SOCK_STREAM类套接字(TCP) | √ |
accept
| errno | 含义 | 可能情况 | 致命 | 解决 |
|---|---|---|---|---|
| EWOULDBLOCK/EAGAIN | 资源暂时不可用 | (非阻塞)队列中无完成握手的连接 | 重试或处理其他事务 | |
| EBADF | fd不可用 | fd已关闭或参数非法 | √ | |
| ECONNABORTED | 连接已中断 | 见握手结束客户端RST | 重试或处理其他事务 | |
| EFAULT | 地址错误 | addr参数没有指向用户可写空间 | √ | |
| EINTR | 调用中断 | 调用被信号中断 | 重试或处理其他事务 | |
| EINVAL | 参数错误 | 套接字未在监听态或addrlen/flags错误 | √ | |
| EMFILE | 打开的文件过多 | 进程级打开的fd达上限 | ulimit -n 调整或等待资源释放 | |
| ENFILE | 打开的文件过多 | 系统级打开的fd达上限 | 等待资源释放 | |
| ENOMEM/ENOBUFS | 内存不足 | 套接字buffer内存不足而非系统内存不足 | 等待资源释放 |
connect
| errno | 含义 | 可能情况 | 致命 | 解决 |
|---|---|---|---|---|
| EACCES/EPERM | 权限不足/操作未允许 | 未设置广播标记但连接广播地址或防火墙禁止连接 | √ | |
| EADDRINUSE | 本地地址已被使用 | 重试,等待资源释放 | ||
| EADDRNOTAVAIL | 地址不可用 | 未bind至确定端口而申请临时端口时已占满 | 重试,等待资源释放 | |
| EAFNOSUPPORT | 地址族错误 | √ | ||
| EAGAIN | 资源暂时不可用 | 无可用本地端口或路由缓存中无记录(?) | 重试,等待资源释放 | |
| EALREADY | 连接正在处理 | (非阻塞)上一个连接还未结束处理,可能是对返回EINPROGRESS重新调用 | √ | |
| EBADF | fd不可用 | fd已关闭或参数非法 | √ | |
| ECONNREFUSED | 连接拒绝 | 给定远端未监听,或见首次握手服务端RST | 重试 | |
| EFAULT | 地址错误 | 地址指针超出用户空间 | √ | |
| EINPROGRESS | 操作正在进行 | (非阻塞)connect无法立即完成 | 使用getsockopt判断连接是出错还是已完成 | |
| EINTR | 调用中断 | 调用被信号中断 | 使用getsockopt判断连接是出错还是已完成 | |
| EISCONN | 套接字已连接 | 使用getsockopt判断连接是出错还是已完成 | ||
| ENETUNREACH | 网络不可达 | 见目的不可达 | 重试 | |
| ENOTSOCK | 非套接字 | fd参数错误 | √ | |
| EPROTOTYPE | 套接字协议错误 | √ | ||
| ETIMEDOUT | 连接超时 | 远端无应答,可能因为系统繁忙 | 重试 |
另附: 对connect下几种无法判断连接是否成功建立的处理方案的讨论http://www.madore.org/~david/computers/connect-intr.html
EINTR/EINPROGRESS/EALREADY代表的情况
If
connect()is interrupted by a signal that is caught while blocked waiting to establish a connection,connect()shall fail and setconnect()to [EINTR], but the connection request shall not be aborted, and the connection shall be established asynchronously.If the connection cannot be established immediately and
O_NONBLOCKis set for the file descriptor for the socket,connect()shall fail and seterrnoto [EINPROGRESS], but the connection request shall not be aborted, and the connection shall be established asynchronously. Subsequent calls toconnect()for the same socket, before the connection is established, shall fail and seterrnoto [EALREADY].When the connection has been established asynchronously,
select()andpoll()shall indicate that the file descriptor for the socket is ready for writing.
read(只涉及套接字)
| errno | 含义 | 可能情况 | 致命 | 解决 |
|---|---|---|---|---|
| EAGAIN/EWOULDBLOCK | 操作将会阻塞 | (非阻塞)Buffer空 | 重试 | |
| EBADF | fd不可用 | fd已关闭或参数非法 | √ | |
| EFAULT | 地址错误 | buffer超出用户空间 | √ | |
| EINTR | 调用中断 | 调用被信号中断 | 重试 | |
| EINVAL | 参数错误 | fd不可读 | √ |
write(只涉及套接字)
| errno | 含义 | 可能情况 | 致命 | 解决 |
|---|---|---|---|---|
| EAGAIN/EWOULDBLOCK | 操作将会阻塞 | (非阻塞)Buffer已满或空闲空间不足 | 重试 | |
| EBADF | fd不可用 | fd已关闭或参数非法 | √ | |
| EDESTADDRREQ | 需要目的地址 | 套接字未连接 | √ | |
| EFAULT | 地址错误 | buffer超出用户空间 | √ | |
| EINTR | 调用中断 | 调用被信号中断 | 重试 | |
| EINVAL | 参数错误 | √ | ||
| EPIPE | 管道错误 | 向读关闭的一端写,见写RST套接字 | √ |

2001

被折叠的 条评论
为什么被折叠?



