socket传送文件丢失数据问题

yts2dx 2013-08-26 04:56:10
我编写了传送文件的服务器/客户端程序,由服务端向客户端传送文件。服务端在Linux下用C语言编写,客户端在为windows下用C#编写。对于较大的文件通过分包的方式进行发送,每个包最大1024个字节。现在的问题是,如果发送的包与包之间不添加ulseep()函数,则会出现数据丢失的现象。如果添加上,让每次发送一个包后休眠一段时间,则客户端能完整的接收所有数据。
服务端主要代码如下:
void SendFile(int socketFd,Connection*conn)
{
//接收客户端发来的文件请求
char recvMsg[BUFFER_SIZE];
ReceiveMessage(socketFd,recvMsg);
string fileName=recvMsg;
cout<<"Client request file: "<<fileName<<endl;


//连接数据库,生成需要发送的文件
//CreateDbXml(conn,fileName);

//获取文件长度,并发送
unsigned long fileLen=get_file_size(recvMsg);
char buffer[BUFFER_SIZE];
bzero(buffer,sizeof(buffer));
snprintf(buffer,sizeof(buffer),"%u",fileLen);
SendMessage(socketFd,buffer,strlen(buffer));

//打开文件
FILE * fp=fopen(recvMsg,"r");
if(fp==NULL)
{
cout<<"File "<<fileName<<" not fount!"<<endl;
}

//发送文件
bzero(buffer,sizeof(buffer));
int file_block_length=0;
while((file_block_length=fread(buffer,sizeof(char),BUFFER_SIZE,fp))>0)
{

SendMessage(socketFd,buffer,file_block_length);
cout<<buffer;
bzero(buffer,sizeof(buffer));

usleep(50000);//如果去掉此行代码,或者usleep()函数的参数过小,监控端都会出现数据丢失的情况
}

fclose(fp);
cout<<"File "<<fileName<<" transfer finished!"<<endl;
}

//为了避免数据的粘粘问题,发送数据时先发送数据长度,再发送数据
int SendMessage(int socketFd,char * sendBuffer,int sendLen)
{
char len[5];
snprintf(len,sizeof(len),"%04d",sendLen);
//printf("len:%s\n",len);
if(send(socketFd,len,sizeof(len)-1,0)<0)
{
perror("send :");
return -1;
}

int sendRecv;
if((sendRecv=send(socketFd,sendBuffer,sendLen,0))<0)
{
perror("send sendBuffer:");
return -1;
}

return sendRecv;


}

//为了避免数据的沾粘问题,接收数据时,先接收数据长度,再接收数据
int ReceiveMessage(int sockFd,char recvMsg[])
{
//获取接收数据的长度
char msgSize[5];
int sizeLen=sizeof(msgSize);
if(recv(sockFd,msgSize,sizeLen-1,0)<0)
{
perror("recv:");
}
msgSize[sizeLen-1]='\0';
int msgLen=atoi(msgSize);

int recvLen;
if((recvLen=recv(sockFd,recvMsg,msgLen,0))<0)
{
perror("recv:");
}

recvMsg[msgLen]='\0';

return recvLen;

}



...全文
651 24 打赏 收藏 转发到动态 举报
写回复
用AI写文章
24 条回复
切换为时间正序
请发表友善的回复…
发表回复
yts2dx 2013-08-30
  • 打赏
  • 举报
