基于WINAPI的CPP进程与线程使用

内核对象

  • 物质

内核对象的本质 : 一个数据块,可理解成结构体,由两部分组成,共有( 安全描述符和使用计数)和私有(不同对象各有不同),属于操作系统

安全描述符(security descriptor,SD) : 指向所有者,其他用户的访问权限
使用计数 : 创建是为1,每次使用加1,关闭时和使用结束后-1,当为0时被操作系统销毁(操作系统管理)

  • 运动

创建 : 通过create api来创建并返回其句柄,创建失败返回0
使用 : 通过WINAPI和句柄来使用
销毁 : 使用close api后,由操作系统根据使用计数来销毁

常见的内核对象 : 进程(process)、线程(thread)、文件(file),存取符号对象、事件对象(event)、文件对象、作业对象、互斥对象(mutex)、管道对象、等待计时器对象,邮件槽对象,信号对象

每一个进程都会创建一个句柄表,用来存放使用的句柄

WaitObject

阻塞代码,直到进程完成

//单个
DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,//线程句柄
__in DWORD dwMilliseconds);//等待时间 INFINITE代表无限,直到waited

//多个
DWORD WINAPI WaitForMultipleObjects(
__in DWORD nCount,//线程个数
__in const HANDLE *lpHandles,//句柄数组
__in BOOL bWaitAll,//TRUE代表所有都结束,FALSE代表任意一个结束
__in DWORD dwMilliseconds);//等待时间

信号量

用于管理多条线程

用来表示内核对象的状态

组成 : 计数器 + 最大资源计数 + 当前资源计数

signaled 有信号/触发状态,当前有资源

//(安全属性,可用资源个数,总资源个数,信号量名称(不要可以NULL))
HANDLE semOne = CreateSemaphore(NULL, 1, 3, NULL);
WaitForSingleObject(semOne, INFINITE);//消耗一个资源
//将可用中已用的和未使用的进行释放
//(信号量句柄,释放的个数,最大资源数(默认
ReleaseSemaphore(semOne, 2, NULL);

互斥量

防止同个变量同时被多条线程访问,导致数据错乱

这不,8848,跑得快的不一定赢,不摔跟头才是成功

  • 结构
CreateMutexW(
    _In_opt_ LPSECURITY_ATTRIBUTES lpMutexAttributes,   //指向安全属性
    _In_ BOOL bInitialOwner,   //初始化互斥对象的所有者  TRUE 立即拥有互斥体,false表示创建的这个mutex不属于任何线程,即signaled
    _In_opt_ LPCWSTR lpName    //指向互斥对象名的指针  L“Bingo”
);
  • 步骤

CreateMutex创建 -> WaitForSingleObject加锁 -> ReleaseMutex解锁 -> 循环往复

//多个
//利用循环创造相对应的自增自减线程,理论上最后num==0,但实际上却是一个随机数,引出进程第二个问题,速度差异问题
#include <stdio.h>
#include <windows.h>
#include <process.h>

#define NUM_THREAD	50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);

long long num=0;
HANDLE hMutex;

int main(int argc, char *argv[]) 
{
	HANDLE tHandles[NUM_THREAD];

	hMutex=CreateMutex(NULL, FALSE, NULL);//互斥量创建
	for(int i=0; i<NUM_THREAD; i++)
		if(i%2) tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
		else 	tHandles[i]=(HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
        
	WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
	CloseHandle(hMutex);//互斥量关闭
	printf("result: %lld \n", num);
	return 0;
}

unsigned WINAPI threadInc(void * arg) 
{
	WaitForSingleObject(hMutex, INFINITE);//对单个互斥量的
	for(int i=0; i<500000; i++)
		num+=1;
	ReleaseMutex(hMutex);
	return 0;
}
unsigned WINAPI threadDes(void * arg)
{
	WaitForSingleObject(hMutex, INFINITE);
	for(int i=0; i<500000; i++)
		num-=1;
	ReleaseMutex(hMutex);
	return 0;
}

互斥事件

有时我们不需要再waitfor之后改变状态时使用

  • 函数原型
HANDLE CreateEvent(   
LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全属性  默认NULL 
BOOL bManualReset,   // TRUE为manual-reset须用(Re)setEvent手动复原 
BOOL bInitialState,   // 初始信号状态 TRUE : signaled  FALSE反之
LPCTSTR lpName )    //对象名称  NULL  无名的事件对象 
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
CloseHandle(hEvent);
//手动复原时
SetEvent(hEvent);//设置为signaled
ResetEvent(hEvent);//设置为non-signaled

进程线程

当我们程序内,需要两个函数同时执行时(如下载与加载),即可开多线程

而多线程有时会存在时序问题,所以需要进行线程同步,由此可能会出现线程死锁

然后就是资源由进程管理,所以不同进程的资源不共享,所以我们需要用到进程通信来实现

  • 区别

程序以进程为单位来进行对系统资源的调度,相当于公司

而线程来执行不同的任务,相当于员工

  • 下面是进程与线程常见所掌管的资源
进程线程
地址空间(虚拟内存)程序计数器
全局变量寄存器
打开文件
子进程状态
即将发生的定时器
信号与信号处理程序
账户信息

线程

tid

因为在速度上 CPU>>内存>>硬盘io

如果程序都是一个执行完了再执行另一个,就会让CPU空闲很久

而进程线程的存在目的就是组织程序,最大化利用cpu

所以,线程本质就是用来管理函数运行的工具

创建线程

  • 结构

CreateThread

HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD
SIZE_T dwStackSize,//initialstacksize
LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction
LPVOID lpParameter,//threadargument
DWORD dwCreationFlags,//creationoption
LPDWORD lpThreadId//threadidentifier
)

