首页 > 其他分享 >多线程网络实战之仿qq群聊的服务器和客户端

多线程网络实战之仿qq群聊的服务器和客户端

时间:2024-07-07 23:30:37浏览次数:24  
标签:qq 群聊 SOCKET int MAX 客户端 多线程 BUF SIZE

目录

一、前言

二、设计需求

1.服务器需求 

2.客户端需求

三、服务端设计

1.项目准备

 2.初始化网络库

3.SOCKET创建服务器套接字

4. bind 绑定套接字

 5. listen监听套接字

 6. accept接受客户端连接

7.建立套接字数组

8. 建立多线程与客户端通信

9. 处理线程函数,收消息

10. 发消息给客户端

 11.处理断开的客户端

四、客户端设计

1.项目准备

2. 处理main函数参数

 3.初始化网络库

4.SOCKET创建客户端套接字

5. 配置IP地址和端口号,连接服务器

6.创建两线程,发送和接收

 7.处理发送消息线程函数

五、项目运行

1.编译生成可执行文件

2.运行可执行程序

3.进行通讯

六、总代码展示

1.服务端代码:

2.客户端代码:

七、最后


一、前言

        今天我们不学习其他的知识点,主要是复习之前学习过的TCP网络通信和多线程以及线程同步互斥,然后结合这以上知识点设计实现一个小的项目,主要仿照qq群聊的服务器可客户端的实现,下面我将会说明一下设计需求,以下是整个设计示意图。

二、设计需求

1.服务器需求 

        需求一对于每一个上线连接的客户端,服务端会起一个线程去维护。        

        需求二:将服务器受到的消息转发给全部的客户端。例如:服务器接收客户端A的消息后,将立即发送给客户端A,B,C...

        需求三:当某个客户端断开(下线),需要处理断开的链接。

2.客户端需求

        需求一:请求连接上线,   

        需求二:发消息给服务器。

        需求三:客户端等待服务端的消息。

        需求四:等待用户自己的关闭(下线)。

三、服务端设计

1.项目准备

        在创建项目后,引入一些必需的头文件以及创建项目需要的宏,例如:允许客户端连接的最大数量,接收文件字节的大小,客户端连接的个数等等。

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


#define MAX_CLEN 256        // 最大连接数量
#define MAX_BUF_SIZE 1024   // 接收文件大小

SOCKET clnSockets[MAX_CLEN]; // 所有的连接客户端的socket
int clnCnt = 0;   // 客户端连接的个数

// 互斥的句柄
HANDLE hMutex; 
 2.初始化网络库

        WSAStartup初始化Winsock,这个函数用于初始化网络环境,都是固定写法,必须要有的,直接复制粘贴即可。

    // 1. 初始化库
    WSADATA wsaData;
	int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (stu != 0) {
		std::cout << "WSAStartup 错误:" << stu << std::endl;
		return 0;
	}
3.SOCKET创建服务器套接字

         这和我们之前学的windwos网络一样都是固定写法,重点时查看函数原型以及它的参数,代码如下:

	// 2. socket 创建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	if (sockSrv == INVALID_SOCKET)
	{
		std::cout << "socket failed!" << GetLastError() << std::endl;
		WSACleanup(); //释放Winsock库资源
		return 1;
	}
4. bind 绑定套接字

        这个流程主要是绑定服务器的IP地址,端口号,以及协议版本。 

	// 3 bind 绑定套接字
    SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   // 地址 IP地址any
	addrSrv.sin_family = AF_INET;    // ipv4协议
	addrSrv.sin_port = htons(6000);  // 端口号
	if ( SOCKET_ERROR == bind(sockSrv, (sockaddr*)&addrSrv, sizeof(SOCKADDR)))
	{
		std::cout << "bind failed!" << GetLastError() << std::endl;
		WSACleanup(); //释放Winsock库资源
		return 1;
	}
 5. listen监听套接字

        listen函数最重要的是理解它的第二个参数,为等待连接的最大队列长度 ,这个解释我有专门出过一篇文章windows网络进阶之listen参数含义

	// 4. 监听
	if (listen(sockSrv, 5) == SOCKET_ERROR) // 5 是指最大的监听数目,执行到listen
	{
		printf("listen error = %d\n", GetLastError());
		return -1;
	}
 6. accept接受客户端连接

        对于每一个被接受的连接请求,accept函数都会创建一个新的套接字,用于与该客户端的后续通信。也都是固定流程,后面互斥和多线程就比较难理解了。

    // 5. accept接受客户端连接
    SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);

	while (true)
	{
		// 接受客户端的连接
		SOCKET sockCon = accept(sockSrv, (sockaddr*)&addrCli, &len);
    }
