[Help]TCP收发文件,发送快接收慢 导致接收数据包变大?

时间:2022-11-16 15:33:36
发送端Socket设置收发延迟时间(2秒)和发送数据的缓冲区32K。

接收端使用Select模型,监听用Socket使用默认属性,接受客户端连接后,
接收数据包的Socket设置接收缓冲32K.


测试结果:
发送慢接收快(通过收发包间隔Sleep来控制),没有问题。

但是发送快,接收慢,导致接收的数据包变大(本来10M的文件,接收后超过10M)。
发送端根据send返回值来判断,是否要重发数据包,以及移动开始发包的索引和还可发送的字节数。


看资料介绍send返回值,不表示实际发出的字节,好像只表示把数据拷贝到发送缓冲区。
是否每次发包前还要getsockopt来判断下缓冲区是否满了?

6 个解决方案

#1


//发送端设置收发数据包等待时间2秒
//设置发送缓冲区为32K

#define BUF_SIZE 1024

//设置异步发送接收等待时间
int nNetTimeout= 2000; //2秒
setsockopt(sockServer, SOL_SOCKET, SO_SNDTIMEO, (char *)&nNetTimeout,sizeof(int)); 
setsockopt(sockServer, SOL_SOCKET, SO_RCVTIMEO, (char *)&nNetTimeout,sizeof(int)); 
    
//设置强制关闭Socket前等待时间
linger m_sLinger;
m_sLinger.l_onoff = 1;
m_sLinger.l_linger = 2; //2秒
setsockopt(sockServer,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

//设置发送缓冲区
int nSendBuf = 32*1024; //32K
setsockopt(sockServer, SOL_SOCKET, SO_SNDBUF, (const char* )&nSendBuf, sizeof(int));  

//连接TCP服务器
int err =  connect(sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));   

//发送文件
int ClientSendFile(SOCKET sock, char *strVideoPath)
{   
int byteRead = 0;
int iSendSize = 0;
int iCurIndex = 0;
        FILE *pfRead = fopen(strVideoPath, "rb");//打开要发送的文件
unsigned char *pBuf = new unsigned char[BUF_SIZE + 10]; 
    
while(TRUE)
{
                byteRead = fread (pBuf, 1, BUF_SIZE, pfRead);
                if (byteRead <= 0)
{
    break; //读文件结束
}

int iRetryTimes = 0; //重发次数
iCurIndex = 0;  //缓冲区发包起始位置
                iSendSize = send(sock, (const char *)pBuf, byteRead, 0);

if (iSendSize != byteRead) //数据包没发完
{  
                        while(byteRead > 0) //继续重发
{
                                if (iSendSize > 0) 
{
                                   //如果已发出一部分,就修改索引位置和数据包长度
                                   //否则, iSendSize<=0 参数不变   
   iCurIndex += iSendSize; //开始发包位置
   byteRead -= iSendSize;  //还没发完的字节
}

if (byteRead > 0 //有数据可发
                                    && iCurIndex < BUF_SIZE) //没越界
{
                                   iRetryTimes++;
   iSendSize = send(sock, (const char *)(pBuf+iCurIndex), byteRead, 0);
   Sleep(500); //重发时,放慢发包速度
}

if (iRetryTimes >= 5)
{
     break; //至多重发5次
}
        } //while(byteRead > 0) //继续重发
}
Sleep(1); //发包间隔时间
 } //while(TRUE)

return 0;
}

#2


//线程内接受文件关键代码, Select模型
//服务端监听绑定的Socket使用默认属性
//接受客户端连接后,设置接收数据的Socket缓冲区为32K.

//监听Socket使用默认属性
SOCKADDR_IN localAddr;
localAddr.sin_family = AF_INET;
localAddr.sin_port = htons(iPort);
localAddr.sin_addr.S_un.S_addr = INADDR_ANY;
bind(serverListen, (SOCKADDR*)&localAddr, sizeof(localAddr));

