1. 程式人生 > >TCP/IP基礎知識複習2

TCP/IP基礎知識複習2

/*
2018-11-15 08:51:41
ch07 優雅的斷開套接字連線
*/
Linux下的shutdown
使用半斷開的方式進行斷開
shutdown函式:關閉函式禁止套接字傳送或者接受訊息
函式原型:
int shutdown(
      SOCKET s,    //要斷開套接字的控制代碼
    int how        //傳遞斷開方式資訊
);

how的引數選擇:
=> SHUT_RD        斷開輸入流
=> SHUT_WR        斷開輸出流
=> SHUT_RDWR    同時斷開I/O流

為什麼需要用到半關閉技術?
當客戶端或者服務端 有一方斷開連線的時候,會導致有資料傳送或者和接收不到,
這個時候使用shutdown函式 只關閉伺服器的輸出流,這樣既可以傳送訊息結束標誌(EOF)
又保留了輸入流,可以接受對方資料

基於windows的實現
shutdown函式詳解
int shutdown(
      SOCKET s,    //要斷開連線字的控制代碼
      int how        //斷開方式的資訊
);

第二引數可取值
SD_RECEIVE:斷開輸入流
SD_SEND:斷開輸出流
SD_BOTH:同時斷開I/OL流

附帶例子程式碼如下:

// Demo_Server.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//
/*
2018-11-15 10:05:48
實際例子:通過傳輸檔案的方式來解決問題
根據檔案內容長度來 傳送送相應的訊息給服務端
*/


#include "pch.h"
#include <iostream>
#include <Winsock2.h>
#include <string>

using namespace std;

void ErrorHandling(const char *message);

int main()
{
	WSAData wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() error!");
		return EXIT_FAILURE;
	}

	FILE *fp = fopen("testfile.cpp", "rb");	//開啟檔案testfile.cpp

	//Step1 建立套接字
	SOCKET hServSock = socket(PF_INET, SOCK_STREAM, 0);	//初始化套接字

	SOCKADDR_IN servAdr;
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAdr.sin_port = htons(8888);

	//Step2 繫結地址和埠資訊
	bind(hServSock, (const sockaddr *)&servAdr, sizeof(servAdr));

	//Step3 監聽客戶端訊息
	listen(hServSock, 5);

	//Step4 接收客戶端的訊息 並返回客戶端的控制代碼以及地址埠相關資訊
	SOCKADDR_IN ClnAddr;
	int length = sizeof(ClnAddr);
	SOCKET hClnSock = accept(hServSock, (sockaddr *)&ClnAddr, &length);

	int readFileLen = 0;
	char buf[30] = { 0 };
	string strShowResult = "";	//這裡用來記錄從檔案中取出的字串
	while (1)
	{
		readFileLen = fread((void*)buf, 1, 30, fp);
		if (readFileLen < 30)
		{
			send(hClnSock, buf, readFileLen, 0);
			break;
		}
		send(hClnSock, buf, readFileLen, 0);
	}

	//實驗shutdown的使用
	shutdown(hClnSock, SD_SEND);
	char message[30] = { 0 };
	recv(hClnSock, (char*)message, 30, 0);
	std::cout << "Message From CLient: " << message << std::endl;

	fclose(fp);
	closesocket(hServSock);
	closesocket(hClnSock);
	WSACleanup();

	system("pause");
	return EXIT_SUCCESS;
}

void ErrorHandling(const char * message)
{
	std::cout << message << std::endl;
}
// Demo_Client.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include "pch.h"
#include <iostream>
#include <Winsock2.h>
#include <string>
using namespace std;

void ErrorHandling(const char *message);

int main()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() Error");
	}

	FILE *fp = fopen("receive.dat", "wb");

	//Step1 建立套接字
	SOCKET hClnSock = socket(PF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == hClnSock)
	{
		ErrorHandling("socket() Error");
	}

	SOCKADDR_IN serAddr;
	memset(&serAddr, 0, sizeof(serAddr));
	serAddr.sin_family = AF_INET;
	serAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	serAddr.sin_port = htons(8888);

	//Step2 connect與服務端進行連線
	connect(hClnSock, (const sockaddr*)&serAddr, sizeof(serAddr));

	int readFileLen = 0;
	char buf[30] = { 0 };
	//迴圈的寫入 一次寫入30個字元
	while ((readFileLen = recv(hClnSock, buf, 30, 0)) != 0)
	{
		fwrite((void*)buf, 1, readFileLen, fp);
	}

	//不會被髮出去
	std::cout << "Received file data!";
	send(hClnSock, "收到", 10, 0);

	fclose(fp);

	closesocket(hClnSock);
	WSACleanup();

	system("pause");
	return EXIT_SUCCESS;
}

void ErrorHandling(const char * message)
{
	std::cout << message << std::endl;
	exit(-1);
}


/*
2018-11-15 11:35:35
ch08 域名及網路地址
*/
DNS是對IP地址和域名進行相互轉換的系統,其核心是DNS伺服器

如何通過域名 解析出來 IP地址
在控制檯視窗
ping 域名
就可以得到 對應的IP地址

通過使用函式轉換來完成 將域名轉換為IP的操作    函式getaddrinfo
函式原型:

關於域名和IP地址 都可以通過下面這個函式進行獲取

int getaddrinfo(
  const char FAR *nodename,        //IP地址
  const char FAR *servname,        //伺服器名稱 埠號
  const struct addrinfo FAR *hints,        //
  struct addrinfo FAR *FAR *res            //
);

/*
2018-11-15 11:51:00
ch09 套接字的多種可選項
*/

可選項的讀取和設定通過下面兩個函式完成

