windows编程之线程同步万字总结(创建线程,互斥对象,互斥事件,信号量,关键段,多线程群聊服务器)

news2024/12/23 9:43:52

文章目录

    • 创建线程
    • 方法一_beginthreadex
      • 函数讲解
      • 使用示例:
    • 方法二CreateThread
      • 函数讲解:
      • 使用示例:
    • 互斥对象:
      • 创建互斥对象CreateMutex
    • 互斥事件
      • 介绍
      • 创建或打开一个未命名的互斥事件对象
    • 信号量
      • 介绍
      • 信号量的相关函数
      • 使用示例
    • 关键段
      • 相关函数
      • 错误使用示例
      • 正确使用示例
    • 综合demo多线程群聊服务器(带客户端)
      • 服务端代码
      • 客户端代码
      • 效果展示

创建线程

window创建线程的方法有两种

方法一_beginthreadex

函数讲解

所在头文件:
#include<process.h>

函数原型:
uintptr_t _beginthreadex( 
   void *security,//指向 SECURITY_ATTRIBUTES 结构的指针,此结构确定返回的句柄是否由子进程继承。 如果 Security 为 NULL,则无法继承句柄。
   unsigned stack_size,//新线程的堆栈大小或 0。
   unsigned ( __clrcall *start_address )( void * ),//启动开始执行新线程的例程的地址。(也就是线程函数的地址)
   void *arglist,/要传递到新线程的参数列表或 NULLunsigned initflag,//控制新线程的初始状态的标志。 将 initflag 设置为 0 以立即运行,或设置为 CREATE_SUSPENDED 以在挂起状态下创建线程
   unsigned *thrdaddr//指向接收线程标识符的 32 位变量。 如果是 NULL,则不使用它。
);

线程函数示例:
unsigned WINAPI 函数名称(void*arg)
{
//参数不能掉
    .........
    return 0;
}

//获取创建的线程的句柄
HANDLE threadHandle=(HANDLE)_beginthreadex(.....);

//_beginthreadex()创建的线程会自动关闭线程句柄

使用示例:

#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
#include<process.h>
unsigned WINAPI thread_01(void*arg)
{
	int cnt = *((int*)arg);
	for (int i = 0; i < cnt; i++)
	{
		printf_s("thread_01\n");
		Sleep(1000);
	}
	return 0;
}

unsigned WINAPI thread_02(void* arg)
{
	int cnt = *((int*)arg);
	for (int i = 0; i < cnt; i++)
	{
		printf_s("thread_02\n");
		Sleep(1000);
	}
	return 0;

}

unsigned WINAPI thread_03(void* arg)
{
	int cnt = *((int*)arg);
	for (int i = 0; i < cnt; i++)
	{
		printf_s("thread_03\n");
		Sleep(1000);
	}
	return 0;

}

int main()
{
	int a = 10, b = 7, c = 4;
	HANDLE thread1=(HANDLE)_beginthreadex(NULL,0,&thread_01,(void*)&a,0,NULL);
	HANDLE thread2=(HANDLE)_beginthreadex(NULL, 0, &thread_02, (void*)&b, 0, NULL);
	HANDLE thread3=(HANDLE)_beginthreadex(NULL, 0, &thread_03, (void*)&c, 0, NULL);
	WaitForSingleObject(thread1);//主线程等待thread1执行完  
    WaitForSingleObject(thread2);
	WaitForSingleObject(thread3);
	//这里要注意WaitForSingleObject(thread1);并不会阻塞 WaitForSingleObject(thread2);
	//WaitForSingleObject(thread3);这两句的执行
	system("pause");
	return 0;
}

演示效果:
在这里插入图片描述

方法二CreateThread

函数讲解:

