MFC下CSocket編程詳解(轉)


原文轉自 http://blog.csdn.net/yejiansnake/article/details/2175778

 

MFC下CSocket編程詳解: 

1. 常用的函數和注意事項(詳細的函數接口說明請查看MSDN):

    CSocket::Create 初始化(一般寫服務器程序都不要用為好,用下面的 CSocket::Socket 初始化)

    CSocket::Socket初始化

    CSocket::SetSockOpt 設置socket選項

    CSocket::Bind 綁定地址端口

    CSocket::Connect 連接

    CSocket::Listen  監聽

    CSocket::Accept 接收外部連接的socket
 
    CSocket::Send 發送內容

    CSocket::Receive 接收內容

    CSocket::Close 關閉(不等於delete)

    (1) 在使用MFC編寫socket程序時,必須要包含<afxsock.h>都文件。

    (2) AfxSocketInit() 這個函數,在使用CSocket前一定要先調用該函數,否則使用CSocket會出錯;並且該函數還有一個重要的使用方式,就是在某個線程下使用 CSocket 前一定要調用,就算主線程調用了該函數,在子線程下使用 CSocket 也要先調用該函數,要不會出錯。

    (3) 還要注意的是, Create 方法已經包含了 Bind 方法,如果是以 Create 方法初始化的前提下不能再調用 Bind ,要不一定出錯。

2. 以下是使用例子代碼,通過例子來學習如何使用 CSocket 進行編程, 並且附件上有完整的例子代碼。例子的可以在我的發布資源中找到:MFC下CSocket編程例子 http://download.csdn.net/source/379597

    (1) 客戶端主要代碼:   

//初始化
AfxSocketInit();

//創建 CSocket 對象
CSocket aSocket;
CString strIP;
CString strPort;
CString strText;

this->GetDlgItem(IDC_EDIT_IP)->GetWindowText(strIP);
this->GetDlgItem(IDC_EDIT_PORT)->GetWindowText(strPort);
this->GetDlgItem(IDC_EDIT_TEXT)->GetWindowText(strText);

//初始化 CSocket 對象, 因為客戶端不需要綁定任何端口和地址, 所以用默認參數即可
if (!aSocket.Create())
{
    char szMsg[1024] = { 0 };
    sprintf(szMsg, "create faild: %d", aSocket.GetLastError());
    AfxMessageBox(szMsg);
    return;
}

//轉換需要連接的端口內容類型
int nPort = atoi(strPort);

//連接指定的地址和端口
if (aSocket.Connect(strIP, nPort))
{
    char szRecValue[1024] = { 0 };

    //發送內容給服務器
    aSocket.Send(strText, strText.GetLength());

    //接收服務器發送回來的內容(該方法會阻塞, 在此等待有內容接收到才繼續向下執行)
    aSocket.Receive((void *)szRecValue, 1024);

    AfxMessageBox(szRecValue);
}
else
{
    char szMsg[1024] = { 0 };
    sprintf(szMsg, "create faild: %d", aSocket.GetLastError());
    AfxMessageBox(szMsg);
}

//關閉
aSocket.Close();

(2) 服務器端代碼:

unsigned int StartServer(LPVOID lParam)
{
    //初始化Winscok
    if (!AfxSocketInit())
    {
        AfxMessageBox(IDP_SOCKETS_INIT_FAILED);
        return 1;
    }

    m_exit = false;
    CServerDlg *aDlg = (CServerDlg *)lParam;
    CString strPort;
    aDlg->GetDlgItemText(IDC_EDIT_PORT, strPort);
    UINT nPort = atoi(strPort);

    //socket------------------------------------------------
    CSocket aSocket, serverSocket;
    //最好不要使用aSocket.Create創建,因為容易會出現10048錯誤
    if (!aSocket.Socket())
    {
        char szError[256] = { 0 };
        sprintf(szError, "Create Faild: %d", GetLastError());
        AfxMessageBox(szError);
        return 1;
    }

    BOOL bOptVal = TRUE;
    int bOptLen = sizeof(BOOL);

    //設置Socket的選項, 解決10048錯誤必須的步驟
    aSocket.SetSockOpt(SO_REUSEADDR, (void *)&bOptVal, bOptLen, SOL_SOCKET);
    //監聽
    if (!aSocket.Listen(10))
    {
        char szError[256] = { 0 };
        sprintf(szError, "Listen Faild: %d", GetLastError());
        AfxMessageBox(szError);
        return 1;
    }

    CString strText;
    aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
    strText += "Server Start!  ";
    aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

    while (!m_exit)
    {
        //接收外部連接
        if (!aSocket.Accept(serverSocket))
        {
            continue;
        }
        else
        {
            char szRecvMsg[256] = { 0 };
            char szOutMsg[256] = { 0 };

            //接收客戶端內容:阻塞
            serverSocket.Receive(szRecvMsg, 256);

            sprintf(szOutMsg, "Receive Msg: %s  ", szRecvMsg);
            aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
            strText += szOutMsg;
            aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

            //發送內容給客戶端
            serverSocket.Send("Have Receive The Msg", 50);

            //關閉
            serverSocket.Close();
        }
    }

    //關閉
    aSocket.Close();
    serverSocket.Close();

    aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
    strText += "Have Close!";
    aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

    return 0;
}

