DevC++ 用C语言的多线程 实现简单的客户端和服务器

news2024/11/26 1:46:09

知识来源一:

使用Dev-C++实现简单的客户端和服务器-CSDN博客

此先生的博客使用的是win32 SDK来创建多线程,然后鄙人对这个版本的多线程细节不明。于是又重新用C语言的线程替代win32API,以此继续学习服务器代码。

知识来源二:DevC++ 多线程创建与删除与exe文件脱离DevC++运行中发现dll文件和exe文件的关系-CSDN博客

这是C语言多线程简单样例。

然后在这两个基础上优化知识来源一的服务端代码,过程中查询函数,然后复制博客链接和截取解释,产生了知识来源三如下,作为函数忘了可以怎么开发了,就翻回去参考的,集成小文案。

知识来源三:

DevC++ socket嵌套字实现局域网客户端服务端函数详解注释-CSDN博客

优化功能说明:

客户端命令: log_out : 切断客户端在服务器的联系

消息反馈功能:客户端输入消息后,服务端发送反馈消息,说明可以服务端可以直接利用已经建立的链接传输消息,不用再另外建一新联系。

增加过程提示:函数执行过程中printf运行阶段,是当时改线程创建忘新建线程的bug加的测试点。

效果如图

零起点的顺序是,先看知识来源二的文章,玩玩里面的代码demo,键盘命令创建取消线程。然后看本节文章,对着知识点三,把本节文章的单线程服务器注释取消掉,删掉多线程部分。理解一个服务器一个客户端的联系的建立,然后再看看多线程代码,读读注释,用自己的话再说说,然后自己独立写写多线程服务器代码,写20min要是写不动了就仔细描述自己怎么写不出来的,是不会无中生有,连猜也猜不出来通信需要什么凭证,还是说是函数参数忘了默认值。然后再看看参考代码,理解自己是通过怎样的观察,在限定的时间里发现自己重复中却不能重复出新的思路。避免死磕,发现规模重复不能产生新意就回归,在现有的参考标准里的继续学习,临摹思路与代码。然后再试试能不能整活加点料,实现新功能。

完整代码如下,如果服务器代码编译的exe文件点击运行时,提示缺少libwinpthread-1.dll文件,有bug原理与解决方案,详情刚才的知识来源二:DevC++ 多线程创建与删除与exe文件脱离DevC++运行中发现dll文件和exe文件的关系-CSDN博客

服务端,有小的注释。

#include <stdio.h>
#include <winsock2.h>
#include<pthread.h>
#include<string.h>
#include<conio.h>
#pragma comment(lib,"ws2_32.lib")


//这两个注释块是win32API函数写的的多线程函数,用于对照c语言多线程 

//typedef struct ThreadNode
//{
//	int index;
//	HANDLE ThreadId;
//	SOCKET Client;
//	struct ThreadNode * next;
//	ThreadNode()
//	{
//		index = 0;
//		this->next = NULL;
//	}
//}hThread;

typedef struct ThreadNode {
	int index;
	pthread_t* Thread;
	SOCKET Client;
	struct ThreadNode * next;
	ThreadNode() {
		index = 0;
		this->next = NULL;
	}
} hThread;

hThread *clientHeadNote, *clientEndNote;
hThread * addClient() {
	hThread * ClientNote = new hThread();
	return ClientNote;
}

//DWORD WINAPI ThreadClient(LPVOID param)
//{
//	if(clientEndNote == NULL)
//	{
//		printf("empty Link\n");
//		return 0;
//	}
//	char revData[255];
//	SOCKET sClient = clientEndNote->Client;
//	while(1)
//	{
//			//接收数据
//	    int ret = recv(sClient, revData, 255, 0);
//	    if(ret > 0)
//	    {
//	        revData[ret] = 0x00;
//	        printf(revData);
//	        puts(0);
//	    }
//	}
//	closesocket(sClient);
//}

