网络编程之Linux服务器与Windows客户端图片传输

前言

网络上有许多关于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(或者评论也行),我很期待和你一起讨论学习,相互进步呐!

你可能感兴趣的:(linux,socket,网络)