1通讯时序(3次握手4次挥手)
1.1标志位
No. | 报文标识 | 英文 | 含义 |
---|---|---|---|
1 | SYN | synchronous | 建立连接 |
2 | ACK | acknowledgement | 确认 |
3 | FIN | finish | 结束 |
4 | PSH | push | 传送 |
5 | RST | reset | 重置 |
6 | URG | urgent | 紧急 |
1.2流程图
1.2.1三次握手
- SYN 1000(0) <mss 1460>
- 1000:发送的数据包的包号
- (0):表示数据包中数据的大小位0
- <mss 1460>后面数据传输最大1460
- SYN 8000(0) ACK 1001<mss 1024>
- 1001:表示前1001号包的数据都接受到了‘
- ACK 8001
- 8001 表示服务器发送的数据包8001之前都接受到了
1.2.2数据传输
- 1001(20)ACK 8001
- 1001(20)client发送一个数据包包号为1001数据大小的20字节
- 8001 读到了前8001位
1.2.3四次挥手
- FIN(1) +ACK(10)
- ACK(FIN+1=2)
- FIN(ACK=10)+ACK(ACk=2)
- ACK(ACK+1=3)
1.3TCP数据包格式
- ACK 1022:确认序号
- SYN 1001:序号
- win 6823:窗口带小
2滑动窗口
- 如果客户端发的数据量特别大是,而数据要存到内核,在当服务器比较慢时,内核缓存区就被写满,所以数据要么阻塞,要莫数据覆盖所以出现流滑动窗口
- win 4096:表示自己的滑动端口大小
- 当数据大小大于滑动端口时 发送端先阻塞:接受端处理,然后发送新的滑动窗口大小
3 TCP状态转换图
3.1主动端总结
- 主动发起连接请求端:CLOSE–(SYN)–>SYN_SEND–(接受ACK,SYN)–>SYN_SEND–(ACK)–>ESTABLISHED(数据通信态)
- 主动关闭连接请求端:ESTABLISHED(数据通信态)–>发送FIN–>FIN_WAIT_1–接受ACK–>FIN_WAIT_2(半关闭)–接受对方发送FIN–>FIN_WAIT_2(半关闭)–发送ACK–>TIME_WAIT(只有主动关闭连接方,才会有这个状态,后面的流程也只有主动关闭一段才会进行,所以一定要先关闭客户端 要不然服务器下次就打不开了)---->等2MSL时长---->CLOSE
3.2被动端总结
- 被动接受连接请求端:CLOSE---->LISTEN–(接受SYN)–>LISTEN–(发送ACK,SYN)–>SYN_RCVD–(接受ACK)–>ESTABLISHED(如果主动端一直没有ACK ,则被动端就会一直发SYN+ACK,直到主动端有响应)
- 被动关闭连接请求端:ESTABLISHED–(接受FIN)–>ESTABLISHED–(发送ACK)–>CLOSE_WAIT(说明对段【主动关闭连接端】,处于半关闭状态)–(发送FIN)–>LAST_ACK–(接受ACK)–>CLOSE
4 2MSL时长
- 存在意义
- 保证最后一个ACK成功被对端接受(等待期间,对端没有接受我发的ACK,读端会再次发送FIN请求)
-一定出现在主动关闭连接请求端
- 保证最后一个ACK成功被对端接受(等待期间,对端没有接受我发的ACK,读端会再次发送FIN请求)
5TCP服务器
5.1TCP基本流程
5.2客户端 流程
- 打开套节字
connfd = socket()
- 连接服务器
connect(connfd,...);// 阻塞
- 写入读取数据
write(connfd)/read(connfd)
- 关闭套节字
close(connfd)
5.3服务器流程
- 打开监听套节字
listenfd = socket()
- 设置监听套节字地址
struct sockaddr_in
- 绑定
bind(listenfd)
- 设置监听上限
listen(listenfd,backlog)
- 阻塞监听 打开连接套节字
connfd = accept(listenfd,...) // 阻塞
- 读写数据
read(connfd)/write(connfd)
recv(connfd)/send(connfd)
recvfrom(connfd)/sendto(connfd);
- 关闭套节字
close(connfd)
close(listenfd)
5.4server代码实现
#include"wrap.h"
#define SERVER_PORT 9521
int main(){
//创建套接字
int lfd=0,cfd=0;
struct sockaddr_in serv_addr,client_addr;
socklen_t cli_addr_len;
int ret;
char buf[BUFSIZ];
char client_IP[1024];
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERVER_PORT);
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
lfd=socket(AF_INET,SOCK_STREAM,0);
int flag = 1;//设置端口复用
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR|SO_REUSEPORT,&flag,sizeof(flag));
int ret1=bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
if(ret1!=0){
sys_error("bind error");
}
Listen(lfd,128);
cli_addr_len=sizeof(client_addr);
cfd=accept(lfd,(struct sockaddr*)&client_addr,&cli_addr_len);
if(cfd== -1){
sys_error("accpet error");
}
printf("accept success\n");
while(1){
ret=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
int i;
for(i=0;i<ret;i++){
buf[i]=toupper(buf[i]);
}
write(cfd,buf,ret);
}
close(lfd);
close(cfd);
return 1;
}
5.5 端口/地址复用
使用下面的代码,是为了避免出现Bind error: Address already in use
int flag = 1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&flag,sizeof(flag));
启用SO_REUSEADDR选项后,bind()函数将允许地址的立即重用。