UDP文件传输的实现

 

/*******************************************************
*       FilePoster关键代码
*E-mail:    andy.zhshg@163.com
*日期:        2008.12.25
*
*程序描述:
*FilePoster是基于Win32平台的网络文件传输程序。开发平台为
*Visual C++6.0。
*程序采用服务器/客户机模式,服务器用于接收数据,客户机负
责发送数据。利用windows多线程原理,集接收和发送功能于一
体。
*网络传输采用UDP原理,为解决UDP传输的不可靠性,用Windows
*消息对收发的双方进行同步,即发送方先发送消息请求传送文件
*同时发送要传文件的基本信息,收取方收到后回复发送发一个确
*认消息,然后发送方依次发送被分成固定大小块(256字节)的文
*件数据,收取方每收到一个数据块就写入自己的文件并回复发送
*方一个接收完毕消息,以使得发送方发送下一个数据块。发送方
*发送最后一个数据块时添加发送结束标记,接受方接收完毕后关
*闭文件,至此发送过程结束。
*
*问题阐述:
*程序的缺陷在于只能用于具有固定IP的主机之间的文件传输。在
*处于不同的内网中的主机无能为力。因为对于内网中的计算机其
*IP是主机通过映射后分配来的所以如果不借助于服务器很难从外
*网访问内网的计算机。不过对于处于同一局域网的计算机,本程
*序还是能够完全应付的。
*限于篇幅,与文件传输无关的代码省略,以"......"代替。
*********************************************************/


/*********************************************************
*程序的初始化函数
*********************************************************/
BOOL CFilePosterApp::InitInstance()
{
    //加载套接字库 
    if(!AfxSocketInit())
    {
        AfxMessageBox("加载套接字库失败!");
        return FALSE;
    }
    ......
}

/**********************************************************
*主窗口的初始化函数
*struct RECVPARAM 用于传递线程数据
**********************************************************/
struct RECVPARAM
{
    HWND hWnd;
    SOCKET sock;
};
BOOL CFilePosterDlg::OnInitDialog()
{
    ......
    //设定进度条的步进值 
    m_progress.SetStep(1);
    m_progress_r.SetStep(1);
    //创建套接字 
    m_socket=socket(AF_INET,SOCK_DGRAM,0);
    if(INVALID_SOCKET==m_socket)
    {
        MessageBox("套接字创建失败!");
        return FALSE;
    }
    SOCKADDR_IN addrSock;
    addrSock.sin_family=AF_INET;
    addrSock.sin_port=htons(6800);
    addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
    //绑定套接字 
    int retval;
    retval=bind(m_socket, (SOCKADDR*)&addrSock,
    sizeof(SOCKADDR));
    if(SOCKET_ERROR==retval)
    {
        closesocket(m_socket);
        MessageBox("绑定失败!");
        return FALSE;
    }
    //产生一个用于接收数据的线程 
    struct RECVPARAM *pRecvParam=new RECVPARAM;
    pRecvParam->sock=m_socket;
    pRecvParam->hWnd=m_hWnd;
    HANDLE hThread=CreateThread(NULL, 0, RecvProc,
        (LPVOID)pRecvParam, 0, NULL);
    CloseHandle(hThread);
    ......
}
/**************************************************************
*接收线程的回调函数
*发送的数据块结构解析:
*类别 字节号     内容
*H      1           通信头,申请发送
*H      2~257       文件名
*H      258~...     文件大小
*D      1           通信头,拒绝接收
*R      1           通信头,同意接收
*F      1           通信头,发送文件块
*F      2~17        保留
*F      18~...      文件数据块
*E      1           通信头,文件尾发送
*E      2~17        块大小
*E      18~...      文件数据块
**************************************************************/

