純C語言在Windows平台下實現多線程監聽並處理Socket請求


#include <stdio.h>//io標准庫
#include <string.h>//string標准庫
#include <winsock2.h>//socket庫,必須在windows.h之前,否則會出現重定義問題。參考https://blog.csdn.net/hanxiaoyang123/article/details/84001362
#include <Windows.h> //windows庫
#include <process.h> //windows下的多線程庫
#include "loghelperheader.h"//自定義日志頭文件

#pragma comment(lib, "ws2_32")//socket的實現庫,winsock2.h只是接口

#define PORT 5778 //socket監聽端口
unsigned __stdcall HandRequest(void *pArg);//聲明socket請求處理函數,否則需要在調用之前定義該函數。
//啟動socket監聽並處理請求
int SocketListenStart()//單文件模式下用 int main()替換即可
{
    //1.初始化WSA
    WORD socketVersion = MAKEWORD(2, 2);//Socket編程中,MAKEWORD(2,2)就是調用2.2版,MAKEWORD(1,1)就是調用1.1版。
    printf("socketVersion:%p\n",socketVersion);
    printf(" MAKEWORD(3, 5):%p\n", MAKEWORD(3, 5));//用於測試MAKEWORD()方法的作用.
    WSADATA wsaData;//WSADATA是一種數據結構。這個結構被用來存儲被WSAStartup函數調用后返回的Windows Sockets數據。它包含Winsock.dll執行的數據。
    if (WSAStartup(socketVersion, &wsaData) != 0)//WSAStartup,即WSA(Windows Sockets Asynchronous,Windows異步套接字)的啟動命令。
    {
        WSACleanup();//必須調用WSACleanup()以允許Windows Sockets DLL釋放任何該應用程序的資源。
        WriteToLog(GetErrorMessage(GetLastError()));//打印最后一次錯誤信息,單文件模式下用printf替換
        return 0;//返回0表示啟動失敗
    }
    //創建套接字
    /*
    int socket(int domain, int type, int protocol);
    domain指定使用何種的地址類型,比較常用的有:
        PF_INET, AF_INET: Ipv4網絡協議;
        PF_INET6, AF_INET6: Ipv6網絡協議。
            AF 表示ADDRESS FAMILY 地址族,PF 表示PROTOCOL FAMILY 協議族,但這兩個宏定義是一樣的,所以使用哪個都沒有關系。UNIX系統支持AF_INET,AF_UNIX,AF_NS等,而DOS,Windows中僅支持AF_INET,它是網際網區域。
    type參數的作用是設置通信的協議類型,可能的取值如下所示:
        SOCK_STREAM: 提供面向連接的穩定數據傳輸,即TCP協議。
        OOB: 在所有數據傳送前必須使用connect()來建立連接狀態。
        SOCK_DGRAM: 使用不連續不可靠的數據包連接。
        SOCK_SEQPACKET: 提供連續可靠的數據包連接。
        SOCK_RAW: 提供原始網絡協議存取。
        SOCK_RDM: 提供可靠的數據包連接。
        SOCK_PACKET: 與網絡驅動程序直接通信。
    protocol用來指定socket所使用的傳輸協議編號。這一參數通常不具體設置,一般設置為0即可。
    */
    SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (slisten == INVALID_SOCKET)
    {
        WSACleanup();//必須調用WSACleanup()以允許Windows Sockets DLL釋放任何該應用程序的資源。
        WriteToLog(GetErrorMessage(GetLastError()));//打印最后一次錯誤信息
        return 0;
    }
    //綁定IP和端口
    struct sockaddr_in sin;
    /*sockaddr結構
    struct sockaddr_in
    {
        short sin_family;//Address family一般來說AF_INET(地址族)PF_INET(協議族)
        unsigned short sin_port;//Port number(必須要采用網絡數據格式,普通數字可以用htons()函數轉換成網絡數據格式的數字)
        struct in_addr sin_addr;//IP address in network byte order(Internet address)
        unsigned char sin_zero[8];//Same size as struct sockaddr沒有實際意義,只是為了 跟SOCKADDR結構在內存中對齊
    };
    */
    sin.sin_family = AF_INET;
    sin.sin_port = htons(PORT);//htons():將主機的無符號短整形數轉換成網絡字節順序。
    sin.sin_addr.S_un.S_addr = INADDR_ANY;//INADDR_ANY:0.0.0.0,表示監控通過任何IP地址訪問到本機的請求
    /*
    1、LPSOCKADDR:
    typedef struct sockaddr {
        #if (_WIN32_WINNT < 0x0600)
            u_short sa_family;
        #else
            ADDRESS_FAMILY sa_family; // Address family.
        #endif //(_WIN32_WINNT < 0x0600)
        CHAR sa_data[14]; // Up to 14 bytes of direct address.
    } SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;
    2、bind():
        #if INCL_WINSOCK_API_PROTOTYPES
            WINSOCK_API_LINKAGE
            int
            WSAAPI
            bind(
                _In_ SOCKET s,
                _In_reads_bytes_(namelen) const struct sockaddr FAR * name,
                _In_ int namelen
            );
        #endif //INCL_WINSOCK_API_PROTOTYPES
    */
    if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
    {
        WriteToLog(GetErrorMessage(GetLastError()));//打印最后一次錯誤信息
        return 0;
    }
    //開始監聽
    if (listen(slisten, 5) == SOCKET_ERROR)//最大連接數5
    {
        WriteToLog(GetErrorMessage(GetLastError()));//打印最后一次錯誤信息
        return 0;
    }
    //定義變量用於循環監聽並接收數據
    SOCKET sClient;
    struct sockaddr_in remoteAddr;
    int nAddrlen = sizeof(remoteAddr);
    while (1 == 1)
    {
        WriteToLog("等待新的連接...\n");
        sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
    // //A、同步接收數據
    // char revData[1024];
    // int ret = recv(sClient, revData, 1024, 0);
    // if (ret > 0)
    // {
    //  WriteToLog("讀取到數據\n");
    //  revData[ret] = 0x00;
    //  WriteToLog(revData);
    // }
    // else
    // {
    //  WriteToLog(GetErrorMessage(GetLastError()));
    // }
        //B、多線程異步接收數據
        HANDLE hth1;        //子線程句柄
        unsigned Thread1ID; //子線程ID
        // printf("%p\n", &sClient);
        // printf("%p\n", sClient);//這兩句語句用於理解指針,查看傳到子線程之前的內存地址,從而分析為什么子線程取得內存地址不對.
        //SOCKET* arg=&sClient;用這種方式進行多線程傳參多此一舉,直接傳指針即可
        //啟動子線程
        hth1 = (HANDLE)_beginthreadex(NULL,//安全屬性, 為NULL時表示默認安全性
                                     0,//線程的堆棧大小, 一般默認為0
                                     HandRequest,//子線程處理函數
                                     &sClient,//子線程參數,是一個void*類型, 傳遞多個參數時用結構體
                                     //(void*)arg,//用這種方式進行多線程傳參多此一舉,直接傳指針即可
                                     0,//線程初始狀態,0:立即運行;CREATE_SUSPEND:suspended(懸掛)
                                     &Thread1ID); //用於記錄線程ID的地址
        if (hth1 == 0)
        {
            WriteToLog(GetErrorMessage(GetLastError()));//打印最后一次錯誤信息
        }
        DWORD ExitCode; //線程退出碼
        GetExitCodeThread(hth1, &ExitCode);//獲取線程退出碼
        ResumeThread(hth1);//運行線程
        WriteToLog("本次連接處理完畢\n");
    }
    closesocket(sClient);
    closesocket(slisten);
    WSACleanup();//必須調用WSACleanup()以允許Windows Sockets DLL釋放任何該應用程序的資源。
}
/*
    __stdcall:被這個關鍵字修飾的函數,其參數都是從右向左通過堆棧傳遞的(__fastcall 的前面部分由ecx,edx傳), 函數調用在返回前要由被調用者清理堆棧。
*/
unsigned __stdcall HandRequest(void *pArg)
{
    // printf("%p\n", &pArg);
    // printf("%p\n",pArg);
    // printf("%p\n", (SOCKET)pArg);//以上代碼用於觀察*pArg指針指到哪里了,為什么總是報"在一個非套接字上嘗試了一個操作。"
    // printf("%p\n", &sClient);
    // printf("%p\n",sClient);//這兩句是用全局變量的方案取主線程的值.
    // SOCKET sClient = (SOCKET)pArg;
    SOCKET sClient = *(SOCKET*)pArg;//這里還沒完全理解,不能用簡單的語言來解釋。如果想不通過全局變量的方式來取socket,則只能通過這種方式來取到真正原始的socket內容.
    char revData[1024];
    if (sClient == INVALID_SOCKET)
    {
        WriteToLog(GetErrorMessage(GetLastError()));//打印最后一次錯誤信息
        return 0;
    }
    // WriteToLog("\n\n接收到一個連接:%s\r\n",inet_ntoa(remoteAddr.sin_addr));
    WriteToLog("開始讀取數據\n");
    //接收數據
    int ret1 = recv(sClient, revData, 1024, 0);//等待sClient傳輸完數據,並COPY一份到revData中
    if (ret1 > 0)
    {
        printf("%d\n",ret1);
        WriteToLog("讀取到數據%d\n",ret1);//為什么printf出來是正常的數字,通過fprintf寫到日志文件里則是"發送完數據-35051392"這樣的數據呢?
        revData[ret1] = 0x00;//標記結束
        WriteToLog(revData);
    }
    else
    {
        WriteToLog(GetErrorMessage(GetLastError()));//打印最后一次錯誤信息
    }
    //發送協議數據給瀏覽器,否則瀏覽器一直處於加載狀態.
    char *sendData = "HTTP/1.1 200 OK\r\nAccess-Control-Allow-Origin:*\r\nContent-Type:text/html;charset=UTF-8\r\nContent-Length:2\r\n\r\n12345";
    int ret2=send(sClient, sendData, strlen(sendData), 0);
    if (ret2 > 0)
    {
        printf("%d\n",ret2);
        WriteToLog("發送完數據%d\n",ret2);//為什么printf出來是正常的數字,通過fprintf寫到日志文件里則是"發送完數據-35051392"這樣的數據呢?
    }
    else
    {
        WriteToLog(GetErrorMessage(GetLastError()));//打印最后一次錯誤信息
    }
    return 1;
}


免責聲明!

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



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