VC++ MFC socket編程

socket編程用法---- 隨着計算機網絡化的深入,計算機網絡編程在程序設計的過程中變得日益重要。由於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_MSG
IDC_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類添加變量。

Control ID Type Member

IDC_Addr CString m_Addr
IDC_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;
//默認端口1088
m_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>10
SetTimer(1,1000,NULL);
      TryCount=0;
}
}

---- 10. 添加Windows消息WM_TIMER響應函數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()
{
//關閉Socket
m_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提供了大量的類庫,我們若能靈活的使用他們,便會大大提高編程的效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章