开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第14天,点击查看活动详情
网络编程疑难
工具使用
-
telnet ip port可以检测是服务器开发还是服务器的问题。先ping一下说明网络是没问题的。再telent一下- 连的上的服务端问题
- 连不上是客户端问题
Socket 函数
-
bind端口号不指定, 操作系统自己选择一个,应用场景:唤醒服务
网络库不能依赖很多特性,因此此时操作系统自己选择一个端口号很方便,实现linux下的唤醒机制。
客户端也是可以用在客户端,将某个sockfd 绑定到指定的地址和端口号来连接服务端,
Socket 函数的非阻塞行为
1. 读取:read,recv, readv
对于非阻塞套接字,在非阻塞下的行为,根据返回值 n 可以有几种行为:
n >0:成功读取数据,n读取到的字节数n =0: 表示对端关闭
为什么不可能接受到0字节的数据 ?
接收到0字节的数据,意味着对端要发送个0字节的数据的。但是一个能在数据链路上发送的最小IP数据包大小是46字节:IP头部一般是20字节,TCP头部一般是20字节,如果应用层数据是0字节,整个IP数据包大小40字节小于46字节,根本无法发送出去,因此接收端不可能接收到大小为0字节的数据包。
-
n < 0:在非阻塞模式下要根据错误码判断是不是真的发生错误。
EAGAIN/EWOULDBLOCK:当前缓冲中没有数据可读,不是真的错误,需要再次调用这个函数进行重试EINTR:被信号中断,不是真的错误,需要再次调用这个函数进行重试- 其他:是真的错误,需要关闭连接
2. 写操作: write,send, writev
在非阻塞套模式下,行为大致和 read 操作返回值含义一致。如果其发送缓冲区中已经没有空间,输出调用将立即返回错误:EWOULDBLOCK。如果发送缓冲区中还有一些空间,返回值就是复制到内核的字节数。
write 不存在返回0,因为如果返回0,就是表示发送缓冲区已满,不可再将数据复制到发送缓冲区,此时就是返回-1 且 erron = EAGAIN/EWOULDBLOCK。
3. 接受外来连接:accept
对于一个非阻塞套接字使用 accept,也是返回 EWOULDBLOCK。不过这个操作一般是阻塞的,因为已经有 epoll/select存在代替这个函数阻塞。
4. 发起连接:connect
TCP连接建立涉及三次握手,客户端要一直等接收到服务对于自己的SYN请求的ACK应答,connect才是会返回,即第二次握手成功,connect才返回,因此每个TCP的 connect 调用都至少阻塞一个 RTT的时间。
对于一个非阻塞套接字调用connect,并且连接不能立即建立,那么连接照样能发起,不过会返回一个 EINPROGRESS 错误。 稍后可以使用 select 是否可读可写来判断是否建立成功:
- 在 Windows 系统上,一个 socket 没有建立连接之前,我们使用 select 函数检测其是否可写,能得到正确的结果(不可写),连接成功后检测,会变为可写。所以,上述介绍的异步 connect 写法流程在 Windows 系统上是没有问题的。
- 在 Linux 系统上一个 socket 没有建立连接之前,用 select 函数检测其是否可写,你也会得到可写的结果,所以上述流程并不适用于 Linux 系统。正确的做法是,connect 之后,不仅要用 select 检测可写,还要检测此时 socket 是否出错,通过错误码来检测确定是否连接上,错误码为 0 表示连接上,反之为未连接上。完整代码如下
域名解析
使用域名的好处在于可以避免服务前当前使用的 ip:port崩溃了,可以使用切换到其他 ip:port,而不至于服务停止。对于客户端而言没有影响,这个可以使用 ifconfig 来判断。
getaddrinfo:域名与ip的转换
设计网络库,需要考虑的功能
1. 使用哪种io复用函数
select 函数
select常常用户客户端的使用
epoll 函数
-
epoll_create的参数任意正数 -
读写+LT/ET 共四种工作方式
- et模式的读和写两个方面考虑,读可能会造成数据积压
2. 事件触发
-
EPOLLIN- 内核中Socket接受缓冲区为空 --> 低电平 --> 不可读
- 内核中Socket接受缓冲区不空 --> 高电平 --> 可读
-
EPOLLOUT- 内核中Socket发送缓冲区为满 --> 低电平 --> 不可写
- 内核中Socket发送缓冲区不满 --> 高电平 --> 可写