DWORD WINAPI LoopRecvFile(void *p)
{
    SOCKET fd; 
    int iRet = -1;
    int *pPort = (int*)p; 
    //iRet = BindSocket(*pPort, fd);
    //iRet  = ListenStart(fd, Max_FSClient);

    SOCKET sockRemote;
    SOCKADDR_IN addrRemote;
    int addrLen = sizeof(addrRemote);
    char buf[BUF_SIZE] = {'\0'}; 

    int maxsock =  fd;
    struct timeval tv;

for (int j = 0; j < Max_FSClient; j++)
{
         myFSClient_Info[j].iClientPort
 = myFSClient_Info[j].iClientSockID = 0;
 memset(myFSClient_Info[j].strClientIP, '\0', STRPARA_LEN);
}

fd_set fdsr;
int i = 0;
int conn_amount = 0; //统计客户端连接数量
FILE *pfRecv = fopen("test.mp4", "wb"); //【保存文件名】

while (loopRecvFileExit == 0) //1结束
{     
FD_ZERO(&fdsr);
FD_SET(fd,  &fdsr);    
tv.tv_sec  =   0;
tv.tv_usec =  100;

for(i = 0 ; i < Max_FSClient; i++ ) 
{
 if (myFSClient_Info[i].iClientSockID > 0)
 {
                  //【把可用的Socket加入Socket数组中】  
          FD_SET(myFSClient_Info[i].iClientSockID,  &fdsr);
 }
}

                //【查询可读的Socket】 
int ret = select(maxsock+1, &fdsr, NULL, NULL, &tv);
if  (ret < 0 ) //查询失败
{
  break;
}  
else if (ret == 0) //没有数据可读
{
 continue;
}

for (i = 0 ; i < Max_FSClient; i ++ ) 
{
if  (FD_ISSET(myFSClient_Info[i].iClientSockID, &fdsr))
{
memset(buf, '\0',  BUF_SIZE);            
ret = recv(myFSClient_Info[i].iClientSockID, buf, BUF_SIZE,  0);

if (ret <= 0) //客户端下线
{   
 closesocket(myFSClient_Info[i].iClientSockID);   
 FD_CLR(myFSClient_Info[i].iClientSockID, &fdsr);
 myFSClient_Info[i].iClientSockID = 0 ;
 conn_amount--;


else  //接收正常
{               
   fwrite( buf, ret, 1, pfRecv);
   fflush(pfRecv);
}
                       }
}
        
        if (FD_ISSET(fd, &fdsr))
        {
            SOCKET new_fd  = accept(fd, (SOCKADDR*)&addrRemote, &addrLen);
            if  (new_fd  <= 0) //接受客户端连接
            {
                continue; 
            }
            
            if (conn_amount < Max_FSClient) 
            {
                for (int k = 0; k < Max_FSClient; k++)
                {
                    if (myFSClient_Info[k].iClientSockID <= 0)
                    {
                        myFSClient_Info[k].iClientSockID  =  new_fd;                                             
                        strcpy(myFSClient_Info[k].strClientIP, inet_ntoa(addrRemote.sin_addr));
                        myFSClient_Info[k].iClientPort = (int)ntohs(addrRemote.sin_port); 
conn_amount++; //已接受的客户端连接数量

int nRecvBuf = 32 * 1024; //接收字节流的Socket, 缓冲区设置为32K
                        setsockopt(myFSClient_Info[k].iClientSockID, SOL_SOCKET, SO_RCVBUF, (const char* )&nRecvBuf, sizeof(int));

                        if (new_fd > maxsock)
                        {
                            maxsock  =  new_fd;
                        }            
                        break;
                    }
                }
            }
            
            else //超过最大连接数量
            {
                for (int iTest = 0; iTest < Max_FSClient; iTest++)
                {
                    if (myFSClient_Info[iTest].iClientSockID == new_fd)
                    {
                        myFSClient_Info[iTest].iClientPort
                        = myFSClient_Info[iTest].iClientSockID   
                        = 0; 
       memset(myFSClient_Info[iTest].strClientIP, '\0', STRPARA_LEN);                        
                    }
                }
                
                send(new_fd,  " bye " ,  4 ,  0 );
                closesocket(new_fd);  
            }
        } 
        
        Sleep(100); //【测试收包速度慢】
    } //while (TRUE)

    return 0;
}

#3


发送部分的代码有问题。
你传输的视频文件一般有多大?上100M了么?
如果文件较小,我给你个发送的例子。

#4


发送部分的代码有问题。

#5


不用判断缓冲区的情况,你可以认为返回值就代表发送出去了多少字节。先发送文件的长度给接收端,用于判断是否收到整个文件。

#6


谢谢楼上各位朋友的提醒,我再试下看看。

#1


//发送端设置收发数据包等待时间2秒
//设置发送缓冲区为32K

#define BUF_SIZE 1024

//设置异步发送接收等待时间
int nNetTimeout= 2000; //2秒
setsockopt(sockServer, SOL_SOCKET, SO_SNDTIMEO, (char *)&nNetTimeout,sizeof(int)); 
setsockopt(sockServer, SOL_SOCKET, SO_RCVTIMEO, (char *)&nNetTimeout,sizeof(int)); 
    
//设置强制关闭Socket前等待时间
linger m_sLinger;
m_sLinger.l_onoff = 1;
m_sLinger.l_linger = 2; //2秒
setsockopt(sockServer,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger));

