网络上有许多关于socket传输文件、图片的文章,但是又许多服务,端客户端都在Linux平台上,而现实中,往往服务端是Linux系统,客户端是Windows系统,所以这次我实现了两个系统平台的文件传输,更符合实际,总之,一句话,socket原理都一样,流程都一样,接下来看代码:
说明:windows下的socket我已经进行了封装,我已经上传到我的资源,大家可以去下载winsocket的封装下载,另外,传输数据过程中,为保证数据的准确性,加入了CRC校验,crc校验的封装下载
#include
#include
#include"CWinSocketClient.h"
#include"crc32.h"
#include
using namespace std;
#define REQUEST_COVER 108 //请求封面
/*数据包头*/
typedef struct header_t
{
int type; //包类型
int size; //包大小,根据包大小读取包体数据
unsigned int crc; //报数据crc校验码
}HEADER_T;
/*客户端请求视频封面*/
typedef struct reqvdcover_t
{
int user_id; //用户id
char picture_name[32]; //图片名字
}REQVDCOVER_T;
/*服务器回应客户端封面*/
typedef struct ansclientcover_t
{
char pic_name[32]; //视频名字
int length; //每次发送长度
char buff[512]; //图片数据,
}ANSCLIENTCOVER_T;
int main(int argc, char** argv)
{
CWinSocketClient t;
t.start();
MyCrc32 *crc_check = new MyCrc32(); //定义一个32位的crc校验对象
crc_check->init_crc_table(); //初始化校验表
//输入向服务器请求的文件名称
char file_name[32];
memset(file_name,0, sizeof(file_name));
printf("Please Input File Name On Server.\n");
scanf("%s", file_name);
//自定义数据包包头
HEADER_T pichead;
memset(&pichead, 0, sizeof(HEADER_T));
pichead.type = REQUEST_COVER;
pichead.size = sizeof(REQVDCOVER_T);
//自定义数据包包体
REQVDCOVER_T picbody;
memset(&picbody, 0, sizeof(REQVDCOVER_T));
picbody.user_id = 2;
memcpy(picbody.picture_name, file_name, strlen(file_name));
//生成数据包校校验码并存入包头
pichead.crc = crc_check->crc32((char *)&picbody, sizeof(REQVDCOVER_T));
//发送缓冲区
char *buff = new char[sizeof(HEADER_T) + sizeof(REQVDCOVER_T)];
memset(buff, 0, sizeof(HEADER_T) + sizeof(REQVDCOVER_T));
memcpy(buff, &pichead, sizeof(HEADER_T));
memcpy(buff+ sizeof(HEADER_T), &picbody, sizeof(REQVDCOVER_T));
//发送数据
send(t.get_wsocketfd(), buff, sizeof(HEADER_T) + sizeof(REQVDCOVER_T), NULL);
//打开一个文件,保存文件数据,注意,这里为了文件能够正常显示,以wb+的形式打开文件
FILE *fp = fopen(file_name, "wb+");
if (fp == NULL) {
printf("File:\t%s Can Not Open To Write!\n", file_name);
exit(1);
}
int length = 0;
int write_length = 0;
int num = 0;
//接受数据包头
HEADER_T pichead1;
memset(&pichead1, 0, sizeof(HEADER_T));
//接收数据包体
ANSCLIENTCOVER_T picbody1;
memset(&picbody1, 0, sizeof(ANSCLIENTCOVER_T));
//循环接受文件数据包,这里先接收包头,判断数据包类型,在接收包体,校验成功之后,写入文件
while ((length = recv(t.get_wsocketfd(), (char *)&pichead1, sizeof(HEADER_T), 0)))
{
if (length < 0)
{
printf("Recieve Data From Server Failed!\n");
break;
}
//接收到的数据包类型位请求封面,图片
if (pichead1.type == REQUEST_COVER)
{
//在接收包体数据
length = recv(t.get_wsocketfd(), (char *)&picbody1, sizeof(ANSCLIENTCOVER_T), 0);
if (length < 0)
{
printf("Recieve Data From Server1 Failed!\n");
break;
}
else
{
//crc校验,检查数据包有没有在传输过程中改变
if (pichead1.crc == crc_check->crc32((char *)&picbody1, pichead1.size))
{
num++;
printf("crc check successfullly\n");
cout << "data length:" << picbody1.length << endl;
//写入文件
write_length = fwrite(picbody1.buff, sizeof(char), picbody1.length, fp);
if (write_length < picbody1.length)
{
printf("File:\t%s Write Failed!\n", file_name);
break;
}
if (picbody1.length < 512 && picbody1.length>0)
{
fclose(fp);
cout << "trance file end!" << num<<endl;
break;
}
}
else
{
printf("crc check failed\n");
break;
}
}
}
memset(&pichead1, 0, sizeof(HEADER_T));
memset(&picbody1, 0, sizeof(ANSCLIENTCOVER_T));
}
printf("Recieve File:\t %s From Server Finished!\n", file_name);
fclose(fp);
WSACleanup(); //停止相关dll的api函数的使用
t.stop();
system("pause");
return 0;
}
分析:
/*服务器回应客户端封面*/
typedef struct ansclientcover_t
{
char pic_name[32]; //视频名字
int length; //每次发送长度
char buff[512]; //图片数据,
}ANSCLIENTCOVER_T;
这每次传输的图片数据为512字节,那么如果大一点的图片是不是要传很久啊,答案是肯定的,我试过一次性传输1024字节,由于这里要写入文件,服务端要读取文件,那么每一个服务端每发一个包,都需要延迟,不然这个包全部会错乱,导致程序段错误,我试过服务端在传输1024字节的时候,需要每个包之间需要延迟100毫秒,每次传输512字节,需要延迟20毫秒左右,假设一张1M的土片,一次性传输1024字节,需要102.4s,而一次性传输512字节,只需要20.48s,所以,选择哪种方案,一算就知道
说明:服务端我也将socket进行了封装,而且我做的事高并发服务器,加入了Epoll,线程池,共享内存,这些我都已经进行了封装,但是由于代码量较多,这里我就放出传输图片的核心代码,大家可以参考,然后自己写,也可用上上面我给出的代码,win与win之间,一个服务器,一个客户端传输图片,如果想要linux服务端socket封装,epoll封装,线程池封装,可以去下载:epoll+serversocket封装下载,线程池封装下载
void CAction::sendPicture(void)
{
cout << "user request picture" << endl;
REQVDCOVER_T rc_picbody; //读取客户端发送的数据包体
bzero(&rc_picbody, sizeof(REQVDCOVER_T));
int res = read(m_actionfd, &rc_picbody, sizeof(REQVDCOVER_T)); //读写数据体
if (res <= 0)
{
cout << "read request picture data failed" << endl;
}
else
{
if (CAction::head.crc == crc32_check->crc32((char *)&rc_picbody, sizeof(REQVDCOVER_T)))
{
cout << "request picture data check successfully" << endl;
}
else
{
cout << "request picture data check failed" << endl;
return;
}
}
//校验成功,服务器遍历视频封面文件,发送图片数据
HEADER_T sw_pichead;
ANSCLIENTCOVER_T sw_picbody;
bzero(&sw_pichead, sizeof(HEADER_T));
bzero(&sw_picbody, sizeof(ANSCLIENTCOVER_T));
sw_pichead.type = REQUEST_COVER;
sw_pichead.size = sizeof(ANSCLIENTCOVER_T);
memcpy(sw_picbody.pic_name, "game.jpg", strlen("game.jpg"));
FILE *fp = fopen("game.jpg", "rb"); //这里修改,遍历当前目录的所有图片文件并发送 (配置文件形式)
if (fp == NULL)
{
cout << "open game.jpg failed " << endl;
}
else
{
int file_block_length = 0;
int write_count = 0;
int count = 0;
char *buff = new char[SENDPICSIZE];
bzero(sw_picbody.buff, sizeof(sw_picbody.buff));
while ((file_block_length = fread(sw_picbody.buff, sizeof(char), sizeof(sw_picbody.buff), fp)) > 0)
{
cout << "file_block_length = " << file_block_length << endl;
sw_picbody.length = file_block_length;
sw_pichead.crc = crc32_check->crc32((char *)&sw_picbody, sizeof(ANSCLIENTCOVER_T));
usleep(20000); //一次性处理512字节,延迟2至少0毫秒
bzero(buff, SENDPICSIZE);
memcpy(buff, &sw_pichead, sizeof(HEADER_T));
memcpy(buff+ sizeof(HEADER_T), &sw_picbody, sizeof(ANSCLIENTCOVER_T));
write_count = write(m_actionfd,buff, SENDPICSIZE);
if (write_count <= 0)
{
cout << "game.jpg send failed" << endl;
break;
}
printf("send sum:%d\n", write_count);
count++;
bzero(sw_picbody.buff, sizeof(sw_picbody.buff));
}
fclose(fp);
cout << "send packet no:" << count << endl;
cout << "File: game.jpg Transfer Finished!\n";
}
}
总结:上述传输图片过程中,目前我还没有彻底弄清楚,为什么一次性传输高于1024个字节,不行,所有数据包都会乱,无论服务端延迟多久,CRC校验不通过,我之前在qt上,使用qtcpsocket传输也有这个问题,使用winsocket传输也是这个问题,但是如果事linux下的客户端与linux下的服务端之间的传输,就没有这个问题,理论上socket传输可以传输的最大字节数为65535,但是我不知道这个为啥,一直很懊恼,如果有人对这个问题有答案的话,能不能联系我??qq:2293330992(或者评论也行),我很期待和你一起讨论学习,相互进步呐!