海阔天空

海阔凭鱼跃 天高任鸟飞

 

转:送给那些一心想要传送文件的朋友(TCP协议).cpp-from CSDN

//以下是测试结构
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;
}

 

posted on 2010-06-03 13:35  liuym  阅读(496)  评论(0编辑  收藏  举报

导航