· 第一个参数 lpThreadAttributes 表示线程内核对象的安全属性,一般传入NULL表示使用默认设置。

· 第二个参数 dwStackSize 表示线程栈空间大小。传入0表示使用默认大小(1MB)。

· 第三个参数 lpStartAddress 表示新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。

· 第四个参数 lpParameter 是传给线程函数的参数。

· 第五个参数 dwCreationFlags 指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,CREATE_SUSPENDED

· 第六个参数 lpThreadId 将返回线程的ID号,传入NULL表示不需要返回该线程ID号

CreateThread封装成了_beginthreadex便于使用

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

DWORD WINAPI ThreadFun(LPVOID p);//WINAPI就是stdcall 来自pascal

int main()
{
	HANDLE hThread;
	DWORD dwThreadID;//用来存放pid
	int m = 5;

	hThread = CreateThread(NULL, 0, ThreadFun, &m, 0, &dwThreadID);
	printf("main thread : PID = %d son thread : %d\n", GetCurrentThreadId(), dwThreadID);
	if (hThread == 0)return -1;
	else CloseHandle(hThread);

	Sleep(10000);//为了让主线程跑完
}

DWORD WINAPI ThreadFun(LPVOID arg)
{
	int cnt = *((int*)arg);//取参
	printf("son thread : %d\n", GetCurrentThreadId());
	for (int i = 0; i < cnt; i++)
	{
		Sleep(1000);
		puts("running thread");
	}
	return 0;
}

_beginthreadex

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

unsigned WINAPI ThreadFun(void* arg);

int main()
{
	HANDLE hThread;
	int param = 6;

	hThread = (HANDLE)_beginthreadex(NULL, 0, &ThreadFun, (void*)&param, 0, 0);

	Sleep(10000);
	return 0;
}

unsigned WINAPI ThreadFun(void* arg)
{
	int cnt = *((int*)arg);
	for (int i = 0; i < cnt; i++)
	{
		Sleep(1000);
		puts("running thread");
	}
	return 0;
}

进程运行于结束

先保证处于非挂起状态,可利用下面两个函数来控制挂起与恢复

 SuspendThread(handle)
 ResumeThread(handle)

创建时,就运行,即两个信号量,wait一个,close一个

多个线程同时创建时,运行无先后顺序,同时运行时,可以同时访问同个变量,但由于cpu速度远大于内存,就会导致数据错乱

Sleep(1000);//注意留时间让线程跑完
WaitForSingleObject(handle, INFINITE);//这个也可以起到保证线程跑完的作用
CloseHandle(Handle);

线程同步

不同步

如果多线程对相同资源进行访问,就会因为资源竞争,导致两边不能同步

如下面代码,调用了两次函数,输出结果并不是想象中的200000

#include <iostream>
#include <thread>
using namespace std;

int n = 0;

void Func(int idx)
{
    for (int i = 0; i < 1000000; i++) n++;
    cout << "Thread " << idx << " ended!" << endl;
}

int main()
{
    thread thr1(Func, 1);
    thread thr2(Func, 2);
    thr1.join();
    thr2.join();
    
    cout << "End: " << n << endl;
    return 0;
}

关键代码段

又名临界区,由windowsAPI提供,处于该段的代码同时只可被一条线程访问

在需要修改公共数据时使用

因为关键代码段很快(因为不需要内核操作),所以比较好用

上面代码可以像下面这样修改

#include <iostream>
#include <thread>
#include <Windows.h>
using namespace std;
int n = 0;
CRITICAL_SECTION g_cs;

