纯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