TCP协议实现文件传输

1 篇文章 0 订阅
1 篇文章 0 订阅
使用TCP协议实现传输文件
    程序分为发送端和接收端。首先在传输文件数据之前,发送端会把将装有文件名称和文件长度等
信息的数据包发送至接收端。接收端收到文件名称和文件长度信息后会创建好空白文件。接着开始传输
文件数据。下面介绍实现功能的主要过程:

1 .创建套接字、绑定、监听、连接、接受连接
// 创建TCP协议的套接字
    m_Socket  =  socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    
if (SOCKET_ERROR  ==  m_Socket)
        AfxMessageBox(
" Create Socket Error!  " 0 0 );

// 绑定与监听
    SOCKADDR_IN   addrSrv;   
    addrSrv.sin_addr.s_addr 
=  inet_addr(sIP);
    addrSrv.sin_family   
=    AF_INET;   
    addrSrv.sin_port   
=    htons(Port);   
    
int    ret    =    bind(m_Socket,   (SOCKADDR    * ) & addrSrv,    sizeof (SOCKADDR));   
    
if (ret == SOCKET_ERROR)   
        AfxMessageBox(
" Bind Socket Error! " 0 0 );

// 连接
    SOCKADDR_IN ServerAddr;
    ServerAddr.sin_addr.s_addr 
=  inet_addr(ServerAddr_in);
    ServerAddr.sin_family 
=  AF_INET;
    ServerAddr.sin_port 
=  htons(ServerPort);
    
int  Result  =  connect(m_Socket, ( struct  sockaddr * ) & ServerAddr,  sizeof ( struct  sockaddr));
    
if (SOCKET_ERROR  ==  Result)
        AfxMessageBox(
" Connet Failed! " );

// 接受连接
    SOCKADDR_IN ClientAddr;
    
int  len  =   sizeof (SOCKADDR_IN);
    SOCKET ClientSock 
=  accept(m_Socket, ( struct  sockaddr * ) & ClientAddr,  & len);
    
if (SOCKET_ERROR  ==  ClientSock)
        AfxMessageBox(
" Accept Failed! " );

2 .声明宏和结构体
声明套接字缓冲区和一次发送文件数据的缓冲区大小
#define  SOCKET_BUFF 80000     // 套接字缓冲区大小
#define  PACK_BUFF 50000         // 数据包缓冲区大小


声明文件I
/ O缓冲区和最大文件路径长度
#define  FILE_NAME_MAX 100        // 文件路径最大长度
#define  FILE_IO_BUFF PACK_BUFF     // 文件IO缓冲区    


// 文件信息
typedef  struct  _FileInfor    
{
    u_long ulFileLen;
    
char  sFileName[ FILE_NAME_MAX ];
}_FileInfor;

// 数据包
typedef  struct  _DataPack
{
    
char  cType;         // 'D'为数据  'M'为文件信息
     int  nPackLen;
    
char  sContent[ PACK_BUFF ];             // 数据包缓冲区
    u_long nPosition;                 // 数据在文件中的位置
     int  nContentLen;                 // 数据字节数
    _FileInfor    FileInfor;         // 文件信息
}_DataPack;



3 .发送端
// 发送线程需要的全局变量
char  sPath[FILE_NAME_MAX];         // 文件地址
u_long FileByteCount;             // 文件大小
SOCKET ClientSocket;             //


(
1 )设置套接字发送缓冲区大小,在32位Windows XP环境下,系统为每个套接字分配的默认发送数据缓
冲区为8192字节。由于传输的文件很大,可能几十兆,或者更大。那么系统为每个套接字分配的默认
缓冲区显然过小。为此在创建套接字之后,需要修改套接字发送数据缓冲尺寸。在这里我修改为80k,
差不多可以够用了。
    
// 设置套接字发送缓冲区
     int  nBuf  =  SOCKET_BUFF;
    
int  nBufLen  =   sizeof (nBuf);
    
int  nRe  =  setsockopt(ClientSock, SOL_SOCKET, SO_SNDBUF, ( char * ) & nBuf, nBufLen);
    
if (SOCKET_ERROR  ==  nRe)
        AfxMessageBox(
" setsockopt error! " );    
    
// 检查缓冲区是否设置成功
    nRe  =  getsockopt(ClientSock, SOL_SOCKET, SO_SNDBUF, ( char * ) & nBuf,  & nBufLen);
    
if (SOCKET_BUFF  !=  nBuf)
        AfxMessageBox(
" 检查缓冲区:setsockopt error! " );

(
2 )测量文件大小并发送文件大小和名称给客户端

    首先使用C库函数对源文件进行测量
    
