3.8 一个使用CSocket类的网络通信实例

本例采用CSocket编程模型建立一个聊天程序的简单实例。建立项目时注意选上“Windows套接字”复选框。

3.8.1 服务器端应用程序设计(ServerDemo)

1)界面

 

各控件属性如下

控件类型

控件ID

Caption属性

控件变量

变量类型

列表框

IDC_Log

m_LogCtrl

CListBox

编辑框

IDC_Message

m_MsgCtrl

CEdit

按钮

IDC_Send

发送

M_SendCtrl

CButton

静态控件

IDC_STATIC

记录

静态控件

IDC_STATIC

待发消息

列表框控件IDC_Log属性“sort”值为false,属性“Horizontal Scroll”值为true。

按钮IDC_Send属性Disable值为true。

2)定义CSocket类的派生类CServSocket和CRecvSocket。

    从CSocket编程模型知道,服务器端需要两种套接字,一个用来侦听连接请求,一个用来与请求连接的套接字建立连接。因此,为程序添加两个CSocket派生类:SServSocket和CRecvSocket,它们与对话框类密切配合,共同完成程序所要求实现的功能。

3)建立套接字与对话框类的关联

在程序中,对话框类要用到套接字类,而套接字类在响应某些消息,如在函数OnAccept、OnReceive中进行处理时,也要改变对话框的某些控件状态,以反映给用户这些事情的发生。

这里存在着两个类相互使用的情况,把套接字类对象定义成对话框类的成员变量,同时在套接字类中也把对话框类定义为成员变量。如何实现这样的用法呢?在对话框类头文件中加入套接字头文件的声明,然后在套接字类头文件中加入对话框类头文件的声明,这样的做法显然行不通。

具体做法应该如下:

首先,在ServerDemoDlg.h中加入套接字类头文件的声明,语句#pragma once的后面加入如下语句:

#include "ServSocket.h"

#include "RecvSocket.h"

然后在该文件中为CServerDemoDlg类增加两个公有成员变量,语句如下:

CServSocket *ServSock;

CRecvSocket *RecvSock;

这样在对话框类中就可以使用套接字类了。

继续在套接字类中加入对话框类信息。

首先,在ServSocket.h文件的开头,语句#pragma once的后面加入如下语句:

class CServerDemoDlg;

然后,在该文件中为CServSocket类添加一个公有成员变量和一个构造函数:

CServSocket(CServerDemoDlg *Dlg);

CServerDemoDlg *m_Dlg;

接着在ServSocket.cpp文件中添加新的构造函数的实现,并添加一条关于ServerDemoDlg.h文件的预编译声明,代码如下:

#include “ServerDemoDlg.h”

CServSocket::CServSocket(CServerDemoDlg *Dlg)

{

       m_Dlg=Dlg;

}

这样,在套接字类中也可以通过成员变量使用对话框了。

使用同样的方法对CRecvSocket类进行设置,使其也可以通过成员变量使用对话框。

4)为套接字添加串行化读写信息的功能。在服务器端的两个套接字中,只有CRecvSocket套接字是真正与客户端套接字建立连接,发送与接收数据的,因此,我们只为该类添加串行化读写信息功能。在RecvSocket.h文件中为类CRecvSocket添加三个公有成员变量。

    CSocketFile *m_File;

       CArchive *m_ArIn;

       CArchive *m_ArOut;

5)在对话框中初始化套接字并侦听连接请求。在OnInitDialog函数中添加如下代码:

    // TODO: 在此添加额外的初始化代码

     if(ServSock=new CServSocket(this))

     {

         if(ServSock->Create (9547))

         {

              m_LogCtrl.AddString ("等待连接......");

              ServSock->Listen ();

         }

         else

         {

              m_LogCtrl.AddString ("初始化失败,请重新启动程序!");

              delete ServSock;

         }

     }

     else

     {

         m_LogCtrl.AddString ("初始化失败,请重新启动程序!");

     }

上述代码主要是创建并初始化ServSock套接字,并开始侦听连接请求。