//设置发送缓冲区
int nSendBuf = 32*1024; //32K
setsockopt(sockServer, SOL_SOCKET, SO_SNDBUF, (const char* )&nSendBuf, sizeof(int));  

//连接TCP服务器
int err =  connect(sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));   

//发送文件
int ClientSendFile(SOCKET sock, char *strVideoPath)
{   
int byteRead = 0;
int iSendSize = 0;
int iCurIndex = 0;
        FILE *pfRead = fopen(strVideoPath, "rb");//打开要发送的文件
unsigned char *pBuf = new unsigned char[BUF_SIZE + 10]; 
    
while(TRUE)
{
                byteRead = fread (pBuf, 1, BUF_SIZE, pfRead);
                if (byteRead <= 0)
{
    break; //读文件结束
}

int iRetryTimes = 0; //重发次数
iCurIndex = 0;  //缓冲区发包起始位置
                iSendSize = send(sock, (const char *)pBuf, byteRead, 0);

if (iSendSize != byteRead) //数据包没发完
{  
                        while(byteRead > 0) //继续重发
{
                                if (iSendSize > 0) 
{
                                   //如果已发出一部分,就修改索引位置和数据包长度
                                   //否则, iSendSize<=0 参数不变   
   iCurIndex += iSendSize; //开始发包位置
   byteRead -= iSendSize;  //还没发完的字节
}

if (byteRead > 0 //有数据可发
                                    && iCurIndex < BUF_SIZE) //没越界
{
                                   iRetryTimes++;
   iSendSize = send(sock, (const char *)(pBuf+iCurIndex), byteRead, 0);
   Sleep(500); //重发时,放慢发包速度
}

if (iRetryTimes >= 5)
{
     break; //至多重发5次
}
        } //while(byteRead > 0) //继续重发
}
Sleep(1); //发包间隔时间
 } //while(TRUE)

return 0;
}

#2


//线程内接受文件关键代码, Select模型
//服务端监听绑定的Socket使用默认属性
//接受客户端连接后,设置接收数据的Socket缓冲区为32K.

//监听Socket使用默认属性
SOCKADDR_IN localAddr;
localAddr.sin_family = AF_INET;
localAddr.sin_port = htons(iPort);
localAddr.sin_addr.S_un.S_addr = INADDR_ANY;
bind(serverListen, (SOCKADDR*)&localAddr, sizeof(localAddr));