DWORD WINAPI CFilePosterDlg::RecvProc(LPVOID lpParameter)
{
    SOCKET sock=((RECVPARAM*)lpParameter)->sock;
    HWND hWnd=((RECVPARAM*)lpParameter)->hWnd;
    delete lpParameter; 

    SOCKADDR_IN addrFrom;
    int len=sizeof(SOCKADDR);
    char recvBuf[0x112];        //256+17字节的接受缓冲数组 
    char fileName[0x100];       //256字节的文件名存储区 
    int retval, i;
    FILE* file = NULL;

    while(TRUE)
    {
        //接受UDP数据 
        retval=recvfrom(sock, recvBuf, 0x112, 0,
            (SOCKADDR*)&addrFrom, &len);
        
        if(SOCKET_ERROR == retval)
            break;
        //收到消息头为'R',即对方同意让你继续发送数据 
        if (recvBuf[0] == 'R')
        {
            char wParam = 'R';
            ::PostMessage(hWnd, WM_READY_TO_RECEIVE,
                (WPARAM)&wParam, 0);
        }
        //收到消息头为'D',即对方拒绝让你继续发送数据 
        else if (recvBuf[0] == 'D')
        {
            char wParam = 'D';
            ::PostMessage(hWnd, WM_READY_TO_RECEIVE,
                (WPARAM)&wParam, 0);
        }
        //收到消息头为'H',即对方申请给你发送信息,并送来文件的信息 
        else if (recvBuf[0] == 'H')
        {   
            //从收到的数据中提取文件名信息         
            for (i = 1; i <= 0x100 && recvBuf[i] != '/0'; i++)
                fileName[i-1] = recvBuf[i];
            fileName[i-1] = '/0';
            //从收到的数据中提取文件大小信息 
            CString recvMsg;
            nFileSize = atoi(&recvBuf[0x101]);
            recvMsg.Format("收到来自于(%s)的文件:%s/n文件大小:%i字节/n是否接收?",
            inet_ntoa(addrFrom.sin_addr), fileName, nFileSize);
            //用消息框提示用户有人要发送文件 
            if (IDOK == AfxMessageBox(recvMsg, MB_OKCANCEL))
            {
                //若用户同意接收,提供一个文件保存对话框用于设定保存的路径 
                CFileDialog saveDlg(false, NULL, fileName);
                if (IDOK == saveDlg.DoModal())
                {
                    //创建一个文件用于复制接收的文件数据 
                    if (!(file = fopen(saveDlg.GetPathName(), "wb")))
                    {
                        AfxMessageBox("创建本地文件失败!");
                        continue;
                    }
                    char wParam = 'H';
                    ::PostMessage(hWnd, WM_READY_TO_RECEIVE, 
                        (WPARAM)&wParam, (LPARAM)&addrFrom);                    
                }
                else
                {
                    char wParam = 'C';
                    ::PostMessage(hWnd, WM_READY_TO_RECEIVE, 
                        (WPARAM)&wParam, (LPARAM)&addrFrom);
                }
            }
            else    //用户拒绝接收 
            {
                char wParam = 'C';
                ::PostMessage(hWnd, WM_READY_TO_RECEIVE, 
                    (WPARAM)&wParam, (LPARAM)&addrFrom);
            }
        }
        //收到的消息头为'F',即对方发来的是文件数据 
        else if (recvBuf[0] == 'F')
        {
            //将文件数据写入本地文件中 
            fwrite(&recvBuf[0x12], 1, 0x100, file);
            char wParam = 'F';
            ::PostMessage(hWnd, WM_READY_TO_RECEIVE, 
                (WPARAM)&wParam, (LPARAM)&addrFrom);
        }
        //收到的消息头为'E',即对方发来最后一个数据块 
        else if (recvBuf[0] == 'E')
        {
            //获取数据块的大小 
            int bufSize = atoi(&recvBuf[1]);
            //将数据块写入本地文件,并关闭文件 
            fwrite(&recvBuf[0x12], 1, bufSize, file);
            fclose(file);
            char wParam = 'E';
            ::PostMessage(hWnd, WM_READY_TO_RECEIVE, 
                (WPARAM)&wParam, (LPARAM)&addrFrom);
        }
        //收到未定义的数据头 
        else
            AfxMessageBox("传送数据过程中出现错误!");
    }
    return (DWORD)NULL;
}
/*************************************************************
*按下发送键,发出文件信息的函数
*************************************************************/
void CFilePosterDlg::OnOK() 
{
    if (m_posting)  //bool m_posting 表示程序是否正在发送文件 
    {
        MessageBox("数据发送中,请稍候再试。");
        return;
    }

    UpdateData();
    
    if (m_filePath == "")
    {
        MessageBox("请输入要发送的文件路径!");
        return;
    }

    if (m_IPAddr.IsBlank())
    {
        MessageBox("请添入接收者的IP地址。");
        return;
    }

    WIN32_FIND_DATA FindFileData;

    if (INVALID_HANDLE_VALUE == FindFirstFile(m_filePath, &FindFileData))
    {
        MessageBox("文件路径错误或文件不存在!/n请重新指定文件路径。");
        return;
    }

    DWORD dwIP;
    m_IPAddr.GetAddress(dwIP);

    SOCKADDR_IN addrTo;
    addrTo.sin_family=AF_INET;
    addrTo.sin_port=htons(6800);
    addrTo.sin_addr.S_un.S_addr=htonl(dwIP);
    //构建文件信息数据块 
    char sendBuf[0x112];
    int i;
    //消息头 
    sendBuf[0] = 'H';
    //文件名 
    for (i = 1; i <= 0x100 && FindFileData.cFileName[i-1] != '/0'; i++)
        sendBuf[i] = FindFileData.cFileName[i-1];
    sendBuf[i] = '/0';
    //文件大小 
    _itoa(FindFileData.nFileSizeLow, &sendBuf[0x101], 10);
    sendBuf[0x111] = '/0';
    //发送数据块 
    sendto(m_socket, sendBuf, 0x112, 0,
        (SOCKADDR*)&addrTo, sizeof(SOCKADDR));
    //打开文件,等待读取 
    if (!(m_file = fopen(m_filePath, "rb")))
    {
        MessageBox("读取文件失败!");
    }
    m_nSend = 0;        //已发送的块数 
    m_nFileSize_s = FindFileData.nFileSizeLow;  //文件大小 
    m_progress.SetRange(0, m_nFileSize_s/0x100+1);//设置发送进度条 
    m_posting = true;   //标明发送正进行 
}
/************************************************************
*消息响应函数
*响应自定义消息WM_READY_TO_RECEIVE
************************************************************/
void CFilePosterDlg::OnReadyToRecv(WPARAM wParam,LPARAM lParam)
{
    char sendBuf[0x112];
    DWORD dwIP;
    m_IPAddr.GetAddress(dwIP);

    SOCKADDR_IN addrTo;
    addrTo.sin_family=AF_INET;
    addrTo.sin_port=htons(6800);
    addrTo.sin_addr.S_un.S_addr=htonl(dwIP);

    int nRead;

    switch (*(char*)wParam)
    {
    //对方拒绝接收文件,关闭已打开的文件 
    case 'D':
        MessageBox("对方拒绝接受你发送的文件!");
        fclose(m_file);
        m_posting = false;
        break;
    //对方同意接收文件 
    case 'R':
        nRead = fread(&sendBuf[0x12], 1, 0x100, m_file);
        //读取的文件小于256字节,则读到文件尾 
        if (nRead < 0x100)
        {
            sendBuf[0] = 'E';
            _itoa(nRead, &sendBuf[1], 10);
            sendto(m_socket, sendBuf, nRead+0x12, 0,
                (SOCKADDR*)&addrTo, sizeof(SOCKADDR));
            fclose(m_file);
            m_progress.SetPos(m_nFileSize_s/0x100+1);
            m_posting = false;
            m_send = "发送进度:(100%)";
            UpdateData(false);
            MessageBox("发送完毕!");
            m_progress.SetPos(0);
            m_send = "发送进度:";
            UpdateData(false);
        }
        //读到文件等于256字节,则文件还未读完 
        else
        {
            sendBuf[0] = 'F';
            sendto(m_socket, sendBuf, 0x112, 0,
                (SOCKADDR*)&addrTo, sizeof(SOCKADDR));
            m_progress.StepIt();
            m_nSend++;
            m_send.Format("发送进度:(%.1f%%)",
                (float)m_nSend/(m_nFileSize_s/0x100+1)*100);
            UpdateData(false);
        }
        break;
    //同意接收对方文件 
    case 'H':
        m_progress_r.SetRange(0, nFileSize/0x100+1);
        m_nRecv = 0;
    case 'F':
        sendto(m_socket, "R", 2, 0,
                (SOCKADDR*)lParam, sizeof(SOCKADDR));
        m_progress_r.StepIt();
        m_nRecv++;
        m_recv.Format("接收进度:(%.1f%%)", (float)m_nRecv/(nFileSize/0x100+1)*100);
        UpdateData(false);
        break;
    //接受完毕,提示用户 
    case 'E':
        m_progress_r.SetPos(nFileSize/0x100+1);
        m_recv = "接收进度:(100%)";
        UpdateData(false);
        MessageBox("接收完毕!");
        m_recv = "接收进度:";
        m_progress_r.SetPos(0);
        UpdateData(false);
        break;
    //拒绝接收,通知对方 
    case 'C':
        sendto(m_socket, "D", 2, 0,
                (SOCKADDR*)lParam, sizeof(SOCKADDR));
        break;
    }
}

 

 

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值