基于TCPIP协议的Winsock编程原理.docx

上传人:b****5 文档编号:5709165 上传时间:2022-12-31 格式:DOCX 页数:39 大小:95.40KB
下载 相关 举报
基于TCPIP协议的Winsock编程原理.docx_第1页
第1页 / 共39页
基于TCPIP协议的Winsock编程原理.docx_第2页
第2页 / 共39页
基于TCPIP协议的Winsock编程原理.docx_第3页
第3页 / 共39页
基于TCPIP协议的Winsock编程原理.docx_第4页
第4页 / 共39页
基于TCPIP协议的Winsock编程原理.docx_第5页
第5页 / 共39页
点击查看更多>>
下载资源
资源描述

基于TCPIP协议的Winsock编程原理.docx

《基于TCPIP协议的Winsock编程原理.docx》由会员分享,可在线阅读,更多相关《基于TCPIP协议的Winsock编程原理.docx(39页珍藏版)》请在冰豆网上搜索。

基于TCPIP协议的Winsock编程原理.docx

基于TCPIP协议的Winsock编程原理

用MFC实现局域网内点对点的大文件传输

相信很多人都用过QQ吧?

而QQ的里面“传送文件”的功能也应该有不少的人用过吧?

而关于实现文件传输的方法也有很多。

趁着暑假的空闲时间,我用VC6.0+SP5按照自己的思路也写了一个,程序的界面如图1所示。

图1

先给大家简单介绍一下基本的思路。

这个程序所采用的是基于TCP/IP协议的Winsock编程原理,相信如果对这方面的程序设计有所了解的人都应该知道对于这种编程模型一般都是采用客户机/服务器(Client/Server)方式,在这个程序里面也正是采用这种基本的方法。

为了方便使用,我把客户机和服务器合而为一,但其实质还是一样的。

在通信的时候主要可以分为两个部分,一个部分是用于传送控制信息,例如发送文件的请求,文件的名称、大小等,由于这方面的数据量比较小,为了方便起见我采用了MFC所提供的CSocket类的串行化技术来实现;而另一个部分就是文件的传输部分,对于文件的传输,由于数据量相对来说比较大,所以我分别写了两个线程,一个用于发送,一个用于接收。

这两个部分在具体实现的时候分别建立有自己的套接字(Socket)。

下面,就让我为大家演示一下详细的实现步骤。

一.建立一个新工程FileTransfers

使用MFCAppWizard(exe)建立一个新项目FileTransfers,选择基于对话框的应用,并在向导的第四步中,选择“WindowsSocket”选项,如图2所示其它步骤中都使用缺省值,然后按下“Finish”按钮,创建如图3所示属性的工程。

图2

图3

AppWizard将自动创建如下的类。

类名定义文件实现文件

CAboutDlgFileTransfers.cppFileTransfers.cpp

CFileTransfersAppFileTransfers.hFileTransfers.cpp

CFileTransfersDlgFileTransfersDlg.hFileTransfersDlg.cpp

二.修改资源

1.修改主对话框风格

修改AppWizard为我们创建的对话框模风格。

点击位于DialogProperties对话框上面的Style标签,然后按照图4设置风格属性。

图4

2.添加控件

按照图1所示在对话框中加入相应的控件。

□“服务选择”分组框(GroupBox)。

用于表明其中的两个单选按钮是属于一组的。

它的标题(Caption)为“服务选择”,使用默认的ID值就行了。

□“服务器(S)”和“客户端(C)”单选按钮(RadioButton)。

这两个单选按钮定位在“服务选择”分组框(GroupBox)中,用于确定软件当前是服务器或是客户端。

将“服务器(S)”按钮的ID设置为IDC_RADIO_SERVER,“客户端(C)”按钮的ID设置为IDC_RADIO_CLIENT,其它的属性分别按照图5和图6所示进行设置。

图5

图6

□“IP地址”IP地址控件(IPAddress)。

当软件做为服务器端时,该控件所显示的是本地的IP地址;当软件做为客户端时,用于输入服务器的IP地址。

其ID为IDC_IPADDRESS,其余属性采用默认值。

□“端口号”编辑控件(EditBox)。

用于确定通讯的端口号。

所有属性均按照默认值既可。

