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*)¶m, 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*)¶m, 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*)¶m, 0, &threadId);
}
else
{
tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, (void*)¶m, 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*)¶m, 0, NULL);
}
else
{
tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, (void*)¶m, 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*)¶m, 0, NULL);
}
else
{
tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, (void*)¶m, 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*)¶m, 0, NULL);
}
else
{
tHandle[i] = (HANDLE)_beginthreadex(NULL, 0, ThreadFun2, (void*)¶m, 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伺服器端向客戶端傳遞的響應訊息的結構
響應訊息組成:狀態行、頭資訊、訊息體
狀態行:包含關於請求的狀態資訊
頭資訊:包含傳輸的資料型別和長度資訊
訊息體:請求的檔案資料