9.12 tcp协议特点
tcp客户端编程流程:
ser 服务器端
socket()
bind() ip+port
listen()
accept()
recv()
send()
close()
cli 客户端
socket()
connect() 开始进行三次握手 建立连接
send()
recv()
close() 开始进行四次挥手
tcp协议特点:面向连接的 可靠的 流式服务
udp协议特点:无连接 不可靠的 数据服
握手挥手对于程序员透明 无法看到
send() recv() 用户无法干预
抓包 tcpdump
命令: tcpdump -i ens33 -mt -S ‘(src 192.168.43.10 and dst 192.168.43.15)or(src192.168.43.15 and dst 192.168.43.10)’
四次挥手发生在客户端或者服务端执行close()关闭连接的时候:
主动断开方 被动断开方
— FIN seq = i -->
<-- ACK = i+1 —
<-- FIN seq = j —
— ACK = j+1 -->
四次挥手可以演化为三次
程序结束后 内核会维持连接一段时间 直到完成四次挥手才断开连接
保证tcp协议可靠性:
1.应答确认机制 超时重传
2.乱序重排
3.通过滑动窗口控制流量速度
滑动窗口大小可调整 进行流量控制
发送缓冲区 send() --滑动窗口–> 接收缓冲区 recv()
4.去重
(A发送数据 B收到 B发送收到信号 中途丢失 A以为B未收到超时重发 B收到两个数据对比序号 相同丢掉一个 此为去重操作)
流式服务特点:
多次发送的数据有可能会被对方一次收到
粘包:
本应分开收到的数据被一次接收粘连在一起导致连接阻塞
(eg:发送长方体长为7 宽为2 高为3
send(7) send(2) send(3)
recv(723) recv() 无法接受到数据阻塞 recv()
本应该为recv(7) recv(2) recv(3) )
tcp状态转移图
三次握手
四次挥手:
主动关闭 -->TIME_WAIT
被动关闭 -->LAST_ACK
TIME_WAIT 状态维持的原因: !!!!!
1.可靠地终止TCP连接
2.保证让迟来的TCP报文段有足够的时间被识别并丢弃
多线程代码实现:
(ser.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int create_socket();
void* thread_fun(void* arg)
{
int c = (int)arg;
while(1)
{
char buff[128] = {0};
int n = recv(c,buff,1,0);
if(n <= 0)
{
break;
}
printf("recv(%d)=%s\n",c,buff);
send(c,"ok",2,0);
}
}
int main()
{
int sockfd = create_scket();
assert(sockfd != -1);
while(1)
{
struct sockaddr_in caddr; //存放客户端地址
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c < 0)
{
continue;
}
//启动线程
pthread_t id;
pthread_create(&id,NULL,thread_fun,(void*)c);
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
return -1;
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(-1 == res)
{
return -1;
}
res = listen(sockfd,5);
if(-1 == res)
{
return -1;
}
return sockfd;
}
(fork.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
int create_socket();
int main()
{
signal(SIGCHLD,SIG_IGN);
int sockfd = create_socket();
assert(sockfd != -1);
while(1)
{
struct sockaddr_in caddr; //存放客户端地址
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c < 0)
{
continue;
}
pid_t pid = fork();
if(pid == -1)
{
close(c);
continue;
}
if(pid == 0)
{
while(1)
{
char buff[128] = {0};
int n = recv(c,buff,127,0);
if(n <= 0)
{
break;
}
printf("buff=%s\n",buff);
send(c,"ok",2,0);
}
close(c); //子进程 关闭
printf("client close\n");
exit(0);
}
close(c); //父进程 关闭
}
}
int create_socket() ///可能有错误!!!
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
return -1;
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6000);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(-1 == res)
{
return -1;
}
res = listen(sockfd,5);
if(-1 == res)
{
return -1;
}
return sockfd;
}