1、基于TCPIP协议的Winsock编程原理用MFC实现局域网内点对点的大文件传输相信很多人都用过QQ吧?而QQ的里面“传送文件”的功能也应该有不少的人用过吧?而关于实现文件传输的方法也有很多。趁着暑假的空闲时间,我用VC 6.0 + SP5按照自己的思路也写了一个,程序的界面如图1所示。图1先给大家简单介绍一下基本的思路。这个程序所采用的是基于TCP/IP协议的Winsock编程原理,相信如果对这方面的程序设计有所了解的人都应该知道对于这种编程模型一般都是采用客户机/服务器(Client/Server)方式,在这个程序里面也正是采用这种基本的方法。为了方便使用,我把客户机和服务器合而为一,但其
2、实质还是一样的。 在通信的时候主要可以分为两个部分,一个部分是用于传送控制信息,例如发送文件的请求,文件的名称、大小等,由于这方面的数据量比较小,为了方便起见我采用了MFC所提供的CSocket类的串行化技术来实现;而另一个部分就是文件的传输部分,对于文件的传输,由于数据量相对来说比较大,所以我分别写了两个线程,一个用于发送,一个用于接收。这两个部分在具体实现的时候分别建立有自己的套接字(Socket)。下面,就让我为大家演示一下详细的实现步骤。一.建立一个新工程FileTransfers 使用 MFC AppWizard (exe) 建立一个新项目FileTransfers,选择基于对话框的
3、应用,并在向导的第四步中,选择“Windows Socket”选项,如图2所示其它步骤中都使用缺省值,然后按下“Finish”按钮,创建如图3所示属性的工程。图2图3AppWizard将自动创建如下的类。 类名 定义文件 实现文件 CAboutDlg FileTransfers.cpp FileTransfers.cpp CFileTransfersApp FileTransfers.h FileTransfers.cpp CFileTransfersDlg FileTransfersDlg.h FileTransfersDlg.cpp二.修改资源1.修改主对话框风格 修改AppWizard为
4、我们创建的对话框模风格。点击位于Dialog Properties对话框上面的Style标签,然后按照图4设置风格属性。图42.添加控件 按照图1所示在对话框中加入相应的控件。“服务选择”分组框(Group Box)。用于表明其中的两个单选按钮是属于一组的。它的标题(Caption)为“服务选择”,使用默认的ID值就行了。“服务器(S)”和“客户端(C)”单选按钮(Radio Button)。这两个单选按钮定位在“服务选择”分组框(Group Box)中,用于确定软件当前是服务器或是客户端。将“服务器(S)”按钮的ID设置为IDC_RADIO_SERVER,“客户端(C)”按钮的ID设置为ID
5、C_RADIO_CLIENT,其它的属性分别按照图5和图6所示进行设置。图5图6“IP地址”IP地址控件(IP Address)。当软件做为服务器端时,该控件所显示的是本地的IP地址;当软件做为客户端时,用于输入服务器的IP地址。其ID为IDC_IPADDRESS,其余属性采用默认值。“端口号”编辑控件(Edit Box)。用于确定通讯的端口号。所有属性均按照默认值既可。对话框中部分静态文本控件(Text)的属性如下所示。 ID 标题(Caption) 用途 IDC_FILE_NAME FileName 显示当前正在传输的文件名 IDC_FILE_SIZE 0 字节 显示当前正在传输的文件尺寸
6、IDC_RECEIVE_SIZE 0 字节 显示已经发送或接受的文件尺寸先删除对话框中原有的确定按钮,然后再按照图1所示添加和修改按钮的属性,最终对话框中按钮的属性如下所示。 ID 标题(Caption) 用途 IDC_BEGIN 启 动(&B) 启动服务程序/连接到服务器 IDC_DISCONNECT 关 闭(&D) 关闭服务程序/断开和服务器的连接 IDC_SELECT_FILE 选择文件(&F) 选择要发送的文件IDC_STOP_TRANSFERS 停止传输(&T) 停止文件的传输 IDCANCEL 退出(&Q) 退出程序三.几个辅助类的介绍1.CMessage类 在前面我们说过程序传送
7、控制信息的时候采用的是CSocket类的串行化技术,这样一来使得发送和接收网络数据就像普通的数据串行化一样简单。因此封装一个可以串行化的消息类是必要的,后面我们将会看到有了这个类,消息的发送和接收只需使用流操作符对缓冲区进行存取就可以了。根据程序的需要,消息类CMessage的定义如下:class CMessage : public CObjectpublic: void Serialize(CArchive& ar); CMessage(); CMessage(int nType); CMessage(int nType, CString strFileName, DWORD dwFileS
8、ize); virtual CMessage();public: int m_nType; CString m_strFileName; DWORD m_dwFileSize;其中,m_nType用于标识消息的类型;m_strFileName为文件的名称;m_dwFileSize为文件的大小。 为了方便使用,我对消息类的CMessage的构造函数进行了重载,CMessage()为默认的构造函数,如果只是发送一般的控制信息我们可以使用CMessage(int nType)构造函数,当需要发送文件名及大小的时候我们可以使用CMessage(int nType, CString strFileNam
9、e, DWORD dwFileSize)构造函数,三个构造函数的源代码如下:/默认的构造函数CMessage:CMessage() m_nType = -1; m_strFileName = _T(); m_dwFileSize = 0;/只需发送一般的控制信息是使用CMessage:CMessage(int nType) m_nType = nType; m_strFileName = _T(); m_dwFileSize = 0;/需要发送文件名及大小时使用CMessage:CMessage(int nType, CString strFileName, DWORD dwFileSize)
10、 m_nType = nType; m_strFileName = strFileName; m_dwFileSize = dwFileSize; 重载CObject类中的Serialize函数,其源代码如下:void CMessage:Serialize(CArchive &ar) if (ar.IsStoring() ar m_nType; ar m_strFileName; ar m_nType; ar m_strFileName; ar m_dwFileSize; 在写这个类的时候,我们可以使用任何一个文本编(如记事本或是UltraEdit等)辑器进行编写,把类的定义保存在Messag
11、e.h中,把类的实现保存在Message.cpp文件中。要注意的是我们还需要在Message.cpp文件的首部加上#include stdafx.h和#include Message.h两行代码,将stdafx.h文件和Message.h文件包含进来。最后我们还需要把这个类加到工程中,先把Message.h和Message.cpp文件复制到工程目录下,然后在VC中通过Project菜单-Add To Project-Files把这两个文件添加到工程中。2.CListenSocket类 负责监听管理的套接字类CListenSocket。使用ClassView或ClassWizard进行创建,如图
12、7所示。图7 使用ClassView或手工加入如下的函数及成员变量的定义:public: CListenSocket(CFileTransfersDlg* pdlgMain);protected: CFileTransfersDlg* m_pdlgMain; CListenSocket(CFileTransfersDlg* pdlgMain)为重载的构造函数;m_pdlgMain为指向主对话框类CFileTransfersDlg的指针。添加完后我们还需要对类中的两个构造函数的内容进行修改以实现对类的初始化,其源代码如下:CListenSocket:CListenSocket(CFileTran
13、sfersDlg* pdlgMain) m_pdlgMain = pdlgMain;CListenSocket:CListenSocket() m_pdlgMain = NULL; 重载基类的OnAccept函数以使对来自客户的连接请求作出响应,OnAccept函数的源代码如下:void CListenSocket:OnAccept(int nErrorCode) m_pdlgMain-ProcessAccept(); CSocket:OnAccept(nErrorCode); 当该套接字接收到客户的连接请求时,就调用CFileTransfersDlg对象的ProcessAccept()函数进
14、行处理。由于该类中使用到了CFileTransfersDlg类,因此在文件ListenSocket.h的首部还需加入如下头文件的包含语句:#include FileTransfersDlg.h3.CClientSocket类 该类用于连接的管理,其创建的方法与CListenSocket相似,其定义如下:class CClientSocket : public CSocket/ Attributespublic:/ Operationspublic: CClientSocket(); virtual CClientSocket();/ Overridespublic: CSocketFile*
15、m_pFile; CArchive* m_pArchiveIn; CArchive* m_pArchiveOut; void Init(); void Abort(); BOOL SendMsg(CMessage* pMsg); void ReceiveMsg(CMessage* pMsg); CClientSocket(CFileTransfersDlg* pdlgMain); / ClassWizard generated virtual function overrides /AFX_VIRTUAL(CClientSocket) public: virtual void OnReceiv
16、e(int nErrorCode); /AFX_VIRTUAL / Generated message map functions /AFX_MSG(CClientSocket) / NOTE - the ClassWizard will add and remove member functions here. /AFX_MSG/ Implementationprotected: CFileTransfersDlg* m_pdlgMain; 在CClientSocket套接字类封装了串行化功能,这也就是说,该类封装了客户应用程序的大部分功能,因此可以认为该套接字类所管理的就是一个客户应用程序
17、。其中,m_pFile为一个CSocketFile类型的指针用于连接到一个CSocket对象;m_pArchiveIn和m_pArchiveOut均为CArchive类型的指针,分别用于接受和发送。 两个构造函数的源代码如下:CClientSocket:CClientSocket(CFileTransfersDlg* pdlgMain) m_pdlgMain = pdlgMain; m_pFile = NULL; m_pArchiveIn = NULL; m_pArchiveOut = NULL;CClientSocket:CClientSocket() m_pdlgMain = NULL;
18、m_pFile = NULL; m_pArchiveIn = NULL; m_pArchiveOut = NULL; Init成员函数用于串行化的初始化,其源代码如下:void CClientSocket:Init() m_pFile = new CSocketFile(this); m_pArchiveIn = new CArchive(m_pFile,CArchive:load); m_pArchiveOut = new CArchive(m_pFile,CArchive:store); Abort成员函数用于对m_pArchiveOut指针进行释放,其源代码如下:void CClient
19、Socket:Abort() if (m_pArchiveOut != NULL) m_pArchiveOut-Abort(); delete m_pArchiveOut; m_pArchiveOut = NULL; SendMsg成员函数用于发送信息,其源代码如下:BOOL CClientSocket:SendMsg(CMessage *pMsg) if (m_pArchiveOut != NULL) TRY /采用串行化技术进行信息的发送 pMsg-Serialize(*m_pArchiveOut); m_pArchiveOut-Flush(); return TRUE; CATCH(CF
20、ileException, e) m_pArchiveOut-Abort(); delete m_pArchiveOut; m_pArchiveOut = NULL; END_CATCH return FALSE; ReceiveMsg成员函数用于接受信息,其源代码如下:void CClientSocket:ReceiveMsg(CMessage *pMsg) /采用串行化技术进行信息的接收 pMsg-Serialize(*m_pArchiveIn); 重载基类函数OnReceive,使用此函数接受Socket连接另一端发送的信息,其源代码如下:void CClientSocket:OnRec
21、eive(int nErrorCode) m_pdlgMain-ProcessReceive(this); CSocket:OnReceive(nErrorCode); 代码的作用是当有信息发送到时,调用主对话框类的ProcessReceive函数进行信息的接收。由于用到了CMessage和CFileTransfersDlg类,所以在ClientSocket.h文件首部还需加入如下的头文件包含:#include Message.h#include FileTransfersDlg.h四.两个线程 上面所定义的类只实现了控制信息的传送,我们还需要写两个线程_SendThread和_ListenT
22、hread,它们分别用于发送和接收文件。1.发送文件线程UINT _SendThread(LPVOID lparam) CFileTransfersDlg* pDlg = (CFileTransfersDlg*) lparam; /创建套接字 CSocket sockClient; if(!sockClient.Create() pDlg-TransfersFailed(); :MessageBox(HWND)lparam, pDlg-GetError(GetLastError(), _T(错误), MB_ICONHAND|MB_OK); return -1; CString strIPAdd
23、ress; UINT nPort; pDlg-m_psockClient-GetPeerName(strIPAddress, nPort); /连接到服务器端 if(!sockClient.Connect(strIPAddress, pDlg-m_wPort + PORT) pDlg-TransfersFailed(); :MessageBox(HWND)lparam, pDlg-GetError(GetLastError(), _T(错误), MB_ICONHAND|MB_OK); return -1; /调用主对话框类中的SendFile成员函数进行文件的发送 pDlg-SendFile(
24、sockClient); return 0;2.接收文件线程UINT _ListenThread(LPVOID lparam) CFileTransfersDlg* pDlg = (CFileTransfersDlg*)lparam; /创建套接字 CSocket sockSrvr; if(!sockSrvr.Create(pDlg-m_wPort + PORT) pDlg-TransfersFailed(); :MessageBox(HWND)lparam, pDlg-GetError(GetLastError(), _T(错误), MB_ICONHAND|MB_OK); return -1
25、; /开始监听 if(!sockSrvr.Listen() pDlg-TransfersFailed(); :MessageBox(HWND)lparam, pDlg-GetError(GetLastError(), _T(错误), MB_ICONHAND|MB_OK); return -1; /向主对话框发送一个自定义消息WM_ACCEPT_TRANSFERS /发送一个信息告诉发送方可以开始发送文件 pDlg-SendMessage(WM_ACCEPT_TRANSFERS); /接受连接 CSocket recSo; if(!sockSrvr.Accept(recSo) :MessageB
26、ox(HWND)lparam, pDlg-GetError(GetLastError(), _T(错误), MB_ICONHAND|MB_OK); return -1; sockSrvr.Close(); /调用主对话框类中的ReceiveFile成员函数进行文件的接受 pDlg-ReceiveFile(recSo); return 0; 这两个函数只需要放在FileTransfersDlg.cpp文件中,在该文件中还需要加入两个CWinThread*类型的全局变量pThreadListen和pThreadSend用于对线程进行管理。五.在主对话框类内组织程序1.添加成员变量 使用ClassW
27、izard为控件添加成员变量,其对应关系如下所示。 控件ID 变量类型 变量名 IDC_FILE_NAME CString m_strFileName IDC_FILE_SIZE CString m_strFileSize IDC_PORT UINT m_wPort IDC_IPADDRESS CProgressCtrl m_ctrlProgress IDC_RADIO_SERVER int m_nServerType2.添加按钮消息的处理函数 添加按钮消息的处理函数,如下所示。 资源ID 消息类型 函数名称 IDC_RADIO_SERVER BN_CLICKED OnRadioServer
28、IDC_RADIO_CLIENT BN_CLICKED OnRadioClient IDC_BEGIN BN_CLICKED OnBegin IDC_DISCONNECT BN_CLICKED OnDisconnect IDC_SELECT_FILE BN_CLICKED OnSelectFileIDC_STOP_TRANSFERS BN_CLICKED OnStopTransfers IDCANCEL BN_CLICKED OnCancel3.添加WM_TIMER消息控制函数 使用ClassWizard为CFileTransfersDlg类中加一个WM_TIMER消息控制函数。4.加入必要的
29、宏 在定义文件FileTransfersDlg.h的首部加入如下的宏,以增强程序的可读性。#define PORT 1024 /文件传输套接字的端口号#define BLOCKSIZE 1024 /每次要发送或是接受的文家大小#define SERVER 0 /表示当前为服务器端#define CLIENT 1 /表示当前为客户端#define CONNECT_BE_ACCEPT 0x00 /客户端的连接申请被接受#define CONNECT_BE_REFUSE 0x01 /客户端的连接申请被拒绝#define DISCONNECT 0x02 /连接被断开#define REQUEST 0x03 /请求发送文件#define ACCEPT 0x04 /同意发送文件#define REFUSE 0x05 /拒绝发送文件#define CANCEL 0x06 /取消文件的发送5.自定义消息 自定义一个消息WM_ACCEPT_TRANSFERS,用于当文件接收方同意接收文件且文件接收线程_ListenThread已经准备好接收文件是,发送一个信息给文件发送方,说可以开始发送文件。 第一步,我需要在主窗口类CFileTransfersDlg的实现文件FileTransfersDlg.cpp的首部加入一句:#define WM_ACCEPT_TRANSFERS WM_USER + 1
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1