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