//函数原型
HANDLE CreateThread(
  [in, optional]  LPSECURITY_ATTRIBUTES   lpThreadAttributes,//指向 SECURITY_ATTRIBUTES 结构的指针,该结构确定是否可由子进程继承返回的句柄。 如果 lpThreadAttributes 为 NULL,则无法继承句柄。
  [in]            SIZE_T                  dwStackSize,//堆栈的初始大小(以字节为单位)。 系统将此值舍入到最近的页面。 如果此参数为零,则新线程将使用可执行文件的默认大小。 
  [in]            LPTHREAD_START_ROUTINE  lpStartAddress,//指向要由线程执行的应用程序定义函数的指针。 此指针表示线程的起始地址。
  [in, optional]  __drv_aliasesMem LPVOID lpParameter,//指向要传递给线程的变量的指针。
  [in]            DWORD                   dwCreationFlags,//控制线程创建的标志。
  [out, optional] LPDWORD                 lpThreadId//指向接收线程标识符的变量的指针。 如果此参数为 NULL,则不返回线程标识符。
);

//参数dwCreationFlags的可能取值
在这里插入图片描述

使用示例:

#include<windows.h>
#include<stdlib.h>
#include<stdio.h>
#include<process.h>
unsigned WINAPI thread_01(void*arg)
{
	int cnt = *((int*)arg);
	for (int i = 0; i < cnt; i++)
	{
		printf_s("thread_01\n");
		Sleep(1000);
	}
	return 0;
}

unsigned WINAPI thread_02(void* arg)
{
	int cnt = *((int*)arg);
	for (int i = 0; i < cnt; i++)
	{
		printf_s("thread_02\n");
		Sleep(1000);
	}
	return 0;

}

unsigned WINAPI thread_03(void* arg)
{
	int cnt = *((int*)arg);
	for (int i = 0; i < cnt; i++)
	{
		printf_s("thread_03\n");
		Sleep(1000);
	}
	return 0;

}

DWORD  WINAPI thread_04 (LPVOID p)
{
	int cnt = *((int*)p);
	for (int i = 0; i < cnt; i++)
	{
		printf_s("我是线程%d\n", GetCurrentThreadId());
		Sleep(1000);
	}
	return 0;
}


int main()
{

	int a = 10, b = 7, c = 4,d=8;
	//方法一
		HANDLE thread1=(HANDLE)_beginthreadex(NULL,0,&thread_01,(void*)&a,0,NULL);
	HANDLE thread2=(HANDLE)_beginthreadex(NULL, 0, &thread_02, (void*)&b, 0, NULL);
	HANDLE thread3=(HANDLE)_beginthreadex(NULL, 0, &thread_03, (void*)&c, 0, NULL);
	/*
	当使用 _beginthreadex 创建线程时,它返回一个线程句柄(HANDLE),
	该句柄可以用于对线程进行操作和控制。这个句柄是由 _beginthreadex 内部创建并分配的。
    在线程运行结束后,系统会自动关闭线程句柄,并释放与之相关的资源。
	*/
	//方法二
	HANDLE thread4 = CreateThread(NULL,0,thread_04,(void*)&d,0,NULL);
	CloseHandle(thread4);//关闭线程的句柄释放相关的操作系统资源
	WaitForSingleObject(thread1,INFINITE);//主线程等待thread1执行完  
    WaitForSingleObject(thread2,INFINITE);
	WaitForSingleObject(thread3,INFINITE);
	WaitForSingleObject(thread4,INFINITE);
	system("pause");
	return 0;
}

演示效果:
在这里插入图片描述

互斥对象:

ps:你也可以理解为互斥锁

创建互斥对象CreateMutex

HANDLE CreateMutex(
  [in, optional] LPSECURITY_ATTRIBUTES lpMutexAttributes,//指向 SECURITY_ATTRIBUTES 结构的指针。 如果此参数为 NULL,则子进程无法继承句柄。
  [in]           BOOL                  bInitialOwner,//如果此值为 TRUE 并且调用方创建了互斥体,则调用线程获取互斥对象的初始所有权。 否则,调用线程不会获得互斥体的所有权。 若要确定调用方是否创建了互斥体,请参阅返回值部分。
  [in, optional] LPCWSTR               lpName//互斥对象的名称。 名称限制为 MAX_PATH 个字符。 名称比较区分大小写。
);