int setsockopt(
  SOCKET s,   //檢視可選項的套接字控制代碼     
  int level,  //要更改的選項協議層        
  int optname,   //選項名           
  const char FAR *optval,      //緩衝地址
  int optlen         //    引數的大小   
);


int getsockopt(
      SOCKET s,    //檢視可選項的套接字控制代碼
      int level,    //檢視可選項協議層
    int optname,    //檢視可選項名
      char FAR *optval,    //儲存緩衝地址
      int FAR *optlen     //上個引數的大小
);

Nagle演算法:
    為了防止資料包過多而發生網路過載
    實現:只有收到前一段資料的ACK訊息時,Nagle演算法才傳送下一條資料
    
TCP預設使用

Nagle演算法並不是什麼時候都適用
根據資料傳輸的特性,網路流量未受太大影響的時候,不使用此演算法傳輸速度更快

禁止使用Nagle演算法的方法
const char chOpt=1;   
int    nErr=setsockopt(m_socket, IPPROTO_TCP,   TCP_NODELAY,   &chOpt,   sizeof(char));   

例子:

// Demo.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//
/*
使用setsockopt 和 getsockopt 來修改socket的相關屬性
*/
#include "pch.h"
#include <iostream>
#include <string>
#include <WinSock2.h>

using namespace std;

void ErrorHandling(const char *message);
void ShowSockBufSize(SOCKET sock);

int main()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() Error");
	}

	//Step1 建立套接字
	SOCKET hClnSock = socket(PF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == hClnSock)
	{
		ErrorHandling("socket() Error");
	}
	ShowSockBufSize(hClnSock);

	int sendBuffer = 1024 * 3;
	int recvBuffer = sendBuffer;

	int state = setsockopt(hClnSock, SOL_SOCKET, SO_SNDBUF, (char*)&sendBuffer,sizeof(sendBuffer));
	if (state == SOCKET_ERROR)
	{
		ErrorHandling("getsockopt() Error");
	}

	state = setsockopt(hClnSock, SOL_SOCKET, SO_RCVBUF, (char*)&recvBuffer, sizeof(recvBuffer));
	if (state == SOCKET_ERROR)
	{
		ErrorHandling("getsockopt() Error");
	}

	ShowSockBufSize(hClnSock);

	closesocket(hClnSock);
	WSACleanup();
	
	system("pause");
	return EXIT_SUCCESS;
}

void ErrorHandling(const char * message)
{
	std::cout << message << std::endl;
	exit(-1);
}

void ShowSockBufSize(SOCKET sock)
{
	int sndBuf, rcvBuf;
	int len = sizeof(sndBuf);
	int state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char*)&sndBuf, &len);
	if (state == SOCKET_ERROR)
	{
		ErrorHandling("getsockopt() Error");
	}
	
	len = sizeof(rcvBuf);
	state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcvBuf, &len);
	if (state == SOCKET_ERROR)
	{
		ErrorHandling("getsockopt() Error");
	}

	std::cout << "Input buffer size: " << rcvBuf << std::endl;
	std::cout << "Output buffer size: " << sndBuf << std::endl;

}


/*
2018-11-15 14:13:17
ch10~ch11 多程序服務端
*/
併發伺服器實現模型和方法
Fun1 多程序伺服器:通過建立多個程序提供服務 (Windows不支援)
Fun2 多路複用伺服器:通過捆綁並同意管理I/O物件提供服務
Fun3 多執行緒伺服器:通過生成與客戶端等量的執行緒提供服務

從作業系統的角度來看。程序是程式流的基本單位,建立多個程序則作業系統將同時執行。
有時一個程式會產生多個程序 (Fun1)

程序具有完全獨立的記憶體結構

程序間通訊 通過管道 可以實現

/*
2018-11-15 14:41:47
ch12 I/O複用
*/
多程序服務端的缺點:記憶體消耗過大,資料交換較為複雜

引入複用:減少程序數

使用關鍵函式select
select函式呼叫順序:
Step1 【設定檔案描述符,指定監視範圍,設定超時】
Step2 【呼叫select函式】
Step3 【檢視呼叫結果】

設定檔案描述符
    FD_ZERO: 將fd_set變數的所有位初始化為0
    FD_SET: 在引數fdset指向變數中註冊檔案描述符fd的資訊
    FD_CLR:    在引數fdset指向變數中清除檔案描述符fd的資訊
    FD_ISSET:判斷是否有fd 資訊
    

例子:

// Select_Server.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include "pch.h"
#include <iostream>
#include <Winsock2.h>
#include <string>
#include <stdio.h>

#define BUF_SIZE 1024

void ErrorHandling(const char *message);

void Test();