7.建立套接字数组

        将accept生成的套接字放入全局套接字数组中,同时加上互斥锁。

    //创建一个互斥对象
    hMutex = CreateMutex(NULL, false, NULL);

    while (true)
	{
		// 接受客户端的连接
		SOCKET sockCon = accept(sockSrv, (sockaddr*)&addrCli, &len);
		// 全局变量要加锁
		WaitForSingleObject(hMutex, INFINITE);
		// 将连接放到数组里面
		clnSockets[clnCnt++] = sockCon;
		// 解锁
		ReleaseMutex(hMutex);
    }

	closesocket(sockSrv);
	CloseHandle(hMutex);
	WSACleanup();
	return 0;
8. 建立多线程与客户端通信

        每通过accept函数返回的新创建的套接字,就建立一个线程去维护。

//创建一个互斥对象
hMutex = CreateMutex(NULL, false, NULL);
while (true)
	{
		// 接受客户端的连接
		SOCKET sockCon = accept(sockSrv, (sockaddr*)&addrCli, &len);

		// 全局变量要加锁
		WaitForSingleObject(hMutex, INFINITE);
		// 将连接放到数组里面
		clnSockets[clnCnt++] = sockCon;
		// 解锁
		ReleaseMutex(hMutex);

		// 每接收一个客户端的连接,都安排一个线程去维护
		hThread = (HANDLE)_beginthreadex(NULL, 0, &handleCln, (void*)&sockCon, 0, NULL);

		printf("Connect client IP = %s\n, Num = %d \n", inet_ntoa(addrCli.sin_addr), clnCnt);
	}

	closesocket(sockSrv);
	CloseHandle(hMutex);
	WSACleanup();
	return 0;
9. 处理线程函数,收消息

        上个步骤我们对每一个接受连接的套接字都创建了线程,现在我们开始来写线程函数中的逻辑代码,主要有三个部分:收到客户端的消息,将收到的消息再发给所有客户端,处理断开的客户端。

        下面我们开始完成第一个部分: 收到客户端的消息。

        因为客户端发消息会不止一个,所以我们要建立while循环,通关判断接收到的消息来判断,如果为0就退出循环。