使用示例:

#include<windows.h>
#include<stdio.h>
#include<stdlib.h>
#include<process.h>
int num = 50000;
HANDLE hMutex;
unsigned WINAPI thread_01(void* arg)
{
	WaitForSingleObject(hMutex,INFINITE);//阻塞直到拿到锁
	for (int i = 0; i < 5000; i++)
	{
		--num;
	}
	ReleaseMutex(hMutex);//释放锁
	return 0;
}


unsigned WINAPI thread_02(void* arg)
{
	WaitForSingleObject(hMutex, INFINITE);

	for (int i = 0; i < 5000; i++)
	{
		++num;
	}
	return 0;
}
int main()
{
	HANDLE tHandles[20];
	hMutex = CreateMutex(NULL, FALSE, NULL);//创建互斥对象
	for (int i = 0; i < 20; i++)
	{
		if (i % 2 == 0)
			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, thread_01, NULL, 0, NULL);
		else
			tHandles[i] = (HANDLE)_beginthreadex(NULL, 0, thread_02, NULL, 0, NULL);
	}
	WaitForMultipleObjects(20, tHandles, TRUE, INFINITE);
	CloseHandle(hMutex);
	printf("num=%d\n", num);//50000
	system("pause");
	return 0;
}

效果演示:
在这里插入图片描述

互斥事件

介绍

Windows中的互斥事件对象是一种同步对象,用于多个线程之间的互斥访问共享资源。它被用作一种机制,确保在任意时刻只有一个线程能够访问被保护的资源。
互斥事件对象有两个状态:有信号(signaled)和无信号(nonsignaled)。当互斥事件对象处于有信号状态时,表示资源可供访问,其他线程可以继续执行;当互斥事件对象处于无信号状态时,表示资源正在被其他线程使用,当前线程需要等待。

以下是使用互斥事件对象的一般流程:
1、创建互斥事件对象:使用CreateEvent函数创建一个互斥事件对象,并指定其初始状态。
2、等待互斥事件对象:使用WaitForSingleObject或WaitForMultipleObjects函数等待互斥事件对象。如果互斥事件对象处于无信号状态,当前线程会被阻塞,直到互斥事件对象变为有信号状态。
3、访问共享资源:互斥事件对象变为有信号状态后,当前线程可以访问共享资源。在访问完毕后,需要释放互斥事件对象。
4、释放互斥事件对象:使用SetEvent函数将互斥事件对象设为有信号状态,以允许其他线程访问共享资源。如果没有其他线程在等待互斥事件对象,它将保持有信号状态。

互斥事件对象的使用可以有效避免多个线程同时访问共享资源而导致的数据竞争和不一致性问题。

创建或打开一个未命名的互斥事件对象

HANDLE CreateEventA(
  [in, optional] LPSECURITY_ATTRIBUTES lpEventAttributes,//指向 SECURITY_ATTRIBUTES 结构的指针。 如果此参数为 NULL,则子进程无法继承句柄。 安全描述符
  [in]           BOOL                  bManualReset,//如果此参数为 TRUE,则函数将创建手动重置事件对象,该对象需要使用 ResetEvent 函数将事件状态设置为非签名。 如果此参数为 FALSE,则函数将创建一个自动重置事件对象,在释放单个等待线程后,系统会自动将事件状态重置为未签名。
  [in]           BOOL                  bInitialState,//如果此参数为 TRUE,则会向事件对象发出初始状态信号;否则,它将不进行签名。
  [in, optional] LPCSTR                lpName//事件对象的名称。 名称限制为 MAX_PATH 个字符。 名称比较区分大小写。
);

/*
如果函数成功,则返回值是事件对象的句柄。
如果命名事件对象在函数调用之前存在,则函数将返回现有对象的句柄,
GetLastError 将返回 ERROR_ALREADY_EXISTS。
如果函数失败,则返回值为 NULL。 要获得更多的错误信息,请调用 GetLastError。
*/