int main()
{
	//Test();
	//system("pause");
	//return EXIT_SUCCESS;
	
	WSAData wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() error!");
		return EXIT_FAILURE;
	}

	//Step1 建立套接字
	SOCKET hServSock = socket(PF_INET, SOCK_STREAM, 0);	//初始化套接字

	SOCKADDR_IN servAdr;
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAdr.sin_port = htons(8888);

	//Step2 繫結地址和埠資訊
	bind(hServSock, (const sockaddr *)&servAdr, sizeof(servAdr));

	//Step3 監聽客戶端訊息
	listen(hServSock, 5);

	//加入select的相關功能
	fd_set reads, cpyReads;
	FD_ZERO(&reads);	//初始化為0
	FD_SET(hServSock, &reads);	//註冊檔案描述符資訊

	TIMEVAL timeout;		//指定時間值
	int fdNum = 0;
	int adrSz = 0;
	SOCKADDR_IN clnAddr;
	SOCKET hClntSock;
	char buf[BUF_SIZE] = { 0 };
	while (1)
	{
		cpyReads = reads;
		timeout.tv_sec = 5;	//5秒
		timeout.tv_usec = 5000;	//5000微秒

		if ((fdNum = select(0, &cpyReads, 0, 0, &timeout)) == SOCKET_ERROR)
		{
			break;
		}

		if (0 == fdNum)
		{
			continue;
		}

		for (int i = 0; i < reads.fd_count; ++i)
		{
			if (FD_ISSET(reads.fd_array[i], &cpyReads))
			{
				if (reads.fd_array[i] == hServSock)	//連線請求
				{
					adrSz = sizeof(clnAddr);
					hClntSock = accept(hServSock, (sockaddr*)&clnAddr, &adrSz);
					FD_SET(hClntSock, &reads);
					std::cout << "Connected client: " << hClntSock;

				}
				else  //read message
				{
					int strLen = recv(reads.fd_array[i], buf, BUF_SIZE, 0);

					if (0 == strLen)
					{
						FD_CLR(reads.fd_array[i], &reads);
						closesocket(cpyReads.fd_array[i]);
						std::cout << "Close client: " << cpyReads.fd_array[i];
					}
					else
					{
						send(reads.fd_array[i], buf, strLen, 0);
					}
				}
			}
		}
	}

	closesocket(hServSock);
	WSACleanup();

	system("pause");
	return EXIT_SUCCESS;
}

void ErrorHandling(const char * message)
{
	std::cout << message << std::endl;
}

void Test()
{
	int iTimes = 0;
	scanf("%d", &iTimes);
	
	int iInputNum = iTimes;
	int iDenominator = 1;
	
	for (int i = 0; i < iTimes; ++i)
	{
		iDenominator *= iInputNum;
		iInputNum++;
	}

	std::cout << iDenominator << std::endl;
}


/*
2018-11-15 17:06:35
ch14 多播 和 廣播
*/
多播:基於UDP完成 向多個主機傳送資料

特點:
1.多播服務端針對特定的多播組 只發送1次資料
2.組內的所有客戶端都會接受到資料
3.可在IP地址範圍內 任意增加
4.加入特定組 即可接收

廣播 :基於UDP 只能在同一網路中傳輸資料

例子:

// News_Sender.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include "pch.h"
#include <iostream>
#include <Winsock2.h>
#include <string>
#include <ws2tcpip.h>

#define BUF_SIZE 30


void ErrorHandling(const char *message);

int main()
{
	WSAData wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() error!");
		return EXIT_FAILURE;
	}

	//Step1 建立套接字
	SOCKET hServSock = socket(PF_INET, SOCK_STREAM, 0);	//初始化套接字

	SOCKADDR_IN servAdr;
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAdr.sin_port = htons(8888);

	//Step2 繫結地址和埠資訊
	bind(hServSock, (const sockaddr *)&servAdr, sizeof(servAdr));

	//Step3 監聽客戶端訊息
	listen(hServSock, 5);

	//加入select的相關功能
	fd_set reads, cpyReads;
	FD_ZERO(&reads);	//初始化為0
	FD_SET(hServSock, &reads);	//註冊檔案描述符資訊

	TIMEVAL timeout;		//指定時間值
	int fdNum = 0;
	int adrSz = 0;
	SOCKADDR_IN clnAddr;
	SOCKET hClntSock;
	char buf[BUF_SIZE] = { 0 };
	while (1)
	{
		cpyReads = reads;
		timeout.tv_sec = 5;	//5秒
		timeout.tv_usec = 5000;	//5000微秒

		if ((fdNum = select(0, &cpyReads, 0, 0, &timeout)) == SOCKET_ERROR)
		{
			break;
		}

		if (0 == fdNum)
		{
			continue;
		}

		for (int i = 0; i < reads.fd_count; ++i)
		{
			if (FD_ISSET(reads.fd_array[i], &cpyReads))
			{
				if (reads.fd_array[i] == hServSock)	//連線請求
				{
					adrSz = sizeof(clnAddr);
					hClntSock = accept(hServSock, (sockaddr*)&clnAddr, &adrSz);
					FD_SET(hClntSock, &reads);
					std::cout << "Connected client: " << hClntSock;

				}
				else  //read message
				{
					int strLen = recv(reads.fd_array[i], buf, BUF_SIZE, 0);

					if (0 == strLen)
					{
						FD_CLR(reads.fd_array[i], &reads);
						closesocket(cpyReads.fd_array[i]);
						std::cout << "Close client: " << cpyReads.fd_array[i];
					}
					else
					{
						send(reads.fd_array[i], buf, strLen, 0);
					}
				}
			}
		}
	}

	closesocket(hServSock);
	WSACleanup();

	system("pause");
	return EXIT_SUCCESS;
}

void ErrorHandling(const char * message)
{
	std::cout << message << std::endl;
}


/*
2018-11-16 14:28:28
ch19 Windows平臺下執行緒的使用
*/
核心物件的定義:作業系統建立的資源,程序 執行緒 檔案 訊號量 互斥量
都是由windows建立和管理的資源

Windows中執行緒建立的方法,關鍵函式CreateThread
函式原型:
HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes, // SD
  SIZE_T dwStackSize,                       // initial stack size
  LPTHREAD_START_ROUTINE lpStartAddress,    // 函式地址
  LPVOID lpParameter,                       // 引數
  DWORD dwCreationFlags,                    // creation option
  LPDWORD lpThreadId                        // thread identifier
);

關鍵的引數 兩個lpStartAddress 函式地址;    lpParameter 函式引數
其餘的引數可以傳遞0或者NULL

