TCP协议

TCP协议

特点

面向链接、字节流、可靠传输

tcp的连接是全双工的

面向连接

使用TCP协议通信的双方必须先建立连接,然后才能开始的数据的读写。双方都必须为连接分配必要的内核资源,一管理连接的状态和连接上数据的传输。

字节流

字节流:发送端执行的写操作次数和接收端执行的读操作此时之间没有任何数量关系。

具体表现在:

  • 发送端应用程序连续执行多次写操作时 TCP模块先将这些数据放入TCP缓冲区中 。当TCP模块 真正开始发送数据时,发送缓冲区中这些等待发送的数据可能被封装成一个或多个TCP报文段发出。因此,TCP模块发送出的TCP报文端的个数和应用程序执行的写操作次数之间没有固定的数量关系。
  • 接收端收到一个或多个TCP报文段后,TCP模块将他们携带的应用程序数据按照TCP报文段的序号依次放入TCP接收端缓冲区中,并通知应用程序读取数据。接收端应用程序可以一次性将TCP接收缓冲区的数据全部读出,也可以分多次读取数据,这取决于用户指定的应用程序读缓冲区的大小。因此,应用程序执行的读操作次数和TCP模块接收的TCP报文段个数之间也没有固定的数量关系。
    在这里插入图片描述

可靠传输

  • TCP协议采用发送应答机制,即发送端发送的每个TCP报文段都必须得到接收方的应答,才认为这个TCP报文段传输成功。
  • TCP采用超时重传机制,发送端在发出一个TCP报文之后启动定时器,如果在定时时间内未收到应答,它将重发该报文段
  • 因为TCP报文段最终是以IP数据报发送的,而IP数据报到达接收端可能乱序、重复,所以TCP协议还会对接收到的TCP报文段重排、整理,再交付给应用层。

TCP头部结构

在这里插入图片描述

一个客户端和服务器进行持续通信

客户端:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<assert.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>

int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);

	struct sockaddr_in  saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family=AF_INET;
	saddr.sin_port=htons(6000);
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

	int res=connect(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
	assert(res!=-1);
    
	while(1)
	{
		char buff[128]={0};
		printf("input:\n");
		fgets(buff,128,stdin);

		if(strncmp(buff,"end",3)==0)
		{
			break;
		}
		send(sockfd,buff,strlen(buff),0);
		//write(sockfd,buff,strlen(buff));
		memset(buff,0,128);
		recv(sockfd,buff,127,0);
		printf("buff=%s\n",buff);
	}
	close(sockfd);
}

三次握手

在这里插入图片描述

客户端可能会发生的阻塞原因分析

connect()失败


其余可能发生阻塞的地方参照:服务器端可能会发生的阻塞原因分析

服务器端:

#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<stdio.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

int main()
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd!=-1);

	struct  sockaddr_in  saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
    
	saddr.sin_family=AF_INET;
	saddr.sin_port=htons(6000);//1024 root,4096 reserve,5000+ tempary
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");

	int res=bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
	assert(res!=-1);
	
	listen(sockfd,5);//create listen queue

	while(1)
	{
		int len=sizeof(caddr);
		int c=accept(sockfd,(struct sockaddr *)&caddr,&len);
		if(c<0)
		{
			continue;
		
		}

		printf("accept c=%d,ip=%s,port=%d\n",c,inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
		while(1)
		{
			char buff[128]={0};
			int n=recv(c,buff,1,0);
	        if(n<=0)
			{
				break;
			}
			printf("buff=%s\n",buff);
			send(c,"ok",2,0);
		}
		printf("one client over\n");
        close(c);
	}

}

c和sockfd的区别

在这里插入图片描述

服务器端可能会发生的阻塞原因分析

bind()失败

  • 试图绑定一个已经在使用的端口
  • 被绑定的地址是受保护的地址,仅超级用户能够访问。比如普通用户将sock绑定到知名服务端口(端口号为0~1023)

listen()永远不会发生阻塞

Linux:linsten  5:已完成三次握手队列的长度
Unix:  listen   x:未完成和已完成三次握手的两个队列的长度

accept()失败

  • 当已完成三次握手的队列为空时,accept阻塞。

recv()阻塞

recv:读取数据的多少取决于recv读取数据的多少和接收缓冲区里有多少数据。
在这里插入图片描述

send()阻塞

  • 发送消息的缓冲区已经满了,再send()就会发生阻塞

粘包

两次send被一次recv到了

解决粘包

  • 加标记
  • 每次只发送一个数据
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值