UDP本身是一种无连接的协议。它只管发送,而不需要知道,发送的包是不是准确的到达了目的地。所以它具有发送效率高的特点,同时也具有丢包的弱点。但如果对udp加一些验证和重发机制,就能很大程度上避免丢包的情况,达到稳定的传输。同时,此种方式的传输速度会比TCP方式的传输快很多。以下是一种串行的带验证重发机制的UDP传输文件,源代码,client负责发文件,server负责接文件。希望对大家有帮助。程序中,发送的包是自己定义的,这样除了可以将需要发送的数据发送过去外,还可以将一些控制信息发过去。

SERVER端:

#include<winsock.h>
#include<stdio.h>
#include<windows.h>
#include<conio.h>
#include<memory.h>
#pragma comment( lib, "ws2_32.lib" )
#define PORT 8000
#define SERVER "192.168.1.211"
SOCKET sock;
int sendexit=0;//控制发送线程状态的全局变量
int recvexit=0;//控制接收程状态的全局变量
int filesize=1;//记录文件大小的全局变量
int recvsize=0;//记录文件大小的全局变量
int id=1;
sockaddr_in server;
int len =sizeof(server);

struct baohead//包头
{
int size;
int id;
int recvsize;
};
typedef baohead ElemType;
baohead datahead;
struct recvbuf//包格式
{
ElemType head;//包头
char buf[1024];//存放数据的变量
int bufSize;//存放数据长度的变量

};
struct recvbuf data;

DWORD WINAPI recvfunc(LPVOID lpParam);//接收线程


int main()
{
WSADATA wsadata;
WSAStartup(MAKEWORD(2,2),&wsadata);
sock=socket(AF_INET,SOCK_DGRAM,0);//建立SOCKET
if(sock==SOCKET_ERROR)
{
printf("socket创建失败\n");
return 0;
}
sockaddr_in addr;
addr. sin_family=AF_INET;
addr. sin_port= htons(PORT);
addr. sin_addr.s_addr= inet_addr(SERVER);
int nResult=bind(sock,(sockaddr*)&addr,sizeof(addr));//绑定SOCKET

if(nResult==SOCKET_ERROR)
{
printf("绑定SOCKET有问题. \n");

return 0;
}
else
{
printf("服务启动成功!\n");
}

DWORD ID;
HANDLE handle=CreateThread(NULL,0,recvfunc,0,0,&ID);//创建接收线程
int i=0;
if((recvexit!=1) && (i++<40))//主线程为创建接收线程等待40秒,如果创建成功则不等待。
{
Sleep(1000);
}
if(recvexit!=1)//40秒后还未创建成功则显示创建失败。
{
printf("接收线程创建失败!\n");
return 0;
}
else
{
printf("接收线程创建成功!\n");
}

while(recvexit<2)//如果接收线程未退出,则主线程睡一段时间。
{
Sleep(100);
}
printf("退出成功!\n");
return 0;

}