void Func(int idx)
{
    for (int i = 0; i < 1000000; ++i)
    {
        EnterCriticalSection(&g_cs);
        ++n;
        LeaveCriticalSection(&g_cs);
    }
    cout << "Thread " << idx << " ended!" << endl;
}

int main()
{
    InitializeCriticalSection(&g_cs);

    thread thr1(Func, 1);
    thread thr2(Func, 2);

    thr1.join();
    thr2.join();

    cout << "End: " << n << endl;

    DeleteCriticalSection(&g_cs);

    return 0;
}

原子操作

原子操作,即不可中断的操作,可以确保在多线程环境中对共享变量的操作是线程安全的。

在C++中,可以使用 <atomic> 头文件提供的原子操作来执行一些基本的原子操作。以下是一些常见的原子操作:

  1. 原子加载和存储:
    • std::atomic<T>::load(): 原子加载操作,用于从原子对象中读取值。
    • std::atomic<T>::store(): 原子存储操作,用于将值存储到原子对象中。
  2. 原子交换:
    • std::atomic<T>::exchange(): 原子交换操作,用于将新值设置到原子对象,并返回原来的值。
  3. 原子递增和递减:
    • std::atomic<T>::fetch_add(): 原子递增操作,用于将值原子地增加。
    • std::atomic<T>::fetch_sub(): 原子递减操作,用于将值原子地减少。
  4. 比较和交换:
    • std::atomic<T>::compare_exchange_strong(): 如果当前值等于预期值,则用新值替换,并返回 true,否则返回 false
    • std::atomic<T>::compare_exchange_weak(): 类似于 compare_exchange_strong(),但可能在某些情况下会更高效。

上面代码通过原子操作来同步

#include <iostream>
#include <thread>
#include <atomic>
using namespace std;

atomic<int> n(0);

void Func(int idx)
{
    for (int i = 0; i < 1000000; i++)n++;
    cout << "Thread " << idx << " ended!" << endl;
}

int main()
{
    thread thr1(Func, 1);
    thread thr2(Func, 2);
    thr1.join();
    thr2.join();

    cout << "End: " << n << endl;

    return 0;
}

线程死锁

两个线程在使用资源时发生冲突,导致一直相互等待,如下

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

CRITICAL_SECTION g_csA;
CRITICAL_SECTION g_csB;

DWORD WINAPI A(void* lpParam)
{
	EnterCriticalSection(&g_csA);
	puts("a1");
	EnterCriticalSection(&g_csB);
	puts("a2");
	return 0;
}

DWORD WINAPI B(void* lpParam)
{
	EnterCriticalSection(&g_csB);
	puts("b1");
	EnterCriticalSection(&g_csA);
	puts("b2");
	return 0;
}

int main()
{
	HANDLE hThreadA, hThreadB;
	InitializeCriticalSection(&g_csA);
	InitializeCriticalSection(&g_csB);
	hThreadA = CreateThread(NULL, 0, A, NULL, 0, NULL); 
	hThreadB = CreateThread(NULL, 0, B, NULL, 0, NULL);  
}

线程池

创建线程和销毁线程带来的开销其实是不算小的,一个想法就是预先开辟多个线程,然后等到需要开辟线程的时候直接使用已经创建好的线程以减小开销。

主要步骤,建立一个队列,每次把新的要起的线程函数直接emplace到结尾,在添加时利用互斥量防止错误.然后就是执行完后要释放线程池申请的内存

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>

class ThreadPool
{
public:
	ThreadPool(size_t numThreads) : stop(false)
	{
		for (size_t i = 0; i < numThreads; ++i)
		{
			workers.emplace_back([this]
				{
					while (true) {
						std::function<void()> task;

						{
							std::unique_lock<std::mutex> lock(queueMutex);

							condition.wait(lock, [this] {
								return stop || !tasks.empty();
								});

							if (stop && tasks.empty()) {
								return;
							}

							task = tasks.front();
							tasks.pop();
						}

						task();
					} });
		}
	}

	template <class F, class... Args>
	void enqueue(F&& f, Args &&...args)
	{
		auto task = std::bind(std::forward<F>(f), std::forward<Args>(args)...);

		{
			std::unique_lock<std::mutex> lock(queueMutex);
			if (stop)
			{
				throw std::runtime_error("enqueue on stopped ThreadPool");
			}

			tasks.emplace(task);
		}

		condition.notify_one();
	}

	~ThreadPool()
	{
		{
			std::unique_lock<std::mutex> lock(queueMutex);
			stop = true;
		}

		condition.notify_all();

		for (std::thread& worker : workers)
		{
			worker.join();
		}
	}

private:
	std::vector<std::thread> workers;
	std::queue<std::function<void()>> tasks;
	std::mutex queueMutex;
	std::condition_variable condition;
	bool stop;
};