使用_beginthreadex來代替上面的函式
函式原型:
unsigned long _beginthreadex(
     void *security,
     unsigned stack_size,
     unsigned ( __stdcall *start_address )( void * ),
     void *arglist,
     unsigned initflag,
     unsigned *thrdaddr
);

知識補給:
控制代碼的整數值在不同的程序中可能出現重複,但執行緒ID在跨程序範圍內不會出現重複

核心物件的一個狀態檢視
程序或者執行緒終止時,相應的核心狀態會切換成signaled狀態

根據上面的場景 引入函式  WaitForSingleObject 和函式 WaitForMultipleObjects

WaitForSingleObject函式,該函式針對單個核心物件的signaled狀態
函式原型:
DWORD WaitForSingleObject(  
    HANDLE hHandle,        //檢視狀態的核心物件控制代碼
      DWORD dwMilliseconds   //設定超時狀態
);
WaitForSingleObject 判斷一個執行緒的狀態是否結束

WaitForMultipleObjects 判斷多個執行緒的狀態是否結束
函式原型:
DWORD WaitForMultipleObjects(
  DWORD nCount,             //核心物件的個數
  CONST HANDLE *lpHandles,  //核心物件的陣列
  BOOL bWaitAll,            // 如果為TRUE 則所有核心物件全部變成signaled時返回; 如果為FALSE 有一個為signaled就返回
  DWORD dwMilliseconds      // time-out interval
);

例子:

// WinThread01.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <process.h>

unsigned WINAPI ThreadFun1(void *pItem);

int main()
{
	unsigned threadId = 0;
	int param = 5;
	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, (void*)&param, 0, &threadId);
	if (0 == hThread)
	{
		std::cout << "_beginthreadex() Error" << std::endl;
		return -1;
	}
	Sleep(3000);
	std::cout << "End of main()" << std::endl;

	system("pause");
	return EXIT_SUCCESS;
}

unsigned __stdcall ThreadFun1(void * pItem)
{
	int iCnt = *(int *)pItem;
	for (int i = 0; i < iCnt; ++i)
	{
		Sleep(3000);
		std::cout << "Running thread" << std::endl;
	}
	return 0;
}
// WinThread01.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//
/*
2018-11-16 15:23:07
使用WaitForSingleObject函式來控制執行緒的一個結束的狀態標誌
讓執行緒按照預設的順序執行
*/
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <process.h>

unsigned WINAPI ThreadFun1(void *pItem);

int main()
{
	unsigned threadId = 0;
	int param = 5;
	HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, (void*)&param, 0, &threadId);
	if (0 == hThread)
	{
		std::cout << "_beginthreadex() Error" << std::endl;
		return -1;
	}
	
	DWORD wr = WaitForSingleObject(hThread, INFINITE);
	if (WAIT_FAILED == wr)
	{
		std::cout << "WaitForSingleObject() Error" << std::endl;
		return -1;
	}
	
	std::cout << "End of main()" << std::endl;

	system("pause");
	return EXIT_SUCCESS;
}

unsigned __stdcall ThreadFun1(void * pItem)
{
	int iCnt = *(int *)pItem;
	for (int i = 0; i < iCnt; ++i)
	{
		Sleep(3000);
		std::cout << "Running thread" << std::endl;
	}
	return 0;
}
// WinThread01.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//
/*
2018-11-16 15:32:21
使用WaitForMultipleObjects函式來使多個執行緒按照預設的順序執行
讓執行緒按照預設的順序執行
得到結果 也不盡然全部同步 下一章解決方案
*/
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <process.h>

#define THREADNUM	50

unsigned WINAPI ThreadFun1(void *pItem);
unsigned WINAPI ThreadFun2(void *pItem);

long long num;
int main()
{
	HANDLE tHandle[THREADNUM] = {0};
	unsigned threadId = 0;
	int param = 5;
	for (int i = 0; i < THREADNUM; ++i)
	{
		if (1 % 2 == 0)
		{
			tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, (void*)&param, 0, &threadId);
		}
		else
		{
			tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, (void*)&param, 0, &threadId);
		}
	}

	DWORD wr = WaitForMultipleObjects(THREADNUM, tHandle, TRUE, INFINITE);
	if (WAIT_FAILED == wr)
	{
		std::cout << "WaitForSingleObject() Error" << std::endl;
		return -1;
	}

	std::cout << "End of main()" << std::endl;

	system("pause");
	return EXIT_SUCCESS;
}

unsigned __stdcall ThreadFun1(void * pItem)
{
	int iCnt = *(int *)pItem;
	for (int i = 0; i < iCnt; ++i)
	{
		Sleep(1000);
		std::cout << "Running thread01" << std::endl;
	}
	return 0;
}

unsigned __stdcall ThreadFun2(void * pItem)
{
	int iCnt = *(int *)pItem;
	for (int i = 0; i < iCnt; ++i)
	{
		Sleep(1000);
		std::cout << "Running thread02" << std::endl;
	}
	return 0;
}


/*
2018-11-16 15:40:58
ch20 Windows中的執行緒同步
*/
同步方法和CRITICAL_SECTION同步

Windows在執行過程中存在如下兩種模式
使用者模式 和 核心模式
使用者模式:執行應用程式的基本模式,禁止訪問物理裝置,而且會限制訪問的記憶體區域
核心模式:作業系統執行時的模式,不僅不會限制訪問記憶體區域,而且訪問的硬體裝置也不會受限
這兩個模式 會互相轉換的

使用者模式同步基於CRITICAL_SECTION同步方法
主要和兩個函式有關
InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
引數是一樣的 都是CRITICAL_SECTION物件的地址值

獲取和釋放上面的物件的函式
EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection)
引數:RITICAL_SECTION物件的地址值


