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服务器端向客户端传递的响应消息的结构
响应消息组成:状态行、头信息、消息体
状态行:包含关于请求的状态信息
头信息:包含传输的数据类型和长度信息
消息体:请求的文件数据

猜你喜欢

转载自blog.csdn.net/Wuzm_/article/details/84257714