用TCP协议传送文件可防止粘包
有时候TCP传输会出现粘包情况,网上流行几种说法,一种是在发送的时候加个sleep,让发送不至于过快引起粘包,但这种会影响发送效率,这个sleep并且微秒级的,也就是说sleep(1)与sleep(40)差别不大,废话不多说,下面这段代码经测试可预防粘包。
//以下是测试结构 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; }