// 处理线程函数, 收发消息
unsigned WINAPI handleCln(void *arg)
{
	SOCKET hClnSock = *((SOCKET *)arg);
	int iLen = 0;
	char recvBuff[MAX_BUF_SIZE] = { 0 };
	
	while (1)
	{
		// iLen 成功时返回接收的字节数(收到EOF时为0),失败时返回SOCKETERROR。
		iLen = recv(hClnSock, recvBuff, MAX_BUF_SIZE, 0);
		// 
		if (iLen >= 0)
		{
			// 将收到的消息转发给所有客户端
			SendMsg(recvBuff,iLen);
		}
		else
		{
			break;
		}
	}
10. 发消息给客户端

        完成第二个部分: 将收到的消息再发给所有客户端

         因为是仿照qq的小demo,所以服务器一旦收到消息,就要再发送给所有的客户端。这段逻辑写在SendMsg 函数中,同时还需要注意因为在多线程中,所以要避免多个线程同时访问共享资源时产生数据不一致的问题,需要加互斥锁和解锁。

// 将收到的消息转发给所有客户端
void SendMsg(char* msg, int len)
{
	int i;
	WaitForSingleObject(hMutex, INFINITE);
	for (i = 0; i < clnCnt; i++)
	{
		send(clnSockets[i], msg, len, 0);
	}
	ReleaseMutex(hMutex);
}
 11.处理断开的客户端

        完成第三个部分: 处理断开的客户端。

        这里也是通过 for 循环遍历 socket 数组,通过匹配每一项,如果相匹配,就然后断开连接。同时  socket 数组 中的数量减 1。

// 处理消息, 收发消息
unsigned WINAPI handleCln(void *arg)
{
	SOCKET hClnSock = *((SOCKET *)arg);
	int iLen = 0;
	char recvBuff[MAX_BUF_SIZE] = { 0 };
	
	while (1)
	{
		// iLen 成功时返回接收的字节数(收到EOF时为0),失败时返回SOCKETERROR。
		iLen = recv(hClnSock, recvBuff, MAX_BUF_SIZE, 0);
		// 
		if (iLen >= 0)
		{
			// 将收到的消息转发给所有客户端
			SendMsg(recvBuff,iLen);
		}
		else
		{
			break;
		}
	}

	printf("此时连接的客户端数量 = %d\n", clnCnt);
	WaitForSingleObject(hMutex, INFINITE);
	for (int i = 0; i < clnCnt; i++)
	{
		// 找到哪个连接下线的,移除这个连接
		if (hClnSock == clnSockets[i])
		{
			while (i++ < clnCnt)
			{
				clnSockets[i] = clnSockets[i + 1];
			}

			break;
		}
	}
	// 断开连接减 1 
	clnCnt--;
	printf("断开连接后连接的客户端数量 = %d\n", clnCnt);
	ReleaseMutex(hMutex);
	// 断开连接
	closesocket(hClnSock);
	return 0;
}

四、客户端设计

1.项目准备

        客户端设计和服务器端其实差别不大,代码有些基本都相同,逻辑也大多一致,所以有些代码不在过多赘述。

        项目准备代码:

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

#define NAME_SIZE 256   
#define MAX_BUF_SIZE 1024

char szName[NAME_SIZE] = "[DEFAULT]"; //  默认的昵称
char szMsg[MAX_BUF_SIZE];    // 收发数据的大小
2. 处理main函数参数

        项目为仿qq群聊,所以我用main函数中的命令行参数作为我们输入的每一个客户端的名字,项目启动在终端开始启动,否则就退出程序。

int main(int argc, char* argv[])
{
	if (argc != 2)
	{
		printf("必须输入两个参数,包括昵称\n");
		printf("例如: WXS\n");
		system("pause");
		return -1;
	}
	sprintf_s(szName, "[%s]", argv[1]);
	printf("this is Client");
}
 3.初始化网络库

        和服务器端代码一样。

    // 初始化库
	WSADATA wsaData;
	int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (stu != 0) {
		std::cout << "WSAStartup 错误:" << stu << std::endl;
		return 0;
	}
4.SOCKET创建客户端套接字

        以服务器类似。 

    SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);

	if (sockCli == INVALID_SOCKET)
	{
		std::cout << "socket failed!" << GetLastError() << std::endl;
		WSACleanup(); //释放Winsock库资源
		return 1;
	}
5. 配置IP地址和端口号,连接服务器

        也是基本固定写法。

	// 配置IP地址 和 端口号
    SOCKADDR_IN addrSrv;
	addrSrv.sin_family = AF_INET;    // ipv4协议
	addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.7"); // 地址 IP地址any
	addrSrv.sin_port = htons(6000);  // 端口号

	// 连接服务器
	int res = connect(sockCli, (sockaddr*)&addrSrv, sizeof(sockaddr));
6.创建两线程,发送和接收

         这里我们创建了两个线程,分别处理发送消息给客户端同时接收消息。同时这个函数WaitForSingleObject 会阻塞主进程代码,直到子进程结束。

	// 定义两个线程 
	HANDLE hSendThread, hRecvThread;
    // 发送消息
	hSendThread = (HANDLE)_beginthreadex(NULL, 0, &SendMsg, (void*)&sockCli, 0, NULL);
	// 接收消息
	hRecvThread = (HANDLE)_beginthreadex(NULL, 0, &RecvMsg, (void*)&sockCli, 0, NULL);
    
    // 阻塞代码,处理子线程执行完后再执行
	WaitForSingleObject(hSendThread,INFINITE);
	WaitForSingleObject(hRecvThread, INFINITE);
 7.处理发送消息线程函数

        我们客户端发送消息是通过控制台程序进行发送的,所以要用到用户输入。同时发送的时候带上自己的名字前缀,也要处理快捷键客户端下线的逻辑,不能一致发送消息。

unsigned WINAPI SendMsg(void* arg)
{
	SOCKET hClnSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 };   // 昵称和消息

	while (1)
	{
		memset(szMsg, 0, MAX_BUF_SIZE);
		// 阻塞这一句,等待控制台的消息
		//fgets(szMsg, MAX_BUF_SIZE, stdin);
		// 第二种写法
		std::cin >> szMsg;
		if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n"))
		{
			// 处理下线
			closesocket(hClnSock);
			exit(0);
		}
		// 拼接  名字和字符串一起发送
		sprintf_s(szNameMsg, "%s %s", szName, szMsg);
		send(hClnSock, szNameMsg, strlen(szNameMsg) + 1, 0);
	}
}

 7.处理接收消息线程函数

        这里接收消息比较简单,和正常接收客户端消息的逻辑差不多,代码如下:

unsigned WINAPI RecvMsg(void* arg)
{
	SOCKET hClnSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 };   // 昵称和消息
	int len;
	while (1)
	{
		
		len = recv(hClnSock, szNameMsg, sizeof(szNameMsg), 0);
		if (len <= 0)
		{
			break;
			return -2;
		}
		szNameMsg[len] = 0;
		std::cout << szNameMsg << std::endl;
		// fputs(szNameMsg, stdout);
	}
}

五、项目运行

        以上我们分别讲解了服务器和客户端代码的实现逻辑,现在我们来进行步骤验证我们的操作结果。

1.编译生成可执行文件

        如图所示:

2.运行可执行程序

        这里要注意服务器直接运行exe文件即可,而客户端要通过命令行输入运行。

        服务器端:

        客户端运行需要打开终端,输入exe文件的路径,以及名字。另外进行通讯还需要打开多个客户端。

3.进行通讯

         结果展示为:

六、总代码展示

1.服务端代码:

        如下所示:

// 1. 对于每一个上线的客户端,服务端会起一个线程去维护
// 2. 将受到的消息转发给全部的客户端
// 3. 当某个客户端断开(下线),需要处理断开的链接。怎么处理呢?
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")


#define MAX_CLEN 256   
#define MAX_BUF_SIZE 1024

SOCKET clnSockets[MAX_CLEN]; // 所有的连接客户端的socket
int clnCnt = 0;   // 客户端连接的个数

HANDLE hMutex; 

// 将收到的消息转发给所有客户端
void SendMsg(char* msg, int len)
{
	int i;
	WaitForSingleObject(hMutex, INFINITE);
	for (i = 0; i < clnCnt; i++)
	{
		send(clnSockets[i], msg, len, 0);
	}
	ReleaseMutex(hMutex);
}

// 处理消息, 收发消息
unsigned WINAPI handleCln(void *arg)
{
	SOCKET hClnSock = *((SOCKET *)arg);
	int iLen = 0;
	char recvBuff[MAX_BUF_SIZE] = { 0 };
	
	while (1)
	{
		// iLen 成功时返回接收的字节数(收到EOF时为0),失败时返回SOCKETERROR。
		iLen = recv(hClnSock, recvBuff, MAX_BUF_SIZE, 0);
		// 
		if (iLen >= 0)
		{
			// 将收到的消息转发给所有客户端
			SendMsg(recvBuff,iLen);
		}
		else
		{
			break;
		}
	}

	printf("此时连接的客户端数量 = %d\n", clnCnt);
	WaitForSingleObject(hMutex, INFINITE);
	for (int i = 0; i < clnCnt; i++)
	{
		// 找到哪个连接下线的,移除这个连接
		if (hClnSock == clnSockets[i])
		{
			while (i++ < clnCnt)
			{
				clnSockets[i] = clnSockets[i + 1];
			}

			break;
		}
	}
	// 断开连接减 1 
	clnCnt--;
	printf("断开连接后连接的客户端数量 = %d\n", clnCnt);
	ReleaseMutex(hMutex);
	// 断开连接
	closesocket(hClnSock);
	return 0;
}