6)接受连接请求。由于是CServSocket类的ServSock对象在侦听连接请求,因此由该类来接受连接请求。

首先,在ServSocket.h文件中加入如下语句:

#iinclude “RecvSocket.h”

然后,重载该类的OnAccept函数,在该函数中添加如下代码:

CRecvSocket *tempSock;

     if(tempSock=new CRecvSocket(this->m_Dlg ))

     {

         if(Accept(*tempSock))

         {

              tempSock->m_File =new CSocketFile(tempSock);

              tempSock->m_ArIn =new CArchive(tempSock->m_File ,CArchive::load );

              tempSock->m_ArOut =new CArchive(tempSock->m_File ,CArchive::store );

              m_Dlg->RecvSock =tempSock;

              tempSock=NULL;

              m_Dlg->m_LogCtrl .AddString ("连接成功,可以开始传递消息");

              m_Dlg->m_SendCtrl.EnableWindow (true);

         }

         else

         {

              m_Dlg->m_LogCtrl .AddString ("客户端当前的连接尝试失败");

              delete tempSock;

         }

     }

     else

     {

         m_Dlg->m_LogCtrl .AddString ("连接套接字初始化失败");

     }

上述代码首先调用Accept函数接受连接请求,然后为该连接创建一个CRecvSocket类型的套接字,并为该套接字关联CArchive对象,使其能实现串行化传输信息的功能。最后把关联好的套接字传回给对话框对象供其使用。这样,对话框对象的成员变量RecvSock套接字便与客户端套接字之间建立了一条信息通道,信息将在两个套接字之间传递。

7)接收信息,连接建立成功后,当有信息到达服务器端时,就会引发RecvSock套接字对象的OnReceive函数,因此需要重载CRecvSocket类的OnReceive函数。添加代码如下:

CString str;

(*m_ArIn)>>str;

m_Dlg->m_LogCtrl .AddString ("对方发来的信息如下:");

    m_Dlg->m_LogCtrl .AddString (str);

m_Dlg->m_LogCtrl .SetCurSel (m_Dlg->m_LogCtrl .GetCount() - 1);

8)发送信息。为对话框“发送”按钮添加事件处理函数OnBnClickedSend(),代码如下:

void CServerDemoDlg::OnBnClickedSend()

{

     // TODO: 在此添加控件通知处理程序代码

     CString str;

     m_MsgCtrl.GetWindowText (str);

     if(str.GetLength ()==0)

         AfxMessageBox("空信息,所以不发出");

     else

     {

         m_LogCtrl.AddString ("你发出的信息如下:");

         m_LogCtrl.AddString (str);

         m_LogCtrl.SetCurSel (m_LogCtrl.GetCount ()-1);

          *(RecvSock->m_ArOut )<<str;

         RecvSock->m_ArOut ->Flush ();

     }

}

3.8.2 客户端应用程序设计(项目名称ClientDemo)

1)界面

 

各控件属性如下

控件类型

控件ID

Caption属性

控件变量

变量类型

列表框

IDC_Log

m_LogCtrl

CListBox

编辑框

IDC_Message

m_MsgCtrl

CEdit

按钮

IDC_Send

发送

M_SendCtrl

CButton

静态控件

IDC_STATIC

记录

静态控件

IDC_STATIC

待发消息

列表框控件IDC_Log属性“sort”值为false,属性“Horizontal Scroll”值为true。

按钮IDC_Send属性Disable值为true。

2)创建套接字类(从CSocket类派生)。客户端只需要一个套接字,命名为CClientSocket。

3)建立对话框类与套接字类的关联。

首先,在ClientDemoDlg.h文件的开头,语句#pragma once后面加入如下语句:

#include “ClientSocket.h”

然后,在该文件中为CClientDemoDlg类添加一个公有成员变量,语句如下:

CClientSocket *ClientSock;

接着,在ClientSocket.h文件的开头,语句#pragma once后面加入如下语句:

class CClientDemoDlg;

然后,在该文件中为CClientSocket类添加一公有成员变量和一个构造函数,语句如下:

