使用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 ;
}
程序分为发送端和接收端。首先在传输文件数据之前,发送端会把将装有文件名称和文件长度等
信息的数据包发送至接收端。接收端收到文件名称和文件长度信息后会创建好空白文件。接着开始传输
文件数据。下面介绍实现功能的主要过程:
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 ;
}