//每个建立链接的客户端分配一个函数,每个线程运行一个这样的函数 
void* ThreadClient(void*) {
	if (clientEndNote == NULL) {
		printf("empty Link\n");
		return 0;
	}
	char revData[255];
	SOCKET sClient = clientEndNote->Client;
	printf("client logged in \n");
	while (1) {
		//接收数据
		int ret = recv(sClient, revData, 255, 0);
		if (ret > 0) {
			revData[ret] = 0x00;
			printf(revData);
//			puts(0);
			printf("\n");
//				    新功能:加入客户端控制签退
			if (strcmp("log_out", revData) == 0) {
				char a[255];
				strcpy(a, "已注销\n");
				send(sClient, a, 255, 0);
//				反馈:向客户端sClienr发送“已注销”消息,发送长度为255个字节,0是指不阻塞。
//				printf("签退成功\n");
				closesocket(sClient);
				printf("签退成功\n");

			} else {
				char a[255];
				strcpy(a, "服务器已收到消息\n");
				send(sClient, a, 255, 0);
			}
		}
	}
	closesocket(sClient);
}




int main(int argc, char* argv[]) {
	//初始化WSA
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA wsaData;
	if (WSAStartup(sockVersion, &wsaData) != 0) {
		return 0;
	}

	//创建套接字
	SOCKET slisten = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (slisten == INVALID_SOCKET) {
		printf("socket error !");
		return 0;
	}

	//绑定IP和端口
	sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(8888);
//	端口和客户端端口要相同,否则链接建立不上 
//		sin.sin_port = htons(8880);
	sin.sin_addr.S_un.S_addr = INADDR_ANY;
//	这个是any指的是不限制访问IP


//	sin.sin_addr.S_un.S_addr =htonl(INADDR_ANY);
//	char loa[16] = "192.168.15.189";
//	sin.sin_addr.S_un.S_addr = inet_addr(loa);


//	把sin强制类型转换为sockadd,sin原来是sockaddr_in类型
//	初始化slisten,加载上面代码设置的属性。 
	if (bind(slisten, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) {
		printf("bind error !");
	}

	//开始监听
	if (listen(slisten, 5) == SOCKET_ERROR) {
		printf("listen error !");
		return 0;
	}


//	以上可以认为是建立服务器联系的固定格式


//下面注释部分是单线程的服务器,就是只能接受一个客户端的服务器 

	//循环接收数据
//	SOCKET sClient;
//	sockaddr_in remoteAddr;
//	int nAddrlen = sizeof(remoteAddr);
//	char revData[255];

	接受消息拉到外部,建立联系就一直接受
//	printf("等待连接...\n");
//	sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
//	if (sClient == INVALID_SOCKET) {
//		printf("accept error !");
	            continue;
//	}
//	printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
//
//
//	while (true) {
//		//接收数据
//		int ret = recv(sClient, revData, 255, 0);
		ret是数据长度 sclient是数据来源 revdata是数据,255是长度上限,0是不阻塞
//		if (ret > 0) {
//			revData[ret] = 0x00;
//			printf(revData);
//			printf("\n");
//		}
//		//发送数据
//		char a[100]="服务器已收到消息\n";
		scanf("%s", a);
//		send(sClient, a, strlen(a), 0);
//	}
//
//	closesocket(slisten);
//	WSACleanup();
//	return 0;


//这里是多线程服务器,接受多个客户端,可以对比单线程看看多了什么,哪些函数的位置调整了
 
	sockaddr_in remoteAddr;
	int nAddrlen = sizeof(remoteAddr);

	while (true) {
		printf("等待连接...\n");
		SOCKET sClient = accept(slisten, (SOCKADDR *)&remoteAddr, &nAddrlen);
		if (sClient == INVALID_SOCKET) {
			printf("accept error !");
			continue;
		}

		printf("接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr));
		if (clientHeadNote == NULL) {
			clientHeadNote = addClient();
//			printf("node creat\n");
			clientEndNote = clientHeadNote;
		}
//		clientEndNote->Client = sClient;
//		clientEndNote->ThreadId = CreateThread(NULL, 0, ThreadClient, NULL, 0, NULL);


//	必须要先创建给end节点,然后再创建线程,因为线程的参数通过全局变量传入,而不是通过线程生成函数传入 
//	原来线程参数除了 pthread_create()还能通过全局变量传参数
		hThread* clientnode = (hThread*)malloc(sizeof(hThread));
		clientnode->Client = sClient;
		clientnode->next = NULL;
		clientHeadNote->next = clientnode;
		clientEndNote = clientnode;
		printf("endnode is created\n");
		clientnode->Thread = (pthread_t*)malloc(sizeof(pthread_t));
		clientnode->index = pthread_create(clientnode->Thread, NULL, ThreadClient, NULL);
		printf("线程创建成功\n");
		
		 
	}

	printf("server is closing\n"); 
	hThread * p;
	while (clientHeadNote) { //释放占用的内存空间
//	    	CloseHandle(clientHeadNote->ThreadId);
		p = clientHeadNote;
		clientHeadNote = clientHeadNote->next;
		delete p;
	}

	closesocket(slisten);
	WSACleanup();
	return 0;
}

客户端,但是注释更零散

#include <WINSOCK2.H>
#include <STDIO.H>
#include<pthread.h>
#pragma  comment(lib,"ws2_32.lib")


 
//格式和服务端一样
int main(int argc, char* argv[]) {
	WORD sockVersion = MAKEWORD(2, 2);
	WSADATA data;
	if (WSAStartup(sockVersion, &data) != 0) {
		return 0;
	}

	SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sclient == INVALID_SOCKET) {
		printf("无效的 socket !");
		return 0;
	}

	sockaddr_in serAddr;
	serAddr.sin_family = AF_INET;
	//表示使用IPv4地址协议
//    https://blog.csdn.net/u012736362/article/details/130392547
	serAddr.sin_port = htons(8888);
	
	
//    puts("请输入对方的IP地址");

	char loa[16] = "127.0.0.1";
//	就是当服务器的电脑的IP , win建+r 输入cmd,打开命令行,输入再直接 ipconfig/all可查看IP。 

// IP是读取目标机器的IP,由于一台电脑当服务器和客户端,127.0.0.1是自回路,自己发送给自己
	serAddr.sin_addr.S_un.S_addr = inet_addr(loa);
//    IP赋值

//链接发消息
	if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) {
		//主动连接服务器
		printf("连接错误 !");
		closesocket(sclient);
		return 0;
	}
	puts("连接成功!!");
	puts("你现在可以向服务器发送信息:");
	
	while (1) {
		char sendData[100];
//		gets(sendData);
		scanf("%s",sendData);
		send(sclient, sendData, strlen(sendData), 0);
//		send(sclient, sendData, 255, 0);
//		匹配长度 255个字节

//		Sleep(1500);
//		if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR) {
//			//主动连接服务器
//			printf("连接错误 !");
//			closesocket(sclient);
//			return 0;
//		}
//		puts("连接成功!!");
//		puts("你现在可以向服务器发送信息:");

		char recData[255];
		int ret = recv(sclient, recData, 255, 0);
//		接收服务端消息 
		if (ret > 0) {
			recData[ret] = 0x00;
			printf(recData);
		}

	}
//    char recData[255];
//    int ret = recv(sclient, recData, 255, 0);
//    if(ret > 0)
//    {
//        recData[ret] = 0x00;
//        printf(recData);
//    }

	closesocket(sclient);
	WSACleanup();
	return 0;
}

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

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

