1. 程式人生 > >MFC封裝Socket伺服器類

MFC封裝Socket伺服器類

在網上找了一大圈沒有一個滿意的,我主要是想把客戶端連線儲存起來這樣可以向所有客戶端發訊息或者管理客戶端進入退出事件,主要卡在我自己定一個socket伺服器類,但是這個自定義類自定義接收客戶端訊息不觸發,看到了這篇部落格http://www.cnblogs.com/rainbow70626/p/8034895.html,知道了原因,但是還是不行,提示錯誤,希望有大神可以解決一下接收資料自定義訊息問題。總之,目前卡在接收這個地方。好了廢話不多說,下面其實改一改是一個非常好的類。經過測試,如果直接在對話方塊裡面實現,基本無什麼大問題。封裝一個類當然是最好。

先定義一個儲存客戶端物件,ip地址和埠的類,標頭檔案程式碼

#pragma once
class CClientSet
{
public:
    CClientSet();
    ~CClientSet();
    SOCKET ClientSocket;//客戶端物件
    int nPort;//客戶端埠
    char *strIp;//客戶端IP
};
這裡cpp就不需要寫程式碼了。

然後就是自己封裝一個類ServerSocket.h程式碼

#pragma once
#include<vector>
#include "ClientSet.h"
using namespace std;
#define WM_SERVER_ACCEPT WM_USER + 101
typedef void(*ReciveData)(char[]);//回撥函式,接受客戶端發來資料
class CServerSocket :public CWnd
{
public:
    CServerSocket();
    //CServerSocket(CWnd *pParent=NULL);
    ~CServerSocket();
    SOCKADDR_IN ServerAddr;
    SOCKET ListeningSocket;//監聽的socket物件
    int nPort;//埠號
    int nClientCount = 0;
    char * strIp;//IP地址
    void Start();//開啟伺服器
    void Stop();//關閉伺服器
    void SendData(char buffer[]);//向所有客戶端發訊息
    void SendData(char buffer[],char* strIp);//向指定的IP發訊息
//    LRESULT  OnSocket(WPARAM wParam, LPARAM lParam);
    vector<CClientSet> vecClientSocket;//儲存所有客戶端

protected:
    // Generated message map functions
    //{{AFX_MSG(CServerDlg)
    
    afx_msg LRESULT OnServerAccept(WPARAM wParam, LPARAM lParam);
    //}}AFX_MSG
    DECLARE_MESSAGE_MAP()
};

實現:

#include "stdafx.h"
#include "ServerSocket.h"
#include "SocketHelperDlg.h"
#include "ClientSet.h"
#include <vector>

using namespace std;
CServerSocket::CServerSocket()
{
    //CWnd::Create(NULL, "ServerSocket", WS_CHILD, CRect(0, 0, 0, 0), ::AfxGetMainWnd(), 1234);
}
//CServerSocket::CServerSocket(CWnd *pParent)
//{
//    CWnd::Create(NULL, "ServerSocket", WS_CHILD, CRect(0, 0, 0, 0), pParent, 1234);
//}

CServerSocket::~CServerSocket()
{
    //CWnd::DestroyWindow();
}

BEGIN_MESSAGE_MAP(CServerSocket, CWnd)
    //{{AFX_MSG_MAP(CServerSocket)
    ON_MESSAGE(WM_SERVER_ACCEPT, OnServerAccept)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()
void CServerSocket::Start()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);
    // WSAStartup(0x0202, &wsaData);
    ListeningSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (ListeningSocket == INVALID_SOCKET)//建立失敗
    {
        WSACleanup();
        closesocket(ListeningSocket);
        return;
    }

    ServerAddr.sin_family = AF_INET;
    ServerAddr.sin_addr.s_addr = inet_addr(strIp);//INADDR_ANY
    ServerAddr.sin_port = htons(nPort);
    WSAAsyncSelect(ListeningSocket,m_hWnd, WM_SERVER_ACCEPT, FD_ACCEPT | FD_READ | FD_CLOSE);
    /*設定為非同步通訊模式,併為它註冊各種網路非同步事件,
    其中m_hWnd 為應用程式的主對話方塊或主視窗的控制代碼
    當檢測到相應時間後才為視窗控制代碼傳送訊息*/