// 得到文件地址
    LPTSTR lpPath  =      m_sPath.GetBuffer(    m_sPath.GetLength ());
    
// 打开文件
    FILE  * File  =  fopen(lpPath,  " rb " ); 
    
if (NULL  ==  File)
        AfxMessageBox(
" 打开文件失败! " );
    
// 测量文件大小
     char  Buff[PACK_BUFF];
    u_long ulFaceReadByte;
    FileByteCount 
=   0 ;
    fseek(File, 
0 , SEEK_SET);
    
while ( ! feof(File))
    {
        ulFaceReadByte 
=  fread(Buff,  1 1 , File);
        FileByteCount 
+=  ulFaceReadByte;
    }
    
// 关闭文件
     int  nRe  =  fclose(File);
    
if (nRe)
        AfxMessageBox(
" 关闭文件失败! " );
        
    此时以获取源文件的长度,我们将文件长度和文件名称放到数据包中,设置数据包为
' M ' 类型。
    
// 打包
    _DataPack Pack;
    Pack.cType 
=   ' M ' ;
    Pack.nPackLen 
=   sizeof (Pack);
    
// 取得文件名
    ZeroMemory(Pack.FileInfor.sFileName, FILE_NAME_MAX);
    GetFIieNameFromPath(lpPath, Pack.FileInfor.sFileName);
    Pack.FileInfor.ulFileLen 
=  FileByteCount;
    
    接着使用send()将打包完成的数据包发送给接收端,把发送线程的全局变量初始化,并创建发送线
程,文件数据将由发送线程负责发送
    
// 发送数据包 文件大小和名称
    nRe  =  send(m_ClientSockFd.fd_array[ 0 ], ( char * ) & Pack, Pack.nPackLen,  0 );
    
if (SOCKET_ERROR  ==  nRe)
            AfxMessageBox(
" Send File Size Failed! " );
    
// 线程准备全局变量
    strcpy(sPath, m_sPath);
    ClientSocket 
=  m_ClientSockFd.fd_array[ 0 ];
    
// 启动线程发送文件
    DWORD ID;
    m_hSendThread 
=  CreateThread( 0 0 , SendDataThrad,  0 0 & ID);


(
3 )发送文件数据线程。先打开源文件,为了一次读取大量数据将文件缓冲区设置为FILE_IO_BUFF大小,
然后将读取的数据打包并发送。这样不断地读、不断地发送,直到将整个文件发送完为止。

DWORD __stdcall CServerDlg::SendDataThrad(LPVOID LpP)
{
    
// 打开文件
    FILE  * File  =  fopen(sPath,  " rb " );
    
if (NULL  ==  File)
    {
        AfxMessageBox(
" SendDataThrad中打开文件失败! " );
        
return   1 ;
    }
    
// 设置文件缓冲区
     int  nBuff  =  FILE_IO_BUFF;
    
if (setvbuf(File, ( char * ) & nBuff, _IOFBF,  sizeof (nBuff)))
        AfxMessageBox(
" 设置文件缓冲区失败! " );
    
// 读取文件数据并发送
    u_long ulFlagCount  =   0 ;             // 记录读了多少数据
    u_long FaceReadByte  =   0 ;     // 一次实际读取的字节数
     char  sBuff[PACK_BUFF];    
    ZeroMemory(sBuff, PACK_BUFF);
    fseek(File, 
0 , SEEK_SET);
    
while  ( ! feof(File))
    {
        FaceReadByte 
=  fread(sBuff,  1 , PACK_BUFF, File);
        
// 打包
        _DataPack DataPack;
        DataPack.cType 
=   ' D ' ;
        DataPack.nPackLen 
=   sizeof (DataPack);
        DataPack.nContentLen 
=  FaceReadByte;
        CopyMemory(DataPack.sContent, sBuff, FaceReadByte);
        DataPack.nPosition 
=  ulFlagCount;
        
// 发送
         int  nResult  =  send(ClientSocket, ( char * ) & DataPack, DataPack.nPackLen,  0 );
        
if  (SOCKET_ERROR  ==  nResult)
        {
            AfxMessageBox(
" SendDataThrad中发送数据失败! " );
        }
else
            ulFlagCount 
+=  FaceReadByte;         // 记录发送字节数
    }
    AfxMessageBox(
" 发送结束 " );
    
// 关闭
     int  nRe  =  fclose(File);
    
if (nRe)
        AfxMessageBox(
" SendDataThrad中关闭文件失败! " );
    
return   0 ;
}

4 .接收端
// 接收线程用的全局变量
_FileInfor FileInfor;         // 文件信息
u_long ulWriteByte;             // 记录总共写入的字节
char  lpPath[FILE_NAME_MAX];     // 文件路径
char  sFilePathAndName[FILE_NAME_MAX];         // 完整的文件路径