相关文章

[Netty实践] 简单WebSocket服务实现

目录 一、介绍 二、依赖导入 三、基础类准备 四、Handler实现 五、WebSocketChannelInitializer实现 六、WebSocketServer实现 七、前端实现 八、测试 九、参考链接 一、介绍 关于WebSocket此处不进行过多介绍&#xff0c;本章主要着重通过Netty实现WebSocket通信服务…

在线客服系统:解决常见问题的实用工具与解决方案

市场得不断发展促使着消费者服务意识的觉醒&#xff0c;越来越多的消费者在购买产品的时候不仅看产品的功能、外观、性能&#xff0c;还关注品牌的服务质量。在线客服系统的出现帮助企业解决了客户服务难的问题。接下来&#xff0c;我们具体聊一聊在线客服系统能解决哪些问题&a…

每日一题——LeetCode888

方法一 个人方法&#xff1a; 交换后要达到相同的数量&#xff0c;那么意味着这个相同的数量就是两个人总数的平均值&#xff0c;假设A总共有4个&#xff0c;B总共有8个&#xff0c;那么最后两个人都要达到6个&#xff0c;如果A的第一盒糖果只有1个&#xff0c;那么B就要给出6…

铁山靠之——HarmonyOS基础 - 1.0

HarmonyOS学习第一章 一、HarmonyOS简介1.1 安装和使用DevEco Studio1.2 环境配置1.3 项目创建1.4 运行程序1.5 基本工程目录1.5.1 工程级目录1.5.2 模块级目录1.5.3 app.json51.5.4 module.json51.5.5 main_pages.json 二、TypeScript快速入门2.1 简介2.2 基础类型2.2.1 布尔值…

