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

文章目录

创建线程

window创建线程的方法有两种

方法一_beginthreadex

函数讲解

cpp 复制代码
所在头文件:
#include<process.h>

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

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

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

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

使用示例:

cpp 复制代码
#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

函数讲解:

cpp 复制代码
//函数原型
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的可能取值

使用示例:

cpp 复制代码
#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

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

使用示例:

cpp 复制代码
#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函数将互斥事件对象设为有信号状态,以允许其他线程访问共享资源。如果没有其他线程在等待互斥事件对象,它将保持有信号状态。

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

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

cpp 复制代码
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 个,直到达到最大资

源数量。

信号量的相关函数

cpp 复制代码
//创建信号量
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的时候会立即返回同时该信号量对象的资源计数值减一

使用示例

cpp 复制代码
#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;
}

关键段

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

相关函数

cpp 复制代码
初始化:
InitializeCriticalSection(CRITICAL_SECTION *cs);

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

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

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



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

错误使用示例

cpp 复制代码
#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判断的时候还没有进入关键段

所以这里两个线程可能同时判断了然后都进入了循环

正确使用示例

cpp 复制代码
#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多线程群聊服务器(带客户端)

服务端代码

cpp 复制代码
#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;
}

客户端代码

cpp 复制代码
#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;
}

效果展示

相关推荐
德迅--文琪7 分钟前
SCDN是服务器吗?SCDN防御服务器有什么特点?
运维·服务器
z2023050818 分钟前
linux 之0号进程、1号进程、2号进程
linux·运维·服务器
秋已杰爱39 分钟前
HTTP中的Cookie与Session
服务器·网络协议·http
Happy鱿鱼1 小时前
C语言-数据结构 有向图拓扑排序TopologicalSort(邻接表存储)
c语言·开发语言·数据结构
KBDYD10101 小时前
C语言--结构体变量和数组的定义、初始化、赋值
c语言·开发语言·数据结构·算法
LWDlwd05251 小时前
shell指令及笔试题
c语言
Crossoads1 小时前
【数据结构】排序算法---桶排序
c语言·开发语言·数据结构·算法·排序算法
偷偷小野猪1 小时前
想要自动删除浏览器历史记录吗?这样设置就对了
windows·edge浏览器
code bean1 小时前
【C#基础】函数传参大总结
服务器·开发语言·c#
shelby_loo1 小时前
通过 Docker 部署 WordPress 服务器
服务器·docker·容器