核心模式的同步方法
基於 事件、訊號量、互斥量

1.基於互斥量的物件同步
建立互斥量的函式:CreateMutex
函式原型:
HANDLE CreateMutex(
  LPSECURITY_ATTRIBUTES lpMutexAttributes,  // 傳遞安全的相關配置資訊,一般設定NULL
  BOOL bInitialOwner,                       // TURE:所屬當前執行緒 FALSE:不屬於任何執行緒
  LPCTSTR lpName                            // object name
);

銷燬核心物件的函式:
通過Closehandle來銷燬 互斥量
一般通過ReleaseMutex來釋放互斥量
BOOL ReleaseMutex(
  HANDLE hMutex   // handle to mutex
);

一般使用互斥量的結構:
WaitForSingleObject    //獲取到互斥量
。。。臨界區開始

。。。臨界區開始
ReleaseMutex 釋放互斥量


補充:關於CloseHandle 和 ReleaseHandle 的一些區別
CloseHandle(handle):是關閉一個控制代碼,並將這個控制代碼的引用計數減1,如果這個控制代碼的引用計數減到0,
    那麼作業系統將釋放這個核心物件的控制代碼

ReleaseMutex():讓當前執行緒釋放對該互斥體的擁有權,把他交給另一個等待中的執行緒

程式碼片段:

在主執行緒中
hMutex = CreateMutex(NULL, FALSE, NULL);
    for (int i = 0; i < THREADNUM; ++i)
    {
        if (i % 2)
        {
            tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, (void*)&param, 0, NULL);
        }
        else
        {
            tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, (void*)&param, 0, NULL);
        }
    }

    DWORD wr = WaitForMultipleObjects(THREADNUM, tHandle, TRUE, INFINITE);
    if (WAIT_FAILED == wr)
    {
        std::cout << "WaitForSingleObject() Error" << std::endl;
        return -1;
    }
    CloseHandle(hMutex);
    
在子執行緒中
unsigned __stdcall ThreadFun1(void * pItem)
{
    int iCnt = *(int *)pItem;
    
    WaitForSingleObject(hMutex, INFINITE);    //等待hMutex的一個狀態
    
    for (int i = 0; i < iCnt; ++i)
    {
        std::cout << "Running thread01" << std::endl;
    }
    ++num;

    ReleaseMutex(hMutex);    //釋放對互斥量的一個擁有權(不是銷燬它)
    return 0;
}

unsigned __stdcall ThreadFun2(void * pItem)
{
    int iCnt = *(int *)pItem;
    
    WaitForSingleObject(hMutex, INFINITE);
    for (int i = 0; i < iCnt; ++i)
    {
        std::cout << "Running thread02" << std::endl;
    }
    --num;
    ReleaseMutex(hMutex);
    return 0;
}


2.基於訊號量物件的同步
關鍵函式CreateSemaphore 
函式原型:
HANDLE CreateSemaphore(
  LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全配置資訊 一般為NULL
  LONG lInitialCount,                          // 指定訊號量的初始值
  LONG lMaximumCount,                          // 訊號量的最大值
  LPCTSTR lpName                               // 命名訊號量物件
);

釋放訊號量:

BOOL ReleaseSemaphore(
  HANDLE hSemaphore,       //需要釋放訊號量的控制代碼
  LONG lReleaseCount,      // 釋放訊號量值的增加 超過最大則不增加
  LPLONG lpPreviousCount   // 儲存之前變數地址 不需要則置為NULL
);

保護臨界區的方式:
WaitForSingleObject    //獲取到訊號量
。。。臨界區開始

。。。臨界區開始
ReleaseSemaphore 釋放互斥量

一般的原則是 儘量縮小臨界區的範圍 以 提高程式的效能
程式碼片段:

#include <iostream>
#include <windows.h>
#include <process.h>

unsigned WINAPI Read(void *pIter);
unsigned WINAPI Accu(void *pIter);

static HANDLE SemaOne;
static HANDLE SemaTwo;

int main()
{
    //訊號量的插入
    SemaOne = CreateSemaphore(NULL, 0, 1, NULL);
    SemaTwo = CreateSemaphore(NULL, 1, 1, NULL);

    HANDLE hThread1 = (HANDLE)_beginthreadex(NULL, 0, Read, NULL, 0, NULL);
    HANDLE hThread2 = (HANDLE)_beginthreadex(NULL, 0, Accu, NULL, 0, NULL);

    WaitForSingleObject(hThread1, INFINITE);
    WaitForSingleObject(hThread2, INFINITE);

    CloseHandle(SemaOne);
    CloseHandle(SemaTwo);
    CloseHandle(hThread1);
    CloseHandle(hThread2);

    system("pause");
    return EXIT_SUCCESS;
}

unsigned __stdcall Read(void * pIter)
{
    int num = 0;
    for (int i = 0; i < 5; ++i)
    {
        std::cout << "Input num: ";
        WaitForSingleObject(SemaTwo, INFINITE);
        std::cin >> num;
        ReleaseSemaphore(SemaOne, 1, NULL);
    }

    return EXIT_SUCCESS;
}

unsigned __stdcall Accu(void * pIter)
{
    int num = 0;
    for (int i = 0; i < 5; ++i)
    {
        WaitForSingleObject(SemaOne, INFINITE);
        num += i;
        ReleaseSemaphore(SemaTwo, 1, NULL);
    }
    std::cout << "Result: " << num << std::endl;

    return EXIT_SUCCESS;
}