通过 Nginx 代理实现网页内容替换

突发奇想&#xff0c;用 Nginx 代理一个网站&#xff0c;把网站的一些关键字替换掉&#xff0c;蛮有意思的。 如下图&#xff1a; 一、编译安装 Nginx 一般 Nginx 中不包含 subs_filter 文本替换的模块&#xff0c;需要自己手动编译安装&#xff0c;步骤如下。 克隆 subs_fi…

linux cpu调度分析

一、cpu调度调试方法 echo 0 > /sys/kernel/debug/tracing/tracing_on echo > /sys/kernel/debug/tracing/trace echo 30720 > /sys/kernel/debug/tracing/buffer_size_kb echo nop > /sys/kernel/debug/tracing/current_tracer echo sched_switch sched_wakeup s…

移除石子使总数最小(LeetCode日记)

LeetCode-1962-移除石子使总数最小 题目信息: 给你一个整数数组 p i l e s piles piles &#xff0c;数组 下标从 0 0 0 开始 &#xff0c;其中 p i l e s [ i ] piles[i] piles[i] 表示第 i i i 堆石子中的石子数量。另给你一个整数 k k k &#xff0c;请你执行下述操作…

Win11右键菜单显示全部的方法

Win11右键菜单显示全部的方法&#xff1a;1. 用鼠标右键点击“开始”按钮&#xff08;或者按WinX键&#xff09;&#xff0c;选择点击 “Windows 终端&#xff08;管理员&#xff09;”。 2.在终端应用程序里粘贴这串代码【reg.exe add “HKCU\Software\Classes\CLSID{86ca1aa…

实现一个最简单的内核

更好的阅读体验&#xff0c;请点击 YinKai s Blog | 实现一个最简单的内核。 ​ 这篇文章带大家实现一个最简单的操作系统内核—— Hello OS。 PC 机的引导流程 ​ 我们这里将借助 Ubuntu Linux 操纵系统上的 GRUB 引导程序来引导我们的 Hello OS。 ​ 首先我们得了解一下&a…

burpsuite与sqlmap联动(sqlipy配置)

首先我们需要在burpsuite的 扩展-选项 里配置两个路径&#xff1a; 第一个路径为 jython-standalone-2.7.3.jar 的路径 这个jar文件我们需要自己下载&#xff0c;下载地址&#xff1a;https://www.jython.org/ 点击 download 点击 Jython Standalone 下载好之后将这个jar文件…

