转:送给那些一心想要传送文件的朋友(TCP协议).cpp-from CSDN
2010-06-03 13:35
417 查看
//以下是测试结构
typedef struct tagRequest
{
UINT nMagic;
UINT ncbSize;
//
// 这中间可以写点别的
//
//
//数据长度
UINT nContentLength;
}REQUEST,*PREQUEST,*LPREQUEST;
#define REQUEST_MAGIC 0x12345678
#define REQUEST_SIZE (sizeof(tagRequest))
typedef struct tagResponse
{
UINT nMagic;
UINT ncbSize;
//
// 这中间可以写点别的
//
//
//数据长度
UINT nContentLength;
}RESPONSE,*PRESPONSE,*LPRESPONSE;
#define RESPONSE_MAGIC 0x87654321
#define RESPONSE_SIZE (sizeof(tagResponse))
void PopErrorMessage(DWORD dwErrorCode)
{
LPVOID lpMsgBuf = NULL;
if( ::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | //这个是要自动分配一块内存,使用完要通过LocalFree来回收
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwErrorCode,//这里是要翻译的代码
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 默认提示语言(也可以随便指定一种)
(LPTSTR) &lpMsgBuf,
0,
NULL
) >0 )
{
// 弹一个对话框出来.
::MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
// 这里释放掉返回的内存......................
::LocalFree( lpMsgBuf );
}
}
//发送数据
int SendHunk(SOCKET h,char *lpBuf,int nBufLen)
{
int nSend = -1 ;
int nSendAll = 0;
if( h != SOCKET_ERROR && lpBuf != NULL && nBufLen > 0 )
{
//循环发送,直到所有数据都发送完毕或出错就返回
do {
nSend = ::send(h, //已经连接的句柄
lpBuf+nSendAll, //跳过已经发送的数据
nBufLen-nSendAll, //计算剩余要发送的数据长度
0);
if( nSend > 0 )
nSendAll +=nSend;//累加已经成功发送的数据长度
else
break;
} while( nSendAll < nBufLen );
}
return nSendAll;
}
//接收数据
int RecvHunk(SOCKET h,char *lpBuf,int nBufLen,int nWillLen)
{
int nRecv = -1 ;
int nRecvAll = 0;
if( h != SOCKET_ERROR && lpBuf != NULL && nBufLen > 0 && nWillLen >0 && nBufLen >= nWillLen )
{
//循环接收,直到数据达到要接收的长度或出错就返回
do {
nRecv = ::recv(h, //已经连接的句柄
lpBuf+nRecvAll,//跳过已经接收的数据
nWillLen-nRecvAll,//计算剩余要接收的数据长度
0);
if( nRecv >0 )
nRecvAll += nRecv;//当前已经接收的长度
else
break;
} while( nRecvAll < nWillLen );
}
return nRecvAll;
}
void Client()
{
//发送一个变长的数据块
//
//创建一个句柄(注:默认的情况下会返回一个阻塞句柄.)
SOCKET hSocket = ::socket(AF_INET, // IPV4
SOCK_STREAM, // 数据流 (像河水一样连绵不断,当然也有断流的时候.^-^)
IPPROTO_TCP // TCP协议(一部分书上说填0,让协议自己选择)
);
if( hSocket == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//没办法继续干活了,退出吧!
return ;
}
//定义一个结构用来保存要连接的远程地址和端口
sockaddr_in stRemote = {0};
stRemote.sin_family=AF_INET; //IPV4
stRemote.sin_addr.s_addr = inet_addr("127.0.0.1"); //远程地址(这里用的本机回路地址)
stRemote.sin_port=htons(12345);//一个端口(这里是随便写的)
//连接远程主机
int nReConn = ::connect(hSocket,//前面创建的句柄
(SOCKADDR*)&stRemote, //上面的结构(已经填好远程主机的地址和端口)
sizeof(stRemote) //结构体长度
);
if( nReConn == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
char * szText[] = {"小鸟,小鸟,我是鸟巢,收到请回答!",
"老鸟,老鸟,我是小鸟,收到请回答!",
"呜呜,为什么没有回答呀?",
"下雪了,该收衣服了!",
"天冷了."};
//随机选一个字符串发过去
char * szRequestText = szText[rand()%(sizeof(szRequestText)/sizeof(szRequestText[0]))];
tagRequest stReq = {0};
stReq.nMagic = REQUEST_MAGIC; //结构体识别编码(随便什么都行,不一定要用数值)
stReq.ncbSize = REQUEST_SIZE; //填上结构体大小,以识别目前能不能处理.
//有可能结构体大小发生变化,这种情通常出现在产品的不同版本中,本例不考虑这种情况.
stReq.nContentLength = ::lstrlen(szRequestText)+1;// 要发送变长数据体长度 (这里的加1,是要把末尾的结束符'\0'也发送过去)
//这里用的是封装后的send 具体到上面看吧
//发送请求头
//返回是已经发送的数据长度
int nSend = ::SendHunk(hSocket, //前面创建的句柄
(char*)&stReq, // 请求结构体指针
REQUEST_SIZE // 请示结构体长度
);
if( nSend != REQUEST_SIZE )
{
//如果返回的长度不等于结构体长度,表示出错了.
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//发送完结体后,接着发送变长体数据
nSend = ::SendHunk(hSocket, //前面创建的句柄
szRequestText, // 变长数据
stReq.nContentLength // 变长数据长度
);
if( nSend != stReq.nContentLength )
{
//如果返回的长度不等于变长数据长度,表示出错了.
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
}
void OnAccept(SOCKET hClient)
{
tagRequest stReq = {0};
//接收数据
//先接受请求头
int nRecv = ::RecvHunk(hClient, //上面接收连接句柄
(char*)&stReq, //接收缓冲区
REQUEST_SIZE, //可用的缓冲区大小
REQUEST_SIZE //将要接收的数据(这里填的请求头大小,也就是小于这个的将不与处理)
);
if( nRecv != REQUEST_SIZE )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//验证结构是否合法
if( stReq.nMagic != REQUEST_MAGIC || stReq.ncbSize != REQUEST_SIZE)
{
//哇,非法数据,警告!!这不是我们自己人!!!
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//在这里可以验证一下其它请求头参数(注:本例不验证).
char * pszRequestText = new char[stReq.nContentLength];
if( pszRequestText )
{
//再接收变长数据
int nRecv = ::RecvHunk(hClient, //上面接收连接句柄
pszRequestText, //接收缓冲区
stReq.nContentLength, //可用的缓冲区大小
stReq.nContentLength //将要接收的数据大小
);
if( nRecv != stReq.nContentLength)
{
//唉!数据呢?????
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//回收上面申请的内存
delete []pszRequestText;
pszRequestText = NULL;
//没办法继续干活了,退出吧!
return ;
}
//
::MessageBox(NULL,pszRequestText,"瞧瞧发来是什么",MB_OK | MB_ICONINFORMATION);
//回收上面申请的内存
delete []pszRequestText;
pszRequestText = NULL;
}
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
}
DWORD WINAPI ThreadOnAccept(LPVOID lpData)
{
OnAccept((SOCKET)lpData);
return 0;
}
void Server()
{
//接收一个变长的数据块
//
//创建一个句柄(注:默认的情况下会返回一个阻塞句柄.)
SOCKET hSocket = ::socket(AF_INET, // IPV4
SOCK_STREAM, // 数据流 (像河水一样连绵不断,当然也有断流的时候.^-^)
IPPROTO_TCP // TCP协议(一部分书上说填0,让协议自己选择)
);
if( hSocket == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//没办法继续干活了,退出吧!
return ;
}
//定义一个结构用来保存本机地址和端口
sockaddr_in stLocal = {0};
stLocal.sin_family=AF_INET; //IPV4
stLocal.sin_addr.s_addr = inet_addr("127.0.0.1"); //本机要使用IP地址(这里用的本机回路地址)
stLocal.sin_port=htons(12345);//一个端口(这里是随便写的)
int nReBind = ::bind(hSocket,//前面创建的句柄
(SOCKADDR*)&stLocal, //上面的结构(已经填好远程主机的地址和端口)
sizeof(stLocal) //结构体长度
);
if( nReBind == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//设置监听数量
int nRelisten = ::listen(hSocket,//前面创建的句柄
SOMAXCONN //同时连接数,(注:这里的连接数,指的是同时可以接受的请求连接数.不是指可以连接到这个端口的数量)
);
if( nRelisten == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
do {
//定义一个结构用来保存要连接的远程地址和端口
sockaddr_in stRemote = {0};
int nRemoteLen = sizeof(stRemote);
//等待远程连接到的到来.(从理论上讲,只要资源足够,可以接受足够多个连接).
//返回一个新的连接句柄
SOCKET hSocketClient = ::accept(hSocket,//前面创建的句柄
(SOCKADDR*)&stRemote, //保存已经连接远程主机地址
&nRemoteLen //保存已经连接远程主机地址长度(注:这个值在传入时表示可用的缓冲区长度,传出时表示已经保存的地址长度)
);
if( hSocketClient == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
HANDLE hThClient = ::CreateThread(NULL,0,ThreadOnAccept,
(LPVOID)hSocketClient,//这个句柄会在线程内关闭
0,NULL);
//关闭线句柄(注:线程不会退出的!!)
::CloseHandle(hThClient);
hThClient = NULL;
} while(TRUE);
return;
}
DWORD WINAPI ThreadServer(LPVOID lpData)
{
Server();
return 0;
}
//发送文件,(这里用的CFile类只发送最大4GB的文件)
//如果要发送大于4GB的文件,需要MFC类库版本支持,或者自己构造一相类似功能类,这里就不说的了,相信你轻松就能搞定的.)
//注:这个函数可以直持多线程分段发送,在这里就不举例了,自己在实际应用当中去实现吧!前提是你得对多线程有所了解才行哦.^-^
#define DEFAULT_BUFFER_SIZE 4096
UINT SendFile(SOCKET h, //SOCKET句柄(这里要独占,否则可以出现发送数据混乱,具体原因....见下一行
//这里是分段发送,而SOKCET本身是线程安全的,
//如果这时再有其它线程用这个句柄发送(接收的就无所为了<TCP是全双工....>),
//就会造成在当前的数据流中插入其它线程发送的数据,产生的问题就不可预测了.呵呵)
LPCTSTR szPathFile, //文件名
UINT nStart /*= 0*/, //文件块起点
UINT nEnd /*= ~0*/ //文件块终点(这里的~0表示直到末尾)
)
{
UINT nSend = 0 ;
UINT nSendAll = 0;
CFile f;
//共享读的方式打开文件
if( f.Open(szPathFile,CFile::modeRead|CFile::shareDenyRead) )
{
//不能超过文件末尾
if( nEnd > f.GetLength() )
nEnd = f.GetLength();
//必须有交集
if( nStart < nEnd )
{
//计算要发送数据长度
UINT nDataLen = nEnd - nStart;
//移动到要发送的起点
if( nStart == f.Seek(nStart,CFile::begin) )
{
ULONG nRead = 0;
BYTE cBuf[DEFAULT_BUFFER_SIZE] = {0};
//得有数据才成(因为是无符号数,而CFile类本身,不会返回一个-1出来,固这里没写 " (nRead = (读文件)) > 0) " )
while( nRead = f.Read(cBuf, min(DEFAULT_BUFFER_SIZE,nDataLen-nSendAll)) )
{
//发送
nSend = ::SendHunk(h,(char*)cBuf,nRead);
//为什么这里直判断相等呢?是因为上面的SendHunk已经尝试发送全部了,包括接收端缓冲区不够再次接收的情况.
if( nSend == nRead )
{
//已发送累加
nSendAll +=nSend;
}
else
{
//发送失败,终止
break;
}
}
}
}
//关闭文件
f.Close();
}
//返回已经发送的长度
return nSendAll;
}
//****要注意哦,这里的注释和SendFile的注释不同哦****//
//接收文件,(这里用的CFile类只发送最大4GB的文件)
//如果要接收大于4GB的文件,需要MFC类库版本支持,或者自己构造一相类似功能类,这里就不说的了,相信你轻松就能搞定的.)
UINT RecvFile(SOCKET h, //SOCKET句柄(这里要独占,具体原因....见下一行
//这里是分段发送,而SOKCET本身是线程安全的,
//如果这时再有其它线程用这个句柄接收(发送的就无所为了<TCP是全双工....>),
//就会造成在当前的数据流中的数据被其它线程接收到,产生的问题就不可预测了.呵呵)
LPCTSTR szPathFile,//文件名
UINT nStart , //写入文件块起点
UINT nWillLen //将要接收的数据长度
)
{
UINT nRecv = 0 ;
UINT nRecvAll = 0;
CFile f;
//打开文件
if( f.Open(szPathFile,CFile::modeCreate| //要创建新文件附加条件CFile::modeNoInherit
CFile::modeWrite| //只写
CFile::modeNoInherit //已经存在就打开,不存在就创建
) )
{
//移动到要写入的起点(如果返回值不等于nStart ,可能磁盘空间不足,因此要退出)
//当然了这么作并不能真正的验证空间是否足够.
//提示作法:nStart+
if( nStart == f.Seek(nStart,CFile::begin) )
{
BYTE cBuf[DEFAULT_BUFFER_SIZE] = {0};
do {
//接收数据
nRecv = RecvHunk(h,(char*)cBuf,DEFAULT_BUFFER_SIZE,
min((nWillLen-nRecvAll), DEFAULT_BUFFER_SIZE) //计算要接收的数据块(分接收)
);
//因为接收时已经尝试填满缓冲区才返回,而数据总量又不一定很整齐,这里"整齐"指的是 DEFAULT_BUFFER_SIZE 的倍数
if( nRecv != 0 )
{
try
{
//写到文件里
f.Write(cBuf,nRecv);
nRecvAll += nRecv; //累加计数
}
// catch (CFileException &e ) //在这里不处理异常了
catch(...)
{
//都出错了还能干什么?退出吧!
break;
}
}
else
{
break;
}
} while( nRecvAll < nWillLen );
}
//关闭文件
f.Close();
}
//返回已经接收的长度
return nRecvAll;
}
//以下是测试结构
typedef struct tagRequest
{
UINT nMagic;
UINT ncbSize;
//
// 这中间可以写点别的
//
//
//数据长度
UINT nContentLength;
}REQUEST,*PREQUEST,*LPREQUEST;
#define REQUEST_MAGIC 0x12345678
#define REQUEST_SIZE (sizeof(tagRequest))
typedef struct tagResponse
{
UINT nMagic;
UINT ncbSize;
//
// 这中间可以写点别的
//
//
//数据长度
UINT nContentLength;
}RESPONSE,*PRESPONSE,*LPRESPONSE;
#define RESPONSE_MAGIC 0x87654321
#define RESPONSE_SIZE (sizeof(tagResponse))
void PopErrorMessage(DWORD dwErrorCode)
{
LPVOID lpMsgBuf = NULL;
if( ::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | //这个是要自动分配一块内存,使用完要通过LocalFree来回收
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwErrorCode,//这里是要翻译的代码
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 默认提示语言(也可以随便指定一种)
(LPTSTR) &lpMsgBuf,
0,
NULL
) >0 )
{
// 弹一个对话框出来.
::MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
// 这里释放掉返回的内存......................
::LocalFree( lpMsgBuf );
}
}
//发送数据
int SendHunk(SOCKET h,char *lpBuf,int nBufLen)
{
int nSend = -1 ;
int nSendAll = 0;
if( h != SOCKET_ERROR && lpBuf != NULL && nBufLen > 0 )
{
//循环发送,直到所有数据都发送完毕或出错就返回
do {
nSend = ::send(h, //已经连接的句柄
lpBuf+nSendAll, //跳过已经发送的数据
nBufLen-nSendAll, //计算剩余要发送的数据长度
0);
if( nSend > 0 )
nSendAll +=nSend;//累加已经成功发送的数据长度
else
break;
} while( nSendAll < nBufLen );
}
return nSendAll;
}
//接收数据
int RecvHunk(SOCKET h,char *lpBuf,int nBufLen,int nWillLen)
{
int nRecv = -1 ;
int nRecvAll = 0;
if( h != SOCKET_ERROR && lpBuf != NULL && nBufLen > 0 && nWillLen >0 && nBufLen >= nWillLen )
{
//循环接收,直到数据达到要接收的长度或出错就返回
do {
nRecv = ::recv(h, //已经连接的句柄
lpBuf+nRecvAll,//跳过已经接收的数据
nWillLen-nRecvAll,//计算剩余要接收的数据长度
0);
if( nRecv >0 )
nRecvAll += nRecv;//当前已经接收的长度
else
break;
} while( nRecvAll < nWillLen );
}
return nRecvAll;
}
void Client()
{
//发送一个变长的数据块
//
//创建一个句柄(注:默认的情况下会返回一个阻塞句柄.)
SOCKET hSocket = ::socket(AF_INET, // IPV4
SOCK_STREAM, // 数据流 (像河水一样连绵不断,当然也有断流的时候.^-^)
IPPROTO_TCP // TCP协议(一部分书上说填0,让协议自己选择)
);
if( hSocket == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//没办法继续干活了,退出吧!
return ;
}
//定义一个结构用来保存要连接的远程地址和端口
sockaddr_in stRemote = {0};
stRemote.sin_family=AF_INET; //IPV4
stRemote.sin_addr.s_addr = inet_addr("127.0.0.1"); //远程地址(这里用的本机回路地址)
stRemote.sin_port=htons(12345);//一个端口(这里是随便写的)
//连接远程主机
int nReConn = ::connect(hSocket,//前面创建的句柄
(SOCKADDR*)&stRemote, //上面的结构(已经填好远程主机的地址和端口)
sizeof(stRemote) //结构体长度
);
if( nReConn == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
char * szText[] = {"小鸟,小鸟,我是鸟巢,收到请回答!",
"老鸟,老鸟,我是小鸟,收到请回答!",
"呜呜,为什么没有回答呀?",
"下雪了,该收衣服了!",
"天冷了."};
//随机选一个字符串发过去
char * szRequestText = szText[rand()%(sizeof(szRequestText)/sizeof(szRequestText[0]))];
tagRequest stReq = {0};
stReq.nMagic = REQUEST_MAGIC; //结构体识别编码(随便什么都行,不一定要用数值)
stReq.ncbSize = REQUEST_SIZE; //填上结构体大小,以识别目前能不能处理.
//有可能结构体大小发生变化,这种情通常出现在产品的不同版本中,本例不考虑这种情况.
stReq.nContentLength = ::lstrlen(szRequestText)+1;// 要发送变长数据体长度 (这里的加1,是要把末尾的结束符'\0'也发送过去)
//这里用的是封装后的send 具体到上面看吧
//发送请求头
//返回是已经发送的数据长度
int nSend = ::SendHunk(hSocket, //前面创建的句柄
(char*)&stReq, // 请求结构体指针
REQUEST_SIZE // 请示结构体长度
);
if( nSend != REQUEST_SIZE )
{
//如果返回的长度不等于结构体长度,表示出错了.
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//发送完结体后,接着发送变长体数据
nSend = ::SendHunk(hSocket, //前面创建的句柄
szRequestText, // 变长数据
stReq.nContentLength // 变长数据长度
);
if( nSend != stReq.nContentLength )
{
//如果返回的长度不等于变长数据长度,表示出错了.
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
}
void OnAccept(SOCKET hClient)
{
tagRequest stReq = {0};
//接收数据
//先接受请求头
int nRecv = ::RecvHunk(hClient, //上面接收连接句柄
(char*)&stReq, //接收缓冲区
REQUEST_SIZE, //可用的缓冲区大小
REQUEST_SIZE //将要接收的数据(这里填的请求头大小,也就是小于这个的将不与处理)
);
if( nRecv != REQUEST_SIZE )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//验证结构是否合法
if( stReq.nMagic != REQUEST_MAGIC || stReq.ncbSize != REQUEST_SIZE)
{
//哇,非法数据,警告!!这不是我们自己人!!!
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//在这里可以验证一下其它请求头参数(注:本例不验证).
char * pszRequestText = new char[stReq.nContentLength];
if( pszRequestText )
{
//再接收变长数据
int nRecv = ::RecvHunk(hClient, //上面接收连接句柄
pszRequestText, //接收缓冲区
stReq.nContentLength, //可用的缓冲区大小
stReq.nContentLength //将要接收的数据大小
);
if( nRecv != stReq.nContentLength)
{
//唉!数据呢?????
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//回收上面申请的内存
delete []pszRequestText;
pszRequestText = NULL;
//没办法继续干活了,退出吧!
return ;
}
//
::MessageBox(NULL,pszRequestText,"瞧瞧发来是什么",MB_OK | MB_ICONINFORMATION);
//回收上面申请的内存
delete []pszRequestText;
pszRequestText = NULL;
}
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
}
DWORD WINAPI ThreadOnAccept(LPVOID lpData)
{
OnAccept((SOCKET)lpData);
return 0;
}
void Server()
{
//接收一个变长的数据块
//
//创建一个句柄(注:默认的情况下会返回一个阻塞句柄.)
SOCKET hSocket = ::socket(AF_INET, // IPV4
SOCK_STREAM, // 数据流 (像河水一样连绵不断,当然也有断流的时候.^-^)
IPPROTO_TCP // TCP协议(一部分书上说填0,让协议自己选择)
);
if( hSocket == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//没办法继续干活了,退出吧!
return ;
}
//定义一个结构用来保存本机地址和端口
sockaddr_in stLocal = {0};
stLocal.sin_family=AF_INET; //IPV4
stLocal.sin_addr.s_addr = inet_addr("127.0.0.1"); //本机要使用IP地址(这里用的本机回路地址)
stLocal.sin_port=htons(12345);//一个端口(这里是随便写的)
int nReBind = ::bind(hSocket,//前面创建的句柄
(SOCKADDR*)&stLocal, //上面的结构(已经填好远程主机的地址和端口)
sizeof(stLocal) //结构体长度
);
if( nReBind == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//设置监听数量
int nRelisten = ::listen(hSocket,//前面创建的句柄
SOMAXCONN //同时连接数,(注:这里的连接数,指的是同时可以接受的请求连接数.不是指可以连接到这个端口的数量)
);
if( nRelisten == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
do {
//定义一个结构用来保存要连接的远程地址和端口
sockaddr_in stRemote = {0};
int nRemoteLen = sizeof(stRemote);
//等待远程连接到的到来.(从理论上讲,只要资源足够,可以接受足够多个连接).
//返回一个新的连接句柄
SOCKET hSocketClient = ::accept(hSocket,//前面创建的句柄
(SOCKADDR*)&stRemote, //保存已经连接远程主机地址
&nRemoteLen //保存已经连接远程主机地址长度(注:这个值在传入时表示可用的缓冲区长度,传出时表示已经保存的地址长度)
);
if( hSocketClient == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
HANDLE hThClient = ::CreateThread(NULL,0,ThreadOnAccept,
(LPVOID)hSocketClient,//这个句柄会在线程内关闭
0,NULL);
//关闭线句柄(注:线程不会退出的!!)
::CloseHandle(hThClient);
hThClient = NULL;
} while(TRUE);
return;
}
DWORD WINAPI ThreadServer(LPVOID lpData)
{
Server();
return 0;
}
//发送文件,(这里用的CFile类只发送最大4GB的文件)
//如果要发送大于4GB的文件,需要MFC类库版本支持,或者自己构造一相类似功能类,这里就不说的了,相信你轻松就能搞定的.)
//注:这个函数可以直持多线程分段发送,在这里就不举例了,自己在实际应用当中去实现吧!前提是你得对多线程有所了解才行哦.^-^
#define DEFAULT_BUFFER_SIZE 4096
UINT SendFile(SOCKET h, //SOCKET句柄(这里要独占,否则可以出现发送数据混乱,具体原因....见下一行
//这里是分段发送,而SOKCET本身是线程安全的,
//如果这时再有其它线程用这个句柄发送(接收的就无所为了<TCP是全双工....>),
//就会造成在当前的数据流中插入其它线程发送的数据,产生的问题就不可预测了.呵呵)
LPCTSTR szPathFile, //文件名
UINT nStart /*= 0*/, //文件块起点
UINT nEnd /*= ~0*/ //文件块终点(这里的~0表示直到末尾)
)
{
UINT nSend = 0 ;
UINT nSendAll = 0;
CFile f;
//共享读的方式打开文件
if( f.Open(szPathFile,CFile::modeRead|CFile::shareDenyRead) )
{
//不能超过文件末尾
if( nEnd > f.GetLength() )
nEnd = f.GetLength();
//必须有交集
if( nStart < nEnd )
{
//计算要发送数据长度
UINT nDataLen = nEnd - nStart;
//移动到要发送的起点
if( nStart == f.Seek(nStart,CFile::begin) )
{
ULONG nRead = 0;
BYTE cBuf[DEFAULT_BUFFER_SIZE] = {0};
//得有数据才成(因为是无符号数,而CFile类本身,不会返回一个-1出来,固这里没写 " (nRead = (读文件)) > 0) " )
while( nRead = f.Read(cBuf, min(DEFAULT_BUFFER_SIZE,nDataLen-nSendAll)) )
{
//发送
nSend = ::SendHunk(h,(char*)cBuf,nRead);
//为什么这里直判断相等呢?是因为上面的SendHunk已经尝试发送全部了,包括接收端缓冲区不够再次接收的情况.
if( nSend == nRead )
{
//已发送累加
nSendAll +=nSend;
}
else
{
//发送失败,终止
break;
}
}
}
}
//关闭文件
f.Close();
}
//返回已经发送的长度
return nSendAll;
}
//****要注意哦,这里的注释和SendFile的注释不同哦****//
//接收文件,(这里用的CFile类只发送最大4GB的文件)
//如果要接收大于4GB的文件,需要MFC类库版本支持,或者自己构造一相类似功能类,这里就不说的了,相信你轻松就能搞定的.)
UINT RecvFile(SOCKET h, //SOCKET句柄(这里要独占,具体原因....见下一行
//这里是分段发送,而SOKCET本身是线程安全的,
//如果这时再有其它线程用这个句柄接收(发送的就无所为了<TCP是全双工....>),
//就会造成在当前的数据流中的数据被其它线程接收到,产生的问题就不可预测了.呵呵)
LPCTSTR szPathFile,//文件名
UINT nStart , //写入文件块起点
UINT nWillLen //将要接收的数据长度
)
{
UINT nRecv = 0 ;
UINT nRecvAll = 0;
CFile f;
//打开文件
if( f.Open(szPathFile,CFile::modeCreate| //要创建新文件附加条件CFile::modeNoInherit
CFile::modeWrite| //只写
CFile::modeNoInherit //已经存在就打开,不存在就创建
) )
{
//移动到要写入的起点(如果返回值不等于nStart ,可能磁盘空间不足,因此要退出)
//当然了这么作并不能真正的验证空间是否足够.
//提示作法:nStart+
if( nStart == f.Seek(nStart,CFile::begin) )
{
BYTE cBuf[DEFAULT_BUFFER_SIZE] = {0};
do {
//接收数据
nRecv = RecvHunk(h,(char*)cBuf,DEFAULT_BUFFER_SIZE,
min((nWillLen-nRecvAll), DEFAULT_BUFFER_SIZE) //计算要接收的数据块(分接收)
);
//因为接收时已经尝试填满缓冲区才返回,而数据总量又不一定很整齐,这里"整齐"指的是 DEFAULT_BUFFER_SIZE 的倍数
if( nRecv != 0 )
{
try
{
//写到文件里
f.Write(cBuf,nRecv);
nRecvAll += nRecv; //累加计数
}
// catch (CFileException &e ) //在这里不处理异常了
catch(...)
{
//都出错了还能干什么?退出吧!
break;
}
}
else
{
break;
}
} while( nRecvAll < nWillLen );
}
//关闭文件
f.Close();
}
//返回已经接收的长度
return nRecvAll;
}
typedef struct tagRequest
{
UINT nMagic;
UINT ncbSize;
//
// 这中间可以写点别的
//
//
//数据长度
UINT nContentLength;
}REQUEST,*PREQUEST,*LPREQUEST;
#define REQUEST_MAGIC 0x12345678
#define REQUEST_SIZE (sizeof(tagRequest))
typedef struct tagResponse
{
UINT nMagic;
UINT ncbSize;
//
// 这中间可以写点别的
//
//
//数据长度
UINT nContentLength;
}RESPONSE,*PRESPONSE,*LPRESPONSE;
#define RESPONSE_MAGIC 0x87654321
#define RESPONSE_SIZE (sizeof(tagResponse))
void PopErrorMessage(DWORD dwErrorCode)
{
LPVOID lpMsgBuf = NULL;
if( ::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | //这个是要自动分配一块内存,使用完要通过LocalFree来回收
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwErrorCode,//这里是要翻译的代码
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 默认提示语言(也可以随便指定一种)
(LPTSTR) &lpMsgBuf,
0,
NULL
) >0 )
{
// 弹一个对话框出来.
::MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
// 这里释放掉返回的内存......................
::LocalFree( lpMsgBuf );
}
}
//发送数据
int SendHunk(SOCKET h,char *lpBuf,int nBufLen)
{
int nSend = -1 ;
int nSendAll = 0;
if( h != SOCKET_ERROR && lpBuf != NULL && nBufLen > 0 )
{
//循环发送,直到所有数据都发送完毕或出错就返回
do {
nSend = ::send(h, //已经连接的句柄
lpBuf+nSendAll, //跳过已经发送的数据
nBufLen-nSendAll, //计算剩余要发送的数据长度
0);
if( nSend > 0 )
nSendAll +=nSend;//累加已经成功发送的数据长度
else
break;
} while( nSendAll < nBufLen );
}
return nSendAll;
}
//接收数据
int RecvHunk(SOCKET h,char *lpBuf,int nBufLen,int nWillLen)
{
int nRecv = -1 ;
int nRecvAll = 0;
if( h != SOCKET_ERROR && lpBuf != NULL && nBufLen > 0 && nWillLen >0 && nBufLen >= nWillLen )
{
//循环接收,直到数据达到要接收的长度或出错就返回
do {
nRecv = ::recv(h, //已经连接的句柄
lpBuf+nRecvAll,//跳过已经接收的数据
nWillLen-nRecvAll,//计算剩余要接收的数据长度
0);
if( nRecv >0 )
nRecvAll += nRecv;//当前已经接收的长度
else
break;
} while( nRecvAll < nWillLen );
}
return nRecvAll;
}
void Client()
{
//发送一个变长的数据块
//
//创建一个句柄(注:默认的情况下会返回一个阻塞句柄.)
SOCKET hSocket = ::socket(AF_INET, // IPV4
SOCK_STREAM, // 数据流 (像河水一样连绵不断,当然也有断流的时候.^-^)
IPPROTO_TCP // TCP协议(一部分书上说填0,让协议自己选择)
);
if( hSocket == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//没办法继续干活了,退出吧!
return ;
}
//定义一个结构用来保存要连接的远程地址和端口
sockaddr_in stRemote = {0};
stRemote.sin_family=AF_INET; //IPV4
stRemote.sin_addr.s_addr = inet_addr("127.0.0.1"); //远程地址(这里用的本机回路地址)
stRemote.sin_port=htons(12345);//一个端口(这里是随便写的)
//连接远程主机
int nReConn = ::connect(hSocket,//前面创建的句柄
(SOCKADDR*)&stRemote, //上面的结构(已经填好远程主机的地址和端口)
sizeof(stRemote) //结构体长度
);
if( nReConn == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
char * szText[] = {"小鸟,小鸟,我是鸟巢,收到请回答!",
"老鸟,老鸟,我是小鸟,收到请回答!",
"呜呜,为什么没有回答呀?",
"下雪了,该收衣服了!",
"天冷了."};
//随机选一个字符串发过去
char * szRequestText = szText[rand()%(sizeof(szRequestText)/sizeof(szRequestText[0]))];
tagRequest stReq = {0};
stReq.nMagic = REQUEST_MAGIC; //结构体识别编码(随便什么都行,不一定要用数值)
stReq.ncbSize = REQUEST_SIZE; //填上结构体大小,以识别目前能不能处理.
//有可能结构体大小发生变化,这种情通常出现在产品的不同版本中,本例不考虑这种情况.
stReq.nContentLength = ::lstrlen(szRequestText)+1;// 要发送变长数据体长度 (这里的加1,是要把末尾的结束符'\0'也发送过去)
//这里用的是封装后的send 具体到上面看吧
//发送请求头
//返回是已经发送的数据长度
int nSend = ::SendHunk(hSocket, //前面创建的句柄
(char*)&stReq, // 请求结构体指针
REQUEST_SIZE // 请示结构体长度
);
if( nSend != REQUEST_SIZE )
{
//如果返回的长度不等于结构体长度,表示出错了.
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//发送完结体后,接着发送变长体数据
nSend = ::SendHunk(hSocket, //前面创建的句柄
szRequestText, // 变长数据
stReq.nContentLength // 变长数据长度
);
if( nSend != stReq.nContentLength )
{
//如果返回的长度不等于变长数据长度,表示出错了.
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
}
void OnAccept(SOCKET hClient)
{
tagRequest stReq = {0};
//接收数据
//先接受请求头
int nRecv = ::RecvHunk(hClient, //上面接收连接句柄
(char*)&stReq, //接收缓冲区
REQUEST_SIZE, //可用的缓冲区大小
REQUEST_SIZE //将要接收的数据(这里填的请求头大小,也就是小于这个的将不与处理)
);
if( nRecv != REQUEST_SIZE )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//验证结构是否合法
if( stReq.nMagic != REQUEST_MAGIC || stReq.ncbSize != REQUEST_SIZE)
{
//哇,非法数据,警告!!这不是我们自己人!!!
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//在这里可以验证一下其它请求头参数(注:本例不验证).
char * pszRequestText = new char[stReq.nContentLength];
if( pszRequestText )
{
//再接收变长数据
int nRecv = ::RecvHunk(hClient, //上面接收连接句柄
pszRequestText, //接收缓冲区
stReq.nContentLength, //可用的缓冲区大小
stReq.nContentLength //将要接收的数据大小
);
if( nRecv != stReq.nContentLength)
{
//唉!数据呢?????
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//回收上面申请的内存
delete []pszRequestText;
pszRequestText = NULL;
//没办法继续干活了,退出吧!
return ;
}
//
::MessageBox(NULL,pszRequestText,"瞧瞧发来是什么",MB_OK | MB_ICONINFORMATION);
//回收上面申请的内存
delete []pszRequestText;
pszRequestText = NULL;
}
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
}
DWORD WINAPI ThreadOnAccept(LPVOID lpData)
{
OnAccept((SOCKET)lpData);
return 0;
}
void Server()
{
//接收一个变长的数据块
//
//创建一个句柄(注:默认的情况下会返回一个阻塞句柄.)
SOCKET hSocket = ::socket(AF_INET, // IPV4
SOCK_STREAM, // 数据流 (像河水一样连绵不断,当然也有断流的时候.^-^)
IPPROTO_TCP // TCP协议(一部分书上说填0,让协议自己选择)
);
if( hSocket == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//没办法继续干活了,退出吧!
return ;
}
//定义一个结构用来保存本机地址和端口
sockaddr_in stLocal = {0};
stLocal.sin_family=AF_INET; //IPV4
stLocal.sin_addr.s_addr = inet_addr("127.0.0.1"); //本机要使用IP地址(这里用的本机回路地址)
stLocal.sin_port=htons(12345);//一个端口(这里是随便写的)
int nReBind = ::bind(hSocket,//前面创建的句柄
(SOCKADDR*)&stLocal, //上面的结构(已经填好远程主机的地址和端口)
sizeof(stLocal) //结构体长度
);
if( nReBind == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//设置监听数量
int nRelisten = ::listen(hSocket,//前面创建的句柄
SOMAXCONN //同时连接数,(注:这里的连接数,指的是同时可以接受的请求连接数.不是指可以连接到这个端口的数量)
);
if( nRelisten == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
do {
//定义一个结构用来保存要连接的远程地址和端口
sockaddr_in stRemote = {0};
int nRemoteLen = sizeof(stRemote);
//等待远程连接到的到来.(从理论上讲,只要资源足够,可以接受足够多个连接).
//返回一个新的连接句柄
SOCKET hSocketClient = ::accept(hSocket,//前面创建的句柄
(SOCKADDR*)&stRemote, //保存已经连接远程主机地址
&nRemoteLen //保存已经连接远程主机地址长度(注:这个值在传入时表示可用的缓冲区长度,传出时表示已经保存的地址长度)
);
if( hSocketClient == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
HANDLE hThClient = ::CreateThread(NULL,0,ThreadOnAccept,
(LPVOID)hSocketClient,//这个句柄会在线程内关闭
0,NULL);
//关闭线句柄(注:线程不会退出的!!)
::CloseHandle(hThClient);
hThClient = NULL;
} while(TRUE);
return;
}
DWORD WINAPI ThreadServer(LPVOID lpData)
{
Server();
return 0;
}
//发送文件,(这里用的CFile类只发送最大4GB的文件)
//如果要发送大于4GB的文件,需要MFC类库版本支持,或者自己构造一相类似功能类,这里就不说的了,相信你轻松就能搞定的.)
//注:这个函数可以直持多线程分段发送,在这里就不举例了,自己在实际应用当中去实现吧!前提是你得对多线程有所了解才行哦.^-^
#define DEFAULT_BUFFER_SIZE 4096
UINT SendFile(SOCKET h, //SOCKET句柄(这里要独占,否则可以出现发送数据混乱,具体原因....见下一行
//这里是分段发送,而SOKCET本身是线程安全的,
//如果这时再有其它线程用这个句柄发送(接收的就无所为了<TCP是全双工....>),
//就会造成在当前的数据流中插入其它线程发送的数据,产生的问题就不可预测了.呵呵)
LPCTSTR szPathFile, //文件名
UINT nStart /*= 0*/, //文件块起点
UINT nEnd /*= ~0*/ //文件块终点(这里的~0表示直到末尾)
)
{
UINT nSend = 0 ;
UINT nSendAll = 0;
CFile f;
//共享读的方式打开文件
if( f.Open(szPathFile,CFile::modeRead|CFile::shareDenyRead) )
{
//不能超过文件末尾
if( nEnd > f.GetLength() )
nEnd = f.GetLength();
//必须有交集
if( nStart < nEnd )
{
//计算要发送数据长度
UINT nDataLen = nEnd - nStart;
//移动到要发送的起点
if( nStart == f.Seek(nStart,CFile::begin) )
{
ULONG nRead = 0;
BYTE cBuf[DEFAULT_BUFFER_SIZE] = {0};
//得有数据才成(因为是无符号数,而CFile类本身,不会返回一个-1出来,固这里没写 " (nRead = (读文件)) > 0) " )
while( nRead = f.Read(cBuf, min(DEFAULT_BUFFER_SIZE,nDataLen-nSendAll)) )
{
//发送
nSend = ::SendHunk(h,(char*)cBuf,nRead);
//为什么这里直判断相等呢?是因为上面的SendHunk已经尝试发送全部了,包括接收端缓冲区不够再次接收的情况.
if( nSend == nRead )
{
//已发送累加
nSendAll +=nSend;
}
else
{
//发送失败,终止
break;
}
}
}
}
//关闭文件
f.Close();
}
//返回已经发送的长度
return nSendAll;
}
//****要注意哦,这里的注释和SendFile的注释不同哦****//
//接收文件,(这里用的CFile类只发送最大4GB的文件)
//如果要接收大于4GB的文件,需要MFC类库版本支持,或者自己构造一相类似功能类,这里就不说的了,相信你轻松就能搞定的.)
UINT RecvFile(SOCKET h, //SOCKET句柄(这里要独占,具体原因....见下一行
//这里是分段发送,而SOKCET本身是线程安全的,
//如果这时再有其它线程用这个句柄接收(发送的就无所为了<TCP是全双工....>),
//就会造成在当前的数据流中的数据被其它线程接收到,产生的问题就不可预测了.呵呵)
LPCTSTR szPathFile,//文件名
UINT nStart , //写入文件块起点
UINT nWillLen //将要接收的数据长度
)
{
UINT nRecv = 0 ;
UINT nRecvAll = 0;
CFile f;
//打开文件
if( f.Open(szPathFile,CFile::modeCreate| //要创建新文件附加条件CFile::modeNoInherit
CFile::modeWrite| //只写
CFile::modeNoInherit //已经存在就打开,不存在就创建
) )
{
//移动到要写入的起点(如果返回值不等于nStart ,可能磁盘空间不足,因此要退出)
//当然了这么作并不能真正的验证空间是否足够.
//提示作法:nStart+
if( nStart == f.Seek(nStart,CFile::begin) )
{
BYTE cBuf[DEFAULT_BUFFER_SIZE] = {0};
do {
//接收数据
nRecv = RecvHunk(h,(char*)cBuf,DEFAULT_BUFFER_SIZE,
min((nWillLen-nRecvAll), DEFAULT_BUFFER_SIZE) //计算要接收的数据块(分接收)
);
//因为接收时已经尝试填满缓冲区才返回,而数据总量又不一定很整齐,这里"整齐"指的是 DEFAULT_BUFFER_SIZE 的倍数
if( nRecv != 0 )
{
try
{
//写到文件里
f.Write(cBuf,nRecv);
nRecvAll += nRecv; //累加计数
}
// catch (CFileException &e ) //在这里不处理异常了
catch(...)
{
//都出错了还能干什么?退出吧!
break;
}
}
else
{
break;
}
} while( nRecvAll < nWillLen );
}
//关闭文件
f.Close();
}
//返回已经接收的长度
return nRecvAll;
}
//以下是测试结构
typedef struct tagRequest
{
UINT nMagic;
UINT ncbSize;
//
// 这中间可以写点别的
//
//
//数据长度
UINT nContentLength;
}REQUEST,*PREQUEST,*LPREQUEST;
#define REQUEST_MAGIC 0x12345678
#define REQUEST_SIZE (sizeof(tagRequest))
typedef struct tagResponse
{
UINT nMagic;
UINT ncbSize;
//
// 这中间可以写点别的
//
//
//数据长度
UINT nContentLength;
}RESPONSE,*PRESPONSE,*LPRESPONSE;
#define RESPONSE_MAGIC 0x87654321
#define RESPONSE_SIZE (sizeof(tagResponse))
void PopErrorMessage(DWORD dwErrorCode)
{
LPVOID lpMsgBuf = NULL;
if( ::FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | //这个是要自动分配一块内存,使用完要通过LocalFree来回收
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dwErrorCode,//这里是要翻译的代码
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 默认提示语言(也可以随便指定一种)
(LPTSTR) &lpMsgBuf,
0,
NULL
) >0 )
{
// 弹一个对话框出来.
::MessageBox( NULL, (LPCTSTR)lpMsgBuf, "Error", MB_OK | MB_ICONINFORMATION );
// 这里释放掉返回的内存......................
::LocalFree( lpMsgBuf );
}
}
//发送数据
int SendHunk(SOCKET h,char *lpBuf,int nBufLen)
{
int nSend = -1 ;
int nSendAll = 0;
if( h != SOCKET_ERROR && lpBuf != NULL && nBufLen > 0 )
{
//循环发送,直到所有数据都发送完毕或出错就返回
do {
nSend = ::send(h, //已经连接的句柄
lpBuf+nSendAll, //跳过已经发送的数据
nBufLen-nSendAll, //计算剩余要发送的数据长度
0);
if( nSend > 0 )
nSendAll +=nSend;//累加已经成功发送的数据长度
else
break;
} while( nSendAll < nBufLen );
}
return nSendAll;
}
//接收数据
int RecvHunk(SOCKET h,char *lpBuf,int nBufLen,int nWillLen)
{
int nRecv = -1 ;
int nRecvAll = 0;
if( h != SOCKET_ERROR && lpBuf != NULL && nBufLen > 0 && nWillLen >0 && nBufLen >= nWillLen )
{
//循环接收,直到数据达到要接收的长度或出错就返回
do {
nRecv = ::recv(h, //已经连接的句柄
lpBuf+nRecvAll,//跳过已经接收的数据
nWillLen-nRecvAll,//计算剩余要接收的数据长度
0);
if( nRecv >0 )
nRecvAll += nRecv;//当前已经接收的长度
else
break;
} while( nRecvAll < nWillLen );
}
return nRecvAll;
}
void Client()
{
//发送一个变长的数据块
//
//创建一个句柄(注:默认的情况下会返回一个阻塞句柄.)
SOCKET hSocket = ::socket(AF_INET, // IPV4
SOCK_STREAM, // 数据流 (像河水一样连绵不断,当然也有断流的时候.^-^)
IPPROTO_TCP // TCP协议(一部分书上说填0,让协议自己选择)
);
if( hSocket == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//没办法继续干活了,退出吧!
return ;
}
//定义一个结构用来保存要连接的远程地址和端口
sockaddr_in stRemote = {0};
stRemote.sin_family=AF_INET; //IPV4
stRemote.sin_addr.s_addr = inet_addr("127.0.0.1"); //远程地址(这里用的本机回路地址)
stRemote.sin_port=htons(12345);//一个端口(这里是随便写的)
//连接远程主机
int nReConn = ::connect(hSocket,//前面创建的句柄
(SOCKADDR*)&stRemote, //上面的结构(已经填好远程主机的地址和端口)
sizeof(stRemote) //结构体长度
);
if( nReConn == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
char * szText[] = {"小鸟,小鸟,我是鸟巢,收到请回答!",
"老鸟,老鸟,我是小鸟,收到请回答!",
"呜呜,为什么没有回答呀?",
"下雪了,该收衣服了!",
"天冷了."};
//随机选一个字符串发过去
char * szRequestText = szText[rand()%(sizeof(szRequestText)/sizeof(szRequestText[0]))];
tagRequest stReq = {0};
stReq.nMagic = REQUEST_MAGIC; //结构体识别编码(随便什么都行,不一定要用数值)
stReq.ncbSize = REQUEST_SIZE; //填上结构体大小,以识别目前能不能处理.
//有可能结构体大小发生变化,这种情通常出现在产品的不同版本中,本例不考虑这种情况.
stReq.nContentLength = ::lstrlen(szRequestText)+1;// 要发送变长数据体长度 (这里的加1,是要把末尾的结束符'\0'也发送过去)
//这里用的是封装后的send 具体到上面看吧
//发送请求头
//返回是已经发送的数据长度
int nSend = ::SendHunk(hSocket, //前面创建的句柄
(char*)&stReq, // 请求结构体指针
REQUEST_SIZE // 请示结构体长度
);
if( nSend != REQUEST_SIZE )
{
//如果返回的长度不等于结构体长度,表示出错了.
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//发送完结体后,接着发送变长体数据
nSend = ::SendHunk(hSocket, //前面创建的句柄
szRequestText, // 变长数据
stReq.nContentLength // 变长数据长度
);
if( nSend != stReq.nContentLength )
{
//如果返回的长度不等于变长数据长度,表示出错了.
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
}
void OnAccept(SOCKET hClient)
{
tagRequest stReq = {0};
//接收数据
//先接受请求头
int nRecv = ::RecvHunk(hClient, //上面接收连接句柄
(char*)&stReq, //接收缓冲区
REQUEST_SIZE, //可用的缓冲区大小
REQUEST_SIZE //将要接收的数据(这里填的请求头大小,也就是小于这个的将不与处理)
);
if( nRecv != REQUEST_SIZE )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//验证结构是否合法
if( stReq.nMagic != REQUEST_MAGIC || stReq.ncbSize != REQUEST_SIZE)
{
//哇,非法数据,警告!!这不是我们自己人!!!
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//在这里可以验证一下其它请求头参数(注:本例不验证).
char * pszRequestText = new char[stReq.nContentLength];
if( pszRequestText )
{
//再接收变长数据
int nRecv = ::RecvHunk(hClient, //上面接收连接句柄
pszRequestText, //接收缓冲区
stReq.nContentLength, //可用的缓冲区大小
stReq.nContentLength //将要接收的数据大小
);
if( nRecv != stReq.nContentLength)
{
//唉!数据呢?????
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
//回收上面申请的内存
delete []pszRequestText;
pszRequestText = NULL;
//没办法继续干活了,退出吧!
return ;
}
//
::MessageBox(NULL,pszRequestText,"瞧瞧发来是什么",MB_OK | MB_ICONINFORMATION);
//回收上面申请的内存
delete []pszRequestText;
pszRequestText = NULL;
}
//关闭连接句柄
::closesocket(hClient);
hClient = SOCKET_ERROR;
}
DWORD WINAPI ThreadOnAccept(LPVOID lpData)
{
OnAccept((SOCKET)lpData);
return 0;
}
void Server()
{
//接收一个变长的数据块
//
//创建一个句柄(注:默认的情况下会返回一个阻塞句柄.)
SOCKET hSocket = ::socket(AF_INET, // IPV4
SOCK_STREAM, // 数据流 (像河水一样连绵不断,当然也有断流的时候.^-^)
IPPROTO_TCP // TCP协议(一部分书上说填0,让协议自己选择)
);
if( hSocket == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//没办法继续干活了,退出吧!
return ;
}
//定义一个结构用来保存本机地址和端口
sockaddr_in stLocal = {0};
stLocal.sin_family=AF_INET; //IPV4
stLocal.sin_addr.s_addr = inet_addr("127.0.0.1"); //本机要使用IP地址(这里用的本机回路地址)
stLocal.sin_port=htons(12345);//一个端口(这里是随便写的)
int nReBind = ::bind(hSocket,//前面创建的句柄
(SOCKADDR*)&stLocal, //上面的结构(已经填好远程主机的地址和端口)
sizeof(stLocal) //结构体长度
);
if( nReBind == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
//设置监听数量
int nRelisten = ::listen(hSocket,//前面创建的句柄
SOMAXCONN //同时连接数,(注:这里的连接数,指的是同时可以接受的请求连接数.不是指可以连接到这个端口的数量)
);
if( nRelisten == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
do {
//定义一个结构用来保存要连接的远程地址和端口
sockaddr_in stRemote = {0};
int nRemoteLen = sizeof(stRemote);
//等待远程连接到的到来.(从理论上讲,只要资源足够,可以接受足够多个连接).
//返回一个新的连接句柄
SOCKET hSocketClient = ::accept(hSocket,//前面创建的句柄
(SOCKADDR*)&stRemote, //保存已经连接远程主机地址
&nRemoteLen //保存已经连接远程主机地址长度(注:这个值在传入时表示可用的缓冲区长度,传出时表示已经保存的地址长度)
);
if( hSocketClient == SOCKET_ERROR )
{
//哇,出错了,//看看是什么错.
DWORD dwErrorCode = ::WSAGetLastError();
//怎么是数字啊!!看不懂,翻译一下.^-^
PopErrorMessage(dwErrorCode);
//关闭句柄,并初化为一个错误值
::closesocket(hSocket);
hSocket = SOCKET_ERROR;
//没办法继续干活了,退出吧!
return ;
}
HANDLE hThClient = ::CreateThread(NULL,0,ThreadOnAccept,
(LPVOID)hSocketClient,//这个句柄会在线程内关闭
0,NULL);
//关闭线句柄(注:线程不会退出的!!)
::CloseHandle(hThClient);
hThClient = NULL;
} while(TRUE);
return;
}
DWORD WINAPI ThreadServer(LPVOID lpData)
{
Server();
return 0;
}
//发送文件,(这里用的CFile类只发送最大4GB的文件)
//如果要发送大于4GB的文件,需要MFC类库版本支持,或者自己构造一相类似功能类,这里就不说的了,相信你轻松就能搞定的.)
//注:这个函数可以直持多线程分段发送,在这里就不举例了,自己在实际应用当中去实现吧!前提是你得对多线程有所了解才行哦.^-^
#define DEFAULT_BUFFER_SIZE 4096
UINT SendFile(SOCKET h, //SOCKET句柄(这里要独占,否则可以出现发送数据混乱,具体原因....见下一行
//这里是分段发送,而SOKCET本身是线程安全的,
//如果这时再有其它线程用这个句柄发送(接收的就无所为了<TCP是全双工....>),
//就会造成在当前的数据流中插入其它线程发送的数据,产生的问题就不可预测了.呵呵)
LPCTSTR szPathFile, //文件名
UINT nStart /*= 0*/, //文件块起点
UINT nEnd /*= ~0*/ //文件块终点(这里的~0表示直到末尾)
)
{
UINT nSend = 0 ;
UINT nSendAll = 0;
CFile f;
//共享读的方式打开文件
if( f.Open(szPathFile,CFile::modeRead|CFile::shareDenyRead) )
{
//不能超过文件末尾
if( nEnd > f.GetLength() )
nEnd = f.GetLength();
//必须有交集
if( nStart < nEnd )
{
//计算要发送数据长度
UINT nDataLen = nEnd - nStart;
//移动到要发送的起点
if( nStart == f.Seek(nStart,CFile::begin) )
{
ULONG nRead = 0;
BYTE cBuf[DEFAULT_BUFFER_SIZE] = {0};
//得有数据才成(因为是无符号数,而CFile类本身,不会返回一个-1出来,固这里没写 " (nRead = (读文件)) > 0) " )
while( nRead = f.Read(cBuf, min(DEFAULT_BUFFER_SIZE,nDataLen-nSendAll)) )
{
//发送
nSend = ::SendHunk(h,(char*)cBuf,nRead);
//为什么这里直判断相等呢?是因为上面的SendHunk已经尝试发送全部了,包括接收端缓冲区不够再次接收的情况.
if( nSend == nRead )
{
//已发送累加
nSendAll +=nSend;
}
else
{
//发送失败,终止
break;
}
}
}
}
//关闭文件
f.Close();
}
//返回已经发送的长度
return nSendAll;
}
//****要注意哦,这里的注释和SendFile的注释不同哦****//
//接收文件,(这里用的CFile类只发送最大4GB的文件)
//如果要接收大于4GB的文件,需要MFC类库版本支持,或者自己构造一相类似功能类,这里就不说的了,相信你轻松就能搞定的.)
UINT RecvFile(SOCKET h, //SOCKET句柄(这里要独占,具体原因....见下一行
//这里是分段发送,而SOKCET本身是线程安全的,
//如果这时再有其它线程用这个句柄接收(发送的就无所为了<TCP是全双工....>),
//就会造成在当前的数据流中的数据被其它线程接收到,产生的问题就不可预测了.呵呵)
LPCTSTR szPathFile,//文件名
UINT nStart , //写入文件块起点
UINT nWillLen //将要接收的数据长度
)
{
UINT nRecv = 0 ;
UINT nRecvAll = 0;
CFile f;
//打开文件
if( f.Open(szPathFile,CFile::modeCreate| //要创建新文件附加条件CFile::modeNoInherit
CFile::modeWrite| //只写
CFile::modeNoInherit //已经存在就打开,不存在就创建
) )
{
//移动到要写入的起点(如果返回值不等于nStart ,可能磁盘空间不足,因此要退出)
//当然了这么作并不能真正的验证空间是否足够.
//提示作法:nStart+
if( nStart == f.Seek(nStart,CFile::begin) )
{
BYTE cBuf[DEFAULT_BUFFER_SIZE] = {0};
do {
//接收数据
nRecv = RecvHunk(h,(char*)cBuf,DEFAULT_BUFFER_SIZE,
min((nWillLen-nRecvAll), DEFAULT_BUFFER_SIZE) //计算要接收的数据块(分接收)
);
//因为接收时已经尝试填满缓冲区才返回,而数据总量又不一定很整齐,这里"整齐"指的是 DEFAULT_BUFFER_SIZE 的倍数
if( nRecv != 0 )
{
try
{
//写到文件里
f.Write(cBuf,nRecv);
nRecvAll += nRecv; //累加计数
}
// catch (CFileException &e ) //在这里不处理异常了
catch(...)
{
//都出错了还能干什么?退出吧!
break;
}
}
else
{
break;
}
} while( nRecvAll < nWillLen );
}
//关闭文件
f.Close();
}
//返回已经接收的长度
return nRecvAll;
}
相关文章推荐
- 处理日志小工具:GetFromFile快速从日志大文件中找到想要的报错信息
- 黑马程序员 TCP协议从客户端传送文件给服务端
- c++ 自己创建的namespace,声明在h文件中,定义在cpp文件中,这种程序结构应该是合理的吧,欢迎不同见解的朋友来讨论。
- 使用Ip/tcp协议进行文件传送
- 06.去.过你想要的人生——朋友和家人:那些最稳固的支持者(笔记)
- 送给那些渐渐远离的朋友(转载)
- 在MFC程序中,加入一个cpp文件,如果想要打印输出信息的话,用这个语句最便捷
- HTTPS传送文件的打包方式(为一个朋友调试的过程)
- 送给那些经常问我如何设置360测速结果为电信的朋友,360测速模块原理简单分析
- 那些一心想要离开 BAT 的人,后来怎么样了?
- 如何用Cocos2d-x创建lua项目以及lua项目如何调用cpp文件(图文讲解)
- springMVC fromdata表单上传文件+ajax ajaxSetup loading效果
- Java利用TCP协议发送文件(三)--客户端设计
- 远程连接linux服务器 & 传送文件 ——VNC & ssh/winSCP
- android提交代码 应该忽略那些文件
- C++ lvalue,prvalue,xvalue,glvalue和rvalue详解(from cppreference)
- .Net 2.0对文件传输协议(FTP)操作(上传,下载,新建,删除,FTP间传送文件等) 2
- source insight中cpp文件和h文件的切换(使用si的内置语言实现)[增加src/include目录切换]
- 写给那些正在找工作的朋友
- C++读取OBJ文件 对于f数据 取出任意自己想要的数据