回复
客户端的代码如下:
 /// <summary>
        /// 发送带有数据长度的数据包
        /// </summary>
        /// <param name="s">发送数据用的套接字</param>
        /// <param name="msg">发送数据的缓冲区</param>
        /// <returns>发送已经发送的数据个数</returns>
        public static int SendMessage(Socket s, byte[] msg)
        {
            int sendLen;
            byte[] msgsize = new byte[4];
            msgsize = Encoding.UTF8.GetBytes(msg.Length.ToString("0000"));

            sendLen = s.Send(msgsize);


            //判断如果要发送的数据长度为零,则直接返回0
            if (sendLen == 0)
            {
                return 0;
            }

            int offset = 0;//用来记录一个数据包已经发送的字节数
            int dataLeft = sendLen;//用来记录一个数据包中还有多少字节的数据没有发送
            int rstLen = 0;//用来记录单次发送数据的字节数

            while (dataLeft>0)
            {
                rstLen = s.Send(msg, 0, msg.Length, SocketFlags.None);
                offset += rstLen;
                dataLeft -= rstLen;
            }

            return offset;
        } /// <summary>
        /// 接收带有数据长度的数据包
        /// </summary>
        /// <param name="s">接收数据用的套接字</param>
        /// <param name="msg">存放接收数据的数组,因为要在函数中分配与数据包长度相同的缓冲区大小,所以此处要用到引用类型</param>
        /// <returns>返回0表示没有接收到数据,大于零的值表示正确接收到的数据个数</returns>
        public static int ReceiveMessage(Socket s, ref byte[] msg)
        {
            int recvLen = -1;   //接收数据时返回的数据长度
            byte[] msgLen = new byte[4];
            recvLen = s.Receive(msgLen, 0, 4, SocketFlags.None);

            //如果接收到的数据长度为0,说明没有接收到数据,直接返回0
            if (recvLen == 0)
            {
                return 0;
            }

            //获得数据包的长度
            int msgLenth = Convert.ToInt32(Encoding.UTF8.GetString(msgLen));


            //用来记录此数据包中要有多少数据没有接收
            int dataLeft = msgLenth;

            //用来记录此数据包中已经接收了多收数据
            int offset = 0;

            //为接收缓冲区分配与数据长度相对于的存储空间
            msg = new byte[msgLenth];

            while (dataLeft > 0)
            {
                recvLen = s.Receive(msg, offset, dataLeft, SocketFlags.None);

                if (recvLen==0)
                {
                    return 0;
                }

                offset += recvLen;
                dataLeft -= recvLen;
            }

            return offset;

        }
yts2dx 2013-08-30
  • 打赏
  • 举报
回复
问题已经解决啦。是因为tcp的粘包问题没有处理好的原因。现在将更改后的服务端和客户端的函数粘贴到下面。
int SendMessage(int socketFd,char * sendBuffer,int sendLen)
{
    //发送数据的长度
    char len[5];
    snprintf(len,sizeof(len),"%04d",sendLen);
    if(send(socketFd,len,sizeof(len)-1,0)<0)
    {
        perror("send :");
        return -1;
    }

    //发送数据
    int sendRecv;
    int offset=0;//数据包中已经发送的字节数
    int dataLeft=sendLen;//数据包中已经发送的字节数
    int rstLen=0; //用来保存一次发送数据所发送的字节数
    
    while(dataLeft>0)
      {
	if((rstLen=send(socketFd,sendBuffer,sendLen,0))<0)
	  {
	    perror("send sendBuffer:");
	    return -1;
	  }

	offset+=rstLen;
	dataLeft-=rstLen;
      }

    return offset;

    
}

int ReceiveMessage(int sockFd,char recvMsg[])
{
    //获取接收数据的长度
    char msgSize[5];
    int  sizeLen=sizeof(msgSize);
    if(recv(sockFd,msgSize,sizeLen-1,0)<0)
    {
        perror("recv:");
    }
    msgSize[sizeLen-1]='\0';
    int msgLen=atoi(msgSize);

    int recvLen;
    int offset=0;
    int dataLeft=msgLen;
    int rstLen=0;

    while(dataLeft>0)
      {
	 if((rstLen=recv(sockFd,recvMsg,msgLen,0))<0)
          {
      
              perror("recv:");
          }

	 offset+=rstLen;
	 dataLeft-=rstLen;
      }
    

    recvMsg[msgLen]='\0';
       
    return offset;
        
}
  • 打赏
  • 举报
回复
引用 13 楼 yts2dx 的回复:
经过抓包测试,服务器发送的数据,在监控端都接收到了,这个可以确定。 但是写入文件就是不对。 我个人认为出现的问题是应用层的Receive函数从socket的缓冲区中取数据时出现了问题,但是我却不知道问题的原因. 望各路大神给点提示。
有两个建议,希望有用: 1. 发送端改为每20ms发送512或1K字节。--不在代码直接加usleep,使用定时器触发。 2. 接收端每次收包时,收10-20个包。
imGala 2013-08-29
  • 打赏
  • 举报