void printHello(int i)
{
	std::cout << i<<std::endl;
}

int main()
{
	ThreadPool pool(4);

	for (int i = 0; i < 8; ++i)
		pool.enqueue(printHello, i);
}

进程

简介

管理程序的资源调度

**虚拟内存 ** 由于每个进程独立,操作系统为每个进程开辟虚拟内存.让进程操作时不受物理内存影响.操作系统负责将虚拟内存映射到物理内存上,方便动态管理

进程调度 操作系统内核需要安排多个进程轮流执行在 CPU 上,这个操作就叫做进程的调度

**进程上下文 **一个进程在执行过程中,需要用到很多状态信息,比如各个寄存器的值,主存的内容,程序计数器的值,这些就被统一称之进程上下文.在进行进程调度时,会切换到自己该进程的上下文.

进程创建

创建一个进程和访问该进程与其主线程的信息

#include <windows.h>
#include <stdio.h>
#include <tchar.h>

int main()
{
    //准备创建进程的三个量
	STARTUPINFO strStartupInfo;
	memset(&strStartupInfo, 0, sizeof(strStartupInfo));
	strStartupInfo.cb = sizeof(strStartupInfo);

	PROCESS_INFORMATION szProcessInformation;
	memset(&szProcessInformation, 0, sizeof(szProcessInformation));

	TCHAR szCommandLine[] = _T("\"C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe\"www.baidu.com");
	//TCHAR szCommandLine[] = _T("\"C:/Program Files (x86)/Microsoft/Edge/Application/msedge.exe\" www.bilibili.com");//这样写也可
	//1. 路径的注意点(x86)前面有个空格,在复制时会覆盖 2. msedge.exe后有一条斜杠
	//"\"表示在路径后面还有一个参数 
	// _T定义在tchar.h中,如果定义了Unicode,则加L修饰.否则就什么也不做,继续使用ascii编码
	//在C:\Windows\SysWOW64中的部分程序可以直接输入程序名运行
    //当前目录的也可以
	//system windows On Windows64在x64系统下兼容x86的软件,
	//所以说,字符串坑最多了

    //创建进程 大部分默认
	int iRet = CreateProcess(
		NULL,
		szCommandLine,//命令行命令
		NULL,
		NULL,
		false,
		CREATE_NEW_CONSOLE,
		NULL,
		NULL,
		&strStartupInfo,//指定主窗口的相关属性
		&szProcessInformation//用来存放进程与主线程的信息
	);
	if (iRet)
	{
		printf_s("Create Success iRet = %d\n", iRet);
		WaitForSingleObject(szProcessInformation.hProcess, 3000);
		CloseHandle(szProcessInformation.hProcess);
		CloseHandle(szProcessInformation.hThread);
		printf("pid : %hd\n", szProcessInformation.dwProcessId);
		printf("tid : %hd\n", szProcessInformation.dwThreadId);
		szProcessInformation.hThread = NULL;
		szProcessInformation.hProcess = NULL;
	}
	else
	{
		printf_s("Create Success iRet = %d\n", iRet);
		printf_s("errorcode = %d\n", GetLastError());

	}
}

进程通信

  1. socket编程 IP和端口 server client
  2. 剪切板 剪切板的内核对象
  3. 邮槽 邮槽的内核对象
  4. 匿名管道(无名管道)
  5. 命名管道
  6. Copy_data findwindows wm_copydata 很多书籍都没有 Sendmessage

剪切板

  • 相关api
OpenClipboard();//打开剪切板
CloseClipboard();//关闭剪切板

EmptyClipboard();//清空剪切板
SetClipboardData(CF_TEXT, hClip);//将数据放到剪切板上
IsClipboardFormatAvailable(CF_TEXT)//判断剪切板是否可用
GetClipboardData(CF_TEXT);//获取剪切板上内容
  • 编辑框
GetDlgItemText(IDC_EDIT_SEND, strSendW);
SetDlgItemText(IDC_EDIT_RECV, strBufW);
  • 实现
void CClipBoardDlg::OnBnClickedSendBtn()
{
	// 1 打开剪切板
	if (OpenClipboard())
	{
		//2 清空剪切板
		EmptyClipboard();
		char* szSendBuf;
		//3 获取编辑框的内容,需要转换
		CStringW strSendW;
		GetDlgItemText(IDC_EDIT_SEND, strSendW);
		CStringA strSend = (CStringA)strSendW;
		//4 分配一个内存对象,内存对象的句柄就是hClip
		HANDLE hClip = GlobalAlloc(GMEM_MOVEABLE, strSend.GetLength() + 1);
		//5 将剪切板句柄加锁
		szSendBuf = (char*)GlobalLock(hClip);
		strcpy(szSendBuf, strSend);
		TRACE("szSendBuf = %s", szSendBuf);
		GlobalUnlock(hClip);
		//6 将数据放入剪切板
		SetClipboardData(CF_TEXT, hClip);
		//关闭剪切板
		CloseClipboard();
	}
}


void CClipBoardDlg::OnBnClickedRecvBtn()
{
	if (OpenClipboard())
	{
		//确认剪切板是否可用
		if (IsClipboardFormatAvailable(CF_TEXT))
		{
			char* pBuf;
			HANDLE hClip = GetClipboardData(CF_TEXT);
			pBuf = (char*)GlobalLock(hClip);
			USES_CONVERSION;
			LPCWSTR strBuf = A2W(pBuf);//ANSI  to unicode
			GlobalUnlock(hClip);
			SetDlgItemText(IDC_EDIT_RECV, strBuf);
		}
		CloseClipboard();
	}
}

邮槽

一个内核对象,只能由client发送到server,通过网络传播时通过UDP

  • 相关api
HANDLE hSlot = CreateMailslot(szSlotName,0, MAILSLOT_WAIT_FOREVER,NULL); 
//(邮槽名\\.\mailslot\name,单次消息最大字节,最大等待时间,安全属性)
ReadFile(hSlot, szBuf, 100, &dwRead, NULL);
WriteFile(hMailSlot, szBuf, strlen(szBuf) + 1, &dwWrite, NULL);

ReadFile(hSlot, szBuf, 100, &dwRead, NULL);
//(文件句柄,写入的缓冲区,要读的大小,实际读的大小,NULL)
WriteFile(hMailSlot, szBuf, strlen(szBuf) + 1, &dwWrite, NULL)

RECV

void CRECVDlg::OnBnClickedRecvBtn()
{
	// 1 创建一个邮槽
	LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
    //名称必须采用这个格式\\.\mailslot\name即'name'是我们要的名字
	HANDLE hSlot = CreateMailslot(
		szSlotName,
		0,                    // no maximum message size 
		MAILSLOT_WAIT_FOREVER,// no time-out for operations 
		NULL); // default security
	if (hSlot == INVALID_HANDLE_VALUE)
	{
		TRACE("CreateMailslot failed with %d\n", GetLastError());
		return;
	}
    
	// 2 读取数据
	char szBuf[100] = { 0 };
	DWORD dwRead;
	TRACE("Begin ReadFile");
	if (!ReadFile(hSlot, szBuf, 100, &dwRead, NULL))
	{
		MessageBox(_T("recv failed"));
		CloseHandle(hSlot);
		return;
	}
	TRACE("End ReadFile");
	MessageBox((CStringW)szBuf);
	CloseHandle(hSlot);
}

SEND

void CSENDDlg::OnBnClickedSendBtn()
{
	// 创建一个文件句柄
	LPCTSTR szSlotName = TEXT("\\\\.\\mailslot\\Mymailslot");
	HANDLE hMailSlot = CreateFile(szSlotName, FILE_GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hMailSlot == INVALID_HANDLE_VALUE)
	{
		TRACE("CreateFile failed with %d\n", GetLastError());
		return;
	}
	//写入数据
	char szBuf[] = "I love hw";
	DWORD dwWrite;
	if (!WriteFile(hMailSlot, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
		MessageBox(_T("send failed"));
		CloseHandle(hMailSlot);
		return;
	}
	CloseHandle(hMailSlot);
}

匿名管道

没有命名的管道,本质是一块共享内存,只能实现本地父子进程的通信

不要同时接收,否则死循环

  • 相关api
HANDLE hReadPipe, hWritePipe;

//创建子进程时绑定匿名管道
strStartupInfo.dwFlags = STARTF_USESTDHANDLES;
//dwFlags使用stdhandle 即下面三个I/O流
strStartupInfo.hStdInput = hReadPipe;
strStartupInfo.hStdOutput = hWritePipe;
strStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);

CreatePipe(&hReadPipe, &hWritePipe, &sa, 0);
//(读句柄,写句柄,安全属性,默认缓冲区大小)
  • 父进程
HANDLE hReadPipe, hWritePipe;
void CRECVDlg::OnBnClickedCreateBtn()
{
	//创建匿名管道
	SECURITY_ATTRIBUTES sa;
	sa.bInheritHandle = TRUE;
	sa.lpSecurityDescriptor = NULL;
	sa.nLength = sizeof(SECURITY_ATTRIBUTES);
	if (!CreatePipe(&hReadPipe, &hWritePipe, &sa, 0))
	{
		MessageBox(_T("pipe create failed"));
		return;
	}
	//创建子进程,绑定管道
	STARTUPINFO strStartupInfo;
	memset(&strStartupInfo, 0, sizeof(strStartupInfo));
	strStartupInfo.cb = sizeof(strStartupInfo);
	strStartupInfo.dwFlags = STARTF_USESTDHANDLES;
	strStartupInfo.hStdInput = hReadPipe;
	strStartupInfo.hStdOutput = hWritePipe;
	strStartupInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);

	PROCESS_INFORMATION szProcessInformation;
	memset(&szProcessInformation, 0, sizeof(szProcessInformation));

	int iRet = CreateProcess(
		_T("SEND.exe"),
		NULL, NULL, NULL, TRUE, 0, NULL, NULL,
		&strStartupInfo,
		&szProcessInformation
	);
	if (iRet)
	{
		CloseHandle(szProcessInformation.hProcess);
		CloseHandle(szProcessInformation.hThread);
		szProcessInformation.dwProcessId = 0;
		szProcessInformation.dwThreadId = 0;
		szProcessInformation.hThread = NULL;
		szProcessInformation.hProcess = NULL;
	}
	else
	{
		CloseHandle(hReadPipe);
		CloseHandle(hWritePipe);
		hReadPipe = NULL;
		hWritePipe = NULL;
		MessageBox(_T("process create failed"));
		return;
	}
}

void CRECVDlg::OnBnClickedSendBtn()
{
	char szBuf[] = "I am Dad process";
	DWORD dwWrite;
	if (!WriteFile(hWritePipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
		MessageBox(_T("send failed"));
		return;
	}
}

void CRECVDlg::OnBnClickedRecvBtn()
{
	char szBuf[100] = { 0 };
	DWORD dwRead;
	if (!ReadFile(hReadPipe, szBuf, 100, &dwRead, NULL))
	{
		MessageBox(_T("read failed"));
		return;
	}
	MessageBox((CStringW)szBuf);
}
  • 子进程
HANDLE hReadCliPipe = GetStdHandle(STD_INPUT_HANDLE);
HANDLE hWriteCliPipe = GetStdHandle(STD_OUTPUT_HANDLE);
void CSENDDlg::OnBnClickedSendBtn()
{
	char szBuf[] = "I am Son process";
	DWORD dwWrite;
	if (!WriteFile(hWriteCliPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
		MessageBox(_T("send failed"));
		return;
	}
}

void CSENDDlg::OnBnClickedRecvBtn()
{
	char szBuf[100] = { 0 };
	DWORD dwRead;
	if (!ReadFile(hReadCliPipe, szBuf, 100, &dwRead, NULL))
	{
		MessageBox(_T("read failed"));
		return;
	}
	MessageBox((CStringW)szBuf);
}

命名管道

  • creater
HANDLE hNamedPipe;
void CRECVDlg::OnBnClickedCreateBtn()
{
	//1 创建一个命名管道
	LPCTSTR szPipeName = TEXT("\\\\.\\pipe\\mypipe");
	hNamedPipe = CreateNamedPipe(szPipeName, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,PIPE_TYPE_BYTE, 1, 1024, 1024, 0, NULL);
	if (hNamedPipe == INVALID_HANDLE_VALUE)
	{
		TRACE("CreateNamedhPipe failed with %d\n", GetLastError());
		MessageBox(_T("创建命名管道失败"));
		return;
	}
	// 2 等待客户端的连接
	HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (NULL == hEvent)
	{
		MessageBox(_T("创建事件失败"));
		CloseHandle(hNamedPipe);
		hNamedPipe = NULL;
		return;
	}

	OVERLAPPED ovlap;
	ZeroMemory(&ovlap, sizeof(OVERLAPPED));
	ovlap.hEvent = hEvent;
	//等待连接
	if (!ConnectNamedPipe(hNamedPipe, &ovlap))
	{
		if (ERROR_IO_PENDING != GetLastError())
		{
			MessageBox(_T("等待客户端连接失败"));
			CloseHandle(hNamedPipe);
			CloseHandle(hEvent);
			hNamedPipe = NULL;
			hEvent = NULL;
			return;
		}
	}
	if (WaitForSingleObject(hEvent, INFINITE) == WAIT_FAILED)
	{
		MessageBox(_T("等待对象失败"));
		CloseHandle(hNamedPipe);
		CloseHandle(hEvent);
		hNamedPipe = NULL;
		hEvent = NULL;
		return;
	}
}


void CRECVDlg::OnBnClickedSendBtn()
{
	char szBuf[] = "creater";
	DWORD dwWrite;
	if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
		MessageBox(_T("send failed"));
		return;
	}
}

void CRECVDlg::OnBnClickedRecvBtn()
{
	char szBuf[100] = { 0 };
	DWORD dwRead;
	if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL))
	{
		MessageBox(_T("read failed"));
		return;
	}
	MessageBox((CStringW)szBuf);
}
  • connector
HANDLE hNamedPipe;
void CSENDDlg::OnBnClickedConnectBtn()
{
	LPCTSTR szNamedPipeName = TEXT("\\\\.\\pipe\\mypipe");
	if (0 == WaitNamedPipe(szNamedPipeName, NMPWAIT_WAIT_FOREVER))
	{
		MessageBox(_T("当前没有可以利用的管道"));
		return;
	}
	 hNamedPipe = CreateFile(szNamedPipeName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hNamedPipe == INVALID_HANDLE_VALUE)
	{
		MessageBox(_T("打开命名管道失败!"));
		hNamedPipe = NULL;
		return;
	}
	MessageBox(_T("打开命名管道成功!"));
}

void CSENDDlg::OnBnClickedSendBtn()
{
	char szBuf[] = "connector";
	DWORD dwWrite;
	if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL))
	{
		MessageBox(_T("写入数据失败"));
		return;
	}
}

void CSENDDlg::OnBnClickedRecvBtn()
{
	char szBuf[100] = { 0 };
	DWORD dwRead;
	if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL))
	{
		MessageBox(_T("读取数据失败"));
		return;
	}
	MessageBox((CStringW)szBuf);
}

WM_COPYDATA

获取句柄和窗口名

工具->spy+±>查找窗口(望远镜图标,alt+F3),拖动到窗口就行了

需要先启动发送端

  • SEND
void CSENDDlg::OnBnClickedSendBtn()
{
	// 必须要知道标题  句柄  
	CString strWindowTitle = _T("RECV");//发送对象窗口名字
	CString strDataToSend = _T("Hello world");
	HWND hRecvWnd = ::FindWindow(NULL, strWindowTitle.GetBuffer(0));
	if (hRecvWnd != NULL && ::IsWindow(hRecvWnd))
	{
		//数据的封装
		COPYDATASTRUCT cpd;
		cpd.dwData = 0;
		cpd.cbData = strDataToSend.GetLength() * sizeof(TCHAR);
		cpd.lpData = (PVOID)strDataToSend.GetBuffer(0);
		::SendMessage(hRecvWnd, WM_COPYDATA, (WPARAM)(AfxGetApp()->m_pMainWnd), (LPARAM)&cpd);
	}
	strDataToSend.ReleaseBuffer();
}

源文件->右键->类向导->类名->CRECVDlg->消息->WM_COPYDATA

  • READ
BOOL CRECVDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
    //数据的读取
	LPCTSTR szText = (LPCTSTR)(pCopyDataStruct->lpData);
	DWORD dwLength = (DWORD)pCopyDataStruct->cbData;
	TCHAR szRecvText[1024] = { 0 };
	memcpy(szRecvText, szText, dwLength);
	MessageBox(szRecvText, _T("Bingo"), MB_OK);
	return CDialogEx::OnCopyData(pWnd, pCopyDataStruct);
}

SOCKET

利用回环地址127.0.0.1进行本地网络通信

实战

多线程群聊(项目)

为什么多线程 : 因为单线程同时只存在一个,只有当前一个客户端结束后,才会开始处理下一个客户端, 就无法达到即时聊天的功能,所以利用多线程的并发性,实现功能

设计

  • 服务器
  1. 管理客户端的连接和断开
  2. 收发数据
  • 客户端
  1. 收发数据
  2. 在发送结束后设置断开机制

实现

客户端

#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <WinSock2.h>
#include <iostream>
#include <windows.h>
#include <process.h>

#pragma comment(lib, "ws2_32.lib")

#define NAME_SIZE 32
#define BUF_SIZE 256
char szName[NAME_SIZE];
char szMsg[BUF_SIZE];

unsigned WINAPI SendMsg(void* arg);
unsigned WINAPI RecvMsg(void* arg);

int main()
{
#if 1
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
		WSACleanup();
		return -1;
	}
	SOCKET hSock = socket(PF_INET, SOCK_STREAM, 0);
	SOCKADDR_IN servAdr;
	servAdr.sin_addr.S_un.S_addr = inet_addr("10.237.73.82");
	servAdr.sin_family = AF_INET;
	servAdr.sin_port = htons(9190);
	if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
	{
		printf("connect error error code = %d\n", GetLastError());
		return -1;
	}
#endif
	//设置昵称
	printf("欢迎来到聊天室,按q退出\n请输入你的昵称 : ");
	scanf_s("%s", szName, NAME_SIZE); 
	getchar();

	//收/发消息的两个线程
	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);
	WSACleanup();
	return 0;
}

unsigned WINAPI SendMsg(void* arg)
{
	SOCKET hClntSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + BUF_SIZE];
	//循环接收来自于控制台的消息
	while (1)
	{
		fgets(szMsg, BUF_SIZE, stdin);
		if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n"))
		{//退出机制  当收到q或Q  退出
			closesocket(hClntSock);
			exit(0);
		}
		sprintf(szNameMsg, "%s : %s", szName, szMsg);//拼接后发送
		send(hClntSock, szNameMsg, strlen(szNameMsg), 0);
	}
	return 0;
}

unsigned WINAPI RecvMsg(void* arg)
{
	SOCKET hClntSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + BUF_SIZE];
	while (1)
	{
		int iLen = recv(hClntSock, szNameMsg, NAME_SIZE + BUF_SIZE - 1, 0);
		if (iLen == SOCKET_ERROR)
			return -1;
		szNameMsg[iLen] = 0;//添加串尾\0
		fputs(szNameMsg, stdout);
	}
	return 0;
}

服务端

#include <WinSock2.h>
#include <stdio.h>
#include <windows.h>
#include <process.h>
#pragma comment(lib, "ws2_32.lib")

#define MAX_CLNT 256//最大用户数
#define MAX_BUF_SIZE 256//最大单次消息量
SOCKET clntSocks[MAX_CLNT];//所有的连接的客户端socket
int clntCnt = 0;  //当前连接的数目

HANDLE hMutex;//修改全局变量时,加锁 clntSocks时加锁,只有对一个用户的处理完之后才会处理下一个

void SendMsg(char* szMsg, int iLen);//将消息发送给所有的客户端
unsigned WINAPI HandleCln(void* arg);//管理消息收发与客户端退出

int main()
{
	// 绑定套接字到本地IP地址,端口号9190
# if 1
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	HANDLE hThread;
	wVersionRequested = MAKEWORD(1, 1);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		return err;
	}
	if (LOBYTE(wsaData.wVersion) != 1 || HIBYTE(wsaData.wVersion) != 1)
	{
		WSACleanup();
		return -1;
	}
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(9190);
	if (bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) == SOCKET_ERROR)
	{
		printf("bind ERRORnum = %d\n", GetLastError());
		return -1;
	}
	if (listen(sockSrv, 5) == SOCKET_ERROR)
	{
		printf("listen ERRORnum = %d\n", GetLastError());
		return -1;
	}
	printf("start listen\n");
# endif

	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);
	hMutex = CreateMutex(NULL, FALSE, NULL);//处理同时得到多个连接
	while (1)
	{//接受客户端连接,并创建线程
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);

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

		hThread = (HANDLE)_beginthreadex(NULL, 0, HandleCln,(void*)&sockConn, 0, NULL);
		printf("Connect client IP: %s \n", inet_ntoa(addrCli.sin_addr));
		printf("Connect client num: %d \n", clntCnt);
	}

	closesocket(sockSrv);
	WSACleanup();
	return 0;
}

void SendMsg(char* szMsg, int iLen)
{
	WaitForSingleObject(hMutex, INFINITE);
	for (int i = 0; i < clntCnt; i++)
		send(clntSocks[i], szMsg, iLen, 0);
	ReleaseMutex(hMutex);
}

unsigned WINAPI HandleCln(void* arg)
{
	SOCKET hClntSock = *((SOCKET*)arg);
	int iLen = 0;
	char szMsg[MAX_BUF_SIZE] = { 0 };
	//循环收发消息
	while (1)
	{
		iLen = recv(hClntSock, szMsg, sizeof(szMsg), 0);
		if (iLen != SOCKET_ERROR)//客户端以closesocket退出,接受时产生错误SOCKET_ERROR
			SendMsg(szMsg, iLen);
		else break;
	}
	//管理退出线程(找到自己在线程数组中的位置,并将后面的进行前移)
	//可用链表来实现
	WaitForSingleObject(hMutex, INFINITE);
	for (int i = 0; i < clntCnt; i++)
	{
		if (hClntSock == clntSocks[i])
		{
			while (i++ < clntCnt)
			{
				clntSocks[i] = clntSocks[i + 1];
			}
			break;
		}
	}
	clntCnt--; //断开后,自减1
	printf("\nconnecting%d", clntCnt);
	ReleaseMutex(hMutex);
	closesocket(hClntSock);
	return 0;
}