**MFC**
要学习好网路编程,主要看以下几个方面:
1、掌握概念,诸如:同步(Sync)/异步(Async),阻塞(Block)/
非阻塞(Unblock)等。
2、在实际Windows网络通信软件开发中,异步非阻塞套接字是用的
最多的。平常所说的C/S(客户端/服务器)结构的软件就是异步
非阻塞模式的。
3、在异步非阻塞模式下的通信,要借助于多线程来开发。
4、许多文章都曾经介绍过用VC++进行Socket编程的方法,直接
利用动态连接库wsock32.dll进行操作,实现比较繁琐。但实际上
网络编程概括为三种套接字方法:流式套接字(SOCK_STREAM)
和数据报式套接字(SOCK_DGRAM)和原始套接字。流式套接字
基于TCP协议,数据报式套接字基于UDP协议实现 .
网络编程,当然要用到Windows Socket(套接字)技术。
Socket相关的操作由一系列API函数来完成,比如socket、
bind、listen、connect、accept、send、sendto、recv、
recvfrom等。调用这些API函数有一定的先后次序,有些
函数的参数还比较复杂,对于开发者来说,不是很好用。
于是,微软的MFC提供了两个类:CAsyncSocket和CSocket,
极大地方便了Socket功能的使用。
CAsyncSocket类在较低层次上封装了Windows Socket API,
并且通过内建一个(隐藏的)窗口,实现了适合Windows应用的
异步机制(Windows Socket API默认情况下工作在阻塞模式,
不方便直接在消息驱动的Windows程序上使用)。CSocket类从
CAsyncSocket类派生,进一步简化了Socket功能的应用。
不过很遗憾,正因为这两个类都内建了一个窗口,它们并不
是线程安全的(thread-safe);如果要在多线程环境下应用
Socket功能,建议自行封装Socket API函数。
基于TCP的socket编程的服务器端程序流程如下:
1、创建套接字
2、将套接字绑定到一个本地地址和端口号上(bind)
3、将套接字设为监听模式,准备接受客户请求(listen)
4、等待客户请求,请求到来时接受请求,建立链接,并返回 一
1、创建套接字
2、将套接字绑定到一个本地地址和端口号上(bind)
3、将套接字设为监听模式,准备接受客户请求(listen)
4、等待客户请求,请求到来时接受请求,建立链接,并返回 一
个新的基于此次通信的套接字(accept)
5、用返回的套接字和客户端进行通信(send、recv)
6、返回,等待另一客户请求
7、关闭套接字
基于TCP的socket编程的客户端程序流程如下:
1、创建套接字
2、向服务器端发送请求(connect)
3、和服务器端进行通信(send、recv)
4、关闭套接字
5、用返回的套接字和客户端进行通信(send、recv)
6、返回,等待另一客户请求
7、关闭套接字
基于TCP的socket编程的客户端程序流程如下:
1、创建套接字
2、向服务器端发送请求(connect)
3、和服务器端进行通信(send、recv)
4、关闭套接字
基于UDP的socket编程的服务器端程序流程如下:
1、创建套接字
2、将套接字绑定到本地地址和端口号上(bind)
3、等待接收数据(recvfrom)
4、关闭套接字
基于UDP的socket编程的客户端程序流程如下:
1、创建套接字
2、和服务器端进行通信(sendto)
3、关闭套接字
1、创建套接字
2、将套接字绑定到本地地址和端口号上(bind)
3、等待接收数据(recvfrom)
4、关闭套接字
基于UDP的socket编程的客户端程序流程如下:
1、创建套接字
2、和服务器端进行通信(sendto)
3、关闭套接字
异步方式指的是发送方不等接收方响应,便接着发下个数据包
的通信方式;而同步指发送方发出数据后,等收到接收方发回
的响应,才发下一个数据包的通信方式。
阻塞套接字是指执行此套接字的网络调用时,直到成功才返回,
阻塞套接字是指执行此套接字的网络调用时,直到成功才返回,
否则一直阻塞在此网络调用上,比如调用recv()函数读取网络缓冲区
中的数据,如果没有数据到达,将一直挂在recv()这个函数调用上,
直到读到一些数据,此函数调用才返回;而非阻塞套接字是指执行
此套接字的网络调用时,不管是否执行成功,都立即返回。比如
调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即
返回,而不会一直挂在此函数调用上。在实际Windows网络通信软件
开发中,异步非阻塞套接字是用的最多的。平常所说的C/S
(客户端/服务器)结构的软件就是异步非阻塞模式的。
对于这些概念,初学者的理解也许只能似是而非,我将用一个
对于这些概念,初学者的理解也许只能似是而非,我将用一个
最简单的例子说明异步非阻塞Socket的基本原理和工作机制。
目的是让初学者不仅对Socket异步非阻塞的概念有个非常透彻
的理解,而且也给他们提供一个用Socket开发网络通信应用程序
的快速入门方法。操作系统是Windows 98(或NT4.0),开发工
具是Visual C++6.0。
MFC提供了一个异步类CAsyncSocket,它封装了异步、非阻塞
MFC提供了一个异步类CAsyncSocket,它封装了异步、非阻塞
Socket的基本功能,用它做常用的网络通信软件很方便。
但它屏蔽了Socket的异步、非阻塞等概念,开发人员无需了
解异步、非阻塞Socket的原理和工作机制。因此,建议初学
者学习编网络通信程序时,暂且不要用MFC提供的类,而先用
Winsock2 API,这样有助于对异步、非阻塞Socket编程
机制的理解。
为了简单起见,服务器端和客户端的应用程序均是基于MFC的
为了简单起见,服务器端和客户端的应用程序均是基于MFC的
标准对话框,网络通信部分基于Winsock2 API实现。
先做服务器端应用程序。
用MFC向导做一个基于对话框的应用程序SocketSever,
先做服务器端应用程序。
用MFC向导做一个基于对话框的应用程序SocketSever,
注意第三步中不要选上Windwos Sockets选项。在做好工程后,
创建一个SeverSock,将它设置为异步非阻塞模式,并为它注册各
种网络异步事件,然后与自定义的网络异步事件联系上,最后
还要将它设置为监听模式。在自定义的网络异步事件的回调函数中,
你可以得到各种网络异步事件,根据它们的类型,做不同的处理。
下面将详细介绍如何编写相关代码。
在SocketSeverDlg.h文件的类定义之前增加如下定义:
#define NETWORK_EVENT WM_USER+166 file://定义网络事件
SOCKET ServerSock; file://服务器端Socket
在类定义中增加如下定义:
class CSocketSeverDlg : CDialog
{
public:
SOCKET ClientSock[CLNT_MAX_NUM]; file://存储与
在SocketSeverDlg.h文件的类定义之前增加如下定义:
#define NETWORK_EVENT WM_USER+166 file://定义网络事件
SOCKET ServerSock; file://服务器端Socket
在类定义中增加如下定义:
class CSocketSeverDlg : CDialog
{
public:
SOCKET ClientSock[CLNT_MAX_NUM]; file://存储与
客户端通信的Socket的数组
/*各种网络异步事件的处理函数*/
void OnClose(SOCKET CurSock); file://对端Socket断开
void OnSend(SOCKET CurSock); file://发送网络数据包
void OnReceive(SOCKET CurSock); file://网络数据包到达
void OnAccept(SOCKET CurSock); file://客户端连接请求
void OnClose(SOCKET CurSock); file://对端Socket断开
void OnSend(SOCKET CurSock); file://发送网络数据包
void OnReceive(SOCKET CurSock); file://网络数据包到达
void OnAccept(SOCKET CurSock); file://客户端连接请求
BOOL InitNetwork(); file://初始化网络函数
void OnNetEvent(WPARAM wParam, LPARAM lParam); file:
void OnNetEvent(WPARAM wParam, LPARAM lParam); file:
//异步事件回调函数
…
};
在SocketSeverDlg.cpp文件中增加消息映射,其中OnNetEvent是
…
};
在SocketSeverDlg.cpp文件中增加消息映射,其中OnNetEvent是
异步事件回调函数名:
ON_MESSAGE(NETWORK_EVENT,OnNetEvent)
定义初始化网络函数,在SocketSeverDlg.cpp文件的OnInitDialog
ON_MESSAGE(NETWORK_EVENT,OnNetEvent)
定义初始化网络函数,在SocketSeverDlg.cpp文件的OnInitDialog
()中调此函数即可。
BOOL CSocketSeverDlg::InitNetwork()
{
WSADATA wsaData;
BOOL CSocketSeverDlg::InitNetwork()
{
WSADATA wsaData;
//初始化TCP协议
BOOL ret = WSAStartup(MAKEWORD(2,2), &wsaData);
if(ret != 0)
{
MessageBox('初始化网络协议失败!');
return FALSE;
}
BOOL ret = WSAStartup(MAKEWORD(2,2), &wsaData);
if(ret != 0)
{
MessageBox('初始化网络协议失败!');
return FALSE;
}
//创建服务器端套接字
ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(ServerSock == INVALID_SOCKET)
{
MessageBox('创建套接字失败!');
closesocket(ServerSock);
WSACleanup();
return FALSE;
}
ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(ServerSock == INVALID_SOCKET)
{
MessageBox('创建套接字失败!');
closesocket(ServerSock);
WSACleanup();
return FALSE;
}
//绑定到本地一个端口上
sockaddr_in localaddr;
localaddr.sin_family = AF_INET;
localaddr.sin_port = htons(8888); //端口号不要与其他应用程序冲突
localaddr.sin_addr.s_addr = 0;
if(bind(ServerSock ,(struct sockaddr*)&localaddr,sizeof(sockaddr))
= = SOCKET_ERROR)
{
MessageBox('绑定地址失败!');
closesocket(ServerSock);
WSACleanup();
return FALSE;
}
//将SeverSock设置为异步非阻塞模式,并为它注册各种网络异步事件,其中m_hWnd
//为应用程序的主对话框或主窗口的句柄
if(WSAAsyncSelect(ServerSock, m_hWnd, NETWORK
sockaddr_in localaddr;
localaddr.sin_family = AF_INET;
localaddr.sin_port = htons(8888); //端口号不要与其他应用程序冲突
localaddr.sin_addr.s_addr = 0;
if(bind(ServerSock ,(struct sockaddr*)&localaddr,sizeof(sockaddr))
= = SOCKET_ERROR)
{
MessageBox('绑定地址失败!');
closesocket(ServerSock);
WSACleanup();
return FALSE;
}
//将SeverSock设置为异步非阻塞模式,并为它注册各种网络异步事件,其中m_hWnd
//为应用程序的主对话框或主窗口的句柄
if(WSAAsyncSelect(ServerSock, m_hWnd, NETWORK
_EVENT, FD_ACCEPT | FD_CLOSE | FD_READ | FD_
WRITE) == SOCKET_ERROR)
{
MessageBox('注册网络异步事件失败!');
WSACleanup();
return FALSE;
}
listen(ServerSock, 5); file://设置侦听模式
return TRUE;
}
{
MessageBox('注册网络异步事件失败!');
WSACleanup();
return FALSE;
}
listen(ServerSock, 5); file://设置侦听模式
return TRUE;
}
下面定义网络异步事件的回调函数
void CSocketSeverDlg::OnNetEvent(WPARAM wParam,
void CSocketSeverDlg::OnNetEvent(WPARAM wParam,
LPARAM lParam)
{
//调用Winsock API函数,得到网络事件类型
int iEvent = WSAGETSELECTEVENT(lParam);
//调用Winsock API函数,得到发生此事件的客户端套接字
SOCKET CurSock= (SOCKET)wParam;
{
//调用Winsock API函数,得到网络事件类型
int iEvent = WSAGETSELECTEVENT(lParam);
//调用Winsock API函数,得到发生此事件的客户端套接字
SOCKET CurSock= (SOCKET)wParam;
switch(iEvent)
{
case FD_ACCEPT: //客户端连接请求事件
OnAccept(CurSock);
break;
case FD_CLOSE: //客户端断开事件:
OnClose(CurSock);
break;
case FD_READ: //网络数据包到达事件
OnReceive(CurSock);
break;
case FD_WRITE: //发送网络数据事件
OnSend(CurSock);
break;
default: break;
}
}
以下是发生在相应Socket上的各种网络异步事件的处理函数,
{
case FD_ACCEPT: //客户端连接请求事件
OnAccept(CurSock);
break;
case FD_CLOSE: //客户端断开事件:
OnClose(CurSock);
break;
case FD_READ: //网络数据包到达事件
OnReceive(CurSock);
break;
case FD_WRITE: //发送网络数据事件
OnSend(CurSock);
break;
default: break;
}
}
以下是发生在相应Socket上的各种网络异步事件的处理函数,
其中OnAccept传进来的参数是服务器端创建的套接字,OnClose()、
OnReceive()和OnSend()传进来的参数均是服务器端在接受
客户端连接时新创建的用与此客户端通信的Socket。
void CSocketSeverDlg::OnAccept(SOCKET CurSock)
{
//接受连接请求,并保存与发起连接请求的客户端进行通信Socket
//为新的socket注册异步事件,注意没有Accept事件
}
void CSocketSeverDlg::OnClose(SOCET CurSock)
{
//结束与相应的客户端的通信,释放相应资源
}
void CSocketSeverDlg::OnAccept(SOCKET CurSock)
{
//接受连接请求,并保存与发起连接请求的客户端进行通信Socket
//为新的socket注册异步事件,注意没有Accept事件
}
void CSocketSeverDlg::OnClose(SOCET CurSock)
{
//结束与相应的客户端的通信,释放相应资源
}
void CSocketSeverDlg::OnSend(SOCET CurSock)
{
//在给客户端发数据时做相关预处理
}
{
//在给客户端发数据时做相关预处理
}
void CSocketSeverDlg::OnReceive(SOCET CurSock)
{
//读出网络缓冲区中的数据包
}
用同样的方法建立一个客户端应用程序。初始化网络部分,不需要将套接字设置为监听模式。注册异步事件时,没有FD_ACCEPT,但增加了FD_CONNECT事件,因此没有OnAccept()函数,但增加了OnConnect()函数。向服务器发出连接请求时,使用connect()函数,连接成功后,会响应到OnConnect()函数中。下面是OnConnect()函数的定义,传进来的参数是客户端Socket和服务器端发回来的连接是否成功的标志。
void CSocketClntDlg::OnConnect(SOCKET CurSock, int error)
{
if(0 = = error)
{
if(CurSock = = ClntSock)
MessageBox('连接服务器成功!');
}
}
定义OnReceive()函数,处理网络数据到达事件;
定义OnSend()函数,处理发送网络数据事件;
定义OnClose()函数,处理服务器的关闭事件。
以上就是用基于Windows消息机制的异步I/O模型做服务器和
{
//读出网络缓冲区中的数据包
}
用同样的方法建立一个客户端应用程序。初始化网络部分,不需要将套接字设置为监听模式。注册异步事件时,没有FD_ACCEPT,但增加了FD_CONNECT事件,因此没有OnAccept()函数,但增加了OnConnect()函数。向服务器发出连接请求时,使用connect()函数,连接成功后,会响应到OnConnect()函数中。下面是OnConnect()函数的定义,传进来的参数是客户端Socket和服务器端发回来的连接是否成功的标志。
void CSocketClntDlg::OnConnect(SOCKET CurSock, int error)
{
if(0 = = error)
{
if(CurSock = = ClntSock)
MessageBox('连接服务器成功!');
}
}
定义OnReceive()函数,处理网络数据到达事件;
定义OnSend()函数,处理发送网络数据事件;
定义OnClose()函数,处理服务器的关闭事件。
以上就是用基于Windows消息机制的异步I/O模型做服务器和
客户端应用程序的基本方法。另外还可以用事件模型、重叠
模型或完成端口模型,读者可以参考有关书籍。
在实现了上面的例子后,你将对Winsock编网络通信程序的
在实现了上面的例子后,你将对Winsock编网络通信程序的
机制有了一定的了解。接下来你可以进行更精彩的编程, 不仅
可以在网上传输普通数据,而且还以传输语音、视频数据,你还
可以自己做一个网络资源共享的服务器软件,和你的同学
在实验室的局域网里可以共同分享你的成果。
同步服务器套接字挂起应用程序的执行,直到套接字上接收到
连接请求。同步服务器套接字不适用于在操作中大量使用网络
的应用程序,但它们可能适用于简单的网络应用程序。使用
被挂起,直到调用 Accept 方法时接收到连接请求。
接收到连接请求时,Accept 返回一个与连接客户端关联的新
Socket 实例。下面的示例读取客户端数据,在控制台上显示
该数据,然后将该数据回显到客户端。Socket 不指定任何
消息协议,因此字符串“<EOF>”标记消息数据的结尾。
它假定一个名为 listener 的 Socket 已初始化,并绑
定到一个终结点。
Console.WriteLine("Waiting for a connection...");
Socket handler = listener.Accept();
String data = null;
Socket handler = listener.Accept();
String data = null;
while (true) {
bytes = new byte[1024];
int bytesRec = handler.Receive(bytes);
data += Encoding.ASCII.GetString(bytes,0,bytesRec);
if (data.IndexOf("<EOF>") > -1) {
break;
}
}
bytes = new byte[1024];
int bytesRec = handler.Receive(bytes);
data += Encoding.ASCII.GetString(bytes,0,bytesRec);
if (data.IndexOf("<EOF>") > -1) {
break;
}
}
Console.WriteLine( "Text received : {0}", data);
byte[] msg = Encoding.ASCII.GetBytes(data);
handler.Send(msg);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
handler.Send(msg);
handler.Shutdown(SocketShutdown.Both);
handler.Close();
CAsyncSocket网络编程(MFC)
随着计算机网络化的深入,计算机网络编程在程序设计的过程中
变得日益重要。由于C++语言对底层操作的优越性,许多文章都曾经
介绍过用VC++进行Socket编程的方法。但由于都是直接利用动态连
接库wsock32.dll进行操作,实现比较繁琐。其实,VC++的MFC类
库中提供了CAsyncSocket这样一个套接字类,用他来实现Socket编程,
是非常方便的。
---- 本文将用一个Echo例程来介绍CAsyncSocket类的用法。
---- 一. 客户端
---- 1. 创建一个Dialog Based项目:CSockClient。
---- 2. 设计对话框
---- 去掉Ok和Cancle两个按钮,增加ID_Connect(连接)、
ID_Send(发送)、ID_Exit(关闭)按钮,增加ListBox控
件IDC_LISTMSG和Edit控件IDC_EDITMSG,并按下表在
ClassWizard中为CCSockClientDlg类添加变量。
Control ID
Type Member
IDC_EDITMSG
CEdit m_MSGIDC_LISTMSG
ClistBox
m_MSGS
---- 3. CAsyncSocket类用DoCallBack函数处理MFC消息,
当一个网络事件发生时,DoCallBack函数按网络事件类型:
FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT分别
调用OnReceive、OnSend、OnAccept、OnConnect函数。
由于MFC把这些事件处理函数定义为虚函数,所以要生成一
个新的C++类,以重载这些函数,做法如下:
---- 以Public方式继承CAsyncSocket类,生成新类MySock;
---- 为MySock类添加虚函数OnReceive、OnConnect、OnSend
---- 4. 在MySock.ccp中添加以下代码
#include "CSockClient.h"#include "CSockClientDlg.h"
---- 5. 在MySock.h中添加以下代码
public: BOOL m_bConnected;
//是否连接 UINT m_nLength;
//消息长度 char m_szBuffer[4096];
//消息缓冲区
---- 6. 在MySock.ccp中重载各函数
MySock::MySock(){ m_nLength=0;
memset(m_szBuffer,0,sizeof(m_szBuffer));
m_bConnected=FALSE;}MySock::~MySock(){
//关闭套接字if(m_hSocket!=INVALID_SOCKET)
Close();}
void MySock::OnReceive(int nErrorCode) {
m_nLength=Receive(m_szBuffer,sizeof(m_szBuffer),0);
//下面两行代码用来获取对话框指针CCSockClientApp*
pApp=(CCSockClientApp*)AfxGetApp();
CCSockClientDlg* pDlg=(CCSockClientDlg*)pApp- >m_pMainWnd;
pDlg- >m_MSGS.InsertString(0,m_szBuffer); memset
(m_szBuffer,0,sizeof(m_szBuffer));
CAsyncSocket::OnReceive(nErrorCode);}void
MySock::OnSend(int nErrorCode) {
Send(m_szBuffer,m_nLength,0); m_nLength=0;
memset(m_szBuffer,0,sizeof(m_szBuffer));
//继续提请一个“读”的网络事件,
接收Server消息AsyncSelect(FD_READ);
CAsyncSocket::OnSend(nErrorCode);}void
MySock::OnConnect(int nErrorCode) {
if (nErrorCode==0) {
m_bConnected=TRUE;
CCSockClientApp* pApp=(CCSockClientApp*)AfxGetApp();
CCSockClientDlg* pDlg=(CCSockClientDlg*)pApp- >m_pMainWnd;
memcpy(m_szBuffer,"Connected to ",13);
strncat(m_szBuffer,pDlg- >m_szServerAdr,
sizeof(pDlg- >m_szServerAdr));
pDlg- >m_MSGS.InsertString(0,m_szBuffer);
AsyncSelect(FD_READ);
////提请一个“读”的网络事件,准备接收 }
CAsyncSocket::OnConnect(nErrorCode);}
---- 7. 新建对话框IDD_Addr,用来输入IP地址和Port;生成
新类CAddrDlg。增加两个Edit控件:IDC_Addr、IDC_Port按下表
在ClassWizard中为CAddrDlg类添加变量。
新类CAddrDlg。增加两个Edit控件:IDC_Addr、IDC_Port按下表
在ClassWizard中为CAddrDlg类添加变量。
Control ID Type MemberIDC_Addr
CString m_AddrIDC_Port Int m_Port
---- 8. 在CSockClientDlg.ccp中添加代码
#include "AddrDlg.h"protected:
int TryCount;
MySock m_clientSocket;
UINT m_szPort;public: char m_szServerAdr[256];
---- 9. 双击IDD_CSOCKCLIENT_DIALOG对话框中的“连接”按钮,
添加以下代码
添加以下代码
void CCSockClientDlg::OnConnect() {
m_clientSocket.ShutDown(2);
m_clientSocket.m_hSocket=INVALID_SOCKET;
m_clientSocket.m_bConnected=FALSE;
CAddrDlg m_Dlg; //默认端口1088m_Dlg.m_Port=1088;
if (m_Dlg.DoModal()==IDOK && !m_Dlg.m_Addr.IsEmpty()) {
memcpy(m_szServerAdr,m_Dlg.m_Addr,sizeof(m_szServerAdr));
m_szPort=m_Dlg.m_Port; //建立计时器,每1秒尝试连接一次,直到连上
或TryCount>10SetTimer(1,1000,NULL);
TryCount=0; }}
---- 10. 添加Windows消息WM_TIMER响应函数
OnTimer
OnTimer
void CCSockClientDlg::OnTimer(UINT nIDEvent) {
if (m_clientSocket.m_hSocket==INVALID_SOCKET) {
BOOL bFlag=m_clientSocket.Create(0,SOCK_STREAM,FD_CONNECT);
if(!bFlag) { AfxMessageBox("Socket Error!");
m_clientSocket.Close();
PostQuitMessage(0);
return;
} }
m_clientSocket.Connect(m_szServerAdr,m_szPort);
TryCount++;
if (TryCount >=10 || m_clientSocket.m_bConnected) {
KillTimer(1);
if (TryCount >=10) AfxMessageBox("Connect Failed!");
return; }
CDialog::OnTimer(nIDEvent);}
---- 11. 双击IDD_CSOCKCLIENT_DIALOG对话框中的
“发送”按钮,添加以下代码
void CCSockClientDlg::OnSend() {
if (m_clientSocket.m_bConnected)
{m_clientSocket.m_nLength=m_MSG.GetWindowText
(m_clientSocket.m_szBuffer, sizeof(m_clientSocket.m_szBuffer));
m_clientSocket.AsyncSelect(FD_WRITE);
m_MSG.SetWindowText(""); }}
---- 12. 双击IDD_CSOCKCLIENT_DIALOG对话框中的
“关闭”按钮,添加以下代码
void CCSockClientDlg::OnExit() {
//关闭Socketm_clientSocket.ShutDown(2);
//关闭对话框EndDialog(0); }
----12.运行此项目,连接时输入主机名或IP均可,
CAsyncSocket类会自动处理。
----二. 服务端
----Server端的编程与Client端的类似,下面主要介绍
他的Listen及Accept函数
----1. 建立一个CNewSocket类,重载CAsyncSocket类
的OnReceive、OnSend函数,
如何进行信息的显示和发送可以参考Client程序。本例中
采用将收到信息原封不动
发回的方法来实现Echo功能,代码如下
CNewSocket::
OnReceive(int nErrorCOde){
m_nLength=Receive(m_szBuffer,sizeof(m_szBuffer),0);
// 直接转发消息AsyncSelect(FD_WRITE);}CNewSocket::
OnSend(int nErrorCode){ Send(m_szBuffer,m_nLength,0);}
----2. 建立一个CMyServerSocket类,重载CAsyncSocket类的
OnAccept函数代码如下
----在MyServerSocket.h中声明变量
public::CNewSocket* m_pSocket;
void CMyServerSocket::OnAccept(int nErrorCode){
//侦听到连接请求,调用Accept函数
CNewSocket* pSocket = new CNewSocket();
if (Accept(*pSocket)) {
pSocket- >AsyncSelect(FD_READ);
m_pSocket=pSocket;
} else
delete pSocket;}
----3. 为对话框添加一个“侦听”按钮,添加如下代码
----在CsockServerDlg.ccp中声明变量
public:
CMyServerSocket m_srvrSocket;
void CCSockServerDlg::OnListen(){ if
(m_srvrSocket.m_hSocket==INVALID_SOCKET) {
BOOL bFlag=m_srvrSocket.Create
(UserPort,SOCK_STREAM,FD_ACCEPT);
if (!bFlag) { AfxMessageBox(“Socket Error!”);
M_srvrSocket.Close();
PostQuitMessage(0);
Return; } }
//“侦听”成功,等待连接请求if (!m_srvrSocket。Listen(1)){
int nErrorCode = m_srvrSocket.GetLastError();
if (nError!=WSAEWOULDBLOCK)
{
AfxMessageBox(“Socket Error!”);
M_srvrSocket.Close();
PostQuitMessage(0);
Return;
} }}
----4. 目前程序只能实现Echo功能,将信息原封不动的转发,
若能将Accept中由CNewSocket* pSocket = new CNewSocket();
得到的Socket指针存入一个CList或一个数组中,便像Client端那样,
对所有的连接进行读写控制。
----三. 总结
----CAsyncSocket类为我们使用Socket提供了极大方便。
建立Socket的WSAStartup过程和bind过程被简化成为Create过程,
IP地址类型转换、主机名和IP地址转换的过程中许多复杂的变量类型
都被简化成字符串和整数操作,特别是CAsyncSocket类的异步特点,
完全可以替代繁琐的线程操作。MFC提供了大量的类库,
我们若能灵活的使用他们,便会大大提高编程的效率
MFC下的网络编程
Visual C++的MFC提供了CSocket类用来实现网络通信。
下图给出了CSocket 类的继承关系。
下面介绍VC++在Windows 95中实现Socket的 CSocket 类相
下面介绍VC++在Windows 95中实现Socket的 CSocket 类相
关成员函数(这些成员函数实际上是从CAsyncSocket 类继承来的)的使用。
(1) BOOL Create( UINT nSocketPort = 0, int nSocketType =
(1) BOOL Create( UINT nSocketPort = 0, int nSocketType =
SOCK_STREAM, long lEvent = FD_READ |FD_WRITE|FD_
OOB|FD_ACCEPT|FD_CONNECT| FD_CLOSE,
LPCTSTR lpszSocketAddress = NULL )
该函数用来建立Socket。 其中,nSocketPort 为所选择的Socket 端口,
一般要大于 1023, 如果该参数为0,则由系统选定一端口,默认值为0 ;
nSocketType 为套接字类型:SOCK_STREAM 表示为流套接字,
SOCK_DGRAM 表示为数据报套接字,默认值为SOCK_STREAM ;
lEvent 标识该Socket 要完成哪种工作,默认值为FD_READ|FD_
WRITE|FD_OOB| FD_ACCEPT|FD_CONNECT|FD_CLOSE ;
lpszSockAddress 为网络地址信息结构指针,包含网络地址, 默认值为NULL 。
(2)BOOL Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL )
该函数的作用是将Socket 端口与网络地址连接起来。参数含义同上 。
(3)BOOL Listen( int nConnectionBacklog = 5 )
该函数的作用是等待Socket请求。其中,nConnec-tionBacklog
该函数用来建立Socket。 其中,nSocketPort 为所选择的Socket 端口,
一般要大于 1023, 如果该参数为0,则由系统选定一端口,默认值为0 ;
nSocketType 为套接字类型:SOCK_STREAM 表示为流套接字,
SOCK_DGRAM 表示为数据报套接字,默认值为SOCK_STREAM ;
lEvent 标识该Socket 要完成哪种工作,默认值为FD_READ|FD_
WRITE|FD_OOB| FD_ACCEPT|FD_CONNECT|FD_CLOSE ;
lpszSockAddress 为网络地址信息结构指针,包含网络地址, 默认值为NULL 。
(2)BOOL Bind( UINT nSocketPort, LPCTSTR lpszSocketAddress = NULL )
该函数的作用是将Socket 端口与网络地址连接起来。参数含义同上 。
(3)BOOL Listen( int nConnectionBacklog = 5 )
该函数的作用是等待Socket请求。其中,nConnec-tionBacklog
表示等待队列的长度,默认值为最大值5 。
(4)virtual BOOL Accept( CAsyncSocket& rConnectedSocket,
(4)virtual BOOL Accept( CAsyncSocket& rConnectedSocket,
SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL )
该函数的作用是取得队列上第一个连接请求并建立一个具有
该函数的作用是取得队列上第一个连接请求并建立一个具有
与Socket相同特性的套接字。其中,rConnectedSocket 表示一
个新的Socket 。
(5)BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort )
该函数的作用是提出请求。其中,lpszHostAddress 和 nHostPort
(5)BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort )
该函数的作用是提出请求。其中,lpszHostAddress 和 nHostPort
为接受请求进程的网络地址和Socket 端口号。
(6)virtual void Close( )
该函数的作用是关闭该Socket 。
利用CSocket类直接进行数据通信有两种方式:一种是利用
(6)virtual void Close( )
该函数的作用是关闭该Socket 。
利用CSocket类直接进行数据通信有两种方式:一种是利用
CSocketFile 类和Archive 类去实现,另一种是利用CSocket的成员
函数 Receive、Send、ReceiveFrom、SendTo、Listen 和 Accept
等来实现(这些成员函
数实际上也是从CAsyncSocket 类继承的)。
两种方法的实现步骤如下 :
Server : Construct-> Creat-> Bind -> Listen->
两种方法的实现步骤如下 :
Server : Construct-> Creat-> Bind -> Listen->
Accept-> Send->Close ;
Cilent : Construct ->Creat-> Connect-> Receive-> Close。
下面就用VC++的代码分别介绍如何运用上述两种
Cilent : Construct ->Creat-> Connect-> Receive-> Close。
下面就用VC++的代码分别介绍如何运用上述两种
方法来实现Socket 编程。
1、 利用CSocketFile类和Archive 类实现
(1)服务器程序流程
// 创建一个套接字对象
CSocket sockSrvr;
//为上述套接字对象创建一个套接字
sockSrvr.Create(nPort);
//开始侦听
sockSrvr.Listen( );
//创建一个新的套接字对象
CSocket sockRecv;
//接受连接
sockSrvr.Accept( sockRecv );
// 创建文件对象
CSocketFile file(&sockRecv);
//创建一个archive对象
CArchive arIn(&file, CArchive::load);
/*or*/_CArchive arOut(&file, CArchive::store);
//使用archive对象传输数据
arIn >> dwValue;
/*or*/ arOut < < dwValue;
(2)客户端程序流程
//创建一个套接字对象
CSocket sockClient;
//为这个对象创建一个套接字
sockClient.Create( );
//寻找一个连接
sockClient.Connect(strAddr, nPort);
//创建一个文件对象
CSocketFile file(&sockClient);
//创建一个archive对象
CArchive arIn(&file, CArchive::load);
/*or*/_CArchive arOut(&file, CArchive::store);
//使用这个对象传输数据
arOut < < dwValue;
/*or*/ arIn >> dwValue;
上述程序中, nPort 是Socket 的端口号,strAddr 是该
1、 利用CSocketFile类和Archive 类实现
(1)服务器程序流程
// 创建一个套接字对象
CSocket sockSrvr;
//为上述套接字对象创建一个套接字
sockSrvr.Create(nPort);
//开始侦听
sockSrvr.Listen( );
//创建一个新的套接字对象
CSocket sockRecv;
//接受连接
sockSrvr.Accept( sockRecv );
// 创建文件对象
CSocketFile file(&sockRecv);
//创建一个archive对象
CArchive arIn(&file, CArchive::load);
/*or*/_CArchive arOut(&file, CArchive::store);
//使用archive对象传输数据
arIn >> dwValue;
/*or*/ arOut < < dwValue;
(2)客户端程序流程
//创建一个套接字对象
CSocket sockClient;
//为这个对象创建一个套接字
sockClient.Create( );
//寻找一个连接
sockClient.Connect(strAddr, nPort);
//创建一个文件对象
CSocketFile file(&sockClient);
//创建一个archive对象
CArchive arIn(&file, CArchive::load);
/*or*/_CArchive arOut(&file, CArchive::store);
//使用这个对象传输数据
arOut < < dwValue;
/*or*/ arIn >> dwValue;
上述程序中, nPort 是Socket 的端口号,strAddr 是该
机器的IP地址(如202.197.1.3 或FTP://RedAlert.com等),这两个
变量在Server和Client中要一致。当Server进程运行至Listen 后便
处于睡眠状态直到Client进程执行Connect 时才被唤醒,而后两
个进程便开始传输数据了。
2、利用CSocket的成员函数实现
(1)服务器流程
//套接字初始化
if(!AfxSocketInit()){
MessageBox("WindowsSocket initial failed!","Send",MB_ICONSTOP);
Return;
}
// 创建两个套接字对象
CSocket ChatSend,server;
// 创建一个套接字
if(!ChatSend.Create(nPort)) // nPort=1025
MessageBox("SendSocket create failed!", "Send",MB_ICONSTOP);
else{
// 把本地地址给套接字
ChatSend.Bind(nProt,strAddr);
// strAddr="202.196.111.1"
// 开始侦听
ChatSend.Listen();
// 创建一个新的套接字并和他相连
ChatSend.Accept(Server);
}
//发送一个CString 对象
Server.SendTo(csSendText,csCounts,nPort,strAddr);
// 关闭这两个套接字
Server.Close();
ChatSend.Close();
(2)客户端程序流程
// 套接字初始化
if(!AfxSocketInit()){
MessageBox("WindowsSocket initial failed!", "Receive",MB_ICONSTOP);
return;
}
// 创建一个套接字对象
CSocket ChatRecieve;
// 创建一个套接字
if(!ChatReceive.Create()){
MessageBox("ReceiveSocket create failed!","Receive",MB_ICONSTOP);
return;
}
else{
// 创建一个对等套接字
ChatReceive.Connect(strAddr,nPort);
}
//接受一个CString 对象
ChatReceive.ReceiveFrom(csReceiveText,csCounts,strAddr,nPort);
// 关闭套接字
ChatReceive.Close();
上述两个进程完成的工作是:由Server 进程发送一字符串,
2、利用CSocket的成员函数实现
(1)服务器流程
//套接字初始化
if(!AfxSocketInit()){
MessageBox("WindowsSocket initial failed!","Send",MB_ICONSTOP);
Return;
}
// 创建两个套接字对象
CSocket ChatSend,server;
// 创建一个套接字
if(!ChatSend.Create(nPort)) // nPort=1025
MessageBox("SendSocket create failed!", "Send",MB_ICONSTOP);
else{
// 把本地地址给套接字
ChatSend.Bind(nProt,strAddr);
// strAddr="202.196.111.1"
// 开始侦听
ChatSend.Listen();
// 创建一个新的套接字并和他相连
ChatSend.Accept(Server);
}
//发送一个CString 对象
Server.SendTo(csSendText,csCounts,nPort,strAddr);
// 关闭这两个套接字
Server.Close();
ChatSend.Close();
(2)客户端程序流程
// 套接字初始化
if(!AfxSocketInit()){
MessageBox("WindowsSocket initial failed!", "Receive",MB_ICONSTOP);
return;
}
// 创建一个套接字对象
CSocket ChatRecieve;
// 创建一个套接字
if(!ChatReceive.Create()){
MessageBox("ReceiveSocket create failed!","Receive",MB_ICONSTOP);
return;
}
else{
// 创建一个对等套接字
ChatReceive.Connect(strAddr,nPort);
}
//接受一个CString 对象
ChatReceive.ReceiveFrom(csReceiveText,csCounts,strAddr,nPort);
// 关闭套接字
ChatReceive.Close();
上述两个进程完成的工作是:由Server 进程发送一字符串,
Client 进程
接收。 strAddr 和 nPort 的含义与方法1 中的相同 ;csSendText 和
csReceiveText 为发送与接收的字符串;csCounts为字串长度,
这一长度
在两个进程中要求接收长度小于或等于发送长度,否则会
导致数据传输错误。另外,在程序中要加入头文件afxsock.h,
CSocket 类
的有关说明均在afxsock.h 中。
方法1 适合于对多个不同类型数据的通信,方法2 适合于对字符串
的通信,具体选用何种方法则取决于具体应用的需求。
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&