3.基於事件物件的同步
建立事件的函式CreateEvent
函式原型:
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全配置引數 一般設定為NULL
  BOOL bManualReset,                       // 傳入TRUE建立manual_reset模式事件物件;傳入FALSE 建立auto_reset模式物件
  BOOL bInitialState,                      // 傳入TRUE物件建立signaled狀態的事件物件;傳入FALSE建立non-signaled狀態的事件物件
  LPCTSTR lpName                           // object name
);

與之相對應的函式 釋放相應的資源
BOOL ResetEvent(
  HANDLE hEvent   // handle to event
);

補充:關Winscok2 標頭檔案放的位置 最好放在最上面 以免出現一些不可預知的錯誤

例子:

// WinThread01.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//
/*
2018-11-16 16:19:18
使用CRITICAL_SECTION來同步執行緒
讓執行緒按照預設的順序執行
*/
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <process.h>

#define THREADNUM	50

unsigned WINAPI ThreadFun1(void *pItem);
unsigned WINAPI ThreadFun2(void *pItem);

CRITICAL_SECTION cs;

long long num;

int main()
{
	HANDLE tHandle[THREADNUM] = { 0 };
	unsigned threadId = 0;
	int param = 5;
	num = 0;

	InitializeCriticalSection(&cs);
	for (int i = 0; i < THREADNUM; ++i)
	{
		if (i % 2)
		{
			tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, (void*)&param, 0, NULL);
		}
		else
		{
			tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, (void*)&param, 0, NULL);
		}
	}


	DWORD wr = WaitForMultipleObjects(THREADNUM, tHandle, TRUE, INFINITE);
	if (WAIT_FAILED == wr)
	{
		std::cout << "WaitForSingleObject() Error" << std::endl;
		return -1;
	}
	DeleteCriticalSection(&cs);

	std::cout << "End of main()" << std::endl;
	std::cout << "THe Num is: " << num << std::endl;
	system("pause");
	return EXIT_SUCCESS;
}

unsigned __stdcall ThreadFun1(void * pItem)
{
	int iCnt = *(int *)pItem;
	EnterCriticalSection(&cs);
	for (int i = 0; i < iCnt; ++i)
	{
		std::cout << "Running thread01" << std::endl;
	}
	++num;
	LeaveCriticalSection(&cs);
	
	return 0;
}

unsigned __stdcall ThreadFun2(void * pItem)
{
	int iCnt = *(int *)pItem;
	EnterCriticalSection(&cs);
	for (int i = 0; i < iCnt; ++i)
	{
		std::cout << "Running thread02" << std::endl;
	}
	--num;
	LeaveCriticalSection(&cs);
	return 0;
}
// WinThread01.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//
/*
2018-11-16 16:55:01
使用互斥量來同步執行緒
讓執行緒按照預設的順序執行
*/
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <process.h>

#define THREADNUM	50

unsigned WINAPI ThreadFun1(void *pItem);
unsigned WINAPI ThreadFun2(void *pItem);

HANDLE hMutex;

long long num;

int main()
{
	HANDLE tHandle[THREADNUM] = { 0 };
	unsigned threadId = 0;
	int param = 5;
	num = 0;

	hMutex = CreateMutex(NULL, FALSE, NULL);
	for (int i = 0; i < THREADNUM; ++i)
	{
		if (i % 2)
		{
			tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun1, (void*)&param, 0, NULL);
		}
		else
		{
			tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, (void*)&param, 0, NULL);
		}
	}

	DWORD wr = WaitForMultipleObjects(THREADNUM, tHandle, TRUE, INFINITE);
	if (WAIT_FAILED == wr)
	{
		std::cout << "WaitForSingleObject() Error" << std::endl;
		return -1;
	}
	CloseHandle(hMutex);

	std::cout << "End of main()" << std::endl;
	std::cout << "THe Num is: " << num << std::endl;
	system("pause");
	return EXIT_SUCCESS;
}

unsigned __stdcall ThreadFun1(void * pItem)
{
	int iCnt = *(int *)pItem;
	
	WaitForSingleObject(hMutex, INFINITE);
	
	for (int i = 0; i < iCnt; ++i)
	{
		std::cout << "Running thread01" << std::endl;
	}
	++num;

	ReleaseMutex(hMutex);
	return 0;
}

unsigned __stdcall ThreadFun2(void * pItem)
{
	int iCnt = *(int *)pItem;
	
	WaitForSingleObject(hMutex, INFINITE);
	for (int i = 0; i < iCnt; ++i)
	{
		std::cout << "Running thread02" << std::endl;
	}
	--num;
	ReleaseMutex(hMutex);
	return 0;
}
// SyncSema.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//
/*
2018-11-16 17:15:44
使用訊號量 來同步執行緒

*/

#include "pch.h"
#include <iostream>
#include <windows.h>
#include <process.h>

unsigned WINAPI Read(void *pIter);
unsigned WINAPI Accu(void *pIter);

static HANDLE SemaOne;
static HANDLE SemaTwo;

int main()
{
	//訊號量的插入
	SemaOne = CreateSemaphore(NULL, 0, 1, NULL);
	SemaTwo = CreateSemaphore(NULL, 1, 1, NULL);

	HANDLE hThread1 = (HANDLE)_beginthreadex(NULL, 0, Read, NULL, 0, NULL);
	HANDLE hThread2 = (HANDLE)_beginthreadex(NULL, 0, Accu, NULL, 0, NULL);

	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);

	CloseHandle(SemaOne);
	CloseHandle(SemaTwo);
	CloseHandle(hThread1);
	CloseHandle(hThread2);

	system("pause");
	return EXIT_SUCCESS;
}

