Unix环境下网络编程核心API
- socket
- bind
- listen
- accept
- recv
- send
- close
- connect
- 无论是java、python、go,底层一定会调用这些API
- strace ./out 追踪系统调用
- 《tcp/ip详解》《unix环境高级编程》
UDP简单回顾
1 2 3 4
| memcpy(buffer, "hello", strlen("hello")); sendto(buffer);
|
TCP三个阶段
- 建立连接
- 传输数据
- 断开连接
建立连接
TCP头与三次握手

- Source port:源端口
- Destination port:目的端口
- Sequence Number:序列号
- Acknowlegment Number:确认号
- CWR、ECE、ACK、SYN…:标志位
- Window Size:窗口大小
- TCP Checksum:校验值
- Urgent Pointer:紧急指针
- Options:可选
三次握手举例

- 客户端->服务端,SYN同步标志位置1,序列号1234(由客户端随机)
- 服务端->客户端,SYN同步标志位置1,序列号4567,ACK确认位置1,确认序列号1235(说明1235号之前的序列都已确认)
- 客户端->服务端,ACK确认位置1,确认序列号4568(说明4567号之前的序列都已确认)
组包时TCP Checksum一定要memset 0,有脏数据会导致对端收不到数据
三次握手与Posix API
- server:listen accept
- client:connect

bind:
- socket() -> 插座
- fd -> tcb
- bind -> fd, tcb(ip, port) 没有改变tcb状态,仍然是closed,只是绑定了ip和port
listen:
- 由listenfd->tcb
- tcb->listen
connect:
- 由connectfd->tcb
- 发送syn
- server对端收到syn,在syn queue上创建tcb
accept:(阻塞IO)
- while(accept queue==empty){
- pthread_cond_wait()
- }
- *tcb = get_tcb_from(accept queue) // 从syn queue move过来
- clientfd = get_fd_fdtable()
- tcb -> clientfd
- return clientfd
如何通过网络数据包找到对应tcb
- 通过五元组(sip,dip,sport,dport,proto),五元组可以唯一标识一个连接
三次握手与TCP状态迁移

传输数据

- 在传输数据层面,是没有client和server之分的
send与recv的底层逻辑
- send: 将数据从app层拷贝到kernel的tcb中,至于如何发送,怎么发送,会不会到达,send是不关心的,返回成功仅仅代表拷贝成功,实际传输由协议栈自己进行。
- recv:将数据从kernel的tcb中拷贝给app,至于怎么收到,是否完整,recv是无法感知的。
send与recv是没有长度的
- tcp报文中是没有指定数据长度的,所以多次send,有可能只recv一次,或者一次send可以recv多次。tcp只保证顺序正确并不保证长度。udp是有长度的。mtu值和send的长度没有关系。
- tcp如何分包?1.报文加入length;2.加入\r\n等分隔符用于分割
如何确保对端收到了数据?
- 唯一做法:对端发送确认包,因为send无法感知是否对端收到了数据,那么只能应用层加入对端发送的确认包。
seqnum与acknum的单位
- seqnum与acknum的单位是字节数,而不是包编号,所以acknum-seqnum就可以得到字节数,所以tcp包不需要长度这个段。
连接断开
- 仅涉及close这个函数,shutdown历史原因几乎不用了
tcp四次挥手

1 2 3 4 5 6 7 8 9 10 11 12 13 14
| void *client_thread(void *arg) { int clientfd = *(int*)arg; while(1){ int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0); if(ret == 0){ close(clientfd); break; } printf("ret: %d, buffer: %s\n", ret, buffer); send(clientfd, buffer, ret, 0); } }
|
TCP四次挥手与状态转移

- 假设出现大量close_wait是什么原因?:因为没有及时调用close(),需要查看recv() == 0时代码是如何处理的,是否出现了调用close之前有耗时的清理操作?解决:recv后立马close,将清理操作做到其他线程或线程池里。
- 双方同时调用close: 双方都会进入time_wait状态,就没有last_ack了

- 假设出现了很多fin_wait_2状态?: 因为对端没有即时调用close(),解决:设定超时,超时强行进入time_wait状态