DWORD WINAPI LoopRecvFile(void *p)
{
    SOCKET fd; 
    int iRet = -1;
    int *pPort = (int*)p; 
    //iRet = BindSocket(*pPort, fd);
    //iRet  = ListenStart(fd, Max_FSClient);

    SOCKET sockRemote;
    SOCKADDR_IN addrRemote;
    int addrLen = sizeof(addrRemote);
    char buf[BUF_SIZE] = {'\0'}; 

    int maxsock =  fd;
    struct timeval tv;

for (int j = 0; j < Max_FSClient; j++)
{
         myFSClient_Info[j].iClientPort
 = myFSClient_Info[j].iClientSockID = 0;
 memset(myFSClient_Info[j].strClientIP, '\0', STRPARA_LEN);
}

fd_set fdsr;
int i = 0;
int conn_amount = 0; //统计客户端连接数量
FILE *pfRecv = fopen("test.mp4", "wb"); //【保存文件名】

while (loopRecvFileExit == 0) //1结束
{     
FD_ZERO(&fdsr);
FD_SET(fd,  &fdsr);    
tv.tv_sec  =   0;
tv.tv_usec =  100;

for(i = 0 ; i < Max_FSClient; i++ ) 
{
 if (myFSClient_Info[i].iClientSockID > 0)
 {
                  //【把可用的Socket加入Socket数组中】  
          FD_SET(myFSClient_Info[i].iClientSockID,  &fdsr);
 }
}

                //【查询可读的Socket】 
int ret = select(maxsock+1, &fdsr, NULL, NULL, &tv);
if  (ret < 0 ) //查询失败
{
  break;
}  
else if (ret == 0) //没有数据可读
{
 continue;
}

for (i = 0 ; i < Max_FSClient; i ++ ) 
{
if  (FD_ISSET(myFSClient_Info[i].iClientSockID, &fdsr))
{
memset(buf, '\0',  BUF_SIZE);            
ret = recv(myFSClient_Info[i].iClientSockID, buf, BUF_SIZE,  0);

if (ret <= 0) //客户端下线
{   
 closesocket(myFSClient_Info[i].iClientSockID);   
 FD_CLR(myFSClient_Info[i].iClientSockID, &fdsr);
 myFSClient_Info[i].iClientSockID = 0 ;
 conn_amount--;


else  //接收正常
{               
   fwrite( buf, ret, 1, pfRecv);
   fflush(pfRecv);
}
                       }
}
        
        if (FD_ISSET(fd, &fdsr))
        {
            SOCKET new_fd  = accept(fd, (SOCKADDR*)&addrRemote, &addrLen);
            if  (new_fd  <= 0) //接受客户端连接
            {
                continue; 
            }
            
            if (conn_amount < Max_FSClient) 
            {
                for (int k = 0; k < Max_FSClient; k++)
                {
                    if (myFSClient_Info[k].iClientSockID <= 0)
                    {
                        myFSClient_Info[k].iClientSockID  =  new_fd;                                             
                        strcpy(myFSClient_Info[k].strClientIP, inet_ntoa(addrRemote.sin_addr));
                        myFSClient_Info[k].iClientPort = (int)ntohs(addrRemote.sin_port); 
conn_amount++; //已接受的客户端连接数量

int nRecvBuf = 32 * 1024; //接收字节流的Socket, 缓冲区设置为32K
                        setsockopt(myFSClient_Info[k].iClientSockID, SOL_SOCKET, SO_RCVBUF, (const char* )&nRecvBuf, sizeof(int));

                        if (new_fd > maxsock)
                        {
                            maxsock  =  new_fd;
                        }            
                        break;
                    }
                }
            }
            
            else //超过最大连接数量
            {
                for (int iTest = 0; iTest < Max_FSClient; iTest++)
                {
                    if (myFSClient_Info[iTest].iClientSockID == new_fd)
                    {
                        myFSClient_Info[iTest].iClientPort
                        = myFSClient_Info[iTest].iClientSockID   
                        = 0; 
       memset(myFSClient_Info[iTest].strClientIP, '\0', STRPARA_LEN);                        
                    }
                }
                
                send(new_fd,  " bye " ,  4 ,  0 );
                closesocket(new_fd);  
            }
        } 
        
        Sleep(100); //【测试收包速度慢】
    } //while (TRUE)

    return 0;
}

#3


发送部分的代码有问题。
你传输的视频文件一般有多大?上100M了么?
如果文件较小,我给你个发送的例子。

#4


发送部分的代码有问题。

#5


不用判断缓冲区的情况,你可以认为返回值就代表发送出去了多少字节。先发送文件的长度给接收端,用于判断是否收到整个文件。

#6


谢谢楼上各位朋友的提醒,我再试下看看。