int main(int argc, char* argv[])
{	
	printf("this is Server\n");
	//0. 初始化网络
#if 1
// 0 初始化网络库
// 初始化库
	WSADATA wsaData;
	int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (stu != 0) {
		std::cout << "WSAStartup 错误:" << stu << std::endl;
		return 0;
	}
#endif
	HANDLE hThread;

	// 1.  创建一个互斥对象
	hMutex = CreateMutex(NULL, false, NULL);

	// 2. socket 创建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	if (sockSrv == INVALID_SOCKET)
	{
		std::cout << "socket failed!" << GetLastError() << std::endl;
		WSACleanup(); //释放Winsock库资源
		return 1;
	}

	// 3 bind 绑定套接字
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   // 地址 IP地址any
	addrSrv.sin_family = AF_INET;    // ipv4协议
	addrSrv.sin_port = htons(6000);  // 端口号
	if ( SOCKET_ERROR == bind(sockSrv, (sockaddr*)&addrSrv, sizeof(SOCKADDR)))
	{
		std::cout << "bind failed!" << GetLastError() << std::endl;
		WSACleanup(); //释放Winsock库资源
		return 1;
	}
	// 4. 监听
	if (listen(sockSrv, 5) == SOCKET_ERROR) // 5 是指最大的监听数目,执行到listen
	{
		printf("listen error = %d\n", GetLastError());
		return -1;
	}
	
	// 5
	SOCKADDR_IN addrCli;
	int len = sizeof(SOCKADDR);

	while (true)
	{
		// 接受客户端的连接
		SOCKET sockCon = accept(sockSrv, (sockaddr*)&addrCli, &len);

		// 全局变量要加锁
		WaitForSingleObject(hMutex, INFINITE);
		// 将连接放到数组里面
		clnSockets[clnCnt++] = sockCon;
		// 解锁
		ReleaseMutex(hMutex);

		// 每接收一个客户端的连接,都安排一个线程去维护
		hThread = (HANDLE)_beginthreadex(NULL, 0, &handleCln, (void*)&sockCon, 0, NULL);

		printf("Connect client IP = %s\n, Num = %d \n", inet_ntoa(addrCli.sin_addr), clnCnt);
	}

	closesocket(sockSrv);
	CloseHandle(hMutex);
	WSACleanup();
	return 0;
}
2.客户端代码:

        如下所示:

// 客户端做的事情:
//1 请求连接上线,
//2 发消息
//3 客户端等待服务端的消息
//4 等待用户自己的关闭(下线)
#include <stdio.h>
#include <windows.h>
#include <process.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")

#define NAME_SIZE 256   
#define MAX_BUF_SIZE 1024

char szName[NAME_SIZE] = "[DEFAULT]"; //  默认的昵称
char szMsg[MAX_BUF_SIZE];    // 收发数据的大小



unsigned WINAPI SendMsg(void* arg)
{
	SOCKET hClnSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 };   // 昵称和消息

	while (1)
	{
		memset(szMsg, 0, MAX_BUF_SIZE);
		// 阻塞这一句,等待控制台的消息
		//fgets(szMsg, MAX_BUF_SIZE, stdin);
		std::cin >> szMsg;
		if (!strcmp(szMsg, "Q\n") || !strcmp(szMsg, "q\n"))
		{
			// 处理下线
			closesocket(hClnSock);
			exit(0);
		}

		// 拼接  名字和字符串一起发送
		sprintf_s(szNameMsg, "%s %s", szName, szMsg);
		send(hClnSock, szNameMsg, strlen(szNameMsg) + 1, 0);

	}
}

unsigned WINAPI RecvMsg(void* arg)
{
	SOCKET hClnSock = *((SOCKET*)arg);
	char szNameMsg[NAME_SIZE + MAX_BUF_SIZE] = { 0 };   // 昵称和消息
	int len;
	while (1)
	{
		
		len = recv(hClnSock, szNameMsg, sizeof(szNameMsg), 0);
		if (len <= 0)
		{
			break;
			return -2;
		}
		szNameMsg[len] = 0;
		std::cout << szNameMsg << std::endl;
		// fputs(szNameMsg, stdout);


	}
	
}
int main(int argc, char* argv[])
{
	if (argc != 2)
	{
		printf("必须输入两个参数,包括昵称\n");
		printf("例如: WXS\n");
		system("pause");
		return -1;
	}
	sprintf_s(szName, "[%s]", argv[1]);
	printf("this is Client");
	//0. 初始化网络
#if 1
// 0 初始化网络库
// 初始化库
	WSADATA wsaData;
	int stu = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (stu != 0) {
		std::cout << "WSAStartup 错误:" << stu << std::endl;
		return 0;
	}
#endif
	
	// 定义两个线程 
	HANDLE hSendThread, hRecvThread;

	// 1. 建立 socket
	SOCKET sockCli = socket(AF_INET, SOCK_STREAM, 0);

	if (sockCli == INVALID_SOCKET)
	{
		std::cout << "socket failed!" << GetLastError() << std::endl;
		WSACleanup(); //释放Winsock库资源
		return 1;
	}

	// 2, 配置IP地址 和 端口号
	SOCKADDR_IN addrSrv;
	addrSrv.sin_family = AF_INET;    // ipv4协议
	addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.7"); // 地址 IP地址any
	addrSrv.sin_port = htons(6000);  // 端口号

	// 3. 连接服务器
	int res = connect(sockCli, (sockaddr*)&addrSrv, sizeof(sockaddr));

	// 4. 发送服务器消息,启动线程
	hSendThread = (HANDLE)_beginthreadex(NULL, 0, &SendMsg, (void*)&sockCli, 0, NULL);
	// 5. 等待
	hRecvThread = (HANDLE)_beginthreadex(NULL, 0, &RecvMsg, (void*)&sockCli, 0, NULL);

	WaitForSingleObject(hSendThread,INFINITE);
	WaitForSingleObject(hRecvThread, INFINITE);

	closesocket(sockCli);
	WSACleanup();

	return 0;
}

