#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;
}