使用TCP协议实现传输文件
程序分为发送端和接收端。首先在传输文件数据之前,发送端会把将装有文件名称和文件长度等
信息的数据包发送至接收端。接收端收到文件名称和文件长度信息后会创建好空白文件。接着开始传输
文件数据。下面介绍实现功能的主要过程:
1.创建套接字、绑定、监听、连接、接受连接
//创建TCP协议的套接字
m_Socket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(SOCKET_ERROR==m_Socket)
AfxMessageBox("CreateSocketError!",0,0);
//绑定与监听
SOCKADDR_INaddrSrv;
addrSrv.sin_addr.s_addr=inet_addr(sIP);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(Port);
intret=bind(m_Socket,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
if(ret==SOCKET_ERROR)
AfxMessageBox("BindSocketError!",0,0);
//连接
SOCKADDR_INServerAddr;
ServerAddr.sin_addr.s_addr=inet_addr(ServerAddr_in);
ServerAddr.sin_family=AF_INET;
ServerAddr.sin_port=htons(ServerPort);
intResult=connect(m_Socket,(structsockaddr*)&ServerAddr,sizeof(structsockaddr));
if(SOCKET_ERROR==Result)
AfxMessageBox("ConnetFailed!");
//接受连接
SOCKADDR_INClientAddr;
intlen=sizeof(SOCKADDR_IN);
SOCKETClientSock=accept(m_Socket,(structsockaddr*)&ClientAddr,&len);
if(SOCKET_ERROR==ClientSock)
AfxMessageBox("AcceptFailed!");
2.声明宏和结构体
声明套接字缓冲区和一次发送文件数据的缓冲区大小
#defineSOCKET_BUFF80000//套接字缓冲区大小
#definePACK_BUFF50000//数据包缓冲区大小
声明文件I/O缓冲区和最大文件路径长度
#defineFILE_NAME_MAX100//文件路径最大长度
#defineFILE_IO_BUFFPACK_BUFF//文件IO缓冲区
//文件信息
typedefstruct_FileInfor
{
u_longulFileLen;
charsFileName[FILE_NAME_MAX];
}_FileInfor;
//数据包
typedefstruct_DataPack
{
charcType;//'D'为数据'M'为文件信息
intnPackLen;
charsContent[PACK_BUFF];//数据包缓冲区
u_longnPosition;//数据在文件中的位置
intnContentLen;//数据字节数
_FileInforFileInfor;//文件信息
}_DataPack;
3.发送端
//发送线程需要的全局变量
charsPath[FILE_NAME_MAX];//文件地址
u_longFileByteCount;//文件大小
SOCKETClientSocket;//
(1)设置套接字发送缓冲区大小,在32位WindowsXP环境下,系统为每个套接字分配的默认发送数据缓
冲区为8192字节。由于传输的文件很大,可能几十兆,或者更大。那么系统为每个套接字分配的默认
缓冲区显然过小。为此在创建套接字之后,需要修改套接字发送数据缓冲尺寸。在这里我修改为80k,
差不多可以够用了。
//设置套接字发送缓冲区
intnBuf=SOCKET_BUFF;
intnBufLen=sizeof(nBuf);
intnRe=setsockopt(ClientSock,SOL_SOCKET,SO_SNDBUF,(char*)&nBuf,nBufLen);
if(SOCKET_ERROR==nRe)
AfxMessageBox("setsockopterror!");
//检查缓冲区是否设置成功
nRe=getsockopt(ClientSock,SOL_SOCKET,SO_SNDBUF,(char*)&nBuf,&nBufLen);
if(SOCKET_BUFF!=nBuf)
AfxMessageBox("检查缓冲区:setsockopterror!");
(2)测量文件大小并发送文件大小和名称给客户端
首先使用C库函数对源文件进行测量
//得到文件地址
LPTSTRlpPath=m_sPath.GetBuffer(m_sPath.GetLength());
//打开文件
FILE*File=fopen(lpPath,"rb");
if(NULL==File)
AfxMessageBox("打开文件失败!");
//测量文件大小
charBuff[PACK_BUFF];
u_longulFaceReadByte;
FileByteCount=0;
fseek(File,0,SEEK_SET);
while(!feof(File))
{
ulFaceReadByte=fread(Buff,1,1,File);
FileByteCount+=ulFaceReadByte;
}
//关闭文件
intnRe=fclose(File);
if(nRe)
AfxMessageBox("关闭文件失败!");
此时以获取源文件的长度,我们将文件长度和文件名称放到数据包中,设置数据包为'M'类型。
//打包
_DataPackPack;
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("SendFileSizeFailed!");
//线程准备全局变量
strcpy(sPath,m_sPath);
ClientSocket=m_ClientSockFd.fd_array[0];
//启动线程发送文件
DWORDID;
m_hSendThread=CreateThread(0,0,SendDataThrad,0,0,&ID);
(3)发送文件数据线程。先打开源文件,为了一次读取大量数据将文件缓冲区设置为FILE_IO_BUFF大小,
然后将读取的数据打包并发送。这样不断地读、不断地发送,直到将整个文件发送完为止。
DWORD__stdcallCServerDlg::SendDataThrad(LPVOIDLpP)
{
//打开文件
FILE*File=fopen(sPath,"rb");
if(NULL==File)
{
AfxMessageBox("SendDataThrad中打开文件失败!");
return1;
}
//设置文件缓冲区
intnBuff=FILE_IO_BUFF;
if(setvbuf(File,(char*)&nBuff,_IOFBF,sizeof(nBuff)))
AfxMessageBox("设置文件缓冲区失败!");
//读取文件数据并发送
u_longulFlagCount=0;//记录读了多少数据
u_longFaceReadByte=0;//一次实际读取的字节数
charsBuff[PACK_BUFF];
ZeroMemory(sBuff,PACK_BUFF);
fseek(File,0,SEEK_SET);
while(!feof(File))
{
FaceReadByte=fread(sBuff,1,PACK_BUFF,File);
//打包
_DataPackDataPack;
DataPack.cType='D';
DataPack.nPackLen=sizeof(DataPack);
DataPack.nContentLen=FaceReadByte;
CopyMemory(DataPack.sContent,sBuff,FaceReadByte);
DataPack.nPosition=ulFlagCount;
//发送
intnResult=send(ClientSocket,(char*)&DataPack,DataPack.nPackLen,0);
if(SOCKET_ERROR==nResult)
{
AfxMessageBox("SendDataThrad中发送数据失败!");
}else
ulFlagCount+=FaceReadByte;//记录发送字节数
}
AfxMessageBox("发送结束");
//关闭
intnRe=fclose(File);
if(nRe)
AfxMessageBox("SendDataThrad中关闭文件失败!");
return0;
}
4.接收端
//接收线程用的全局变量
_FileInforFileInfor;//文件信息
u_longulWriteByte;//记录总共写入的字节
charlpPath[FILE_NAME_MAX];//文件路径
charsFilePathAndName[FILE_NAME_MAX];//完整的文件路径
(1)设置套接字接收缓冲区大小。
//设置套接字接收缓冲区
intnBuf=SOCKET_BUFF;
intnBufLen=sizeof(nBuf);
SOCKETClientSock=m_Sock.GetSocket();
intnRe=setsockopt(ClientSock,SOL_SOCKET,SO_RCVBUF,(char*)&nBuf,nBufLen);
if(SOCKET_ERROR==nRe)
AfxMessageBox("setsockopterror!");
//检查缓冲区是否设置成功
nRe=getsockopt(ClientSock,SOL_SOCKET,SO_RCVBUF,(char*)&nBuf,&nBufLen);
if(SOCKET_BUFF!=nBuf)
AfxMessageBox("检查缓冲区:setsockopterror!");
(2)接收文件信息和文件数据线程。先判断数据包是属于文件信息还是文件类型,如果是文件信息就根
据信息创建空文件,如果是文件数据就将数据已追加的方式写进目的文件中。
DWORD__stdcallCClientDlg::ReceiveDataPro(LPVOIDLpP)
{
CSocket_Win32*pCSock=(CSocket_Win32*)LpP;
u_longulWriteByteCount=0;
while(1)
{
_DataPackDataPack;
ZeroMemory(&DataPack,sizeof(DataPack));
if(!(*pCSock).Receive(&DataPack,sizeof(DataPack)))
{
AfxMessageBox("ReceiveDataPackFailed!");
}
//判断数据包类型
if('M'==DataPack.cType)//包为文件信息
{
//接收文件信息
FileInfor.ulFileLen=DataPack.FileInfor.ulFileLen;//获取文件长度
strcpy(FileInfor.sFileName,DataPack.FileInfor.sFileName);//获取文件名称
//得到文件目录
charsFilePath[FILE_NAME_MAX];
ZeroMemory(sFilePath,FILE_NAME_MAX);
strcpy(sFilePath,lpPath);
strcat(sFilePath,FileInfor.sFileName);
strcat(sFilePathAndName,sFilePath);//保存完整的文件路径
//创建文件
SECURITY_ATTRIBUTESattr;
attr.nLength=FileInfor.ulFileLen;
attr.lpSecurityDescriptor=NULL;
HANDLEhRe=CreateFile(sFilePath,GENERIC_WRITE,FILE_SHARE_WRITE,&attr,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
if(INVALID_HANDLE_VALUE==hRe)
AfxMessageBox("创建文件失败!");
boolbRe=::CloseHandle(hRe);
//可以开始接受文件
bIsStartReceive=true;
}elseif('D'==DataPack.cType)//包为文件数据
{
//打开文件
charsPath[FILE_NAME_MAX];
strcpy(sPath,sFilePathAndName);
FILE*File=fopen(sPath,"ab");
if(0==File)
AfxMessageBox("打开文件失败!");
//设置文件缓冲区
intnBuff=FILE_IO_BUFF;
if(setvbuf(File,(char*)&nBuff,_IOFBF,sizeof(nBuff)))
AfxMessageBox("设置文件缓冲区失败!");
//定位文件
u_longnPosition=DataPack.nPosition;
intnRe=fseek(File,nPosition,SEEK_SET);
if(nRe)
AfxMessageBox("SendDataThrad中定位失败!");
//写文件
u_longnNumberOfBytesWritten=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;
}
}
}
return0;
}