// 创建在调用进程的虚拟地址空间内执行的线程,Windows API提供的函数
HANDLE CreateThread(
[in, optional] LPSECURITY_ATTRIBUTES lpThreadAttributes,//线程内核对象的安全属性,一般传入 NULL 表示使用默认设置。
[in] SIZE_T dwStackSize,//线程栈空间大小。传入0表示使用默认大小 (1MB)
[in] LPTHREAD_START_ROUTINE lpStartAddress,//新线程所执行的线程函数地址,多个线程可以使用同一个函数地址。
[in, optional] __drv_aliasesMem LPVOID lpParameter,//传给线程函数的参数
[in] DWORD dwCreationFlags,//指定额外的标志来控制线程的创建,为0表示线程创建之后立即就可以进行调度,如果为 CREATE_SUSPENDED 则表 示线程创建后暂停运行,这样它就无法调度,直到调用 ResumeThread()
[out, optional] LPDWORD lpThreadId//返回线程的ID 号,传入 NULL 表示不需要返回该线程ID号
);
//C运行时库提供的函数,使用了本地代码实现
uintptr_t _beginthreadex( // NATIVE CODE
[in] void *security,// 用于指定线程安全特性,通常为NULL(表示使用默认安全设置)。
[in] unsigned stack_size,// 用于指定线程的栈大小,通常为0(表示使用默认栈大小)。
[in] unsigned ( __stdcall *start_address )( void * ),// 指向线程函数的指针,这个函数会作为线程的入口点来执行。
[in] void *arglist,// 传递给线程函数的参数。
[in] unsigned initflag,// 用于指定线程的启动标志,通常为0(表示使用默认标志)。
[out] unsigned *thrdaddr// 接收用于返回新线程标识符的指针。
);
CreateThread 是一种微软在 Windows API中提供了建立新的线程的函数,该函数在主线程的基础上创建一个新线程。线程终止运行后,线程对象仍然在系统中,必须通过 CloseHandle 函数来关闭该线程对象
?_beginthreadex?函数则更适合在使用C/C++运行时库的环境中创建线程。
1. 第一阶段:主线程和子线程的结束时间
main 函数返回后,整个进程终止,同时终止其包含的所有线程
代码如下:
#include <stdio.h>
#include <windows.h>
#include <process.h>
unsigned WINAPI ThreadFunc(void* arg);
int main(int argc, char* argv[])
{
HANDLE hThread;
unsigned threadID;
int param = 5;
// 创建子线程
hThread = (HANDLE)_beginthreadex(NULL, 0, &ThreadFunc, (void*)¶m, 0, &threadID);
if (hThread == 0)
{
puts("_beginthreadex() error");
return -1;
}
Sleep(3000);
puts("end of main");
return 0;
}
unsigned WINAPI ThreadFunc(void* arg)
{
int i;
int cnt = *((int*)arg);// 将函数参数类型转换回int *;再取一下该指针指向的内容
for (i = 0; i < cnt; i++) {
Sleep(1000);
puts("running thread");
}
return 0;
}
结果:线程函数里是每隔一秒钟打印一次"running thread",但是却只打印2次就结束了,这是因为主线程这边已经等了3s,执行到return 0了导致整个进程结束。
2. 第二阶段WaitForSingleObject 来等待一个内核对象变为已通知状态
?WaitForSingleObject?函数可以通过操作系统内部的机制接收到线程结束的信号,并相应地处理等待结果。
需要注意的是,?WaitForSingleObject?函数只能等待一个对象的状态对齐。如果需要等待多个对象,可以考虑使用 ?WaitForMultipleObjects?函数。
// 等待指定的对象处于信号状态或超时间隔已过,用于等待各种内核对象:线程、进程、事件、互斥体
DWORD WaitForSingleObject(
[in] HANDLE hHandle,// 对象的句柄
[in] DWORD dwMilliseconds// 超时间隔(以毫秒为单位)。
);
代码如下
unsigned int __stdcall ThreadFun(LPVOID p)
{
int cnt = *((int*)p);
for (int i = 0; i < cnt; i++)
{
Sleep(1000);
puts("running thread");
}
return 0;
}
int main()
{
printf("main begin\n");
int iParam = 5;
unsigned int dwThreadID;
DWORD wr;
HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, ThreadFun, (void*)&iParam, 0, &dwThreadID);
if (hThread == NULL)
{
puts("_beginthreadex() error");
return -1;
}
printf("WaitForSingleObject begin\n");
// WaitForSingleObject?函数在等待线程?hThread?的结束时,使用INFINITE?作为等待时间,表示无限期等待。
if ((wr = WaitForSingleObject(hThread, INFINITE)) == WAIT_FAILED)
{
puts("thread wait error");
return -1;
}
printf("WaitForSingleObject end\n");
printf("main end\n");
system("pause");
return 0;
}
结果:等待子线程完整的打印了5次以后,结束子线程。然后主线程才继续执行后面的代码
3. 第二阶段起两个线程,一个加+1,一个减 1
DWORD WaitForMultipleObjects(
[in] DWORD nCount,//lpHandles 指向的数组中的对象句柄数。不可为0
[in] const HANDLE *lpHandles,// 对象句柄的数组。
//如果此参数为 TRUE,则当发出 lpHandles 数组中**所有对象的状态信号**时,函数将返回 。
//如果 为 FALSE,则当任一对象的状态设置为“已发出信号”时,函数将返回。在后一种情况下,返回值指示其状态导致函数返回的对象。
[in] BOOL bWaitAll,
[in] DWORD dwMilliseconds//超时间隔(以毫秒为单位)。
);
代码如下:
#define NUM_THREAD 50
unsigned WINAPI threadInc(void * arg);
unsigned WINAPI threadDes(void * arg);
long long num=0;
int main(int argc, char *argv[])
{
HANDLE tHandles[NUM_THREAD];
int i;
printf("sizeof long long: %d \n", sizeof(long long));
// 奇数线程++;偶数线程--;每个线程执行循环500000次
for(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);
printf("result: %lld \n", num);
return 0;
}
// num++
unsigned WINAPI threadInc(void* arg)
{
int i;
for (i = 0; i < 500000; i++)
num += 1;
return 0;
}
// num--
unsigned WINAPI threadDes(void* arg)
{
int i;
for (i = 0; i < 500000; i++)
num -= 1;
return 0;
}
结果: 我们可以看到每一次num的结果并不相同,这是因为没有对 num?进行同步,不同线程之间可能会出现竞争条件,导致最终的结果不确定。
Windows 提供了一组函数进行操作内核对象。成功调用一个创建内核对象的函数后,会返回一个句柄,它表示了所创建的内核对象,可由进程中的任何线程使用。在 32 位进程中,句柄是一个 32 位值,在 64 位进程中句柄是一个 64 位值。我们可以使用唯一标识内核对象的句柄,调用内核操作函数对内核对象进行操作。
Windows 进程中除了内核对象还有其他类型的对象,比如窗口,菜单,字体等,这些属于用户对 象和 GDI 对象。要区分内核对象与非内核对象,最简单的方式就是查看创建这个对象的函数,几乎所有创建内核对象的函数都有一个允许我们指定安全属性的参数
一个对象是不是内核对象,通常可以看创建此对象 API 的参数中是否需要: PSECURITY_ATTRIBUTES 类型的参数。
内核对象只是一个内存块,这块内存位于操作系统内核的地址空间,内存块中存放一个数据结构(此数据结构的成员有如:安全描述符、使用计数等)。
每个进程中有一个句柄表(handle table),这个句柄表仅供内核对象使用,如下图: 4. 调用
(1) hThread = CreateThread(... , &threadId);
当调用了 CreateThread 、CreateFile 等创建内核对象的函数后, 就是相当于操作系统多了一个内存块,这个内存块就是内核对象 也是此时内核对象被创建,其数据结构中的引用计数初始为 1(这样理解:只要内 核对象被创建,其引用计数被初始化为1),这里实则发生两件事:创建了一个内核对象和创建线程的函数打开(访问)了此对象,所以内核对象的引用计数加 1, 这时引用计数就为 2 了。
(2)当调用 CloseHandle(hThread);
时发生这样的事情:
系统通过 hThread 计算出此句柄在句柄表中的索引
然后把那一项处理后标注为空闲可用的项,内核对象的引用计数减 1 即此时此内核对象的引用计数为 1。
之后这个线程句柄与创建时产生的内核对象已经没有任何关系了。不能通过 hThread 句柄去访问内核对象了 只有当内核对象的引用计数为 0 时,内核对象才会被销毁,而此时它的引用计数 为 1,那它什么时候会被销毁?
当此线程结束的时候,它的引用计数再减 1 即为 0,内核对象被销毁。此时又有一 个新问题产生:我们已经关闭了线程句柄,也就是这个线程句柄已经和内核对象没 有瓜葛了,那么那个内核对象是怎么又可以和此线程联系起来了呢?
其实是创建 线程时产生的那个线程 ID,代码如下:
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
printf("I am comming...");
return 0;
}
int main() {
HANDLE hThread;
HANDLE headle2;
DWORD threadId;
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &threadId);
CloseHandle(hThread); // 关闭了线程句柄
// 打开先用的线程对象
headle2 = OpenThread(THREAD_QUERY_INFORMATION, FALSE, threadId);
system("pause");
return 0;
}
其中对于openThread
// 打开现有线程对象
HANDLE OpenThread(
[in] DWORD dwDesiredAccess,// 对线程对象的访问
[in] BOOL bInheritHandle,// 如果此值为 TRUE,则此进程创建的进程将继承句柄。 否则,进程不会继承此句柄。
[in] DWORD dwThreadId// 要打开的线程的标识符。
);
OpenThread 返回的句柄可用于需要线程句柄的任何函数(例如 wait 函数),前提是你请求了适当的访问权限。
// 1 创建或打开命名的或未命名的互斥体对象。
HANDLE CreateMutexA(
//指向 SECURITY_ATTRIBUTES 结构的指针。 如果此参数为 NULL,则子进程无法继承句柄。
[in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,
//如果此值为TRUE并且调用方创建了互斥体,则调用线程获取互斥对象的初始所有权。 否则,调用线程不会获得互斥体的所有权。
[in] BOOL bInitialOwner,
//互斥对象的名称。 名称限制为 MAX_PATH 个字符。
[in, optional] LPCSTR lpName
);
// 2 等待指定的对象处于信号状态或超时间隔已过。
DWORD WaitForSingleObject(
[in] HANDLE hHandle,
[in] DWORD dwMilliseconds
);
// 3 释放指定互斥对象的所有权
BOOL ReleaseMutex(
[in] HANDLE hMutex
);
#include <stdio.h>
#include <windows.h>
#include <process.h>
#define NUM_THREAD 10
// 定义线程数量
unsigned WINAPI threadInc(void* arg);
// 线程函数:对全局变量进行加1操作
unsigned WINAPI threadDes(void* arg);
// 线程函数:对全局变量进行减1操作
long long num = 0;
// 全局变量
HANDLE hMutex;
// 互斥锁句柄
int main(int argc, char* argv[]) {
HANDLE tHandles[NUM_THREAD];
int i;
//1 创建互斥锁
hMutex = CreateMutex(NULL, FALSE, NULL);
for (i = 0; i < NUM_THREAD; i++) {
if (i % 2) {
tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadInc, NULL, 0, NULL);
// 创建偶数线程,对num进行加1操作
}
else {
tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, threadDes, NULL, 0, NULL);
// 创建奇数线程,对num进行减1操作
}
}
WaitForMultipleObjects(NUM_THREAD, tHandles, TRUE, INFINITE);
// 等待所有线程执行完毕
CloseHandle(hMutex);
// 关闭互斥锁句柄
printf("result:%lld\n", num);
// 打印最终结果
return 0;
}
unsigned WINAPI threadInc(void* arg) {
int i;
//2 请求互斥锁
WaitForSingleObject(hMutex, INFINITE);
printf("这是threadInc()\n");
for (i = 0; i < 5; i++) {
num += 1;
printf("num: %d\n", num);
// 对全局变量进行加1操作,并打印循环变量i的值
}
printf("threadInc()调用结束\n");
//3 释放互斥锁
ReleaseMutex(hMutex);
return 0;
}
unsigned WINAPI threadDes(void* arg) {
int i;
//2 请求互斥锁
WaitForSingleObject(hMutex, INFINITE);
printf("这是threadDes()\n");
for (i = 0; i < 5; i++) {
num -= 1;
printf("num: %d\n", num);
// 对全局变量进行减1操作,并打印循环变量i的值
}
printf("threadDes()调用结束\n");
//3 释放互斥锁
ReleaseMutex(hMutex);
return 0;
}
// 1 创建事件对象
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes,
//决定事件对象的自动重置或手动重置。如果为 TRUE,则事件对象会保持触发状态,直到对应的 ?ResetEvent? 函数被调用将其重置;
// 如果为 FALSE,则事件对象会在有一个等待线程被通知后自动重置为非触发状态。
BOOL bManualReset,
// 指定事件对象的初始状态。如果为 TRUE,则事件对象初始为触发状态;如果为 FALSE,则事件对象初始为非触发状态。
BOOL bInitialState,
// 事件对象的名称,在系统中必须唯一。可以为 NULL。
LPCTSTR lpName
);
// 2 用于将事件对象(Event Object)设置为触发状态
BOOL SetEvent(
HANDLE hEvent
);
// 3 用于重置事件对象(Event Object)的触发状态
BOOL ResetEvent(
HANDLE hEvent
);
5.实例
#define STR_LEN 100
unsigned WINAPI NumberOfA(void* arg);
unsigned WINAPI NumberOfOthers(void* arg);
static char str[STR_LEN];
static HANDLE hEvent;
int main(int argc, char* argv[]) {
HANDLE hThread1, hThread2;
fputs("Input string: ", stdout);
fgets(str, STR_LEN, stdin);
//NUll 默认的安全符 手动 FALSE 初始状态为无信号状态
hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
hThread1 = (HANDLE)_beginthreadex(NULL, 0, NumberOfA, NULL, 0, NULL);
hThread2 = (HANDLE)_beginthreadex(NULL, 0, NumberOfOthers, NULL, 0, NULL);
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
//直到2个线程执行完之后,再把事件设置为无信号状态
ResetEvent(hEvent);
CloseHandle(hEvent);
system("pause");
return 0;
}
unsigned WINAPI NumberOfA(void* arg) {
int i, cnt = 0;
WaitForSingleObject(hEvent, INFINITE);
for (i = 0; str[i] != 0; i++)
{
if (str[i] == 'A') cnt++;
}
printf("Num of A: %d \n", cnt);
return 0;
}
unsigned WINAPI NumberOfOthers(void* arg)
{
int i, cnt = 0;
SetEvent(hEvent);
WaitForSingleObject(hEvent, INFINITE);
for (i = 0; str[i] != 0; i++)
{
if (str[i] != 'A') cnt++;
}
printf("Num of others: %d \n", cnt - 1);
//把事件对象设置为有信号状态
SetEvent(hEvent);
return 0;
}
当一个进程或线程想要访问共享资源时,它必须首先获取信号量,如果信号量的计数器大于零,则减少计数器的值并允许访问共享资源。如果计数器的值为零,则进程或线程将被阻塞,直到有另一个进程或线程释放信号量并增加计数器的值。
// 1创建信号量
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
LONG lInitialCount,//信号量对象的初始计数值,即可用资源的初始数量
LONG lMaximumCount,//信号量对象的最大计数值,即可用资源的最大数量。
LPCTSTR lpName// 信号量对象的名称,在系统中必须唯一。可以为 NULL
);
// 2 增加信号量
BOOL ReleaseSemaphore(
HANDLE hSemaphore,// 要释放资源的信号量对象的句柄。
LONG lReleaseCount,// 要释放的资源数量。每次调用 ?ReleaseSemaphore?,信号量对象的计数值将会增加 ?lReleaseCount?。
LPLONG lpPreviousCount//用于获取调用 ?ReleaseSemaphore? 前的信号量计数值。可以为 NULL。
);
unsigned WINAPI Read(void* arg);
unsigned WINAPI Accu(void* arg);
static HANDLE semOne;
static HANDLE semTwo;
static int num;
int main(int argc, char* argv[]) {
HANDLE hThread1, hThread2;
semOne = CreateSemaphore(NULL, 0, 1, NULL); //semOne 没有可用资源 只能表示0或者1的二进制信号量 无信号
semTwo = CreateSemaphore(NULL, 1, 1, NULL); //semTwo 有可用资源,有信号状态 有信号
hThread1 = (HANDLE)_beginthreadex(NULL, 0, Read, NULL, 0, NULL);
hThread2 = (HANDLE)_beginthreadex(NULL, 0, Accu, NULL, 0, NULL);
WaitForSingleObject(hThread1, INFINITE);
WaitForSingleObject(hThread2, INFINITE);
CloseHandle(semOne);
CloseHandle(semTwo);
system("pause");
return 0;
}
unsigned WINAPI Read(void* arg)
{
int i;
for (i = 0; i < 5; i++) {
fputs("Input num: ", stdout); // 1 5 11
printf("begin read\n"); // 3 6 12 //等待内核对象semTwo的信号,如果有信号,继续执行;如果没有信号,等待
WaitForSingleObject(semTwo, INFINITE);
printf("beginning read\n"); //4 10 16
scanf("%d", &num);
ReleaseSemaphore(semOne, 1, NULL);
}return 0;
}
unsigned WINAPI Accu(void* arg)
{
int sum = 0, i;
for (i = 0; i < 5; i++)
{
printf("begin Accu\n"); //2 9 15 //等待内核对象semOne的信号,如果有信号,继续执行;如果没有信号,等待
WaitForSingleObject(semOne, INFINITE);
printf("beginning Accu\n"); //7 13
sum += num;
printf("sum = %d \n", sum); // 8 14
ReleaseSemaphore(semTwo, 1, NULL);
}
printf("Result: %d \n", sum);
return 0;
}
关键代码段,也称为临界区,工作在用户方式下。它是指一个小代码段,在代码能够执行前,它必须独占对某些资源的访问权。通常把多线程中访问同一种资源的那部分代码当做关键代码段。
// 1 初始化一个新的临界区对象
InitializeCriticalSection(
_Out_ LPCRITICAL_SECTION lpCriticalSection//初始化的临界区对象的指针
);
// 2 尝试进入一个临界区对象来进行线程同步
VOID WINAPI EnterCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection//要进入的临界区对象的指针。该参数必须是一个有效的地址,并且在调用此函数之前,对应的临界区对象必须已经成功初始化。
);
// 3 用于离开一个已进入的临界区对象
VOID WINAPI LeaveCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection// 要离开的临界区对象的指针。该参数必须是一个有效的地址,并且在调用此函数之前,对应的临界区对象必须已经成功初始化。
);
// 4 删除临界区
WINBASEAPI VOID WINAPI DeleteCriticalSection(
_Inout_ LPCRITICAL_SECTION lpCriticalSection//它是要删除的临界区对象的指针。该参数必须是一个有效的地址,并且在调用此函数之前,对应的临界区对象必须已经成功初始化。
);
int iTickets = 10;
CRITICAL_SECTION g_cs; // A窗口 B窗口
DWORD WINAPI SellTicketA(void* lpParam)
{
while (1)
{
EnterCriticalSection(&g_cs);//进入临界区
if (iTickets > 0) {
Sleep(1);
iTickets--;
printf("A remain %d\n", iTickets);
LeaveCriticalSection(&g_cs);//离开临界区
}else
{
LeaveCriticalSection(&g_cs);//离开临界区
break;
}
}
return 0;
}
DWORD WINAPI SellTicketB(void* lpParam) {
while (1) {
EnterCriticalSection(&g_cs);//进入临界区
if (iTickets > 0)
{
Sleep(1);
iTickets--;
printf("B remain %d\n", iTickets);
LeaveCriticalSection(&g_cs);//离开临界区
}else {
LeaveCriticalSection(&g_cs);//离开临界区
break;
}
}
return 0;
}
int main()
{
HANDLE hThreadA, hThreadB;
InitializeCriticalSection(&g_cs); //初始化关键代码段
hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL); //2
hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL); //2
CloseHandle(hThreadA); //1
CloseHandle(hThreadB); //1
Sleep(20000);
DeleteCriticalSection(&g_cs);//删除临界区
system("pause");
return 0;
}
死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进
int iTickets = 10;
CRITICAL_SECTION g_csA; // A窗口 B窗口
CRITICAL_SECTION g_csB;
DWORD WINAPI SellTicketA(void* lpParam)
{
while (1)
{
EnterCriticalSection(&g_csA);//进入临界区
Sleep(1);
EnterCriticalSection(&g_csB);//进入临界区
if (iTickets > 0) {
Sleep(1);
iTickets--;
printf("A remain %d\n", iTickets);
LeaveCriticalSection(&g_csB);//离开临界区
LeaveCriticalSection(&g_csA);
}
else
{
LeaveCriticalSection(&g_csB);//离开临界区
LeaveCriticalSection(&g_csA);
break;
}
}
return 0;
}
DWORD WINAPI SellTicketB(void* lpParam) {
while (1) {
EnterCriticalSection(&g_csB);//进入临界区
Sleep(1);
EnterCriticalSection(&g_csA);//进入临界区
if (iTickets > 0)
{
Sleep(1);
iTickets--;
printf("B remain %d\n", iTickets);
EnterCriticalSection(&g_csA);//进入临界区
LeaveCriticalSection(&g_csB);//离开临界区
}
else {
LeaveCriticalSection(&g_csA);//离开临界区
LeaveCriticalSection(&g_csB);
break;
}
}
return 0;
}
int main()
{
HANDLE hThreadA, hThreadB;
InitializeCriticalSection(&g_csA); //初始化关键代码段
InitializeCriticalSection(&g_csB);
hThreadA = CreateThread(NULL, 0, SellTicketA, NULL, 0, NULL); //2
hThreadB = CreateThread(NULL, 0, SellTicketB, NULL, 0, NULL); //2
CloseHandle(hThreadA); //1
CloseHandle(hThreadB); //1
Sleep(1000);
DeleteCriticalSection(&g_csA);
DeleteCriticalSection(&g_csB);//删除临界区
system("pause");
return 0;
}
假如你的代码在多线程执行和单线程执行永远是完全一样的结果,那么你的代码是线程安全的。
就是个多线程的群聊+互斥锁
//多线程+socket编程的一个联合使用
//用互斥体进行线程同步 socket编程 临界区 全局变量
#include <WinSock2.h>
#include <iostream>
#include <windows.h>
#include <process.h>
#pragma comment(lib, "ws2_32.lib")
#define MAX_CLNT 256
#define MAX_BUF_SIZE 256
//所有的连接的客户端socket
SOCKET clntSocks[MAX_CLNT];
HANDLE hMutex;
int clntCnt = 0; //clntSocks[MAX_CLNT]里的变量
//当前连接的数目
// 服务端的设计:
// 1 每来一个连接,服务端起一个线程(安排一个工人)维护
// 2 将收到的消息转发给所有的客户端
// 3 某个连接断开,需要处理断开的连接
//发送给所有的客户端
void SendMsg(char *szMsg, int iLen)
{
int i = 0;
WaitForSingleObject(hMutex, INFINITE);
for (i = 0; i < clntCnt; i++)
{
send(clntSocks[i], szMsg, iLen, 0);
}
ReleaseMutex(hMutex);
}
//子线程中处理客户端连接的函数
unsigned WINAPI HandleCln(void* arg)
{
//1 接收传递过来的参数
SOCKET hClntSock = *((SOCKET*)arg);
int iLen = 0, i;
char szMsg[MAX_BUF_SIZE] = { 0 };
//2 进行数据的收发 循环接收
//接收到客户端的数据
while(1)
{
iLen = recv(hClntSock, szMsg, sizeof(szMsg), 0);
if (iLen != -1)
{
//收到的数据立马发给所有的客户端
SendMsg(szMsg, iLen);
}
else
{ // ilen == -1 说明客户端断开
break;
}
}
printf("此时连接数目为 %d\n", clntCnt);
//3 某个连接断开,需要处理断开的连接 遍历
WaitForSingleObject(hMutex, INFINITE);
for (i = 0; i<clntCnt; i++)
{
if (hClntSock == clntSocks[i]) { //移位
while (i++ < clntCnt)
{
clntSocks[i] = clntSocks[i+1];
}
break;
}
}
clntCnt--; //当前连接数的一个自减
printf("断开此时连接数目 %d", clntCnt);
ReleaseMutex(hMutex);
closesocket(hClntSock);
return 0;
}
int main()
{
// 加载套接字库
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;
}
//创建一个互斥对象
hMutex = CreateMutex(NULL, FALSE, NULL);
// 新建套接字
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);
// 绑定套接字到本地IP地址,端口号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");
SOCKADDR_IN addrCli;
int len = sizeof(SOCKADDR);
while (1)
{
// 接收客户连接 sockConn此时来的客户端连接
SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrCli, &len);
//每来一个连接,服务端起一个线程(安排一个工人)维护客户端的连接
//每来一个连接,全局数组应该加一个成员,最大连接数加1
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;
}
// 1 接收服务端的消息 安排一个工人 起一个线程接收消息
// // 2 发送消息给服务端 安排一个工人 起一个线程发送消息
// // 3 退出机制
#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] = "[DEFAULT]";
char szMsg[BUF_SIZE];
//发送消息给服务端
unsigned WINAPI SendMsg(void* arg) {
//1 接收传递过来的参
SOCKET hClntSock = *((SOCKET*)arg);
char szNameMsg[NAME_SIZE + BUF_SIZE];
//又有名字,又有消息
// //循环接收来自于控制台的消息
while (1) {
fgets(szMsg, BUF_SIZE, stdin); //阻塞在这一句 //退出机制 当收到q或Q 退出
if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n"))
{
closesocket(hClntSock);
exit(0);
}
sprintf(szNameMsg, "%s %s", szName, szMsg);//字符串拼接
send(hClntSock, szNameMsg, strlen(szNameMsg), 0);//发送
}return 0;
}//接收服务端的消息
unsigned WINAPI RecvMsg(void* arg) {
//1 接收传递过来的参数
SOCKET hClntSock = *((SOCKET*)arg);
char szNameMsg[NAME_SIZE + BUF_SIZE]; //又有名字,又有消息
int iLen = 0; while (1)
{ //recv阻塞
iLen = recv(hClntSock, szNameMsg, NAME_SIZE + BUF_SIZE - 1, 0); //服务端断开
if (iLen == -1)
{
return -1;
}// szNameMsg的0到iLen -1 都是收到的数据 iLen个
szNameMsg[iLen] = 0; //接收到的数据输出到控制台
fputs(szNameMsg, stdout);
}
return 0;
}// 带参数的main函数,用命令行启动 在当前目录按下shift + 鼠标右键 cmd
int main(int argc, char *argv[]) {
// 加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
int err;
SOCKET hSock;
SOCKADDR_IN servAdr;
HANDLE hSendThread, hRecvThread;
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;
}
sprintf(szName, "[%s]", argv[1]);
//1 建立socket
hSock = socket(PF_INET, SOCK_STREAM, 0);
// 2 配置端口和地址
memset(&servAdr, 0, sizeof(servAdr));
servAdr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
servAdr.sin_family = AF_INET;
servAdr.sin_port = htons(9190);
// 3 连接服务器
if (connect(hSock, (SOCKADDR*)&servAdr, sizeof(servAdr)) == SOCKET_ERROR)
{
printf("connect error error code = %d\n",GetLastError());
return -1;
}
// 4 发送服务端的消息 安排一个工人 起一个线程发送消息
hSendThread = (HANDLE)_beginthreadex(NULL, 0, SendMsg, (void*)&hSock, 0, NULL);
// 5 接收消息给服务端 安排一个工人 起一个线程接收消息
hRecvThread = (HANDLE)_beginthreadex(NULL, 0, RecvMsg, (void*)&hSock, 0, NULL);
//等待内核对象的信号发生变化
WaitForSingleObject(hSendThread, INFINITE);
WaitForSingleObject(hRecvThread, INFINITE);
// 6 关闭套接字
closesocket(hSock);
WSACleanup();
return 0;
}