Django之DRF框架三,序列化组件

一、序列化类的常用字段和字段参数 常用字段 字段名字段参数CharFieldmax_lengthNone, min_lengthNone, allow_blankFalse, trim_whitespaceTrueIntegerFieldmax_valueNone, min_valueNoneFloatFieldmax_valueNone, min_valueNoneBooleanFieldNullBooleanFieldFloatFieldmax_…

基于Python的音乐数据可视化与推荐系统开发

基于Python的音乐数据可视化与推荐系统开发 导言&#xff1a; 音乐是人们生活中不可或缺的一部分&#xff0c;而对于音乐数据的收集、分析和可视化正逐渐成为技术领域的热点。本文介绍了一款基于Python开发的音乐数据可视化与推荐系统&#xff0c;通过爬取千千音乐网站的数据&a…

C# 实现虚拟数字人

随着Ai技术的提升和应用&#xff0c;虚拟数字人被广泛应用到各行各业中。为我们的生活和工作提供了非常多的便利和色彩。 通过设置虚拟数字人的位置大小&#xff0c;可以让数字人可以在电脑屏幕各个位置显示&#xff1a; 虚拟数字人素材&#xff1a; 虚拟数字人(实际有语音&am…

双向A*算法-python

GitHub - LittleFox99/B_A_star: Bidirectional A Star 其中a&#xff0c;b分别为双向A*搜索的权重 #-*- coding:utf-8 -*- # Time : 2020/11/11 1:21 下午 # Author : LittleFox99 # File : a_star.py # 参考&#xff1a; # https://blog.csdn.net/lustyoung/article/d…

前缀和+单调双队列+贪心:LeetCode2945:找到最大非递减数组的长度

本文涉及知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 单调双队列 贪心 题目 给你一个下标从 0 开始的整数数组 nums 。 你可以执行任意次操作。每次操作中&#xff0c;你需要选择一个 子数组 &#xff0c;并将这个子数组用它所…

资深13年测试整理,性能测试指标-评估方法,一篇搞懂...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、软件性能的关注…

深入探讨DNS数据包注入与DNS中毒攻击检测 (C/C++代码实现)

DNS数据包注入和DNS中毒攻击是网络安全领域中的两个重要主题。DNS&#xff08;域名系统&#xff09;是互联网中的一项核心服务&#xff0c;负责将域名转换为与之相对应的IP地址。 DNS数据包注入是指攻击者通过篡改或伪造DNS请求或响应数据包来干扰或破坏DNS服务的过程。攻击者…

webots仿真报警[ERROR] [1703399199.459991029]: Sampling period is not valid.

一、故障现象 在运行interace传感器使能程序时&#xff0c;报警[ERROR] [1703399199.459991029]: Sampling period is not valid. [ERROR] [1703399199.460080083]: Failed to enable lidar.并发生崩溃。 二、解决方式 1、尝试将程序中的TIME_STEP数值改为与WOrldInfo中的bas…

LeetCode 1954. 收集足够苹果的最小花园周长

一、题目 1、题目描述 给你一个用无限二维网格表示的花园&#xff0c;每一个 整数坐标处都有一棵苹果树。整数坐标 (i, j) 处的苹果树有 |i| |j| 个苹果。 你将会买下正中心坐标是 (0, 0) 的一块 正方形土地 &#xff0c;且每条边都与两条坐标轴之一平行。 给你一个整数 need…

Cross-Drone Transformer Network for Robust Single Object Tracking论文阅读笔记

Cross-Drone Transformer Network for Robust Single Object Tracking论文阅读笔记 Abstract 无人机在各种应用中得到了广泛使用&#xff0c;例如航拍和军事安全&#xff0c;这得益于它们与固定摄像机相比的高机动性和广阔视野。多无人机追踪系统可以通过从不同视角收集互补的…