Linux网络编程之基于UDP实现可靠的文件传输示例
了解网络传输协议的人都知道,采用TCP实现文件传输很简单。相对于TCP,由于UDP是面向无连接、不可靠的传输协议,所以我们需要考虑丢包和后发先至(包的顺序)的问题,所以我们想要实现UDP传输文件,则需要解决这两个问题。方法就是给数据包编号,按照包的顺序接收并存储,接收端接收到数据包后发送确认信息给发送端,发送端接收确认数据以后再继续发送下一个包,如果接收端收到的数据包的编号不是期望的编号,则要求发送端重新发送。
下面展示的是基于linux下C语言实现的一个示例程序,该程序定义一个包的结构体,其中包含数据和包头,包头里包含有包的编号和数据大小,经过测试后,该程序可以成功传输一个视频文件。
具体实现代码如下:
server端代码如下:
/*************************************************************************
>FileName:server.c
>Author:SongLee
************************************************************************/
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<netdb.h>
#include<stdarg.h>
#include<string.h>
#defineSERVER_PORT8000
#defineBUFFER_SIZE1024
#defineFILE_NAME_MAX_SIZE512
/*包头*/
typedefstruct
{
intid;
intbuf_size;
}PackInfo;
/*接收包*/
structSendPack
{
PackInfohead;
charbuf[BUFFER_SIZE];
}data;
intmain()
{
/*发送id*/
intsend_id=0;
/*接收id*/
intreceive_id=0;
/*创建UDP套接口*/
structsockaddr_inserver_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(SERVER_PORT);
/*创建socket*/
intserver_socket_fd=socket(AF_INET,SOCK_DGRAM,0);
if(server_socket_fd==-1)
{
perror("CreateSocketFailed:");
exit(1);
}
/*绑定套接口*/
if(-1==(bind(server_socket_fd,(structsockaddr*)&server_addr,sizeof(server_addr))))
{
perror("ServerBindFailed:");
exit(1);
}
/*数据传输*/
while(1)
{
/*定义一个地址,用于捕获客户端地址*/
structsockaddr_inclient_addr;
socklen_tclient_addr_length=sizeof(client_addr);
/*接收数据*/
charbuffer[BUFFER_SIZE];
bzero(buffer,BUFFER_SIZE);
if(recvfrom(server_socket_fd,buffer,BUFFER_SIZE,0,(structsockaddr*)&client_addr,&client_addr_length)==-1)
{
perror("ReceiveDataFailed:");
exit(1);
}
/*从buffer中拷贝出file_name*/
charfile_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name,FILE_NAME_MAX_SIZE+1);
strncpy(file_name,buffer,strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
printf("%s\n",file_name);
/*打开文件*/
FILE*fp=fopen(file_name,"r");
if(NULL==fp)
{
printf("File:%sNotFound.\n",file_name);
}
else
{
intlen=0;
/*每读取一段数据,便将其发给客户端*/
while(1)
{
PackInfopack_info;
if(receive_id==send_id)
{
++send_id;
if((len=fread(data.buf,sizeof(char),BUFFER_SIZE,fp))>0)
{
data.head.id=send_id;/*发送id放进包头,用于标记顺序*/
data.head.buf_size=len;/*记录数据长度*/
if(sendto(server_socket_fd,(char*)&data,sizeof(data),0,(structsockaddr*)&client_addr,client_addr_length)<0)
{
perror("SendFileFailed:");
break;
}
/*接收确认消息*/
recvfrom(server_socket_fd,(char*)&pack_info,sizeof(pack_info),0,(structsockaddr*)&client_addr,&client_addr_length);
receive_id=pack_info.id;
}
else
{
break;
}
}
else
{
/*如果接收的id和发送的id不相同,重新发送*/
if(sendto(server_socket_fd,(char*)&data,sizeof(data),0,(structsockaddr*)&client_addr,client_addr_length)<0)
{
perror("SendFileFailed:");
break;
}
/*接收确认消息*/
recvfrom(server_socket_fd,(char*)&pack_info,sizeof(pack_info),0,(structsockaddr*)&client_addr,&client_addr_length);
receive_id=pack_info.id;
}
}
/*关闭文件*/
fclose(fp);
printf("File:%sTransferSuccessful!\n",file_name);
}
}
close(server_socket_fd);
return0;
}
client端代码如下:
/*************************************************************************
>FileName:client.c
>Author:SongLee
************************************************************************/
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<netdb.h>
#include<stdarg.h>
#include<string.h>
#defineSERVER_PORT8000
#defineBUFFER_SIZE1024
#defineFILE_NAME_MAX_SIZE512
/*包头*/
typedefstruct
{
intid;
intbuf_size;
}PackInfo;
/*接收包*/
structRecvPack
{
PackInfohead;
charbuf[BUFFER_SIZE];
}data;
intmain()
{
intid=1;
/*服务端地址*/
structsockaddr_inserver_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
server_addr.sin_port=htons(SERVER_PORT);
socklen_tserver_addr_length=sizeof(server_addr);
/*创建socket*/
intclient_socket_fd=socket(AF_INET,SOCK_DGRAM,0);
if(client_socket_fd<0)
{
perror("CreateSocketFailed:");
exit(1);
}
/*输入文件名到缓冲区*/
charfile_name[FILE_NAME_MAX_SIZE+1];
bzero(file_name,FILE_NAME_MAX_SIZE+1);
printf("PleaseInputFileNameOnServer:");
scanf("%s",file_name);
charbuffer[BUFFER_SIZE];
bzero(buffer,BUFFER_SIZE);
strncpy(buffer,file_name,strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
/*发送文件名*/
if(sendto(client_socket_fd,buffer,BUFFER_SIZE,0,(structsockaddr*)&server_addr,server_addr_length)<0)
{
perror("SendFileNameFailed:");
exit(1);
}
/*打开文件,准备写入*/
FILE*fp=fopen(file_name,"w");
if(NULL==fp)
{
printf("File:\t%sCanNotOpenToWrite\n",file_name);
exit(1);
}
/*从服务器接收数据,并写入文件*/
intlen=0;
while(1)
{
PackInfopack_info;
if((len=recvfrom(client_socket_fd,(char*)&data,sizeof(data),0,(structsockaddr*)&server_addr,&server_addr_length))>0)
{
if(data.head.id==id)
{
pack_info.id=data.head.id;
pack_info.buf_size=data.head.buf_size;
++id;
/*发送数据包确认信息*/
if(sendto(client_socket_fd,(char*)&pack_info,sizeof(pack_info),0,(structsockaddr*)&server_addr,server_addr_length)<0)
{
printf("Sendconfirminformationfailed!");
}
/*写入文件*/
if(fwrite(data.buf,sizeof(char),data.head.buf_size,fp)<data.head.buf_size)
{
printf("File:\t%sWriteFailed\n",file_name);
break;
}
}
elseif(data.head.id<id)/*如果是重发的包*/
{
pack_info.id=data.head.id;
pack_info.buf_size=data.head.buf_size;
/*重发数据包确认信息*/
if(sendto(client_socket_fd,(char*)&pack_info,sizeof(pack_info),0,(structsockaddr*)&server_addr,server_addr_length)<0)
{
printf("Sendconfirminformationfailed!");
}
}
else
{
}
}
else
{
break;
}
}
printf("ReceiveFile:\t%sFromServerIPSuccessful!\n",file_name);
fclose(fp);
close(client_socket_fd);
return0;
}
感兴趣的朋友可以动手测试一下该程序,相信会对大家的Linux下C语言网络编程带来一定的帮助。