SetEvent(Handle event);//将事件对象event设置为有信号状态,这相当于还没线程拿到这把锁
ResetEvent(Handle event);//将事件对象重置为无信号状态

信号量

介绍

触发状态(有信号状态),表示有可用资源。
未触发状态(无信号状态),表示没有可用资源

信号量的组成
①计数器:该内核对象被使用的次数
②最大资源数量:标识信号量可以控制的最大资源数量(带符号的 32 位)
③当前资源数量:标识当前可用资源的数量(带符号的 32 位)。即表示当前
开放资源的个数(注意不是剩下资源的个数),只有开放的资源才能被线程所申
请。但这些开放的资源不一定被线程占用完。比如,当前开放 5 个资源,而只有
3 个线程申请,则还有 2 个资源可被申请,但如果这时总共是 7 个线程要使用信
号量,显然开放的资源 5 个是不够的。这时还可以再开放 2 个,直到达到最大资
源数量。

信号量的相关函数

//创建信号量
HANDLE WINAPI CreateSemaphoreW(
_In_opt_ LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // Null 安全属性
_In_ LONG lInitialCount, //初始化时,共有多少个资源是可以用的。 0:未触发状//态(无信号
状态),表示没有可用资源
_In_ LONG lMaximumCount, //能够处理的最大的资源数量 3
_In_opt_ LPCWSTR lpName //NULL 信号量的名称
);

//增加信号量

WINAPI ReleaseSemaphore(
_In_ HANDLE hSemaphore, //信号量的句柄
_In_ LONG lReleaseCount, //将lReleaseCount值加到信号量的当前资源计数上面 0-> 1
_Out_opt_ LPLONG lpPreviousCount //当前资源计数的原始值
);


 WaitForSingleObject(信号量对象的句柄, INFINITE) 
当这个函数检测到信号量对象的资源计数大于0的时候会立即返回同时该信号量对象的资源计数值减一

使用示例

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

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

关键段

这个和互斥事件,互斥对象差不多

相关函数

初始化:
InitializeCriticalSection(CRITICAL_SECTION *cs);

进入关键段:
EnterCriticalSection(&cs);

离开关键段:
LeaveCriticalSection(&cs);

删除关键段:
DeleteCriticalSection(&cs);//删除临界区



进入关键段之后到离开关键段之前的所有资源都被保证同一时刻只允许一个线程访问

错误使用示例

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

CRITICAL_SECTION cs;
static int ticketsNum = 200;
unsigned WINAPI thread_01(void*arg)
{
;
	//开始卖票
	while (ticketsNum > 0)
	{
		//进入关键段
		EnterCriticalSection(&cs);
		printf_s("窗口一卖出一张票,还剩%d张票\n", --ticketsNum);
		//离开关键段
		LeaveCriticalSection(&cs);
	}
	return 0;
}

unsigned WINAPI thread_02(void*Arg)
{
	;
	//开始卖票
	while (ticketsNum > 0)
	{
		//进入关键段
		EnterCriticalSection(&cs);
		printf_s("窗口二卖出一张票,还剩%d张票\n", --ticketsNum);
		//离开关键段
		LeaveCriticalSection(&cs);
	}
	return 0;
}

int main()
{
	HANDLE thread1, thread2;
	InitializeCriticalSection(&cs);
	thread1 = (HANDLE)_beginthreadex(NULL, 0, thread_01, NULL, 0, NULL);
	thread2 = (HANDLE)_beginthreadex(NULL, 0, thread_02, NULL, 0, NULL);
	WaitForSingleObject(thread1,INFINITE);
	WaitForSingleObject(thread2, INFINITE);
	CloseHandle(thread1);
	CloseHandle(thread2);
	DeleteCriticalSection(&cs);//删除临界区
	printf_s("票卖完了\n");
	system("pause");
	return 0;
}

错误效果演示:
在这里插入图片描述
为什么会多卖出一张票呢,因为这里对于循环条件ticketsNum > 0判断的时候还没有进入关键段
所以这里两个线程可能同时判断了然后都进入了循环