unsigned __stdcall Read(void * pIter)
{
	int num = 0;
	for (int i = 0; i < 5; ++i)
	{
		std::cout << "Input num: ";
		WaitForSingleObject(SemaTwo, INFINITE);
		std::cin >> num;
		ReleaseSemaphore(SemaOne, 1, NULL);
	}

	return EXIT_SUCCESS;
}

unsigned __stdcall Accu(void * pIter)
{
	int num = 0;
	for (int i = 0; i < 5; ++i)
	{
		WaitForSingleObject(SemaOne, INFINITE);
		num += i;
		ReleaseSemaphore(SemaTwo, 1, NULL);
	}
	std::cout << "Result: " << num << std::endl;

	return EXIT_SUCCESS;
}

/*
同步後的執行結果:
Input num: 1
Input num: 2
Input num: 3
Input num: 4
Input num: 5
Result: 10
請按任意鍵繼續. . .

*/
// SyncEvent.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

/*
2018-11-19 09:40:45
利用Event的方式來實現同步
經測試:還是不能完全實現同步
*/

#include "pch.h"
#include <iostream>
#include <windows.h>
#include <process.h>

#define STR_LEN		100

unsigned WINAPI NumberOfA(void *arg);
unsigned WINAPI NumberOfOthers(void *arg);

static char str[STR_LEN];
static HANDLE hEvent;

int main()
{
	hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	HANDLE hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
	HANDLE hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);

	std::cout << "Input string: ";
	std::cin.get(str, STR_LEN);

	//設定事件 來 控制同步
	
	SetEvent(hEvent);
	WaitForSingleObject(hThread1, INFINITE);
	WaitForSingleObject(hThread2, INFINITE);

	//解除事件的一個佔用
	//ResetEvent(hEvent);

	//釋放相應的事件資源
	CloseHandle(hEvent);

	system("pause");
	return EXIT_SUCCESS;
}

unsigned __stdcall NumberOfA(void * arg)
{
	int cnt = 0;
	WaitForSingleObject(hEvent, INFINITE);
	for (int i = 0; str[i] != 0; ++i)
	{
		if (str[i] == 'A')
			cnt++;
	}
	std::cout << "Num of A: " << cnt << std::endl;
	return EXIT_SUCCESS;
}

unsigned __stdcall NumberOfOthers(void * arg)
{
	int cnt = 0;
	WaitForSingleObject(hEvent, INFINITE);
	for (int i = 0; str[i] != 0; ++i)
	{
		if (str[i] != 'A')
		{
			cnt++;
		}
	}
	std::cout << "Num of ohters: " << cnt << std::endl;
	return EXIT_SUCCESS;
}
// Chat_serv_win.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include "pch.h"
#include <Winsock2.h>
#include <iostream>
#include <string>
#include <windows.h>
#include <process.h>


#define BUF_SIZE	100
#define MAX_CLNT	256

unsigned WINAPI HandleClnt(void *arg);
void SendMsg(char *msg, int len);
void ErrorHandling(const char *msg);

int clntCnt = 0;
SOCKET clntSocks[MAX_CLNT];
HANDLE hMutex;

int main()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() Error");
	}

	hMutex = CreateMutex(NULL, FALSE, NULL);
	SOCKET hServSock = socket(PF_INET, SOCK_STREAM, 0);	//Step1 建立套接字

	SOCKADDR_IN servAdr;
	memset(&servAdr, 0, sizeof(servAdr));
	servAdr.sin_family = AF_INET;
	servAdr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAdr.sin_port = htons(8888);
	if (bind(hServSock, (const sockaddr *)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)	//Step2 繫結地址和埠
	{
		ErrorHandling("bind() Error");
	}

	if (listen(hServSock, 5) == SOCKET_ERROR)	//Step3 監聽套接字
	{
		ErrorHandling("listen() Error");
	}

	SOCKADDR_IN clntAdr;
	int clntAdrsz = sizeof(clntAdr);
	SOCKET hClntSockt;
	HANDLE hThread;

	while (1)
	{
		hClntSockt = accept(hServSock, (SOCKADDR*)&clntAdr, &clntAdrsz);	//Step4 接收客戶端的訊息

		WaitForSingleObject(hMutex, INFINITE);
		clntSocks[clntCnt++] = hClntSockt;
		ReleaseMutex(hMutex);

		hThread = (HANDLE)_beginthreadex(NULL, 0, HandleClnt, (void*)&hClntSockt, 0, NULL);
		std::cout << "Connected client IP: " << inet_ntoa(clntAdr.sin_addr) << std::endl;
	}
	
	closesocket(hServSock);

	CloseHandle(hMutex);
	WSACleanup();

	system("pause");
	return EXIT_SUCCESS;
}

unsigned __stdcall HandleClnt(void * arg)
{
	SOCKET hClntSock = *((SOCKET*)arg);
	int strLen = 0, i;
	char msg[BUF_SIZE];

	while (1)
	{
		strLen = recv(hClntSock, msg, sizeof(msg), 0);
		if (strLen == 0 || -1 == strLen)	break;
		SendMsg(msg, strLen);
		msg[strLen] = 0;
		std::cout << "接收來自客戶端的值: " << msg << std::endl;
	}

	//WaitForSingleObject(hMutex, INFINITE);
	//for (int i = 0; i < clntCnt; ++i)	//移除未連線的客戶端
	//{
	//	if (hClntSock == clntSocks[i])
	//	{
	//		while (++i < clntCnt - 1)
	//		{
	//			clntSocks[i] = clntSocks[i + 1];
	//		}
	//		break;
	//	}
	//}

	//clntCnt--;
	//ReleaseMutex(hMutex);
	closesocket(hClntSock);

	return 0;
}

