网络编程常用接口
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags,
struct sockaddr *restrict src_addr,
socklen_t *restrict addrlen);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
int close(int fd);
socket: 创建socket
bind:将socket与ip/port绑定。服务端必须绑定,客户端可不绑定,如果不绑定内核会随机分配一个端口
listen:服务端socket进入LISTEN状态,并指定三次握手队列大小。对于tcp,内核内部有两个队列:半连接队列和全连接队列。backlog在linux下指定全连接队列最大长度,在其他unix下指定半连接+全连接队列的长度。
connect: 用于客户端连接服务端,发送SYN
recv/send用于tcp收发数据
recvfrom/sendto用于udp收发数据
close:释放fd,发送fin,此时连接(tcp control block)仍然存在,进入FIN_WAIT_1状态。当收到ack与fin时进入TIME_WAIT
tcp状态图
三次握手
connect触发三次握手
1:客户端(内核协议栈)发送SYN+init_seqnum(1000),进入SYN_SENT
2:服务端(内核协议栈)接受到SYN,返回ACK(1001)以及SYN+服务端的init_seqnum(2000),进入SYN_RCVD
3:客户端(内核协议栈)接受到2,发送ACK(2001),进入ESTABLISHED
4:服务端接收到ACK后进入ESTABLISHED
之后两边对称
非阻塞connect
connect是否等待取决于fd是否为阻塞,如果设为非阻塞则不会等待,可以使用epoll监听EPOLLOUT事件
使用fcntl把fd设成非阻塞
int res = connect(fd, ...);
if (res < 0 && errno != EINPROGRESS) {
// error, fail somehow, close socket
return;
}
if (res == 0) {
// connection has succeeded immediately
} else {
// connection attempt is in progress
// 使用epoll等待EPOLLOUT
}
内核如何区分连接
通过(src_ip, src_port, dst_ip, dst_port, protocol) 五元组
流量控制
ack中会有一个window size,告诉发送方接受方的buffer还有多少byte可以写
有序性
使用seq num
tcp的缺点
- 延迟ack:只有在一段时间没收到新的包时才会发送ack,这样可以接受多个包只返回一个ack
不利于及时性(不过可以关闭) - 发送方会重发ack num之后所有的包(可能会重复发送一些对方已经接收到的数据,有一些浪费)
以上两种场景可以使用udp
四次挥手
- A发送fin,进入fin_wait_1
- B接受到fin,发送ack,进入close_wait
- A接受到ack,进入fin_wait_2 (等待B发送fin)
- B发送fin,进入last_ack
- A接受到fin,发送ack,进入time_wait
- B接受到ack,关闭连接
- 2MSL之后A关闭连接
为什么有time_wait?
避免最后一次ack的丢失(不然另一方可能一直处在last_ack)
tcp设置不活跃自动断线的两种方法
- 用tcp自带的keepalive(https://stackoverflow.com/questions/17740492/how-i-will-use-setsockopt-and-getsockopt-with-keep-alive-in-linux-c-programming)不推荐
- 业务层心跳机制(推荐)
参考资料
[1] 零声教育c/c++后台开发2.2.3
[2] stackoverflow