正确使用示例

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

CRITICAL_SECTION cs;
static int ticketsNum = 200;
unsigned WINAPI thread_01(void* arg)
{
    ;
    //开始卖票
    for (;;)
        {
            //进入关键段
            EnterCriticalSection(&cs);
            if (ticketsNum <= 0)
                break;
            printf_s("窗口一卖出一张票,还剩%d张票\n", --ticketsNum);
            //离开关键段
            LeaveCriticalSection(&cs);
        }
    return 0;
}

unsigned WINAPI thread_02(void* Arg)
{
    ;
    //开始卖票
    for (;;)
        {
            //进入关键段
            EnterCriticalSection(&cs);
            if (ticketsNum <= 0)
                break;
            printf_s("窗口二卖出一张票,还剩%d张票\n", --ticketsNum);
            //离开关键段
            LeaveCriticalSection(&cs);
        }
    return 0;
}

int main()
{
    HANDLE thread1, thread2;
    InitializeCriticalSection(&cs);
    thread1 = (HANDLE)_beginthreadex(NULL, 0, thread_01, NULL, 0, NULL);
    thread2 = (HANDLE)_beginthreadex(NULL, 0, thread_02, NULL, 0, NULL);
    WaitForSingleObject(thread1, INFINITE);
    WaitForSingleObject(thread2, INFINITE);
    CloseHandle(thread1);
    CloseHandle(thread2);
    DeleteCriticalSection(&cs);//删除临界区
    printf_s("票卖完了\n");
    system("pause");
    return 0;
}

效果演示:
在这里插入图片描述

综合demo多线程群聊服务器(带客户端)

服务端代码

#include<stdio.h>
#include<windows.h>
#include<process.h>
#pragma comment(lib,"ws2_32.lib")	//库文件

#define MAX_CLIENT   256//最大连接数
#define MAX_BUF_SIZE 1024//发送数据最大大小
SOCKET clients[MAX_CLIENT];//客户端连接队列
int clientNum = 0;//客户端连接数
HANDLE hMutex;//互斥对象

void SendMsg(char* msg,int iLen)
{
	WaitForSingleObject(hMutex, INFINITE);

	//转发消息
	for (int i = 0; i < clientNum; i++)
	{
		send(clients[i], msg, iLen, 0);
	}
	ReleaseMutex(hMutex);
}
unsigned WINAPI HandleCln(void* arg)
{
	//线程处理函数
	SOCKET Client = *((SOCKET*)arg);
	int iLen = 0;
	char szMsg[MAX_BUF_SIZE] = { 0 };
	for(;;)
	{
		iLen = recv(Client, szMsg, sizeof(szMsg), 0);
		if (iLen != -1)
		{
			SendMsg(szMsg, iLen);
		}
		else
		{
			break;
		}
	}
	//处理下线过程
	WaitForSingleObject(hMutex, INFINITE);
	for (int i = 0; i < clientNum; i++)
	{
		if (clients[i] == Client)
		{
			while (i++ < clientNum)
			{
				clients[i] = clients[i + 1];
		    }
			clientNum--;
			printf_s("当前连接客户端数:%d\n",clientNum);
			break;
		}
	}
	ReleaseMutex(hMutex);
	return 0;
}
int main()
{
	/*******************初始化服务器*******************/
	//初始化网络库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 2);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		printf_s("WSAStartup errorNum = % d\n", GetLastError());
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		printf_s("LOBYTE errorNum=%d\n", GetLastError());
		WSACleanup();
	}
    
	//创建服务器套接字
	SOCKET serverSocket = 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(6000);

	//绑定
	if (SOCKET_ERROR == bind(serverSocket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
		printf_s("bind error=%d\n", GetLastError());
		return -1;
	}
	//创建互斥对象
	hMutex = CreateMutex(NULL, FALSE, NULL);

	//监听

	if (SOCKET_ERROR == listen(serverSocket, MAX_CLIENT))//最大连接/监听数为5
	{
		printf_s("listen error=%d\n", GetLastError());
		return -1;
	}

	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR_IN);
	HANDLE hThread;
	for(;;)
	{
		//接收客户端连接
		SOCKET sockConnect = accept(serverSocket, (SOCKADDR*)&addrClient, &len);
		//放入队列
		WaitForSingleObject(hMutex, INFINITE);
		clients[clientNum++] = sockConnect;
		ReleaseMutex(hMutex);
		hThread = (HANDLE)_beginthreadex(NULL,0,&HandleCln,(void*)&sockConnect,0,NULL);
		printf_s("connect client IP=%s   clientNum=%d\n",inet_ntoa(addrClient.sin_addr),clientNum);
	}
	WSACleanup();

	return 0;
}