//綁定端口
if (!aSocket.Bind(nPort))
{
    char szError[256] = { 0 };
    sprintf(szError, "Bind Faild: %d", GetLastError());
    AfxMessageBox(szError);
    return 1;
}

(3) SDK 下的服務器端代碼

//子線程函數
unsigned int StartServer(LPVOID lParam)
{
    //初始化Winsock, AfxSocketInit() 也是封裝了這些語句, 不過 AfxSocketInit() 所做的事比這里多些
    WSADATA wsaData;

    //Winsock 的版本, 建議用1.1 ,兼容性好
    WORD wVersionRequested = MAKEWORD(1, 1);
    int nResult = WSAStartup(wVersionRequested, &wsaData);
    if (nResult != 0)
    {
        return 1;
    }

    //----------------------------------------------------- 
    m_exit = false;
    CServerDlg *aDlg = (CServerDlg *)lParam;
    CString strPort;
    aDlg->GetDlgItemText(IDC_EDIT_PORT, strPort);
    UINT nPort = atoi(strPort);

    //socket------------------------------------------------
    //接口對象
    SOCKET aSocket, serverSocket;

    //尋址相關結構
    sockaddr_in serverSockaddr;
    memset(&serverSockaddr, 0, sizeof(serverSockaddr));
    aSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (aSocket == INVALID_SOCKET)
    {
        char szError[256] = { 0 };
        sprintf(szError, "Create Faild: %d", GetLastError());
        AfxMessageBox(szError);
        return 1;
    }

    //注意,該處非常重要,取值的正確與否決定關閉scoket后端口是否能正常釋放
    BOOL bOptVal = TRUE;
    int bOptLen = sizeof(BOOL);

    //設置 socket 選項, SOL_SOCKET 和 SO_REUSEADDR 一起使用, 並且后面的參數如上, 關閉scoket后端口便能正常釋放
    setsockopt(aSocket, SOL_SOCKET, SO_REUSEADDR, (char *)&bOptVal, bOptLen);

    //尋址相關結構
    sockaddr_in aSockaddr;
    memset(&aSockaddr, 0, sizeof(aSockaddr));
    aSockaddr.sin_family = AF_INET;
    aSockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    aSockaddr.sin_port = htons((u_short)nPort);

    //綁定: 注意參數的類型轉換
    if (bind(aSocket, (sockaddr *)&aSockaddr, sizeof(aSockaddr)) == SOCKET_ERROR)
    {
        char szError[256] = { 0 };
        sprintf(szError, "Bind Faild: %d", GetLastError());
        AfxMessageBox(szError);
        return 1;
    }

    //監聽
    if (listen(aSocket, 10) == SOCKET_ERROR)
    {
        char szError[256] = { 0 };
        sprintf(szError, "Listen Faild: %d", GetLastError());
        AfxMessageBox(szError);
        return 1;
    }

    CString strText;
    aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
    strText += "Server Start!  ";
    aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

    while (!m_exit)
    {
        //接收外部連接, 非阻塞
        serverSocket = accept(aSocket, (sockaddr *)&serverSockaddr, 0);
        if (serverSocket == INVALID_SOCKET)
        {
            continue;
        }
        else
        {
            char szRecvMsg[256] = { 0 };
            char szOutMsg[256] = { 0 };

            //接收客戶端內容: 阻塞
            recv(serverSocket, szRecvMsg, 256, 0);

            sprintf(szOutMsg, "Receive Msg: %s  ", szRecvMsg);
            aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
            strText += szOutMsg;
            aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

            //發送內容給客戶端
            send(serverSocket, "Have Receive The Msg", 50, 0);

            //關閉
            closesocket(serverSocket);
        }
    }

    //關閉
    closesocket(aSocket);
    closesocket(serverSocket);

    aDlg->GetDlgItemText(IDC_EDIT_LOG, strText);
    strText += "Have Close!";
    aDlg->SetDlgItemText(IDC_EDIT_LOG, strText);

    //當你使用完Winsock接口后,要調用下面的函數對其占用的資源進行釋放
    WSACleanup();
    return 0;
}

3. 總結
   (1) MFC進行編程的確比較簡單, 用的代碼比較少, 又容易管理。唯一不好的地方在於很多細節上的東西在資料上不容易查出來, 關聯性非常緊密, 象 AfxSocketInit() 函數就是,函數的實現里包含着很多不容易理解的類, 並且記錄了非常多的環境信息, 比如創建的線程等等, 這樣在主線程調用后子線程沒有調用執行 CSocket 的操作就會出錯。還有就是有些接口的設計非常離奇, 象 CSocket::Create 的接口就是, 實現上還執行了 CSocket::Bind , 非常不容易被發現。並且MSDN上對 CSocket::Bind 的說明又明顯的提示需要顯示執行 CSocket::Bind 操作。

   (2) SDK 編程能理解函數的調用順序和代碼的結構就比較容易,省去了MFC下封裝了不知道什么東西的部分,使得代碼的流程容易控制。但是從上面的例子來看非常明顯的並且不是那么容易理解。不僅僅有很多奇怪的結構(微軟的命名一直如此, 無所雲雲), 並且函數相關太過於緊密, 初學者想一下子熟悉使用並不容易, 對開發者來說代碼管理起來非常麻煩。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM