基于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;
}

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在现有省、市港口信息化系统进行有效整合基础上,借鉴新 一代的感知-传输-应用技术体系,实现对码头、船舶、货物、重 大危险源、危险货物装卸过程、航管航运等管理要素的全面感知、 有效传输和按需定制服务,为行政管理人员和相关单位及人员提 供高效的管理辅助,并为公众提供便捷、实时的水运信息服务。 建立信息整合、交换和共享机制,建立健全信息化管理支撑 体系,以及相关标准规范和安全保障体系;按照“绿色循环低碳” 交通的要求,搭建高效、弹性、高可扩展性的基于虚拟技术的信 息基础设施,支撑信息平台低成本运行,实现电子政务建设和服务模式的转变。 实现以感知港口、感知船舶、感知货物为手段,以港航智能 分析、科学决策、高效服务为目的和核心理念,构建“智慧港口”的发展体系。 结合“智慧港口”相关业务工作特点及信息化现状的实际情况,本项目具体建设目标为: 一张图(即GIS 地理信息服务平台) 在建设岸线、港口、港区、码头、泊位等港口主要基础资源图层上,建设GIS 地理信息服务平台,在此基础上依次接入和叠加规划建设、经营、安全、航管等相关业务应用专题数据,并叠 加动态数据,如 AIS/GPS/移动平台数据,逐步建成航运管理处 "一张图"。系统支持扩展框架,方便未来更多应用资源的逐步整合。 现场执法监管系统 基于港口(航管)执法基地建设规划,依托统一的执法区域 管理和数字化监控平台,通过加强对辖区内的监控,结合移动平 台,形成完整的多维路径和信息追踪,真正做到问题能发现、事态能控制、突发问题能解决。 运行监测和辅助决策系统 对区域港口与航运业务日常所需填报及监测的数据经过科 学归纳及分析,采用统一平台,消除重复的填报数据,进行企业 输入和自动录入,并进行系统智能判断,避免填入错误的数据, 输入的数据经过智能组合,自动生成各业务部门所需的数据报 表,包括字段、格式,都可以根据需要进行定制,同时满足扩展 性需要,当有新的业务监测数据表需要产生时,系统将分析新的 需求,将所需字段融合进入日常监测和决策辅助平台的统一平台中,并生成新的所需业务数据监测及决策表。 综合指挥调度系统 建设以港航应急指挥中心为枢纽,以各级管理部门和经营港 口企业为节点,快速调度、信息共享的通信网络,满足应急处置中所需要的信息采集、指挥调度和过程监控等通信保障任务。 设计思路 根据项目的建设目标和“智慧港口”信息化平台的总体框架、 设计思路、建设内容及保障措施,围绕业务协同、信息共享,充 分考虑各航运(港政)管理处内部管理的需求,平台采用“全面 整合、重点补充、突出共享、逐步完善”策略,加强重点区域或 运输通道交通基础设施、运载装备、运行环境的监测监控,完善 运行协调、应急处置通信手段,促进跨区域、跨部门信息共享和业务协同。 以“统筹协调、综合监管”为目标,以提供综合、动态、实 时、准确、实用的安全畅通和应急数据共享为核心,围绕“保畅通、抓安全、促应急"等实际需求来建设智慧港口信息化平台。 系统充分整合和利用航运管理处现有相关信息资源,以地理 信息技术、网络视频技术、互联网技术、移动通信技术、云计算 技术为支撑,结合航运管理处专网与行业数据交换平台,构建航 运管理处与各部门之间智慧、畅通、安全、高效、绿色低碳的智 慧港口信息化平台。 系统充分考虑航运管理处安全法规及安全职责今后的变化 与发展趋势,应用目前主流的、成熟的应用技术,内联外引,优势互补,使系统建设具备良好的开放性、扩展性、可维护性。
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值