(
1 )设置套接字接收缓冲区大小。
// 设置套接字接收缓冲区
     int  nBuf  =  SOCKET_BUFF;
    
int  nBufLen  =   sizeof (nBuf);
    SOCKET ClientSock 
=  m_Sock.GetSocket();
    
int  nRe  =  setsockopt(ClientSock, SOL_SOCKET, SO_RCVBUF, ( char * ) & nBuf, nBufLen);
    
if (SOCKET_ERROR  ==  nRe)
            AfxMessageBox(
" setsockopt error! " );
    
// 检查缓冲区是否设置成功
    nRe  =  getsockopt(ClientSock, SOL_SOCKET, SO_RCVBUF, ( char * ) & nBuf,  & nBufLen);
    
if (SOCKET_BUFF  !=  nBuf)
        AfxMessageBox(
" 检查缓冲区:setsockopt error! " );
        
(
2 )接收文件信息和文件数据线程。先判断数据包是属于文件信息还是文件类型,如果是文件信息就根
据信息创建空文件,如果是文件数据就将数据已追加的方式写进目的文件中。

DWORD __stdcall CClientDlg::ReceiveDataPro(LPVOID LpP)
{
    CSocket_Win32 
* pCSock  =  (CSocket_Win32 * )LpP;
    u_long ulWriteByteCount 
=   0 ;
    
while  ( 1 )
    {
        _DataPack DataPack;
        ZeroMemory(
& DataPack,  sizeof (DataPack));
        
if ( ! ( * pCSock).Receive( & DataPack,  sizeof (DataPack)))
        {
            AfxMessageBox(
" Receive DataPack Failed! " );
        }
        
// 判断数据包类型
         if ( ' M '   ==  DataPack.cType)     // 包为文件信息
        {
            
// 接收文件信息
            FileInfor.ulFileLen  =  DataPack.FileInfor.ulFileLen;   // 获取文件长度
            strcpy(FileInfor.sFileName, DataPack.FileInfor.sFileName);  // 获取文件名称
            
// 得到文件目录
             char  sFilePath[FILE_NAME_MAX];
            ZeroMemory(sFilePath, FILE_NAME_MAX);
            strcpy(sFilePath, lpPath);
            strcat(sFilePath, FileInfor.sFileName);
            strcat(sFilePathAndName, sFilePath);    
// 保存完整的文件路径
            
// 创建文件
            SECURITY_ATTRIBUTES attr;
            attr.nLength 
=  FileInfor.ulFileLen;
            attr.lpSecurityDescriptor 
=  NULL;
            HANDLE hRe 
=  CreateFile(sFilePath, GENERIC_WRITE, FILE_SHARE_WRITE,  & attr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,  0 );
            
if (INVALID_HANDLE_VALUE  ==  hRe)
                AfxMessageBox(
" 创建文件失败! " );
            
bool  bRe  =  ::CloseHandle(hRe);
            
// 可以开始接受文件
            bIsStartReceive  =   true ;
        }
else   if ( ' D '   ==  DataPack.cType)         // 包为文件数据
        {
            
// 打开文件
             char  sPath[FILE_NAME_MAX];
            strcpy(sPath, sFilePathAndName);
            FILE 
* File  =  fopen(sPath,  " ab " );
            
if ( 0   ==  File)
                AfxMessageBox(
" 打开文件失败! " );
            
// 设置文件缓冲区
             int  nBuff  =  FILE_IO_BUFF;
            
if (setvbuf(File, ( char * ) & nBuff, _IOFBF,  sizeof (nBuff)))
                AfxMessageBox(
" 设置文件缓冲区失败! " );
            
// 定位文件
            u_long nPosition  =  DataPack.nPosition;
            
int  nRe  =  fseek(File, nPosition, SEEK_SET);
            
if (nRe)
                AfxMessageBox(
" SendDataThrad中定位失败! " );
            
// 写文件
            u_long nNumberOfBytesWritten  =  fwrite( & DataPack.sContent,  1 , DataPack.nContentLen, File);
            
if (DataPack.nContentLen  !=  nNumberOfBytesWritten)
                AfxMessageBox(
" 写文件失败! " );
            
else
            {
                ulWriteByteCount 
+=  nNumberOfBytesWritten;
            }
            fflush(File);                                
// 清除文件缓冲区
            
// 关闭文件
            nRe  =  fclose(File);
            
if (nRe)
                AfxMessageBox(
" 关闭文件失败! " );

            
if (ulWriteByteCount  >=  FileInfor.ulFileLen)
            {
                AfxMessageBox(
" 接收结束 " );
                
break ;
            }    
        }
    }
    
return   0 ;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值