CClientSocket(CClientDemoDlg *Dlg);

CClientDemoDlg *m_Dlg;

接着,在ClientSocket.cpp文件中添加新的构造函数的实现代码,并添加一条关于CClientDemoDlg.h文件的预编译声明,代码如下:

#include "ClientDemoDlg.h"

CClientSocket::CClientSocket(CClientDemoDlg *Dlg)

{

         m_Dlg=Dlg;

}

这样,便完成了对话框和套接字之间的连接了。

4)为套接字添加串行化读写信息的功能。在ClientSocket.h文件中,为类CClientSocket添加三个公有成员变量,代码如下:

CSocketFile *m_File;

CArchive *m_ArIn;

CArchive *m_ArOut;

5)在对话框中初始化套接字并建立连接

在对话框类的OnInitDialog函数中添加如下代码

// TODO: 在此添加额外的初始化代码

     m_LogCtrl.AddString ("正在连接......");

     if(ClientSock=new CClientSocket(this))

     {

         if(ClientSock->Create())

         {

              if(ClientSock->Connect ("localhost",9547))

              {

                   ClientSock->m_File =new CSocketFile(ClientSock);

                   ClientSock->m_ArIn =new CArchive(ClientSock->m_File ,CArchive::load );

                   ClientSock->m_ArOut =new CArchive(ClientSock->m_File,CArchive::store );

                   m_LogCtrl.AddString ("连接成功,可以开始传递消息");

                   m_SendCtrl.EnableWindow (true);

              }

              else

              {

                   m_LogCtrl.AddString ("连接不成功");

                   delete ClientSock;

              }

         }

         else

         {

              m_LogCtrl.AddString ("初始化失败,请重新启动程序");

              delete ClientSock;

         }

     }

     else

     {

         m_LogCtrl.AddString ("初始化失败,请重新启动程序");

     }

6)接收消息。消息到来时,会引发套接字的OnReceive消息,因此要重载CClientSocket类的OnReceive函数,在其中添加代码如下

// TODO: 在此添加专用代码和/或调用基类

    CString str;

     m_Dlg->m_LogCtrl .AddString ("对方发来消息如下:");

     *m_ArIn>>str;

     m_Dlg->m_LogCtrl .AddString (str);

     m_Dlg->m_LogCtrl .SetCurSel (m_Dlg->m_LogCtrl .GetCount ()-1);

7)发送信息。为对话框“发送”按钮添加事件处理函数OnBnClickedSend(),代码如下:

    void CClientDemoDlg::OnBnClickedSend()

{

     // TODO: 在此添加控件通知处理程序代码

     CString str;

     m_MsgCtrl.GetWindowText (str);

     if(str.GetLength ()==0)

         AfxMessageBox("空信息,所以不发出");

     else

     {

         m_LogCtrl.AddString ("你发的信息如下:");

         m_LogCtrl.AddString (str);

         m_LogCtrl.SetCurSel (m_LogCtrl.GetCount ()-1);

         *(ClientSock->m_ArOut )<<str;

         ClientSock->m_ArOut ->Flush ();

     }

}

 

# re: 一个使用CSocket类的网络通信实例
服务器程序按照您的步骤编译好后
if(ServSock=new CServSocket(this))
{
if(ServSock->Create (9547))
{
m_LogCtrl.AddString ("等待连接......");
ServSock->Listen ();
}
else
{
m_LogCtrl.AddString ("初始化失败,请重新启动程序!");
delete ServSock;
}
}
create失败,总是跳到else里面去了,请问是什么原因啊?如果方便的话,可以把源程序发我邮箱号码,我自己研究!zqxwce111@163.com
# re: 一个使用CSocket类的网络通信实例
加入 

if (!AfxSocketInit()) 

AfxMessageBox(IDP_SOCKETS_INIT_FAILED); 
return FALSE; 


在资源头文件中加入
#define IDP_SOCKETS_INIT_FAILED 104 

还要在string table中加入 
ID IDP_SOCKETS_INIT_FAILED
Caption Windows 通信端口初始化失败。