□对话框中部分静态文本控件(Text)的属性如下所示。

ID标题(Caption)用途

IDC_FILE_NAMEFileName显示当前正在传输的文件名

IDC_FILE_SIZE0字节显示当前正在传输的文件尺寸

IDC_RECEIVE_SIZE0字节显示已经发送或接受的文件尺寸

□先删除对话框中原有的确定按钮,然后再按照图1所示添加和修改按钮的属性,最终对话框中按钮的属性如下所示。

ID标题(Caption)用途

IDC_BEGIN启动(&B)启动服务程序/连接到服务器

IDC_DISCONNECT关闭(&D)关闭服务程序/断开和服务器的连接

IDC_SELECT_FILE选择文件(&F)选择要发送的文件

IDC_STOP_TRANSFERS停止传输(&T)停止文件的传输

IDCANCEL退出(&Q)退出程序

三.几个辅助类的介绍

1.CMessage类

在前面我们说过程序传送控制信息的时候采用的是CSocket类的串行化技术,这样一来使得发送和接收网络数据就像普通的数据串行化一样简单。

因此封装一个可以串行化的消息类是必要的,后面我们将会看到有了这个类,消息的发送和接收只需使用流操作符对缓冲区进行存取就可以了。

根据程序的需要,消息类CMessage的定义如下:

classCMessage:

publicCObject

{

public:

voidSerialize(CArchive&ar);

CMessage();

CMessage(intnType);

CMessage(intnType,CStringstrFileName,DWORDdwFileSize);

virtual~CMessage();

public:

intm_nType;

CStringm_strFileName;

DWORDm_dwFileSize;

};

其中,m_nType用于标识消息的类型;m_strFileName为文件的名称;m_dwFileSize为文件的大小。

为了方便使用,我对消息类的CMessage的构造函数进行了重载,CMessage()为默认的构造函数,如果只是发送一般的控制信息我们可以使用CMessage(intnType)构造函数,当需要发送文件名及大小的时候我们可以使用CMessage(intnType,CStringstrFileName,DWORDdwFileSize)构造函数,三个构造函数的源代码如下:

//默认的构造函数

CMessage:

:

CMessage()

{

m_nType=-1;

m_strFileName=_T("");

m_dwFileSize=0;

}

//只需发送一般的控制信息是使用

CMessage:

:

CMessage(intnType)

{

m_nType=nType;

m_strFileName=_T("");

m_dwFileSize=0;

}

//需要发送文件名及大小时使用

CMessage:

:

CMessage(intnType,CStringstrFileName,DWORDdwFileSize)

{

m_nType=nType;

m_strFileName=strFileName;

m_dwFileSize=dwFileSize;

}

重载CObject类中的Serialize函数,其源代码如下:

voidCMessage:

:

Serialize(CArchive&ar)

{

if(ar.IsStoring())

{

ar<

ar<

ar<

}

else

{

ar>>m_nType;

ar>>m_strFileName;

ar>>m_dwFileSize;

}

}

在写这个类的时候,我们可以使用任何一个文本编(如记事本或是UltraEdit等)辑器进行编写,把类的定义保存在Message.h中,把类的实现保存在Message.cpp文件中。

要注意的是我们还需要在Message.cpp文件的首部加上#include"stdafx.h"和#include"Message.h"两行代码,将stdafx.h文件和Message.h文件包含进来。

最后我们还需要把这个类加到工程中,先把Message.h和Message.cpp文件复制到工程目录下,然后在VC中通过Project菜单->AddToProject->Files把这两个文件添加到工程中。

2.CListenSocket类

负责监听管理的套接字类CListenSocket。

使用ClassView或ClassWizard进行创建,如图7所示。

图7

使用ClassView或手工加入如下的函数及成员变量的定义:

public:

CListenSocket(CFileTransfersDlg*pdlgMain);

protected:

CFileTransfersDlg*m_pdlgMain;

CListenSocket(CFileTransfersDlg*pdlgMain)为重载的构造函数;m_pdlgMain为指向主对话框类CFileTransfersDlg的指针。

添加完后我们还需要对类中的两个构造函数的内容进行修改以实现对类的初始化,其源代码如下:

CListenSocket:

:

