我最近在写一个自动升级的程序,因为只是给几个人用,服务端也没用用完成端口,重叠端口之类。只是用了mfc的CAsyncSocket。写好以后,在内网测试,速度还可以,也没有什么错误。后来给几个人到公网测试,发现总是失败,不是超时,就是没有连接后没有其他操作,继而超时。试了很多方法,都以失败告终,由于客户端无法进行调试,而服务器端调试时也没有错误,不过传输过程中会有“您的主机关闭了一个连接这样的错误。真是百思不得其解,还请各位大侠指教。谢谢!

解决方案 »

  1.   

    服务端:
    void CUpdateThread::OnReceiveSocket(CString strIP, UINT nPort)
    {
    CString strID;
    int nReceived;

    CClientSocket * cntSocket = NULL;
    //找到那个控制端
    if (GetSocket(strIP, nPort, cntSocket, m_clientList) == NULL)
    {
    return;
    }
    //分配缓冲区并接收
    ZeroMemory(m_pszBuf, BUFSIZE);
    nReceived = cntSocket->Receive(m_pszBuf, BUFSIZE);
    if( nReceived== SOCKET_ERROR || nReceived < sizeof(CAskData))
    {
    return;
    }
    CAskDataEx * pAsk = new CAskDataEx; 
    memcpy(&(pAsk->m_ask), m_pszBuf, sizeof(CAskData));
    pAsk->cntSocket = cntSocket;
    CSingleLock lock(&(m_mutex));
    lock.Lock();
    m_listFile.AddTail(pAsk);
    m_eventFile.SetEvent(); lock.Unlock();
    }
    工作线程代码:
    void CUpdateThread::OnFiles()
    {
    CAskDataEx * pAsk = NULL;
    while (TRUE)
    {
    if(m_bStop)
    {
    break;
    }
    if(m_listFile.GetCount() > 0)
    {
    CSingleLock lock(&(m_mutex));
    lock.Lock();
    pAsk = (CAskDataEx*) m_listFile.RemoveHead();
    lock.Unlock();
    if(pAsk == NULL)
    {
    continue;
    }
    CClientSocket * cntSocket = pAsk->cntSocket;
    if(cntSocket == NULL || cntSocket->m_hSocket == INVALID_SOCKET)
    {
    delete pAsk;
    pAsk = NULL;
    continue;
    }
    CString strFile = m_strUpdatePath + pAsk->m_ask.m_pszFile;
    FILE * fp = fopen(strFile, "rb");
    if(fp == NULL)
    {
    delete pAsk;
    pAsk = NULL;
    continue;
    }
    fseek(fp, 0, SEEK_END);
    int nFileLen = ftell(fp);
    fseek(fp, pAsk->m_ask.m_dwIndex, SEEK_SET);
    DWORD dwLen = nFileLen - pAsk->m_ask.m_dwIndex;
    if(dwLen < 0)
    {
    dwLen = 0;
    }
    char *pBuf = new char[pAsk->m_ask.m_dwBlockLen+1];
    //TransmitFile(pAsk->m_sock, fp, dwLen, pAsk->m_ask.m_dwBlockLen, NULL, NULL, TF_DISCONNECT);
    int nReSend = 0;
    int nRead = 0;
    while(TRUE)
    {
    if(cntSocket->m_hSocket == INVALID_SOCKET)
    {
    break;
    }
    if(nReSend <= 0)
    {
    ZeroMemory(pBuf, pAsk->m_ask.m_dwBlockLen+1);
    nRead = fread(pBuf, 1, pAsk->m_ask.m_dwBlockLen, fp);
    }

    if (cntSocket->Send( pBuf, nRead) == SOCKET_ERROR)
    {
    #ifdef _DEBUG
    CheckLastError();
    #endif
    int n = GetLastError();
    if(n == WSAEWOULDBLOCK && nReSend  <= 10 )
    {
    nReSend++;
    Sleep(200);
    continue;
    }
    cntSocket->ShutDown();
    // cntSocket->Close();
    /*
    cntSocket->Close();
    delete cntSocket;
    cntSocket = NULL;*/
    break; }
    nReSend = 0;
    (cntSocket->m_nLived) = 0;
    if(nRead <= 0)
    {
    break;
    }
    }

    delete pBuf;
    pBuf = NULL; delete pAsk;
    pAsk = NULL;
    fclose(fp);

    }
    ::WaitForSingleObject(m_eventFile, 1000); }
    }客户端:BOOL CCheckVersion::GetUpdateListFile()
    {
    CFile destFile;
    CString strFile = m_strRootPath + _T("temp.idx");
    if(!destFile.Open(strFile, CFile::modeCreate|CFile::modeWrite | CFile::typeBinary))
    {
    CString strLog;
    strLog.Format(_T("打开文件[%s]错误%s"),strFile, CheckLastError());
    WriteLog(strLog);
    return FALSE;
    } CAskData ask;
    strcpy(ask.m_pszFile, m_strListFile); char pszBuf[BUFSIZE+1];
    int nRev = 0;
    DWORD dwSum = 0;

    for(int i = 0; i < 10; i++)
    {//如果10次请求还不能下载这个文件的话,那只有退出了
    sockaddr_in local;
    SOCKET sock;
    int rc=0;
    //初使化服务器地址
    local.sin_family=AF_INET;
    local.sin_port=htons(m_nServerPort);
    local.sin_addr.S_un.S_addr=inet_addr(m_strServerIP.GetBuffer(1));
    sock= socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); int ret;
    //联接服务器
    ret=connect(sock,(LPSOCKADDR)&local,sizeof(local));
    //有错的话
    if(ret < 0)
    {
    CString strLog;
    strLog.Format(_T("获取升级文件列表时错误:1:%s"),CheckLastError());
    WriteLog(strLog);
    closesocket(sock);
    continue;
    }

    if(send(sock, (char*)&ask, sizeof(CAskData), 0) != sizeof(CAskData))
    {
    CString strLog;
    strLog.Format(_T("获取升级文件列表时错误:2:%s"),CheckLastError());
    WriteLog(strLog);
    shutdown(sock, 1);
    closesocket(sock);
    continue;
    }
    while (TRUE)
    {
    memset(pszBuf, 0x00, BUFSIZE+1);
    nRev =recv(sock, pszBuf, BUFSIZE, 0);
    dwSum += nRev; if(nRev == SOCKET_ERROR)
    {
    CString strLog;
    strLog.Format(_T("获取升级文件列表时错误:3:%s"),CheckLastError());
    WriteLog(strLog);
    shutdown(sock, 1);
    closesocket(sock);
    break;
    }
    destFile.Write(pszBuf, nRev);
    ask.m_dwIndex = dwSum;
    if(nRev < BUFSIZE) 
    {
    break;
    }
    }
    shutdown(sock, 1);
    closesocket(sock); if(nRev != SOCKET_ERROR && nRev < BUFSIZE)
    {
    break;
    } } destFile.Close();

    return TRUE;
    }
      

  2.   

    nRev =recv(sock, pszBuf, BUFSIZE, 0);
    if(nRev < BUFSIZE) 
    {
       break;
    }//接收的时候,很多时候是接不够你要的数据,这不是发生错误,可能由于网络原因,socket的缓冲区暂时没不够你想要的数据,遇到这种情况呢要等待,而你却break了,跳出循环后就closesocket了,服务器自然就接到您的主机关闭了一个连接错误以上只是我的看法,不一定对,当然,代码中还有几个,在我看来处理的不是很好的地方,就不一一列出了
      

  3.   

    建议将 BUFSIZE 的大小改到1K或更小
      

  4.   

    我是用VB写的。客户端
    Option ExplicitDim fl As Integer
    Dim byterec As LongConst HOST = "192.168.0.168"
    Const PORT = 32654Private Sub Command1_Click()
    If Winsock1.State = sckConnected Then
        Winsock1.SendData "flash"
    End If
    End SubPrivate Sub Command2_Click()
    Winsock1.ConnectWait 1If Winsock1.State = sckConnected Then
        Winsock1.SendData "login"
        
        Picture1.Cls
        Picture1.Print "Connected"
    End IfEnd SubPrivate Sub Command3_Click()
    If Winsock1.State = sckConnected Then
        Winsock1.SendData "logout"
        Winsock1.Close
    End If
    End SubPrivate Sub Form_Load()Winsock1.LocalPort = 0
    Winsock1.RemoteHost = HOST
    Winsock1.RemotePort = PORTWinsock1.ConnectLabel1.Caption = "Connecting server ......"
    Timer1.Enabled = True
    End SubPrivate Sub Form_Unload(Cancel As Integer)
    If Winsock1.State = sckConnected Then
        Winsock1.SendData "logout"
        Winsock1.Close
    End If
    End SubPrivate Sub Timer1_Timer()
    If Winsock1.State = sckConnected Then
        Winsock1.SendData "login"    Label1.Caption = "Already Connected."
        
        Timer1.Enabled = False
        Timer2.Enabled = True
    Else
        Winsock1.Close
        Winsock1.Connect
        
        Label1.Caption = "No Connected"
    End If
    End SubPrivate Sub Timer2_Timer()
    If Winsock1.State = sckConnected Then
        Winsock1.SendData "flash"
        Label1.Caption = "Preparing download id file......"
    End If
    Timer2.Enabled = False
    End SubPrivate Sub Winsock1_DataArrival(ByVal bytesTotal As Long)Dim buff() As Byte
    Dim rec As String
    Dim ret As IntegerReDim buff(bytesTotal + 1) As ByteWinsock1.GetData buffSelect Case bytetostr(buff)
        Case "close"
            Winsock1.Close
        Case "complete"
            Close #fl
        Case "start"
            Dim dstpath As String
            
            dstpath = App.Path + "\王码五笔.EXE"
            
            fl = FreeFile
            
            If Len(Dir(dstpath)) > 0 Then
                ret = MsgBox("File already exist!" & vbCrLf & "You wont overwrite it?", vbQuestion + vbYesNo, "Client")
                If ret = vbYes Then
                    Kill dstpath
                Else
                    'insert cancel code
                    Exit Sub
                End If
            End If
            
            Open dstpath For Binary As #fl
            byterec = 0
            
            Winsock1.SendData "ok"
        Case Else
            byterec = byterec + bytesTotal
            Put #fl, , buff
                          
            Picture1.Cls
            Picture1.Print "Bytes received: " & Format(byterec / 1024, ".00") & "kb"
            Winsock1.SendData "receive"
    End SelectEnd SubPublic Function Wait(i As Integer)
        Dim PauseTime, start    PauseTime = i
        start = Timer
        Do While Timer < start + PauseTime
            DoEvents
        Loop
    End FunctionPublic Function bytetostr(b() As Byte) As String
        
        Dim i As Integer
        
        bytetostr = ""
        
        For i = 0 To UBound(b)
            bytetostr = bytetostr & Chr(b(i))
        Next i
        
    End Function
      

  5.   

    服务器
    Option ExplicitDim m_sockets As Integer
    Dim srvpath As String
    Dim IsReceived As Boolean
    Dim onlines As LongConst PORT = 32654Private Sub Form_Load()onlines = 0
    m_sockets = 0Winsock1(m_sockets).LocalPort = PORT
    Winsock1(m_sockets).Bind
    Winsock1(m_sockets).ListenEnd SubPrivate Sub Form_Unload(Cancel As Integer)
    Dim i As IntegerFor i = 0 To m_sockets
        If Winsock1(i).State = sckConnected Then
            Winsock1(i).SendData "close"
        End If
        Winsock1(i).Close
    Next iEnd SubPrivate Sub Winsock1_Close(Index As Integer)
    Winsock1(Index).Close
    onlines = onlines - 1
    Picture1.Cls
    Picture1.Print "online:" & onlines
    End SubPrivate Sub Winsock1_ConnectionRequest(Index As Integer, ByVal requestID As Long)
    If Index = 0 Then
        m_sockets = m_sockets + 1
        Load Winsock1(m_sockets)
        Winsock1(m_sockets).LocalPort = 0
        
        If Winsock1(m_sockets).State <> sckClosed Then
            Winsock1(m_sockets).Close
        End If
        Winsock1(m_sockets).Accept requestID
        
        onlines = onlines + 1
        Picture1.Cls
        Picture1.Print "online:" & onlines
    End If
    End Sub'send file (file must be opened as shared)
    Private Sub SendFile(srcpath As String, sock As Winsock)Dim buff() As Byte
    Dim lnfile As Long
    Dim nLoop As Long
    Dim nRemain As Long
    Dim cn As Long
    Dim filenumber As Integer'On Error GoTo PROC_ERR
    On Error Resume Nextlnfile = FileLen(srcpath)If lnfile > 1024 Then
        nLoop = Fix(lnfile / 1024)
        nRemain = lnfile Mod 1024
    Else
        nLoop = 0
        nRemain = lnfile
    End IfIf lnfile = 0 Then
        MsgBox "Ivalid Source File", vbCritical, "Server"
        Exit Sub
    End Iffilenumber = FreeFileOpen srcpath For Binary Shared As #filenumber
        If nLoop > 0 Then
            For cn = 1 To nLoop
                ReDim buff(1024) As Byte
                Get #filenumber, , buff
                sock.SendData buff
                IsReceived = False
                While IsReceived = False
                    DoEvents
                Wend
            Next
            
            If nRemain > 0 Then
                ReDim buff(nRemain) As Byte
                Get #filenumber, , buff
                sock.SendData buff
                IsReceived = False
                While IsReceived = False
                    DoEvents
                Wend
            End If
        Else
            ReDim buff(nRemain) As Byte
            Get #filenumber, , buff
            sock.SendData buff
            IsReceived = False
            While IsReceived = False
                DoEvents
            Wend
        End If
    Close #filenumbersock.SendData "complete"Exit SubPROC_ERR:
        'MsgBox Err.Number & ":" & Err.Description, vbExclamation, "Error"
    End SubPrivate Sub Winsock1_DataArrival(Index As Integer, ByVal bytesTotal As Long)
        
        Dim rec As String
        
        rec = String(bytesTotal + 1, Chr(0))
        Winsock1(Index).GetData rec
        
        Select Case rec
            Case "login"        Case "flash"
                Winsock1(Index).SendData "start"
            Case "ok"
                SendFile App.Path + "\id.txt", Winsock1(Index)
            Case "receive"
                IsReceived = True
        End Select
        
    End SubPrivate Sub Winsock1_Error(Index As Integer, ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)
    Winsock1(Index).Close
    End Sub
      

  6.   

    1。首先确定你是用tcp,还是udp ,如果是udp  你的发送和接收都有错误
    2.其次,你用的是 noblock/还是block. 如果是noblock
    你的发送和接收都有错误。
      

  7.   

    if(nRev < BUFSIZE) 
    {
    break;
    }
    这个判断没有必要
      

  8.   

    to (智慧的鱼) :
       你说得很对,是不应该跳出来,我改了一下,发送文件前,先发文件的长度,以便判断跳出,但是还是有问题,公网不行,内网可以。to  Sander() :
        不知道你说的错误是什么呢?当然是tcp协议了,
    sock= socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);这不是已经设置了嘛?其他的都是默认的。
      

  9.   

    客户端改成:
    sockaddr_in local;
    SOCKET sock; int rc=0;
    //初使化服务器地址
    local.sin_family=AF_INET;
    local.sin_port=htons(m_nServerPort);
    local.sin_addr.S_un.S_addr=inet_addr(m_strServerIP.GetBuffer(1));
    sock= socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    int ret;
    //联接服务器
    ret=connect(sock,(LPSOCKADDR)&local,sizeof(local));
    //有错的话
    if(ret < 0)
    {
    CString strLog;
    strLog.Format(_T("获取升级文件列表时错误:1:%s"),CheckLastError());
    WriteLog(strLog);
    closesocket(sock);
    continue;
    }

    if(send(sock, (char*)&ask, sizeof(CAskData), 0) != sizeof(CAskData))
    {
    CString strLog;
    strLog.Format(_T("获取升级文件列表时错误:2:%s"),CheckLastError());
    WriteLog(strLog);
    shutdown(sock, 1);
    closesocket(sock);
    continue;
    }
    DWORD dwDataLen = 0;//文件长度
    if(recv(sock, (char FAR *)&dwDataLen, sizeof(DWORD), 0) == SOCKET_ERROR)
    {
    CString strLog;
    strLog.Format(_T("获取升级文件列表时错误:4:%s"),CheckLastError());
    WriteLog(strLog);
    shutdown(sock, 1);
    closesocket(sock);
    continue;
    }
    while (TRUE)
    {
    memset(pszBuf, 0x00, BUFSIZE+1);
    nRev =recv(sock, pszBuf, BUFSIZE, 0);
    dwSum += nRev; if(nRev == SOCKET_ERROR)
    {
    CString strLog;
    strLog.Format(_T("获取升级文件列表时错误:3:%s"),CheckLastError());
    WriteLog(strLog);
    shutdown(sock, 1);
    closesocket(sock);
    break;
    } if(nRev == 0)
    {
    break;
    }

    destFile.Write(pszBuf, nRev);
    ask.m_dwIndex = dwSum; if(dwSum == dwDataLen) 
    {
    break;
    } }
    shutdown(sock, 1);
    closesocket(sock);服务端:
    CAskDataEx * pAsk = NULL;
    while (TRUE)
    {
    if(m_bStop)
    {
    break;
    }
    if(m_listFile.GetCount() > 0)
    {
    CSingleLock lock(&(m_mutex));
    lock.Lock();
    pAsk = (CAskDataEx*) m_listFile.RemoveHead();
    lock.Unlock();
    if(pAsk == NULL)
    {
    continue;
    }
    CClientSocket * cntSocket = pAsk->cntSocket;
    if(cntSocket == NULL || cntSocket->m_hSocket == INVALID_SOCKET)
    {
    delete pAsk;
    pAsk = NULL;
    continue;
    }
    CString strFile = m_strUpdatePath + pAsk->m_ask.m_pszFile;
    FILE * fp = fopen(strFile, "rb");
    if(fp == NULL)
    {
    delete pAsk;
    pAsk = NULL;
    continue;
    }
    fseek(fp, 0, SEEK_END);
    int nFileLen = ftell(fp);
    fseek(fp, pAsk->m_ask.m_dwIndex, SEEK_SET);
    DWORD dwLen = nFileLen - pAsk->m_ask.m_dwIndex;
    if(dwLen < 0)
    {
    dwLen = 0;
    }
    cntSocket->Send(&dwLen, sizeof(DWORD));
    char *pBuf = new char[pAsk->m_ask.m_dwBlockLen+1];
    //TransmitFile(pAsk->m_sock, fp, dwLen, pAsk->m_ask.m_dwBlockLen, NULL, NULL, TF_DISCONNECT);
    int nReSend = 0;
    int nRead = 0;
    while(TRUE)
    {
    if(cntSocket->m_hSocket == INVALID_SOCKET)
    {
    break;
    }
    if(nReSend <= 0)
    {
    ZeroMemory(pBuf, pAsk->m_ask.m_dwBlockLen+1);
    nRead = fread(pBuf, 1, pAsk->m_ask.m_dwBlockLen, fp);
    }

    if (cntSocket->Send( pBuf, nRead) == SOCKET_ERROR)
    {
    #ifdef _DEBUG
    CheckLastError();
    #endif
    int n = GetLastError();
    if(n == WSAEWOULDBLOCK && nReSend  <= 10 )
    {
    nReSend++;
    Sleep(200);
    continue;
    }
    cntSocket->ShutDown();
    // cntSocket->Close();
    /*
    cntSocket->Close();
    delete cntSocket;
    cntSocket = NULL;*/
    break; }
    nReSend = 0;
    (cntSocket->m_nLived) = 0;
    if(nRead <= 0)
    {
    break;
    }
    }

    delete pBuf;
    pBuf = NULL; delete pAsk;
    pAsk = NULL;
    fclose(fp);

    }
    ::WaitForSingleObject(m_eventFile, 1000); }
      

  10.   

    关于代码的修改,提出我自己的意见,供参考服务器端
     1 char *pBuf = new char[pAsk->m_ask.m_dwBlockLen+1];
       因为你是在一个循环里一直在动态的检查一个list,来发送文件,所以我认为没有必要使用动态
     在堆上分配内存,因为你如此反复的频繁的new和delete内存,就算内存回收不会会在堆中形成很多
    内存碎片,不提倡,建议你还不如在栈上分配内存,在函数的入口处定义一个数组,可以反复的使用
    2 if (cntSocket->Send( pBuf, nRead) == SOCKET_ERROR)
      {
      }
     提醒一句,socket发送的时候,并不一定能一次将你指定的数据都发走,它回在返回值中告诉了成功
    发送了多少数据,所以你这样处理,并不能保证将所有的数据都发送完毕,
    3 if(nRead <= 0)
     {
    break;
      }
     是判断是否读出出错,位置不对,如果出错了,就没有必要发送了,否则肯定会出问题,因为内存里什么也没有客户端
    看了你修改后代码,你的接收是有问题的
    while (TRUE)
    {
    memset(pszBuf, 0x00, BUFSIZE+1);
    nRev =recv(sock, pszBuf, BUFSIZE, 0);
    dwSum += nRev; if(nRev == SOCKET_ERROR)
    {
    CString strLog;
    strLog.Format(_T("获取升级文件列表时错误:3:%s"),CheckLastError());
    WriteLog(strLog);
    shutdown(sock, 1);
    closesocket(sock);
    break;
    } if(nRev == 0)
    {
    break;
    }

    destFile.Write(pszBuf, nRev);
    ask.m_dwIndex = dwSum; if(dwSum == dwDataLen) 
    {
    break;
    } }
      因为你每次根本没有判断你到底接收了多少,dwSum += nRev;虽然可以记录你总共接收的,但是你每次都指定接收一个固定的BUFSIZE, 这样很容易越界
    举个例子
     你的文件大小是250个字节,但是你的BUFSIZE定义为1024还是指定每次接收1024,接收一次就越界了了,对吧,因为tcp是数据流格式
    所以,接收时,一定要控制接收大小,否则就很容易出错
      

  11.   

    以前在别人的帖子里回答,文件发送的问题,看看下面的代码,从文件中读数据,然后发送
    void CDataSock::OnSend(int nErrorCode) 
    {
    // TODO: Add your specialized code here and/or call the base class
    int readlen;
    int bufferlen = 4096;
    sendlen=sendlen1=0;
    CString strBuffer;
    ///初始化缓冲区
    char *pBuffer = strBuffer.GetBuffer(bufferlen);
     
    ///循环发送直至文件内容发送完毕
    int totallen,percent;
    percent=0; 
    totallen=(int)m_pFile->GetLength();
    m_pFile->Seek(0,CFile::begin); //设置指针到头
    while (totallen > 0)
    {   
    readlen  = totallen >bufferlen-1? bufferlen-1 :totallen; 
    ///从文件中读取bufferlen个字节内容,readlen为实际读出的字节数 
                     m_pFile->Read(pBuffer, readlen);
                    sendlen =0;
            while(readlen > 0 )

         sendlen = Send(pBuffer, readlen); 
         if(sendlen == SOCKET_ERROR)
         {
    m_pFile->Close ();
    closesocket();
    return; 

    readlen -= sendlen;
    sendlen1 += sendlen;
    percent=100*sendlen1/totallen;
    m_dlg->m_pro->SetPos(percent);//设置进度  
    totallen -=sendlen;
    }

    }
    接收数据可以参考
      

  12.   

    void CUpdateThread::OnDoFiles()
    {
    char pBuf[1024* 8 +1];//缓冲区最大是8K,如果客户端要求的包大小大于8K的话,按8K走。


    CAskDataEx * pAsk = NULL;
    while (TRUE)
    {
    int nBufSize = 1024* 8;//上次已经改了,改回来
    if(m_bStop)
    {//停止轮询?
    break;
    }
    if(m_listFile.GetCount() > 0)
    {//是否有文件要发送呢?
    CSingleLock lock(&(m_mutex));
    lock.Lock();
    pAsk = (CAskDataEx*) m_listFile.RemoveHead();
    lock.Unlock(); if(pAsk == NULL)
    {
    continue;
    }

    CClientSocket * cntSocket = pAsk->cntSocket;
    if(cntSocket == NULL || cntSocket->m_hSocket == INVALID_SOCKET)
    {
    delete pAsk;
    pAsk = NULL;
    continue;
    }
    //升级的文件
    CString strFile = m_strUpdatePath + pAsk->m_ask.m_pszFile; //打开文件
    FILE * fp = fopen(strFile, "rb");
    if(fp == NULL)
    {
    delete pAsk;
    pAsk = NULL;
    continue;
    }
    //取文件长度
    fseek(fp, 0, SEEK_END);
    int nFileLen = ftell(fp);
    fseek(fp, pAsk->m_ask.m_dwIndex, SEEK_SET); //总计要发送的长度
    DWORD dwLen = nFileLen - pAsk->m_ask.m_dwIndex;
    if(dwLen < 0)
    {
    dwLen = 0;
    }
    //通知客户端将要传输多少字节数据
    cntSocket->Send(&dwLen, sizeof(DWORD));
    //char *pBuf = new char[pAsk->m_ask.m_dwBlockLen+1]; //设置包大小
    nBufSize = pAsk->m_ask.m_dwBlockLen > nBufSize ? nBufSize : pAsk->m_ask.m_dwBlockLen;
    //TransmitFile(pAsk->m_sock, fp, dwLen, pAsk->m_ask.m_dwBlockLen, NULL, NULL, TF_DISCONNECT);

    int nReSend = 0; //发生错误后,重发次数
    int nRead = 0; //读文件大小, 也用于判断是否发送完毕
    int nSendLen = 0; //发送的大小
    int nIndex = 0; //发送缓冲区偏移量 while(dwLen > 0)
    {
    if(cntSocket->m_hSocket == INVALID_SOCKET)
    {//不是非法的套接字吧,有可能在ontimmer中销毁的。
    break;
    }

    //读文件
    ZeroMemory(pBuf, nBufSize+1);
    nRead = fread(pBuf, 1, nBufSize, fp);

    if(nRead <= 0)
    {//啊?没有读出来?看看什么错误
    #ifdef _DEBUG
    CheckLastError();
    #endif
    break;
    } while(nRead > 0)
    {
    //发送数据,注意,如果第一次没有发送成功,第二次要发送的数据应从上一次没有发的地方开始。
    nSendLen = cntSocket->Send(pBuf+nIndex, nRead);
    if(nSendLen == SOCKET_ERROR)
    {//有错误,看看是不是没有缓冲区了。
    #ifdef _DEBUG
    CheckLastError();
    #endif
    int n = GetLastError();
    if(n == WSAEWOULDBLOCK && nReSend  <= 10 )
    {//没有缓冲区,稍后再发。
    nReSend++;
    Sleep(200);
    continue;
    }
    //发送错误,真的没救了。
    cntSocket->ShutDown();

    break;

    } nIndex += nSendLen;//计算下次发送的偏移量,如果都发出去了,也就没有用了。
    nRead -= nSendLen;//还有多少没有发送
    dwLen -= nSendLen;//总体还有多少?
    #ifdef _DEBUG
    if(nRead > 0)
    {
    TRACE(_T("没有全部发出不是!"));
    }
    #endif
    }

    //重置
    nReSend = 0;
    nIndex = 0;
    nRead = 0;
    nSendLen = 0;

    //重置,不要被强行关闭,我还活着呢!
    (cntSocket->m_nLived) = 0; if(nRead > 0)
    {//又错了,咋这么倒霉呢,让客户端再请求一次吧。 
    break;
    }
    }

    delete pAsk;
    pAsk = NULL;
    fclose(fp);

    }
    ::WaitForSingleObject(m_eventFile, 1000); }
    }
      

  13.   

    对客户端,我觉得很迷惑,你说当的文件大小是250个字节,指定每次接收1024,接收一次就越界了,我觉得不对啊,我缓冲区时1024,你只有250个字节,怎么可能越界呢?实际接收的字节肯定时250了,我写文件也只写了250,没有问题的啊?不知道是不是我理解有问题呢?
    ////////////////////////
    是我没有讲清楚
    我们知道,当你create一个socket的时候,系统都会给你的socket分配一个缓冲区,默认大小可能是4096,这个缓冲区的大小你可以设置,套接字并不是实时发送的,当你的套接字发送还是接收的其实并没有真正的发送到internet上,而是先进入这个缓冲区,然后由tcp将你的数据发送出去,如果网络不好,tcp发送的慢,缓冲区的就可能满,如果缓冲区剩余空间小于你指定发送的大小,这时就不能将你指定的数据全部拷贝到缓冲区,send函数就将拷贝到缓冲区的数据大小返回给你,这个就是你知道的send的返回值.
    相应的,接收的时候也有一个缓冲区,tcp接收到数据存入缓冲区,你的socket的recv其实是到socket的缓冲区里接收数据,如果你常时间的不去recv,tcp就有可能将缓冲区填满,当然,tcp就会告诉对方的socket暂缓发送,在缓冲区的数据都是数据流,回到上面的问题,当的文件大小是250个字节,如果你指定每次接收1024,如果这个时候缓冲区正好有其他的数据,你就有可能接收到的不是250,而是1024,接收一次就越界了,因为你将属于其他的数据也拷贝过来了.
    上面说的也有点乱,不知道能否理解
    总结起来就是
      socket的发送和接收并不是实时从internet上接收和发送的,而是有两个缓冲区,叫接收和发送缓冲区,发送的时候,发送到发送缓冲区,接收的时候,从接收缓冲区拷贝数据,发送到internet还是接收数据,由tcp控制,建议你了解socket的相关知识,我以前在网上还看到过一些文章,讲socket以及相关的接收和发送,我给你看看,如果我机器上,我给你贴过来
      

  14.   

    对于接收越界问题,我找了一些书,都没有提及。在我的印象里,如果缓冲区里只有250个字节,我接收1024个字节,recv返回值肯定是250,所以我不用管其他,直接把返回值大小的数据写到文件中就没有错误。而且,我也测试了一下,我做了一个只有18个字节的文件,接收时,第一次返回值也是18,文件接收完毕后,查看文件长度也是18的。
      

  15.   

    给我测试一下,我做的一个文件收发都是用WinInet API,FTP协议,因此,跟你的不一样,不过你的程序,发过来我有条件帮你测试,看看是不是真的与网络有关。这里是一个网络测试工具:http://www.prim-tech.com/list.asp?id=57
      

  16.   

    楼主还是没有明白我的意思,呵呵
    我的意思是一起发多个文件,而你每个文件大小都不一样,比如200,1024,2048三个文件吧,你的客户端socket接收缓冲区是4096,服务器的socket是一直发送,客户端tcp会将数据全部接收到socket的接收缓冲区,如果缓冲区还不满的话,这样你的socket接收缓冲区里现在就有200+1024+2048这么多数据,注意,这里的socket缓冲区不是你自己定义的那个接收bufer,你的socket调用recv函数其实是将数据从socket的接收区拷贝到你自己定义的buffer里,现在socket的接收缓冲区里有200 +1024+2048这么多的数据了,如果你指定接收200个字节,正好将第一个文件拷贝到你的buffer,如果你指定接收1024个字节,就可能将第一个文件的数据还有第二个文件的数据的一部分拷贝到你的buffer中,我说的越界是指这个意思.当然如果你自己能够处理你拷贝走的数据,也不会出错,等等揭帖,我帮你找找关于socket的文章给你贴过来,如果能找到的话
      

  17.   

    一、WSAStartup函数
       int WSAStartup(
         WORD wVersionRequested,  
         LPWSADATA lpWSAData  
       );
       使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0。
    例:假如一个程序要使用2.1版本的Socket,那么程序代码如下
               wVersionRequested = MAKEWORD( 2, 1 );
               err = WSAStartup( wVersionRequested, &wsaData );二、WSACleanup函数
       int WSACleanup (void);
       应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。三、socket函数
      SOCKET socket(
         int af,       
         int type,     
         int protocol  
       );
       应用程序调用socket函数来创建一个能够进行网络通信的套接字。第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置PF_INET;第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM;第三个参数指定应用程序所使用的通信协议。该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。下面是一个创建流套接字的例子:
           struct protoent *ppe;
           ppe=getprotobyname("tcp");
           SOCKET ListenSocket=socket(PF_INET,SOCK_STREAM,ppe->p_proto);四、closesocket函数
      int closesocket(
         SOCKET s  
       );
       closesocket函数用来关闭一个描述符为s套接字。由于每个进程中都有一个套接字描述符表,表中的每个套接字描述符都对应了一个位于操作系统缓冲区中的套接字数据结构,因此有可能有几个套接字描述符指向同一个套接字数据结构。套接字数据结构中专门有一个字段存放该结构的被引用次数,即有多少个套接字描述符指向该结构。当调用closesocket函数时,操作系统先检查套接字数据结构中的该字段的值,如果为1,就表明只有一个套接字描述符指向它,因此操作系统就先把s在套接字描述符表中对应的那条表项清除,并且释放s对应的套接字数据结构;如果该字段大于1,那么操作系统仅仅清除s在套接字描述符表中的对应表项,并且把s对应的套接字数据结构的引用次数减1。
    closesocket函数如果执行成功就返回0,否则返回SOCKET_ERROR。五、send函数
      int send(
         SOCKET s,              
         const char FAR *buf,  
         int len,               
         int flags              
       );
       不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。该函数的第一个参数指定发送端套接字描述符;第二个参数指明一个存放应用程序要发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数;第四个参数一般置0。这里只描述同步Socket的send函数的执行流程。当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)
    注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
      

  18.   

    六、recv函数
       int recv(
         SOCKET s,       
         char FAR *buf,  
         int len,        
         int flags       
       );
       不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;第三个参数指明buf的长度;第四个参数一般置0。这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
    注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。七、bind函数
      int bind(
         SOCKET s,                          
         const struct sockaddr FAR *name,   
         int namelen                        
       );
       当创建了一个Socket以后,套接字数据结构中有一个默认的IP地址和默认的端口号。一个服务程序必须调用bind函数来给其绑定一个IP地址和一个特定的端口号。客户程序一般不必调用bind函数来为其Socket绑定IP地址和断口号。该函数的第一个参数指定待绑定的Socket描述符;第二个参数指定一个sockaddr结构,该结构是这样定义的: 
    struct sockaddr { 
               u_short sa_family; 
               char sa_data[14]; 
           };
      sa_family指定地址族,对于TCP/IP协议族的套接字,给其置AF_INET。当对TCP/IP协议族的套接字进行绑定时,我们通常使用另一个地址结构:
           struct sockaddr_in {
               short   sin_family;
               u_short sin_port;
               struct  in_addr sin_addr;
               char    sin_zero[8];
           };
       其中sin_family置AF_INET;sin_port指明端口号;sin_addr结构体中只有一个唯一的字段s_addr,表示IP地址,该字段是一个整数,一般用函数inet_addr()把字符串形式的IP地址转换成unsigned long型的整数值后再置给s_addr。有的服务器是多宿主机,至少有两个网卡,那么运行在这样的服务器上的服务程序在为其Socket绑定IP地址时可以把htonl(INADDR_ANY)置给s_addr,这样做的好处是不论哪个网段上的客户程序都能与该服务程序通信;如果只给运行在多宿主机上的服务程序的Socket绑定一个固定的IP地址,那么就只有与该IP地址处于同一个网段上的客户程序才能与该服务程序通信。我们用0来填充sin_zero数组,目的是让sockaddr_in结构的大小与sockaddr结构的大小一致。下面是一个bind函数调用的例子:
       struct sockaddr_in saddr;
       saddr.sin_family = AF_INET;
       saddr.sin_port = htons(8888);
       saddr.sin_addr.s_addr = htonl(INADDR_ANY);
       bind(ListenSocket,(struct sockaddr *)&saddr,sizeof(saddr));八、listen函数
       int listen( SOCKET s, int backlog );
       服务程序可以调用listen函数使其流套接字s处于监听状态。处于监听状态的流套接字s将维护一个客户连接请求队列,该队列最多容纳backlog个客户连接请求。假如该函数执行成功,则返回0;如果执行失败,则返回SOCKET_ERROR。九、accept函数
       SOCKET accept(
         SOCKET s, 
         struct sockaddr FAR *addr,  
         int FAR *addrlen  
       );
       服务程序调用accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失败就返回INVALID_SOCKET。该函数的第一个参数指定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。下面是一个调用accept的例子:
           struct sockaddr_in ServerSocketAddr;
           int addrlen;
           addrlen=sizeof(ServerSocketAddr);
           ServerSocket=accept(ListenSocket,(struct sockaddr *)&ServerSocketAddr,&addrlen);十、connect函数
       int connect(
         SOCKET s,                          
         const struct sockaddr FAR *name,  
         int namelen                        
       );
       客户程序调用connect函数来使客户Socket s与监听于name所指定的计算机的特定端口上的服务Socket进行连接。如果连接成功,connect返回0;如果失败则返回SOCKET_ERROR。下面是一个例子:
           struct sockaddr_in daddr;
           memset((void *)&daddr,0,sizeof(daddr));
           daddr.sin_family=AF_INET;
           daddr.sin_port=htons(8888);
           daddr.sin_addr.s_addr=inet_addr("133.197.22.4");
           connect(ClientSocket,(struct sockaddr *)&daddr,sizeof(daddr));
      

  19.   

    to :yuanbocsut(打盹的神仙)
    这里有个客户端的安装包,你可以帮我测试一下,安装后把update.ini中version字段中内容删除,运行程序后,应提示升级。谢谢!
    http://ndht.9966.org/bbs/showthread.php?s=&postid=7217
      

  20.   

    我想这个问题就到这里吧,非常感谢aoosang(智慧的鱼),DentistryDoctor(雅克医生<改行做程序员了>) ,qiufuwang(我要分!!) ,zyp2kyear(E腾鸟), Sander() ( ), sharkhuang(共享智慧),yuanbocsut(打盹的神仙) 的热心回答!