void SendMsg(char * msg, int len)
{
	WaitForSingleObject(hMutex, INFINITE);
	for (int i = 0; i < clntCnt; ++i)
	{
		send(clntSocks[i], msg, len, 0);	//傳送訊息
	}
	ReleaseMutex(hMutex);
}

void ErrorHandling(const char * msg)
{
	std::cout << msg << std::endl;
	exit(-1);
}
// chat_clnt_win.cpp : 此檔案包含 "main" 函式。程式執行將在此處開始並結束。
//

#include "pch.h"
#include <Winsock2.h>
#include <iostream>
#include <string>
#include <windows.h>
#include <process.h>

#define BUF_SIZE	100
#define NAME_SIZE	20

unsigned WINAPI SendMsg(void *arg);
unsigned WINAPI RecvMsg(void *arg);
void Errorhandling(const char *msg);

char name[NAME_SIZE] = "[DFFAULT]";
char msg[BUF_SIZE];

HANDLE hMutex;

int main()
{
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		Errorhandling("WSAStartup() Error");
	}

	hMutex = CreateMutex(NULL, FALSE, NULL);
	SOCKET hSock = socket(PF_INET, SOCK_STREAM, 0);		//Step1 建立套接字

	SOCKADDR_IN servAddr;
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	servAddr.sin_port = htons(8888);

	if (connect(hSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)	//Step2 與伺服器建立連線
	{
		Errorhandling("Connect() Error");
	}

	HANDLE hSendThread, hRecvThread;
	hSendThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&hSock, 0, NULL);
	hRecvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)&hSock, 0, NULL);

	WaitForSingleObject(hSendThread, INFINITE);
	WaitForSingleObject(hRecvThread, INFINITE);

	closesocket(hSock);	//關閉控制代碼

	CloseHandle(hMutex);
	WSACleanup();	//釋放socket的資源
	system("pause");
	return EXIT_SUCCESS;
}

unsigned __stdcall SendMsg(void * arg)
{
	SOCKET hSock = *((SOCKET*)arg);
	char nameMsg[NAME_SIZE + BUF_SIZE];
	std::string strInput;
	WaitForSingleObject(hMutex, INFINITE);
	while (1)
	{
		std::cout << "輸入傳送的內容:";
		std::getline(std::cin, strInput);
		if (!strcmp(strInput.c_str(), "exit") || !strcmp(strInput.c_str(), "EXIT\n"))
		{
			closesocket(hSock);
			exit(0);
		}
		
		send(hSock, strInput.c_str(), strInput.length(), 0);
		strcpy(nameMsg, strInput.c_str());
	}
	ReleaseMutex(hMutex);
	return EXIT_SUCCESS;
}

unsigned __stdcall RecvMsg(void * arg)
{
	SOCKET hSock = *((SOCKET*)arg);
	char nameMsg[NAME_SIZE + BUF_SIZE];
	int strLen = 0;
	WaitForSingleObject(hMutex, INFINITE);
	while (1)
	{
		strLen = recv(hSock, nameMsg, NAME_SIZE + BUF_SIZE, 0);
		if (-1 == strLen)
		{
			return -1;
		}

		nameMsg[strLen] = 0;
		std::cout << "接收來自客戶端的訊息:";
		std::cout << nameMsg << std::endl;
	}
	ReleaseMutex(hMutex);

	return EXIT_SUCCESS;
}

void Errorhandling(const char * msg)
{
	std::cout << msg << std::endl;
	exit(-1);
}


/*
2018-11-19 14:26:52
ch21 非同步通知I/O模型
*/
同步I/O的缺點:進行I/O的過程中函式無法返回,所以不能執行其他任務!

非同步通知I/O模型的實現方法有2種:
1. 使用WSAEventSelect函式
2. 使用WSAAsyncSelect函式

WSAEventSelect函式和通知
=> 套接字的狀態變化:套接字的I/O狀態變化
=> 發生套接字相關事件:發生套接字I/O相關事件

函式原型:(連線事件和套接字的函式)
int WSAEventSelect(
  SOCKET s,        //監視物件的套接字控制代碼
  WSAEVENT hEventObject,    //傳遞事件物件控制代碼以驗證事件發生與否
  long lNetworkEvents    //希望監視的事件型別資訊
);

第三個引數詳細:(常用的可選項)
FD_READ:是否存在需要接收的資料
FD_WRITE:能否以非阻塞方式傳輸資料
FD_OOB:是否收到帶外資料
FD_ACCEPT:是否有新的連線請求
FD_CLOSE:是否有斷開連線的請求

補充關於函式WSAEventSelect:
WSAEventSelect函式第二個引數用到的事件物件的建立方法
呼叫WSAEventSelect函式後發生事件的驗證方法
驗證事件發生後事件型別的檢視方法

後補再補充

/*
2018-11-19 15:39:30
製作HTTP伺服器端
*/
web服務端的定義:
=>基於HTTP協議,將網頁對應檔案傳輸給客戶端的伺服器端

請求訊息的結構
=>客戶端向服務端傳送請求的訊息結構
請求訊息分為:請求行、訊息頭、訊息體
請求行:包含請求方式資訊;典型的請求方式有Get和Post,Get用於獲取資料,Post用於傳輸資料
訊息頭:包含傳送請求的瀏覽器資訊、使用者認證資訊等關於HTTP訊息的附加資訊。
訊息體:裝有客戶端向伺服器端傳輸的資料,為了裝入資料,需要以POST方式傳送請求


響應訊息結構
=>Web伺服器端向客戶端傳遞的響應訊息的結構
響應訊息組成:狀態行、頭資訊、訊息體
狀態行:包含關於請求的狀態資訊
頭資訊:包含傳輸的資料型別和長度資訊
訊息體:請求的檔案資料