七、最后

        制作不易,熬夜肝的,还请多多点赞,拯救下秃头的博主吧!!          

标签:qq,群聊,SOCKET,int,MAX,客户端,多线程,BUF,SIZE
From: https://blog.csdn.net/m0_62681656/article/details/140123306

相关文章

  • Qt之多线程编程(QThread)
    文章目录前言Qt多线程的基本使用如何移动线程常用的一些函数示例代码总结前言在现代计算机系统中,多线程编程已经成为一种常见的编程模式,它可以有效地利用多核处理器的计算能力,提高程序的执行效率。Qt作为一种跨平台的应用程序开发框架,提供了QThread类来支持多线程编......
  • 多线程
    多线程概念并发:两个事件在同一时间段进行并行:两个事件在同一时刻进行程序:代码(指令和数据),静态进程Process:执行程序的一次过程,动态,系统分配资源的最小单位线程Thread:负责执行当前进程中程序的运行,一个进程中至少有一个线程,CPU调度和执行的单位多线程:真正的多线程是多个CPU多......
  • 多线程二-同步锁
    关于线程安全问题的简述多个线程做同一件事的时候原子性:Syncronized,AtomicXXX,Lock可见性:Syncronized,volatile有序性:Syncronized,volatile原子性问题代码演示了两个线程分别调用incr()方法来对i进行累加,预期结果应该是20000,但是实际结果却是小于等于20000的值,这就是线......
  • 单/多线程--协程--异步爬虫
    免责声明:本文仅做技术交流与学习... 目录了解进程和线程单个线程(主线程)在执行多线程线程池协程(爬虫多用)假异步:(同步)真异步:爬虫代码模版异步-爬虫同步效果--19+秒异步效果--7+秒了解进程和线程​#-------------------->#------>#   ----......
  • 多线程一
    线程启动线程生命周期阻塞状态分为Blocked,time-waiting.外在表现区别不大,产生的原因不同,可以通过jstack查看,更具体的状态有助于我们排查线程相关问题。下面这个时更为详细的生命周期图线程停止stop方法:不建议使用,类似于kill-9,不够优雅interrupt():通过Thread.current......
  • 并发、多线程和HTTP连接之间有什么关系?
    一、并发的概念 并发是系统同时处理多个任务或事件的能力。在计算中,这意味着系统能够在同一时间段内处理多个任务,而不是严格按照顺序一个接一个地执行它们。并发提高了系统的效率和资源利用率,从而更好地满足用户的需求。在现代应用程序中,用户可能会同时执行多个操作,例如同时......
  • Rust简明教程第九章-多线程和并发
    并发并发指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果进程是一个程序的实例线程是一个进程中执行的一个单一线性执行流程,一个进程包含多个线程,线程可以并发执行main是主线程,系统的入口区别:并发指一个系统能够......
  • python多线程与多进程开发实践及填坑记(1)
    1.需求分析1.1.概述基于Flask、Pika、Multiprocessing、Thread搭建一个架构,完成多线程、多进程工作。具体需求如下:并行计算任务:使用multiprocessing模块实现并行计算任务,提高计算效率、计算能力。消息侦听任务:使用threading模块完成RabbitMQ消息队列的侦听任务,将接收到......
  • Python多线程-线程池ThreadPoolExecutor
    1.线程池不是线程数量越多,程序的执行效率就越快。线程也是一个对象,是需要占用资源的,线程数量过多的话肯定会消耗过多的资源,同时线程间的上下文切换也是一笔不小的开销,所以有时候开辟过多的线程不但不会提高程序的执行效率,反而会适得其反使程序变慢,得不偿失。为了防止无尽的线程......
  • Windows编程之多线程事件对象(Event Object)用法详解
    目录一、前言二、基础用法三、API详解1.创建事件对象2控制事件状态3.等待事件对象:四、实战案例1.案例描述 2.代码设计 3.总设计代码4.运行结果一、前言        事件对象(EventObject)是我们在大型项目中,进行多线程同步处理的时候经常用到的一种内核对象......