CListenSocket(CFileTransfersDlg*pdlgMain)

{

m_pdlgMain=pdlgMain;

}

CListenSocket:

:

CListenSocket()

{

m_pdlgMain=NULL;

}

重载基类的OnAccept函数以使对来自客户的连接请求作出响应,OnAccept函数的源代码如下:

voidCListenSocket:

:

OnAccept(intnErrorCode)

{

m_pdlgMain->ProcessAccept();

CSocket:

:

OnAccept(nErrorCode);

}

当该套接字接收到客户的连接请求时,就调用CFileTransfersDlg对象的ProcessAccept()函数进行处理。

由于该类中使用到了CFileTransfersDlg类,因此在文件ListenSocket.h的首部还需加入如下头文件的包含语句:

#include"FileTransfersDlg.h"

3.CClientSocket类

该类用于连接的管理,其创建的方法与CListenSocket相似,其定义如下:

classCClientSocket:

publicCSocket

{

//Attributes

public:

//Operations

public:

CClientSocket();

virtual~CClientSocket();

//Overrides

public:

CSocketFile*m_pFile;

CArchive*m_pArchiveIn;

CArchive*m_pArchiveOut;

voidInit();

voidAbort();

BOOLSendMsg(CMessage*pMsg);

voidReceiveMsg(CMessage*pMsg);

CClientSocket(CFileTransfersDlg*pdlgMain);

//ClassWizardgeneratedvirtualfunctionoverrides

//{{AFX_VIRTUAL(CClientSocket)

public:

virtualvoidOnReceive(intnErrorCode);

//}}AFX_VIRTUAL

//Generatedmessagemapfunctions

//{{AFX_MSG(CClientSocket)

//NOTE-theClassWizardwilladdandremovememberfunctionshere.

//}}AFX_MSG

//Implementation

protected:

CFileTransfersDlg*m_pdlgMain;

};

在CClientSocket套接字类封装了串行化功能,这也就是说,该类封装了客户应用程序的大部分功能,因此可以认为该套接字类所管理的就是一个客户应用程序。

其中,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;

m_pFile=NULL;

m_pArchiveIn=NULL;

m_pArchiveOut=NULL;

}

Init成员函数用于串行化的初始化,其源代码如下:

voidCClientSocket:

:

Init()

{

m_pFile=newCSocketFile(this);

m_pArchiveIn=newCArchive(m_pFile,CArchive:

:

load);

m_pArchiveOut=newCArchive(m_pFile,CArchive:

:

store);

}

Abort成员函数用于对m_pArchiveOut指针进行释放,其源代码如下:

voidCClientSocket:

:

Abort()

{

if(m_pArchiveOut!

=NULL)

{

m_pArchiveOut->Abort();

deletem_pArchiveOut;

m_pArchiveOut=NULL;

}

}

SendMsg成员函数用于发送信息,其源代码如下:

BOOLCClientSocket:

:

SendMsg(CMessage*pMsg)

{

if(m_pArchiveOut!

=NULL)

{

TRY

{

//采用串行化技术进行信息的发送

pMsg->Serialize(*m_pArchiveOut);

m_pArchiveOut->Flush();

returnTRUE;

}

CATCH(CFileException,e)

{

m_pArchiveOut->Abort();

deletem_pArchiveOut;

m_pArchiveOut=NULL;

}

END_CATCH

}

returnFALSE;

}

ReceiveMsg成员函数用于接受信息,其源代码如下:

voidCClientSocket:

:

ReceiveMsg(CMessage*pMsg)

{

//采用串行化技术进行信息的接收

pMsg->Serialize(*m_pArchiveIn);

}

重载基类函数OnReceive,使用此函数接受Socket连接另一端发送的信息,其源代码如下:

voidCClientSocket:

:

OnReceive(intnErrorCode)

{

m_pdlgMain->ProcessReceive(this);

CSocket:

:

OnReceive(nErrorCode);

}

代码的作用是当有信息发送到时,调用主对话框类的ProcessReceive函数进行信息的接收。

由于用到了CMessage和CFileTransfersDlg类,所以在ClientSocket.h文件首部还需加入如下的头文件包含:

#include"Message.h"

#include"FileTransfersDlg.h"

四.两个线程

