tcp可靠数据传输(c语言实现)
并没有考虑比特位差的错误(我并没有将操作严谨到将操作降低到比特级别,因为在模拟过程中丢包是用随机丢包实现的,而存在比特位差也是类似,并且后果与丢包一致,都是重传,所以没有必要)
1.快速重传,
2.超时重传(超时重传的计时器可以直接通过函数去设置recv和send,并不复杂)
客户端:
发送
- 包
2.序号——> 何种:(窗口长度是序号的一半一下,待考虑) (字节流顺序作为序号,待考虑)(将已经发送的包进行缓存) (确定database和nextseqnum(下一个要发送的包)) (可以简化后续检验操作)
要点: 时刻保证缓存数据包的清晰,缓存与回应包的对应的要搞清楚:
接收端:
接收:接收数据包按照顺序来: 1.按序,seq是你将要接受的包的序号,也是你上次发送回客户端的包的确认序号(ack+1)%10
1.(这里引出,确认报只包含一个数据,已被接收方接收到的最新数据报的序号,不存在缓存不足的问题,处理速度很够)
2. 如果出现乱序那你就需要将提前发送到的包放入缓存(并返回ACK.内容是,而对于已经接受到的重复的包你应该直接丢弃。
3.在接收到正确的数据包时你应该去检查缓存中的数据包是否存在你紧接着需要的序号的数据包若有直接提取出来
4.重要 :将缓存中的数据包直接提取出来后,回应包的内容是什么, 客户端的窗口要怎样操作,(未被确定的包,空出来的空间,应该怎样处理)
4.1 回应包铁定应该是最后被提取出来的包的序号
客户端
接收:接受来自接收端的ACK(由于模拟tcp的可靠数据传输所以,没有NAK)
1.若ACK 中的被确认序号正好是database,那么利用database_last将database保存,(database+1)%序号长度,窗口长度(不变)是(nextseqnum-database)
2.若ACK中的被确认序号是 database<ACK.num<nextseqnum, 那么database_last=ACK.num,同时发送(ACK.num-database+1)个数据包,之后database=(ACK.num+1)%10.
3.若等待接受ACK超时,那将重传database所对应的数据包.
4.快速重传:即如果收到连续三个ACK的确认序号是database_last,那么将会重传database所对应的数据包。
难点:我们如何将缓存做好,即在每次发送性的数据包时,我们的缓存应该如何与数据包序号对应,以便于重传。
****灵光一闪:假设窗口长度:0-3,序号长度0-9,那么我们的缓存可以设置十个包的空间,按照序号存储,每次需要重传的时候只要:send( cache[ACK.num] ) 即可。
代码:
client(发送方)
在这里插入代码片
```#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <inaddr.h>
#pragma comment <lib,"ws2_32.lib">
/**********************************************/
#define window_len 4
#define number_len 10
int number=0,window=0,now_char,whole_char;//number<10,window<4。
// now_char是文件中即将被提取的字符位置,whole_char是文件总字符数
int database=0,nextseqnum=0,database_last=-1,reback=0;//reback==3时快速重传
typedef struct Send_bag
{
int number;
char string[11];
}Send_bag;
typedef struct ACK
{
int num;
}ACK;
Send_bag sendbag[10],send_Bag;
char recv_str[30];
ACK ack;
int re_cir=0;
char saddr[10]="127.0.0.1";
/**********************************************/
void Set_sockaddr( sockaddr_in &addr,char *str,short port);
//void Get_bag();//取包
void Get_whole_char();//获取whole_char
void send_bag(SOCKET csc);//发送几个包
void Repeat_send(SOCKET csc);//重发database对应的包
int main()
{
WSADATA wsadata;
WSAStartup(MAKEWORD(2,1),&wsadata);
SOCKET csc;
csc=socket(PF_INET,SOCK_STREAM,0);
sockaddr_in serve_addr;
Set_sockaddr(serve_addr,saddr,2019);
connect(csc,(sockaddr*)&serve_addr,sizeof(sockaddr_in));
/**********************************************/
Get_whole_char();
//printf("%d",whole_char);
//Get_bag();
send_bag(csc);
int nNetTimeout=500;
setsockopt(csc,SOL_SOCKET,SO_RCVTIMEO,(char*)&nNetTimeout,sizeof(int));
/**********************************************/
while(true)
{
if(!recv(csc,recv_str,sizeof(ACK),MSG_WAITALL))
{
Repeat_send(csc);
}
else
{
memcpy(&ack,recv_str,sizeof(ACK));
printf("recv:::%d ::%d ::%d\n",ack.num,database,nextseqnum);
if(ack.num==database)
{
window--;
database=(ack.num+1)%number_len;
database_last=ack.num;
//Get_bag();
if(now_char<whole_char)
send_bag(csc);
}
else if(ack.num>database&&ack.num-database<5)
{
window-=(ack.num-database+1);
database=(ack.num+1)%number_len;
database_last=ack.num;
//Get_bag();
if(now_char<whole_char)
send_bag(csc);
}
else if (ack.num<database&&ack.num<nextseqnum&&database-ack.num>number_len/2)
{
window-=(number_len-database+ack.num+1);
database=(ack.num+1)%number_len;
database_last=ack.num;
//Get_bag();
if(now_char<whole_char)
send_bag(csc);
}
else if(ack.num==database_last&&reback<2)
{
reback++;
}
else if(ack.num==database_last&&reback==2)
{
Repeat_send(csc);
reback=0;
}
}
if(database==nextseqnum&&now_char>=whole_char)
{
send_Bag.number=100;
strcpy(send_Bag.string,"shutdown");
send(csc,(char*)&send_Bag,sizeof(Send_bag),0);
break;
}
}
WSACleanup();
}
/****************************************************************/
void Set_sockaddr( sockaddr_in &addr,char *str,short port)
{
addr.sin_port=htons(port);
addr.sin_addr.S_un.S_addr=inet_addr(str);
addr.sin_family=PF_INET;
}
/***************************************************************/
//void Get_bag()
//{
//}
/************************************************************/
void Get_whole_char()
{
FILE *fp;
fp=fopen("H:\\clion_code\\TCP_client\\client.txt","r+");
fseek(fp,0,SEEK_END);
whole_char=ftell(fp);
fclose(fp);
}
/************************************************************/
void send_bag(SOCKET csc)
{
FILE *fp;
fp=fopen("H:\\clion_code\\TCP_client\\client.txt","r+");
fseek(fp,now_char,SEEK_SET);
for(window;window<window_len;window++)
{
if(now_char<whole_char)
{
strcpy(sendbag[number].string,"");
fgets(sendbag[number].string,5,fp);
sendbag[number].number=number;
now_char+=strlen(sendbag[number].string);
number=(number+1)%number_len;
}
else
{
strcpy(sendbag[number].string,"");
sendbag[number].number=number;
}
if(!strcmp(sendbag[nextseqnum].string,""))
break;
printf("%s %d \n",sendbag[nextseqnum].string,sendbag[nextseqnum].number);
send(csc,(char*)&sendbag[nextseqnum],sizeof(Send_bag),0);
nextseqnum=(nextseqnum+1)%number_len;
}
fclose(fp);
}
/***********************************************************/
void Repeat_send(SOCKET csc)
{
send(csc,(char*)&sendbag[database],sizeof(Send_bag),0);
}
接收方(serve)
#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <time.h>
#pragma comment <lib,"ws2_32.lib">
/***********************************************************/
#define number_len 10
#define min 10
#define cache_all_num 5
typedef struct Recv_bag
{
int number;
char string[11];
}Recv_bag;
typedef struct ACK
{
int num;
}ACK;
Recv_bag recvbag[cache_all_num],recvBag;
char recv_str[30];
ACK ack;
int getbase=0;
int cache_tail=0,cache_num=0,cache_head=0;//cache<5
char saddr[10]="127.0.0.1";
/***********************************************************/
void Set_sockaddr( sockaddr_in &addr,char *str,short port);
void Input_File(Recv_bag recvBag);
void Check_cache();
int main()
{
WSADATA wsadata;
WSAStartup(MAKEWORD(2,1),&wsadata);
SOCKET ssc;
ssc=socket(PF_INET,SOCK_STREAM,0);
sockaddr_in serve_addr,client_addr;
Set_sockaddr(serve_addr,saddr,2019);
bind(ssc,(sockaddr*)&serve_addr,sizeof(serve_addr));
int err=listen(ssc,1);
int size_client=sizeof(serve_addr);
SOCKET nssc=accept(ssc,(sockaddr*)&client_addr,&size_client);
printf("succeed to conect with %s\n",inet_ntoa(client_addr.sin_addr));
srand((unsigned) time(NULL));
/*********************************************************/
int cir=0;
ack.num=-1;
while(true)
{
int random=rand()%100;
if(recv(nssc,recv_str, sizeof(Recv_bag),0))
{
printf("ran:%d:: %d: ",random,getbase);/
memcpy(&recvBag,recv_str,sizeof(Recv_bag));
if(!strcmp(recvBag.string,"shutdown"))
break;
if(random<10)
continue;
if(recvBag.number==getbase)
{
Input_File(recvBag);
ack.num=recvBag.number;
getbase=(getbase+1)%number_len;
printf("%d %d ****\n",ack.num,getbase);/
Check_cache();
}
else if(recvBag.number<getbase&&getbase-recvBag.number<=number_len/2)
{
send(nssc,(char*)&ack,sizeof(ACK),0);
continue;
}
else if(recvBag.number>getbase&&recvBag.number-getbase>=number_len/2)
{
send(nssc,(char*)&ack,sizeof(ACK),0);
continue;
}
else if(recvBag.number>getbase&&recvBag.number-getbase<number_len/2)
{
printf("case: %d %d \n",recvBag.number,getbase);/
recvbag[cache_tail].number=recvBag.number;
strcpy(recvbag[cache_tail].string,recvBag.string);
cache_tail=(cache_tail+1)%cache_all_num;
cache_num++;
}
else if(recvBag.number<getbase&&getbase-recvBag.number>number_len/2)
{
printf("case: %d %d \n",recvBag.number,getbase);/
recvbag[cache_tail].number=recvBag.number;
strcpy(recvbag[cache_tail].string,recvBag.string);
cache_tail=(cache_tail+1)%cache_all_num;
cache_num++;
}
send(nssc,(char*)&ack,sizeof(ACK),0);
}
}
WSACleanup();
}
void Set_sockaddr( sockaddr_in &addr,char *str,short port)
{
addr.sin_port=htons(port);
addr.sin_addr.S_un.S_addr=inet_addr(str);
addr.sin_family=PF_INET;
}
void Input_File(Recv_bag recvbags)
{
FILE *hp;
hp=fopen("H:\\clion_code\\TCP_serve\\serve.txt","a+");
printf(" \" %s \" %d %d ",recvbags.string ,recvbags.number, getbase);
fputs(recvbags.string,hp);
fclose(hp);
}
void Check_cache()
{
if(cache_num==0)
return ;
else
{
int cache_te=cache_num;
for(int i=0;i<cache_te;i++)
{
if(recvbag[cache_head].number==getbase)
{
Input_File(recvbag[cache_head]);
cache_num--;
cache_head=(cache_head+1)%cache_all_num;
ack.num=getbase;
getbase=(getbase+1)%number_len;
}
else
break;
}
}
}
此模拟并没有尝试流量控制