DWORD WINAPI recvfunc(LPVOID lpParam)
{
recvexit=1;//告诉主线程,接收线程已经创建成功。
FILE *fp;
fp=fopen("E:\\1.exe", "wb");//如果原文件存在,则将原来文件删除。并新建一个空文件
fclose(fp);
fp=fopen("E:\\1.exe", "ab");//打开那个空文件。
FD_SET fdread;//建立集合
while( recvexit==1 )
{
FD_ZERO(&fdread);//清空结合
FD_SET(sock,&fdread);//将socket放进集合内
timeval val;//设置超时时间
val.tv_sec=60;
val.tv_usec=0;
int Result=select(0,&fdread,0,0,&val);//检查sokcet的可读性
if(Result==SOCKET_ERROR)//错误则退出线程
{
break;
}
if(Result==0)//没数据到则马上返回。
{
continue;
}

int rec = recvfrom(sock, (char *)(&data), 2048, 0,(sockaddr *)&server,&len);//读取到来的数据报
if( rec > 0 )
{

if(data.head.id == id)//包ID是所期望的,则读入数据
{
// printf("id=%d\n",data.head.id);
// printf("bufsize=%d\n",data.bufSize);
fwrite( data.buf, sizeof(char), data.bufSize, fp);
recvsize=recvsize+data.bufSize;//记录新的接收到的文件长度
// printf("recvsize=%d\n",recvsize);
filesize=data.head.size;
datahead.id= data.head.id;
datahead.recvsize=recvsize;
datahead.size=filesize;

int d=sendto(sock,(char*)(&datahead),sizeof(datahead),0,(sockaddr *)&server,len);//发送收到数据的确认包
if(d>0)
{
// printf("发送收到数据包的确认信息成功!\n");
}
id=id+1;
}
else if(data.head.id < id)//如果是已经接收过的包的重发包,则丢弃,并重发一次确认包告诉发送端该包已收。
{
datahead.id = data.head.id;
datahead.recvsize=recvsize;
datahead.size=filesize;

int d=sendto(sock,(char*)(&datahead),sizeof(datahead),0,(sockaddr *)&server,len);
if(d>0)
{
// printf("发送收到数据包的确认信息成功!\n");
}


}

if(filesize == recvsize )//如果接收到的文件长度等于文件大小,
{ //等发送一个确认包告诉发送端文件已接收完成,并退出自己的接收线程
datahead.id = data.head.id;
datahead.recvsize=-1;
datahead.size=filesize;
printf("filesize=%d\n",filesize);
printf("recvsize=%d\n",recvsize);
printf("文件接收完成!\n");
int d=sendto(sock,(char*)(&datahead),sizeof(datahead),0,(sockaddr *)&server,len);
sendexit=2;
recvexit=2;
break;
}
}
}
fclose(fp);
recvexit=2;
return 0;
}


CLIENT端:
#include<winsock.h>
#include<stdio.h>
#include<windows.h>
#include<conio.h>
#include<memory.h>
#pragma comment( lib, "ws2_32.lib" )
#define SERVER "192.168.1.211"
struct baohead//包头格式
{
int size;
int id;
int recvsize;
};
typedef baohead ElemType;
struct sendbuf//数据包格式
{
ElemType head;//包头
char buf[1024];//存放数据的变量
int bufSize;//存放本包存放数据的buf变量的长度的变量。
};
struct sendbuf data;
DWORD WINAPI sendfunc(LPVOID lpParam);//发送线程
DWORD WINAPI recvfunc(LPVOID lpParam);//街收线程
SOCKET sock;
int sendexit=0;//控制发送线程状态的全局变量
int recvexit=0;//控制接收线程状态的全局变量
int filesize=0;//记录文件大小的全局变量
int recvsize=0;//返回已接收文件大小的全局变量
sockaddr_in server;
int len =sizeof(server);
int Sendid=0;//发送ID
int ReceiveId =0;//接收ID
HANDLE hEvent;//句炳