上面所定义的类只实现了控制信息的传送,我们还需要写两个线程_SendThread和_ListenThread,它们分别用于发送和接收文件。

1.发送文件线程

UINT_SendThread(LPVOIDlparam)

{

CFileTransfersDlg*pDlg=(CFileTransfersDlg*)lparam;

//创建套接字

CSocketsockClient;

if(!

sockClient.Create())

{

pDlg->TransfersFailed();

:

:

MessageBox((HWND)lparam,pDlg->GetError(GetLastError()),_T("错误"),MB_ICONHAND|MB_OK);

return-1;

}

CStringstrIPAddress;

UINTnPort;

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(sockClient);

return0;

}

2.接收文件线程

UINT_ListenThread(LPVOIDlparam)

{

CFileTransfersDlg*pDlg=(CFileTransfersDlg*)lparam;

//创建套接字

CSocketsockSrvr;

if(!

sockSrvr.Create(pDlg->m_wPort+PORT))

{

pDlg->TransfersFailed();

:

:

MessageBox((HWND)lparam,pDlg->GetError(GetLastError()),_T("错误"),MB_ICONHAND|MB_OK);

return-1;

}

//开始监听

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);

//接受连接

CSocketrecSo;

if(!

sockSrvr.Accept(recSo))

{

:

:

MessageBox((HWND)lparam,pDlg->GetError(GetLastError()),_T("错误"),MB_ICONHAND|MB_OK);

return-1;

}

sockSrvr.Close();

//调用主对话框类中的ReceiveFile成员函数进行文件的接受

pDlg->ReceiveFile(recSo);

return0;

}

这两个函数只需要放在FileTransfersDlg.cpp文件中,在该文件中还需要加入两个CWinThread*类型的全局变量pThreadListen和pThreadSend用于对线程进行管理。

五.在主对话框类内组织程序

1.添加成员变量

使用ClassWizard为控件添加成员变量,其对应关系如下所示。

控件ID变量类型变量名

IDC_FILE_NAMECStringm_strFileName

IDC_FILE_SIZECStringm_strFileSize

IDC_PORTUINTm_wPort

IDC_IPADDRESSCProgressCtrlm_ctrlProgress

IDC_RADIO_SERVERintm_nServerType

2.添加按钮消息的处理函数

添加按钮消息的处理函数,如下所示。

资源ID消息类型函数名称

IDC_RADIO_SERVERBN_CLICKEDOnRadioServer

IDC_RADIO_CLIENTBN_CLICKEDOnRadioClient

IDC_BEGINBN_CLICKEDOnBegin

IDC_DISCONNECTBN_CLICKEDOnDisconnect

IDC_SELECT_FILEBN_CLICKEDOnSelectFile

IDC_STOP_TRANSFERSBN_CLICKEDOnStopTransfers

IDCANCELBN_CLICKEDOnCancel

3.添加WM_TIMER消息控制函数

使用ClassWizard为CFileTransfersDlg类中加一个WM_TIMER消息控制函数。

4.加入必要的宏

在定义文件FileTransfersDlg.h的首部加入如下的宏,以增强程序的可读性。

#definePORT1024//文件传输套接字的端口号

#defineBLOCKSIZE1024//每次要发送或是接受的文家大小

#defineSERVER0//表示当前为服务器端

#defineCLIENT1//表示当前为客户端

#defineCONNECT_BE_ACCEPT0x00//客户端的连接申请被接受

#defineCONNECT_BE_REFUSE0x01//客户端的连接申请被拒绝

#defineDISCONNECT0x02//连接被断开

#defineREQUEST0x03//请求发送文件

#defineACCEPT0x04//同意发送文件

#defineREFUSE0x05//拒绝发送文件

#defineCANCEL0x06//取消文件的发送

5.自定义消息

自定义一个消息WM_ACCEPT_TRANSFERS,用于当文件接收方同意接收文件且文件接收线程_ListenThread已经准备好接收文件是,发送一个信息给文件发送方,说可以开始发送文件。

第一步,我需要在主窗口类CFileTransfersDlg的实现文件FileTransfersDlg.cpp的首部加入一句:

#defineWM_ACCEPT_TRANSFERSWM_USER+1

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 医药卫生 > 基础医学

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1