//關鍵問題在m_hWnd控制代碼問題,到底是主視窗控制代碼還是這個類控制代碼,目前沒解決
    if (bind(ListeningSocket, (sockaddr *)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
    {
        
        WSACleanup();
        closesocket(ListeningSocket);
        return;
    }
    if (listen(ListeningSocket, 20) == SOCKET_ERROR)
    {
        WSACleanup();
        closesocket(ListeningSocket);
        return;
    }


}
void CServerSocket::Stop()
{
    //清除所有客戶端
    for (int i = 0; i<vecClientSocket.size(); i++)
    {
            vecClientSocket[i].ClientSocket = INVALID_SOCKET;
            vecClientSocket.erase(vecClientSocket.begin() + i);
    }
    nClientCount = 0;//客戶端歸0,其實用不上,一般關閉,軟體也關閉
    WSACleanup();
    shutdown(ListeningSocket, 2);
    closesocket(ListeningSocket);
}
void CServerSocket::SendData(char buffer[])
{
    //向所有客戶端發訊息
    for (int i = 0; i<vecClientSocket.size(); i++)
    {
        if (vecClientSocket[i].ClientSocket != INVALID_SOCKET)
            send(vecClientSocket[i].ClientSocket, buffer, sizeof(buffer),NULL);
    }
}
void CServerSocket::SendData(char buffer[],char* strIp)
{
    //向指定IP端發訊息
    for (int i = 0; i<vecClientSocket.size(); i++)
    {
        if (vecClientSocket[i].ClientSocket != INVALID_SOCKET && vecClientSocket[i].strIp==strIp)
            send(vecClientSocket[i].ClientSocket, buffer, sizeof(buffer), NULL);
    }
}

//新增非同步處理訊息響應函式  ,訊息沒有響應,目前網上資料就一篇講述自定義類自定義訊息觸發問題,資料試過,不知道為什麼不行
LRESULT  CServerSocket::OnServerAccept(WPARAM wParam, LPARAM lParam){
    int iEvent = WSAGETSELECTEVENT(lParam);
    CString str;
    str.Format("%d", iEvent);
    AfxMessageBox(str);
    switch (iEvent)
    {
    case FD_ACCEPT://客戶端連線事件
    {
                       SOCKADDR_IN addr;
                       int nLen = sizeof(SOCKADDR_IN);
                       SOCKET ClientSocket = accept(ListeningSocket, (sockaddr*)&addr, &nLen);
                       if (ClientSocket != INVALID_SOCKET)

                       {
                           CClientSet ccs;//宣告一個客戶端物件集
                           ccs.ClientSocket = ClientSocket;//儲存客戶端物件
                           ccs.nPort = addr.sin_port;//儲存客戶端埠
                           ccs.strIp = inet_ntoa(addr.sin_addr);//儲存客戶端的ip地址
                           vecClientSocket.push_back(ccs);//儲存的客戶端物件
                           nClientCount++;//客戶端來了就加一個
                       }
    }
                
                    
                       break;
    case FD_READ://客戶端傳送資料過來
    {
                     int nMsgLen;
                     char buffer[1024];
                     for (int i = 0; i < vecClientSocket.size(); i++)
                     {
                         if (vecClientSocket[i].ClientSocket == wParam);
                         {
                             nMsgLen = recv(vecClientSocket[i].ClientSocket, buffer, 1024, NULL);
                             //查詢是哪個客戶端發資料過來,找到就接收它發來的資料
                             buffer[nMsgLen] = '\0';
                             ReciveData(buffer);//回撥函式,注意回撥函式必須是靜態或者全域性函式
                             break;
                         }
                     }
    }
        
                     break;
    
    case FD_CLOSE://客戶端斷開事件
    {
                      for (int i = 0; i<vecClientSocket.size(); i++)
                      {
                          if (vecClientSocket[i].ClientSocket == wParam);
                          {
                              //查詢是誰斷開客戶端,找到就將客戶端物件清除
                              vecClientSocket[i].ClientSocket = INVALID_SOCKET;
                              vecClientSocket.erase(vecClientSocket.begin() + i);
                              nClientCount--;//客戶端減一
                              break;
                          }

                      }
    }
        
        

        break;


    }
    return NULL;
}
 

如果這個類訊息可以觸發,則呼叫非常簡單,新建一個對話方塊,初始化加入程式碼:

  css.strIp = "127.0.0.1";
    css.nPort = 5200;
    css.Start();

注意標頭檔案需要什麼CServerSocket css;

視窗destroy則只需要呼叫一下程式碼

 css.Stop();

處理客戶端訊息可以用回撥函式:


void CSocketHelperDlg::OnReciveData(char buffer[])
{
    CString str(buffer);
    AfxMessageBox(str);
}

這個構架很好,只是卡在不能接受訊息,如果大神看到這篇文章,請指點一下。