回复
我提个建议: if((recvLen=recv(sockFd,recvMsg,msgLen,0))<0) { perror("recv:"); } 这一段改成: nleft = msgLen; ptr = recvMsg; while(nleft){ if ((recvLen = recv(sockFd, ptr, nleft, 0)) < 0){ if (EINTR == errno) continue; else perror("recv:\n"); } //写入文件... nleft -= recvLen; ptr += recvLen; } 没准是一次recv没将data接收完,你试下 对了的话给我分啊
yts2dx 2013-08-29
  • 打赏
  • 举报
回复
问题已经解决了。在文件传送的过程中我不调用自己定义的函数 ReceiveMessage(),而是直接调用socket的receive函数。因为文件相当于一个很大的数据包,只要在发送文件之前发送一次文件的长度,然后接收端按照文件的长度接收即可,文件的传送过程无需再考虑粘包问题。 更改后的代码如下:

            BinaryWriter bWriter = new BinaryWriter(fileStream);
            byte[] recvBuffer = new byte[1024];

            //int rstLen = GlobalFunction.ReceiveMessage(sock, ref recvBuffer);
            int rstLen = sock.Receive(recvBuffer);

            bWriter.Write(recvBuffer);
            //bWriter.Flush();

            while (rstLen < iFileLen)
            {
                //int tempLen = GlobalFunction.ReceiveMessage(sock, ref recvBuffer);
                byte[] recBuffer = new byte[1024];
                int tempLen = sock.Receive(recBuffer);
                bWriter.Write(recBuffer,0,tempLen);
                Console.WriteLine(tempLen);
                //bWriter.Flush();
                rstLen += tempLen;
            }
这样,在发送端不设置sleep函数,接收端也能够完整的接收数据。 但是,我仍然不明白为什么利用自定义的ReceiveMessage函数会有问题,难道是我这个函数写的有问题? 但是我平时用的时候没有问题啊,难道跟数据量有关?
追_逐 2013-08-29
  • 打赏
  • 举报
回复
引用 20 楼 xnini632d 的回复:
[quote=引用 19 楼 su_787910081 的回复:] [quote=引用 18 楼 xnini632d 的回复:] [quote=引用 17 楼 su_787910081 的回复:] SendMessage() ReceiveMessage() 这两个函数中接收和发送数据的次序你应该没法确定的。 SendMessage() 中有两个send() 函数用来发送数据,但是到了网络上可能会由于网络堵或者其它原因会让第二个send() 过去的数据在客户端先接收到。而你接收到的数据主观的你认为他是一个长度的话这是不对的。 你可以直接把长度放到发送的数据里面,把这些数据的前面几个字节应为是小端或者大端字节序的整形数,到客户端后将这几个字节提取出来就可以知道这后面的数据有多长了。 你改改看,对不对!
他这不是字节序的问题吧,他写得: 现在的问题是,如果发送的包与包之间不添加ulseep()函数,则会出现数据丢失的现象。如果添加上,让每次发送一个包后休眠一段时间,则客户端能完整的接收所有数据。 应该就是一次接收不完整[/quote] 我说的主要也不是字节序,我是说他使用两次send() 这两次发送可以会因为网络的原因使得接收的顺序不是他想的那样先接收到第一个send() 的内容。使得接收到的数据错误的使用了。 [/quote] 还是不太明白,数据失序问题不是在TCP就解决了吗,应用层不用考虑了吧 [/quote] 我说的不是数据失序,我是说两次send() 发送两个数据包,这两个数据包在网络上不知道谁会先到,谁后到。接收方哪知道奇数次接收到的就一定是发送的下一个包的数据字节大小啊?
imGala 2013-08-29
  • 打赏
  • 举报
回复
引用 19 楼 su_787910081 的回复:
[quote=引用 18 楼 xnini632d 的回复:] [quote=引用 17 楼 su_787910081 的回复:] SendMessage() ReceiveMessage() 这两个函数中接收和发送数据的次序你应该没法确定的。 SendMessage() 中有两个send() 函数用来发送数据,但是到了网络上可能会由于网络堵或者其它原因会让第二个send() 过去的数据在客户端先接收到。而你接收到的数据主观的你认为他是一个长度的话这是不对的。 你可以直接把长度放到发送的数据里面,把这些数据的前面几个字节应为是小端或者大端字节序的整形数,到客户端后将这几个字节提取出来就可以知道这后面的数据有多长了。 你改改看,对不对!
他这不是字节序的问题吧,他写得: 现在的问题是,如果发送的包与包之间不添加ulseep()函数,则会出现数据丢失的现象。如果添加上,让每次发送一个包后休眠一段时间,则客户端能完整的接收所有数据。 应该就是一次接收不完整[/quote] 我说的主要也不是字节序,我是说他使用两次send() 这两次发送可以会因为网络的原因使得接收的顺序不是他想的那样先接收到第一个send() 的内容。使得接收到的数据错误的使用了。 [/quote] 还是不太明白,数据失序问题不是在TCP就解决了吗,应用层不用考虑了吧
追_逐 2013-08-29
  • 打赏
  • 举报
回复
引用 18 楼 xnini632d 的回复:
[quote=引用 17 楼 su_787910081 的回复:] SendMessage() ReceiveMessage() 这两个函数中接收和发送数据的次序你应该没法确定的。 SendMessage() 中有两个send() 函数用来发送数据,但是到了网络上可能会由于网络堵或者其它原因会让第二个send() 过去的数据在客户端先接收到。而你接收到的数据主观的你认为他是一个长度的话这是不对的。 你可以直接把长度放到发送的数据里面,把这些数据的前面几个字节应为是小端或者大端字节序的整形数,到客户端后将这几个字节提取出来就可以知道这后面的数据有多长了。 你改改看,对不对!
他这不是字节序的问题吧,他写得: 现在的问题是,如果发送的包与包之间不添加ulseep()函数,则会出现数据丢失的现象。如果添加上,让每次发送一个包后休眠一段时间,则客户端能完整的接收所有数据。 应该就是一次接收不完整[/quote] 我说的主要也不是字节序,我是说他使用两次send() 这两次发送可以会因为网络的原因使得接收的顺序不是他想的那样先接收到第一个send() 的内容。使得接收到的数据错误的使用了。
imGala 2013-08-29
  • 打赏
  • 举报
回复
引用 17 楼 su_787910081 的回复:
SendMessage() ReceiveMessage() 这两个函数中接收和发送数据的次序你应该没法确定的。 SendMessage() 中有两个send() 函数用来发送数据,但是到了网络上可能会由于网络堵或者其它原因会让第二个send() 过去的数据在客户端先接收到。而你接收到的数据主观的你认为他是一个长度的话这是不对的。 你可以直接把长度放到发送的数据里面,把这些数据的前面几个字节应为是小端或者大端字节序的整形数,到客户端后将这几个字节提取出来就可以知道这后面的数据有多长了。 你改改看,对不对!
他这不是字节序的问题吧,他写得: 现在的问题是,如果发送的包与包之间不添加ulseep()函数,则会出现数据丢失的现象。如果添加上,让每次发送一个包后休眠一段时间,则客户端能完整的接收所有数据。 应该就是一次接收不完整
追_逐 2013-08-29
  • 打赏
  • 举报
回复
SendMessage() ReceiveMessage() 这两个函数中接收和发送数据的次序你应该没法确定的。 SendMessage() 中有两个send() 函数用来发送数据,但是到了网络上可能会由于网络堵或者其它原因会让第二个send() 过去的数据在客户端先接收到。而你接收到的数据主观的你认为他是一个长度的话这是不对的。 你可以直接把长度放到发送的数据里面,把这些数据的前面几个字节应为是小端或者大端字节序的整形数,到客户端后将这几个字节提取出来就可以知道这后面的数据有多长了。 你改改看,对不对!
卢飞鹏 2013-08-28
  • 打赏
  • 举报
回复
网络字节序的问题, 发送和接收整形, 都要用(htons ntohs htonl ntohl)转化
yts2dx 2013-08-28
  • 打赏
  • 举报
回复
经过抓包测试,服务器发送的数据,在监控端都接收到了,这个可以确定。 但是写入文件就是不对。 我个人认为出现的问题是应用层的Receive函数从socket的缓冲区中取数据时出现了问题,但是我却不知道问题的原因. 望各路大神给点提示。
yts2dx 2013-08-28
  • 打赏
  • 举报
回复
引用 10 楼 songsong33 的回复:
看代码是TCP,TCP理论上是不会错序的,你是不是收包后解析包代码有点问题?
我的代码又改了,利用二进制的文件写入方式,这样就不涉及到数据包的解析啦。代码如下:
/////////////利用二进制文件写入方式,接收到什么字节数据,就写入什么字节数据/////////
            BinaryWriter bWriter = new BinaryWriter(fileStream);
            byte[] recvBuffer = new byte[2048];

            //int rstLen = GlobalFunction.ReceiveMessage(sock, ref recvBuffer);
            int rstLen = sock.Receive(recvBuffer);

            bWriter.Write(recvBuffer);
            bWriter.Flush();

            while (rstLen < iFileLen)
            {
                //int tempLen = GlobalFunction.ReceiveMessage(sock, ref recvBuffer);
                int tempLen = sock.Receive(recvBuffer);
                bWriter.Write(recvBuffer);
                bWriter.Flush();
                rstLen += tempLen;
            }
利用的是BinaryWriter进行二进制的文件写入,这样我个人感觉就不涉及到编码的问题了。 请问是不是C#中套接字的Receive函数在socket缓冲区中取数据时出现了问题?
yts2dx 2013-08-28
  • 打赏
  • 举报
回复
引用 9 楼 songsong33 的回复:
LZ用的是UDP吧?改TCP试试?
用的是tcp啊
  • 打赏
  • 举报
回复
看代码是TCP,TCP理论上是不会错序的,你是不是收包后解析包代码有点问题?
  • 打赏
  • 举报
回复
LZ用的是UDP吧?改TCP试试?
yts2dx 2013-08-28
  • 打赏
  • 举报
回复
引用 7 楼 songsong33 的回复:
抓包是挺好的办法,可以确定是发送方还是接收方的问题,但是没有包序号之类的标识,很难去把发出的包和接收的包一一对应。你要是加包头,加序号,那倒可以的。

根据LZ的描述,怀疑是接收端处理过慢,建议接收端每次执行收包时,循环收10-20个包,再一次写入硬盘文件;或者接收端socket阻塞收包可能也有效果。
已经进行抓包分析,初步判定服务器发送的字节数与客户端接收的字节数是相同的。感觉是应用层从socket缓冲区取数据时出现了问题。
一下是文件中的一个部分内容,出现了错乱:
945后面应该是946的记录,却出现了898.
但是抓包中的数据内容确是正确的:


这样的情况是什么原因导致的啊?
求大神解答

  • 打赏
  • 举报
回复
抓包是挺好的办法,可以确定是发送方还是接收方的问题,但是没有包序号之类的标识,很难去把发出的包和接收的包一一对应。你要是加包头,加序号,那倒可以的。 根据LZ的描述,怀疑是接收端处理过慢,建议接收端每次执行收包时,循环收10-20个包,再一次写入硬盘文件;或者接收端socket阻塞收包可能也有效果。
mmxopq 2013-08-27
  • 打赏
  • 举报
回复
看到底是没发出来,还是发出来了,对方没收到
imGala 2013-08-27
  • 打赏
  • 举报
回复
数据丢失是什么情况,能不能说具体点? 主函数里的链接处理没什么问题吧?
加载更多回复(4)

18,773

社区成员

发帖
与我相关
我的任务
社区描述
Linux/Unix社区 专题技术讨论区
社区管理员
  • 专题技术讨论区社区
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