int main()
{
hEvent=CreateEvent(NULL,FALSE,FALSE,NULL);//创建一个事件
WSADATA wsadata;
WSAStartup(MAKEWORD(2,2),&wsadata);
sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock==SOCKET_ERROR)
{
printf("socket创建失败\n");
return 0;
}
else
{
printf("服务启动成功!\n");
}
server.sin_family=AF_INET;
server.sin_port=htons(8000); ///server的监听端口
server.sin_addr.s_addr=inet_addr(SERVER); ///server的地址
getch();
DWORD ID;
HANDLE handle=CreateThread(NULL,0,recvfunc,0,0,&ID);//创建接收线程
int i=0;
if((recvexit!=1) && (i++<40))//主线程等待40秒为创建接收线程,
{
Sleep(1000);
}
if(recvexit!=1)//如果40秒后任未创建成功,显示创建失败。
{
printf("接收线程创建失败!\n");
return 0;
}
else
{
printf("接收线程创建成功!\n");
}
handle=CreateThread(NULL,0,sendfunc,0,0,&ID);//创建发送线程
int j=0;
if(sendexit!=1 && j++<40)//主线程为创建发送线程等待40秒
{
Sleep(1000);
}
if(sendexit!=1)//40秒后还未创建,则显示创建失败。
{
printf("发送线程创建失败!\n");
return 0;
}
else
{
printf("发送线程创建成功!\n");
}
while(recvexit<2 ||sendexit<2 )//如果发送和接收线程未全部退出,则主线程等待。
{
Sleep(100);
}
printf("退出成功!\n");
return 0;

}
DWORD WINAPI sendfunc(LPVOID lpParam)
{
sendexit=1;//告诉主线程,发送线程已经创建完毕
static int filepos=0;//记录文件偏移量的变量
FILE *fp1;
fp1 = fopen( "D:\\1.exe", "rb" );//打开要发送的文件
fseek(fp1,0,SEEK_END);//将文件指针移动到文件尾部
filesize=ftell(fp1);//得到文件长度。
// printf("filesize=%d\n",filesize);
rewind(fp1);//将文件指针重新指到头部

while( sendexit==1 )
{

if(ReceiveId == Sendid) //确认包头内的ID是所期望的。
{
Sendid++;
fseek(fp1,filepos,SEEK_SET);//根据偏移量移动文件指针
int readsize=fread(data.buf,sizeof(char),1024,fp1);//读取下段文件
filepos = ftell(fp1);
data.head.size=filesize;//将文件大小放进包头
data.head.id=Sendid;//发送ID放进包头(标志包顺序)
data.bufSize = readsize;//记录数据段长度的变量
// printf("id=%d\n",Sendid);
// printf("sendsize=%d\n",data.bufSize);
// printf("filesize=%d\n",data.head.size);
int d=sendto(sock,(char*)(&data),sizeof(data),0,(sockaddr *)&server,len);//发送数据报
if(d>0)//成功发送
{
// printf("发送数据%s\n",(char*)(&data));
}
}

DWORD dRet=WaitForSingleObject(hEvent,500);//发送完后,等待500毫秒。等待中如果等待的事件被激活,则马上返回。
switch(dRet)
{
case WAIT_TIMEOUT://一段时间内没收到确认包,重发刚才的那个数据报
sendto(sock,(char*)(&data),sizeof(data),0,(sockaddr *)&server,len);
// printf("重新发送数据%s\n",(char*)(&data));
break;
default:break;//否则返回。
}
}
fclose(fp1);
sendexit=2;
return 0;
}

DWORD WINAPI recvfunc(LPVOID lpParam)
{
recvexit=1;//告诉主线程,接收线程已经启动。
baohead recvbao;
FD_SET fdread;

while( recvexit==1 )
{
FD_ZERO(&fdread);
FD_SET(sock,&fdread);
timeval val;
val.tv_sec=60;
val.tv_usec=0;
int Result=select(0,&fdread,0,0,&val);//查看socket是否可读
if(Result==SOCKET_ERROR)
{
break;
}
if(Result==0)//不可读则马上返回。重新判断
{
continue;
}

int rec = recvfrom(sock, (char *)(&recvbao), 2048, 0,(sockaddr *)&server,&len);//可读状态下接收数据报
if( rec > 0 )
{

// printf("收到确认信息 %s\n" , (char*)(&recvbao));
recvsize=recvbao.recvsize;//将接收大小传给全局变量
ReceiveId = recvbao.id;//记录此次接收到包的ID
SetEvent(hEvent);//激活事件
if(recvbao.recvsize==-1)//如果接收端发送接收完毕的信号,则发送,接收线程均自行退出,并显示发送文件完成
{
sendexit=2;
recvexit=2;
printf("文件发送完成!\n");
break;
}
}
}
recvexit=2;
return 0;
}

UDP本身是一种无连接的协议。它只管发送,而不需要知道,发送的包是不是准确的到达了目的地。所以它具有发送效率高的特点,同时也具有丢包的弱点。但如果对udp加一些验证和重发机制,就能很大程度上避免丢包的情况,达到稳定的传输。同时,此种方式的传输速度会比TCP方式的传输快很多。以下是一种串行的带验证重发机制的UDP传输文件,源代码,client负责发文件,server负责接文件。希望对大家有帮助。程序中,发送的包是自己定义的,这样除了可以将需要发送的数据发送过去外,还可以将一些控制信息发过去。

本程序,虽然是串行传输的,但由于是UDP所以传输速度比起TCP传输会快上很多。如果你用循环队列,把UDP的传输方式设计成并行多包发送的话,速度还会快很多。其中的一些输出语句是用来查看程序的传输状态和进度的。因为影响传输速度,被我屏蔽了。

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