客户端代码

#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<process.h>
#pragma comment(lib,"ws2_32.lib")	//库文件
#define MAX_SIZE 1024 //收发消息的最大长度
#define MAX_NAME_SIZE 32//昵称最大长度 

HANDLE hSend;//发送线程
HANDLE hAceept;//接收线程
char nickName[MAX_NAME_SIZE];//昵称

unsigned WINAPI sendMsg(void* arg)
{
	//发送消息给服务端
	SOCKET ClientSocket = *((SOCKET*)arg);
	char msg[MAX_SIZE] = { 0 };
	char name_msg[MAX_SIZE + MAX_NAME_SIZE] = { 0 };
	for (;;)
	{
		memset(msg, 0, MAX_SIZE);
		//阻塞在这一句直到接收到控制台输入
		fgets(msg, MAX_SIZE, stdin);
		if (!strcmp(msg, "Q\n") || !strcmp(msg, "q\n"))
		{
			//客户端口下线
			closesocket(ClientSocket);
			exit(0);
		}
		//将消息拼包发送给服务器
		sprintf(name_msg, "%s :%s", nickName,msg);
		send(ClientSocket, name_msg, strlen(name_msg), 0);
	}
	return 0;
}

unsigned WINAPI  acceptMsg(void* arg)
{
	//接收服务端的消息
	SOCKET ClientSocket = *((SOCKET*)arg);
	char name_msg[MAX_SIZE + MAX_NAME_SIZE] = { 0 };
	int iLen = 0;
	for (;;)
	{
		memset(name_msg, 0, MAX_NAME_SIZE + MAX_SIZE);
		iLen = recv(ClientSocket, name_msg, sizeof(name_msg),0);
		if (iLen == -1)
		{
			return 2;
		}
		name_msg[iLen] = 0;
		//将接收到的消息输出到控制台
		fputs(name_msg, stdout);

	}
	return 0;
}
int main()
{
	//初始化网络库
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;
	wVersionRequested = MAKEWORD(2, 2);
	err = WSAStartup(wVersionRequested, &wsaData);
	if (err != 0)
	{
		printf_s("WSAStartup errorNum = % d\n", GetLastError());
	}
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
	{
		printf_s("LOBYTE errorNum=%d\n", GetLastError());
		WSACleanup();
	}

	//安装电话机
	SOCKET ClientSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == ClientSocket)
	{
		printf_s("socket error=%d\n", GetLastError());
		return -1;
	}

	//配置要连接的服务器
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);

	//连接服务器
	if (SOCKET_ERROR == connect(ClientSocket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
		printf_s("connect error=%d\n", GetLastError());
		return -1;
	}
	//输入昵称
	printf_s("请输入您的昵称:");
	scanf("%s", nickName);
	//清空输入缓冲区
	while (getchar() != '\n') {
		continue;
	}
	//开启发送接收线程
	hSend = (HANDLE)_beginthreadex(NULL, 0, &sendMsg, (void*)&ClientSocket, 0, NULL);
	hAceept = (HANDLE)_beginthreadex(NULL, 0, &acceptMsg, (void*)&ClientSocket, 0, NULL);
	//主线程等待
	WaitForSingleObject(hSend, INFINITE);
	WaitForSingleObject(hAceept, INFINITE);
	
	WSACleanup();
	printf_s("退出");
	return 0;
}

效果展示

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/972995.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vite介绍

vite vite是一种新的前端构建工具&#xff0c;vite借助了浏览器对ESM的支持&#xff0c;采用和传统webpack打包完全不一致的unbundle打包机制&#xff1b; vite的快主要体现在两个方面&#xff0c;快速的冷启动和快速的热更新 快速的冷启动&#xff1a;vite只需启动一台静态页…

如何将Express项目部署到Vercel

什么是Vercel&#xff1f; 想必好多前端同学都知道Vercel吧&#xff01;如果还不了解的同学也没关系&#xff0c;好好看这篇文章&#xff0c;认识认识Vercel&#xff0c;我想对你部署项目有一定帮助。 Vercel 是一个云平台&#xff0c;用于托管和部署静态网站、前端应用程序以…

2023年无形资产评估研究报告

第一章 无形资产概况 1.1 定义 无形资产是一种缺乏物质实体的资产。例如&#xff0c;专利、版权、特许权、商誉、商标和商号&#xff0c;以及软件等。这与物质资产&#xff08;如机器、建筑等&#xff09;和金融资产&#xff08;如政府证券等&#xff09;形成了对比。无形资产…

Mybatis 动态SQL – 使用choose标签动态生成条件语句

之前我们介绍了if,where标签的使用&#xff1b;本篇我们需要在if,where标签的基础上介绍如何使用Mybatis提供的choose标签动态生成条件语句。 如果您对if,where标签动态生成条件语句不太了解&#xff0c;建议您先进行了解后再阅读本篇&#xff0c;可以参考&#xff1a; Mybat…

将序数与比特币智能合约集成:第 1 部分

将序数与比特币智能合约集成&#xff1a;第 1 部分 最近&#xff0c;比特币序数在区块链领域引起了广泛关注。 据称&#xff0c;与以太坊 ERC-721 等其他代币标准相比&#xff0c;Ordinals 的一个主要缺点是缺乏对智能合约的支持。 我们展示了如何向 Ordinals 添加智能合约功…

插入排序,选择排序,交换排序,归并排序和非比较排序(C语言版)

前言 所谓排序&#xff0c;就是将一组数据按照递增或者递减的方式进行排列&#xff0c;让这组数据变得有序起来。排序在生活中运用的是十分广泛的&#xff0c;各行各业都用到了排序&#xff0c;比如我们在网购的时候就是按照某种排序的方式来选择东西的。所以去了解排序的实现也…

vue 使用qrcode生成二维码并可下载保存

安装qrcode npm install qrcode --save代码 <template><div style"display: flex; flex-direction: column; align-items: center; justify-content center;"><div>查看溯源码&#xff0c;<a id"saveLink" style"text-decorati…

Ae 效果:CC Glue Gun

生成/CC Glue Gun Generate/CC Glue Gun CC Glue Gun&#xff08;CC 胶水枪&#xff09;可以用于生成仿佛由胶水枪绘制的线条或图案&#xff0c;它模拟了胶水枪绘制在不同表面上的纹理和反光效果。 CC Glue Gun 效果实质上是通过设置画笔笔触的位置来构成画笔描边路径&#xff…

ModaHub魔搭社区专访百度智能云李莅:向量数据库市场会不会更卷?

ModaHub魔搭社区:在当今的信息化时代,数据库技术已经渗透到了我们生活的各个角落。传统的关系型数据库在市场上的竞争已经非常激烈,据统计,市面上有数百种不同类型的数据库产品在竞争。那么,在未来,随着人工智能和大数据技术的发展,向量数据库市场会否也会陷入同样的激烈…

js中如何判断一个变量的数据类型?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐typeof 运算符⭐instanceof 运算符⭐Object.prototype.toString 方法⭐Array.isArray 方法⭐自定义类型检查⭐null 和 undefined 检查⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订…

ClickHouse进阶(七):Clickhouse数据查询-1

进入正文前&#xff0c;感谢宝子们订阅专题、点赞、评论、收藏&#xff01;关注IT贫道&#xff0c;获取高质量博客内容&#xff01; &#x1f3e1;个人主页&#xff1a;含各种IT体系技术,IT贫道_Apache Doris,大数据OLAP体系技术栈,Kerberos安全认证-CSDN博客 &#x1f4cc;订阅…

RK3568-i2c-适配8010rtc时钟芯片

硬件连接 从硬件原理图中可以看出&#xff0c;rtc时钟芯片挂载在i2c3总线上&#xff0c;设备地址需要查看芯片数据手册。编写设备树 &i2c3 {status "okay";rx8010: rx801032 {compatible "epson,rx8010";reg <0x32>;}; };使能驱动 /kernel/…

NPM 常用命令(三)

目录 1、npm compltion 1.1 描述 2、npm config 2.1 常用命令 2.2 描述 set get list delete edit fix 2.3 配置 json global editor location long 3、npm dedupe 3.1 描述 3.2 配置 4、npm deprecate 4.1 命令使用 4.2 描述 4.3 配置 registry ot…

【huggingface】数据集及模型下载并保存至本地

目录 数据集ChnSentiCorppeoples_daily_ner 模型bert-base-chinesehfl/rbt3t5-baseopus-mt-zh-enChinese_Chat_T5_Base 环境&#xff1a;没有代理&#xff0c;无法访问部分国外网络 数据集 正常情况下通过load_dataset加载数据集&#xff1b;save_to_disk保存至本地&#xff1b…

佳作导读 | 《C++ Core Guidelines》

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; 佳作导读 | 《C Core Guidelines》 《C Core Guidelines》由Bjarne Stroustrup和Herb Sutter等共同编写关于使用C编程语言的指南&#xff1b;旨在提供关于如何使用C进…

Linux常用命令——csplit命令

在线Linux命令查询工具 csplit 将一个大文件分割成小的碎片文件 补充说明 csplit命令用于将一个大文件分割成小的碎片&#xff0c;并且将分割后的每个碎片保存成一个文件。碎片文件的命名类似“xx00”&#xff0c;“xx01”。csplit命令是split的一个变体&#xff0c;split只…

如何在Win10系统上安装WSL(适用于 Linux 的 Windows 子系统)

诸神缄默不语-个人CSDN博文目录 本文介绍的方法不是唯一的安装方案&#xff0c;但在我的系统上可用。 文章目录 1. 视频版2. 文字版和代码3. 本文撰写过程中使用到的其他网络参考资料 1. 视频版 B站版&#xff1a;在Windows上安装Linux (WSL, 适用于 Linux 的 Windows 子系统…

【Rust 日报】2023-09-03 sudo-rs:sudo 和 su 的内存安全实现

sudo-rs&#xff1a;sudo 和 su 的内存安全实现 sudo-rs 项目通过以下方式改进了原始 sudo 的安全性&#xff1a; 使用内存安全语言&#xff08;Rust&#xff09;&#xff0c;因为估计原始 sudo 中三分之一的安全漏洞都与内存管理问题有关。省略不常用的功能&#xff0c;以减少…

记2个library cache lock故障case

第一个case 客户说晚上10点的时候做业务很卡&#xff0c;遂取对应时段awr 非常明显的library cache lock事件。这个事件是元数据锁&#xff0c;一旦泛滥对数据库的影响范围很大。 因此&#xff0c;他的泛滥第一时间应该想到会有大量持有元数据的动作。对sql进行检查 这个自动…

精益制造、质量管控,盛虹百世慧共同启动MOM(制造运营管理)

百世慧科技依托在电池智能制造行业中的丰富经验&#xff0c;与盛虹动能达成合作&#xff0c;为其提供MOM制造运营管理平台&#xff0c;并以此为起点&#xff0c;全面提升盛虹动能的制造管理水平与运营体系。 行业困境 中国动力电池已然发展为全球最大的电池产业&#xff0c;但…