【计网】从零开始使用TCP进行socket编程 ---服务端业务模拟Xshell

devtools/2024/9/19 17:43:58/ 标签: tcp/ip, 服务器, 网络协议, 网络

在这里插入图片描述

最糟糕的情况,
不是你出了错,
而是你没有面对出错的勇气。

从零开始使用TCP进行socket编程

  • 1 通信过程的多版本实现
    • 1.1 多进程版本
    • 1.2 多线程版本
  • 2 服务端业务模拟Xshell
    • 2.1 整体框架设计
    • 2.2 Command类设计

1 通信过程的多版本实现

在前一篇的文章中,实现了基于TCP协议的服务端与客户端的通信过程!当时我们是使用“不靠谱版本”,直接通过service函数执行代码,这样导致服务端只能为一个客户端进行服务,另一个客户端进入时就阻塞住了,只有上一个客户端连接退出,才会再次接入新的连接,这样可不行,服务器需要能够同时接入多个客户端!
那么帮助服务端实现同时接入多个客户端的做法有以下两种:

  1. 多进程版本:接收到连接后,创建子进程去执行任务。
  2. 多线程版本:接收到连接后,创建新线程去执行任务。

1.1 多进程版本

我们来实现多进程版本,多进程之前详细讲过:进程控制
创建的子进程会对父进程的数据进行写时拷贝,父子进程分别拥有独立的地址空间,但是需要注意的是:子进程的数据是根据父进程数据写时拷贝获取的,那么文件描述符也会一同拷贝,但是文件只打开了一份!所以为了避免不必要的问题要及时关闭文件描述符!!!

void Loop(){_isrunning = true;while (_isrunning){// accept接收sockfdstruct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(WARNING, "accept error\n");sleep(1);continue;}InetAddr addr(client);// 读取数据LOG(INFO, "get a new link, client info : %s, sockfd is : %d\n", addr.AddrStr().c_str(), sockfd);//version 2 --- 多进程版本int n = fork();//signal(SIGCHLD , SIG_IGN);//忽略子进程退出的信息!if(n == 0){//child::close(_listensockfd);//关闭listen文件 子进程不需要if(fork() > 0) exit(0);//孙子进程!!!//数据会进行写时拷贝 子进程中直接执行任务就可以!Service(sockfd, addr);exit(0);}//parent::close(sockfd); //父进程不需要管连接文件!!!}_isrunning = false;}

来看效果:
在这里插入图片描述
现在就可以适配多个客户端的情况了,但是我们知道切换进程时,CPU会切换上下文和热点数据。在并发场景下多进程的不断切换会消耗大量的性能!

而作为轻量级进程的线程就可以避免这样的问题!

1.2 多线程版本

现在我们来实现多线程的版本,我们先使用原生线程:

//...
// version 3 --- 多线程版本
pthread_t tid;
ThreadData td(sockfd , addr , this);
pthread_create(&tid, nullptr, Execute, &td);
pthread_detach(tid) ;//线程分离!!!
//...

这里需要为线程提供一个void*(void*)类型的函数,新线程就去执行这个任务。这个函数中为了可以执行Service任务,我们就需要传入对应的TcpServer类对象的指针、sockfd文件描述符以及InetAddr addr发送者的信息。

那么我们就设计一个结构体,里面储存着这些数据,一起通过void*传入!

class ThreadData{public:int _sockfd;InetAddr _addr;TcpServer *_this;public:ThreadData(int sockfd,  InetAddr addr ,TcpServer *p) : _sockfd(sockfd),_this(p),_addr(addr){}};

这样在Execute函数中就可以执行任务了

	// 注意设置为静态函数 , 不然参数默认会有TcpServer* this!!!static void *Execute(void *args){//执行Service函数TcpServer::ThreadData* td = static_cast<TcpServer::ThreadData*>(args);td->_this->Service(td->_sockfd , td->_addr);delete td;return nullptr;}

来看效果:
在这里插入图片描述
效果非常的好!!!

说到多线程了,那为什么不来使用线程池来实现呢???
线程池实际上并不适合当前场景,TCP通信是长服务,那么这个线程就会长时间运行,不能做到高效率的高并发
也就是说线程池在长服务场景不会提高效率!

但是我们也来实现一下线程池版本,帮助我们巩固知识!

  1. 首先我们设置一个task_t类型,这是线程池中需要执行的任务!
  2. 通过bind包装器将Service函数包装为task_t类型!
  3. 之后就等线程池分配线程执行任务即可!
using task_t = std::function<void()>;
//...
void Loop(){_isrunning = true;while (_isrunning){// accept接收sockfdstruct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){LOG(WARNING, "accept error\n");sleep(1);continue;}InetAddr addr(client);// 读取数据LOG(INFO, "get a new link, client info : %s, sockfd is : %d\n", addr.AddrStr().c_str(), sockfd);// version 4 --- 线程池版本task_t t = std::bind(&TcpServer::Service , this , sockfd , addr);ThreadPool<task_t>::GetInstance()->Equeue(t);}_isrunning = false;}

来看效果:
在这里插入图片描述

2 服务端业务模拟Xshell

我们实现服务端与客户端的通信逻辑,接下来就来加入业务逻辑!

这次选择的业务逻辑是模拟实现Xshell远程控制主机,之前我们实现过一个本地操作的shell程序在这里我们就实现过识别字符串指令然后进行进程替换执行任务!今天我们不再需要自己编写,我们直接使用popen接口:

NAMEpopen, pclose - pipe stream to or from a processSYNOPSIS#include <stdio.h>FILE *popen(const char *command, const char *type);int pclose(FILE *stream);Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

popen函数中会自动帮我们识别字符串指令,并创建进程去执行,然后将结果通过文件返回!
我们来逐步实现一下!

2.1 整体框架设计

首先我们要做到工作是将各个模块进行解耦:

  1. TcpServer类只负责获取客户端与服务端的连接。进行accept接收客户端连接,然后去执行回调函数任务,再将结果返回给客户端。
  2. Command类负责对字符串指令进行执行,并将结果返回!

为了做到这样的效果,TcpServer类中需要加入回调函数,在构造时就确定好回调函数,然后通过新线程去执行回调函数!回调函数的类型和Service一致:

using command_service_t = std::function<void(int sockfd, InetAddr addr)>;

2.2 Command类设计

Command类首先需要一个对外的HandlerHelper接口,这个接口是作为TcpServer类对象构造时的回调函数。函数中执行的任务就去从连接流中获取客户端传入的数据,通过Execute函数去执行指令任务,并返回对应的结果!

HandlerHelper执行的逻辑其实和原本的Service是一致的:

  1. sockfd文件中获取客户端传入的数据!
  2. 然后传给核心函数去执行任务!
  3. 最后将结果发送回去!

需要注意的是:不是所有这里都可以让客户端执行,如果客户端可以执行rm -rf这样的指令,那么破坏性是很强的,这里可以采用白名单(或黑名单)的方法去规避一下!如果要做到无敌防御就要麻烦的多,这里只是简单模拟一下!

#include <set>
#include <iostream>
#include <string>
#include <cstring>
#include <stdio.h>#include "InetAddr.hpp"
#include "Log.hpp"using namespace log_ns;class Command
{
private://指令白名单 保证安全!void InitCommand(){_command.insert("ls");_command.insert("pwd");_command.insert("mkdir");_command.insert("sleep");_command.insert("clear");_command.insert("touch");}bool CheckCommand(std::string &command){for (auto &e : _command){// LOG( DEBUG , "%s : %s", command.c_str(), e.c_str() );if (strncmp(command.c_str(), e.c_str(), e.size()) == 0){return true;}}return false;}public:Command(){InitCommand();}std::string Execute(std::string command){// 先进行安全检查if (!CheckCommand(command)){return "Unsafe command!!!";}// 开始执行指令FILE *fp = popen(command.c_str(), "r"); // 以读方式进行// 读取结果std::string result;char line[1024];if (fp){while (fgets(line, sizeof(line), fp)){result += line;}pclose(fp);return result.empty() ? "success" : result;}return "execute error";}void HandlerHelper(int sockfd, InetAddr addr){LOG(INFO, "service start!!!\n");while (true){char buffer[1024];ssize_t n = ::recv(sockfd, buffer, sizeof(buffer) - 1, 0);if (n > 0){buffer[n] = 0;LOG(INFO, "sockfd read success!!! buffer: %s\n", buffer);std::string str = Execute(buffer);send(sockfd, str.c_str(), str.size(), 0);}else if (n == 0){LOG(INFO, "client %s quit!\n", addr.AddrStr().c_str());break;}else{LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());break;}}::close(sockfd);}~Command(){}private:std::set<std::string> _command;
};

来看效果:
在这里插入图片描述
非常好!这样我们就完成了Xshell的模拟项目!!!

后续我们来学习序列化与反序列化!!!


http://www.ppmy.cn/devtools/114216.html

相关文章

Linux网络服务只iptables防火墙工具

目录 一、防火墙简介 1.iptables概述 2.netfilter 及 iptables关系 二、四表五链 1.四表 2.五链 三、iptables 工作原理 1.原理 2.三种报文流向 四、配置ipables 1. 下载并查看ipables 2. 基本格式 3. 常用选项 3.1 常用的控制类型&#xff1a; 3.2常用的管理选项&#x…

【OpenGL 学习笔记】01 - CLion 配置 CMake 运行初始 demo

环境 CLion, GLFW 代码 使用 GLFW 文档中第一个示例&#xff0c;显示一个用于渲染的窗口。 文件结构 当前源文件在根目录下 // PowerShell 中使用 Tree 命令 ─.idea ├─cmake-build-debug │ ├─.cmake │ │ └─api │ │ └─v1 │ │ ├─quer…

https和http区别

HTTP&#xff08;HyperText Transfer Protocol&#xff1a;超文本传输协议&#xff09;和 HTTPS&#xff08;HyperText Transfer Protocol Secure&#xff1a;安全超文本传输协议&#xff09;是用于在互联网上传输数据的两种协议。 它们之间的主要区别在于安全性和数据加密&…

DFS算法专题(四)——综合练习【含矩阵回溯】【含3道力扣困难级别算法题】

目录 1、字母大小写全排列 1.1 算法原理 1.2 算法代码 2、优美的排列 2.1 算法原理 2.2 算法代码 3、N皇后【困难】★★★ 3.1 算法原理 3.2 算法代码 4、有效的数独【解数独铺垫】 4.1 算法原理 4.2 算法代码 5、解数独【困难】★★★ 5.1 算法原理 5.2 算法代码…

用stm开发人体感应报警实现红灯报警wifi模块上传服务器登记时间和应用名称

使用 STM32 微控制器实现人体感应报警器的示例代码&#xff0c;包括通过 WiFi 模块上传报警信号到服务器端并记录时间和报警应用名称。 一、硬件准备 STM32 开发板&#xff08;例如 STM32F103 系列&#xff09;。人体感应传感器&#xff08;如 HC-SR501&#xff09;。红色 LE…

collocate join,bucket join,broadcast join,shuffle join对比分析

在分布式计算和大数据处理中,尤其是在使用像 Apache Spark、Hive 等大数据处理框架时,Join 操作是非常常见的。根据数据分布方式和执行机制,Join 操作可以分为不同的类型,如 Collocate Join、Bucket Join、Broadcast Join 和 Shuffle Join。以下是它们的详细对比分析: 1.…

react 组件化开发_生命周期_表单处理

组件基本介绍 我们从上面可以清楚地看到&#xff0c;组件本质上就是类和函数&#xff0c;但是与常规的类和函数不同的是&#xff0c;组件承载了渲染视图的 UI 和更新视图的 setState 、 useState 等方法。React 在底层逻辑上会像正常实例化类和正常执行函数那样处理的组件。 因…

HarmonyOS 实现自定义启动页

&#x1f468;&#x1f3fb;‍&#x1f4bb; 热爱摄影的程序员 &#x1f468;&#x1f3fb;‍&#x1f3a8; 喜欢编码的设计师 &#x1f9d5;&#x1f3fb; 擅长设计的剪辑师 &#x1f9d1;&#x1f3fb;‍&#x1f3eb; 一位高冷无情的全栈工程师 欢迎分享 / 收藏 / 赞 / 在看…

C# 开发教程-中级教程

1.C# 多线程/异步 C# 异步编程Task整理&#xff08;一&#xff09; C# 异步编程Task整理&#xff08;二&#xff09;异常捕捉 C# 异步编程Task(三) async、await C#中创建线程&#xff0c;创建带参数的线程 C# 线程同步之排它锁/Monitor监视器类 C# lock关键词/lock语句块…

【Vue】自定义指令 - 点击当前区域外

<template><div><div v-click-outside"onClickOutside" class"box">点击内容外区域会触发事件</div></div> </template><script setup> // 定义自定义指令 v-click-outside const vClickOutside {mounted(el, …

前后端数据交互 笔记03(get和post方法)

1.解决页面网站中&#xff0c;中文出现乱码的情况&#xff1a; request.setCharacterEncoding("utf-8") response.setCharaterEncoding("utf-8") 2.给后端设置返回json数据&#xff1a; response.setContentType("text/json,charsetutf-8") …

java 面试题总结(基础篇)

一、编程基础 1.JDK、JRE、JVM的区别 JVM&#xff08;Java Virtual Machine&#xff09;&#xff1a; Java虚拟机&#xff0c;负责加载、执行字节码文件&#xff08;.class&#xff09;和垃圾回收等任务。 JRE&#xff08;java Runtime Environment&#xff09;: Java运行环境…

【STM32】BH1750光敏传感

1.BH1750介绍 BH1750是一个光敏传感&#xff0c;采用I2C协议&#xff0c;对于I2C的从机&#xff0c;都有自己的地址&#xff0c;用来主机选择和哪个从机通信&#xff0c;对于OLED来说&#xff0c;只有单片机通过I2C往OLED中写数据。而BH1750来说&#xff0c;有单片机往BH1750写…

宠物定位技术升级,蓝牙定位让爱宠随时在线

担心爱宠在外玩耍时走失?通过蓝牙定位技术&#xff0c;我们为您的爱宠提供精准、实时的追踪服务。无论是在家中、户外&#xff0c;还是在人流密集的场所&#xff0c;蓝牙定位都能确保您随时掌握爱宠的动向。 一、蓝牙技术的基本原理&#xff1a; 蓝牙技术是一种短距离无线通…

Vue 常见的几种通信方式(总结)

前言 Vue的通信方式&#xff0c;相信各位小伙伴都已经滚瓜烂收了&#xff0c;但是我估计咱们平常用到的就那么几个&#xff0c;那么剩余的哪些具体是怎么使用的&#xff0c;或者再去温习一下&#xff0c;我觉得也是很有必要的。 1.props/emit 父组件 <template><h…

【CTF Web】BUUCTF BUU BURP COURSE 1 Writeup(X-Real-IP伪造+POST请求)

BUU BURP COURSE 1 1 点击启动靶机。 解法 用 hackbar 将 X-Forwarded-For 设为 127.0.0.1&#xff0c;无效。提示&#xff1a;只能本地访问。 将 Referer 设为 127.0.0.1&#xff0c;无效。提示&#xff1a;只能本地访问。 将 X-Real-IP 设为 127.0.0.1&#xff0c;成功&am…

【开源免费】基于SpringBoot+Vue.JS在线文档管理系统(JAVA毕业设计)

本文项目编号 T 038 &#xff0c;文末自助获取源码 \color{red}{T038&#xff0c;文末自助获取源码} T038&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 查…

深度学习Day-32:CycleGAN实战

&#x1f368; 本文为&#xff1a;[&#x1f517;365天深度学习训练营] 中的学习记录博客 &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制] 一、 基础配置 语言环境&#xff1a;Python3.8编译器选择&#xff1a;Pycharm深度学习环境&#xff1a; torch1.12.1c…

第五章 网络编程 TCP/UDP/Socket

第五章 网络编程 一、IP地址 唯一标识网络上的每一台计算机 32位&#xff0c;由4个8位二进制数组成1.IP地址的组成 IP地址 网络地址 主机地址 网络地址&#xff1a;标识计算机或网络设备所在的网段 主机地址&#xff1a;标识特定主机或网络设备A类地址通常分配给具有大量主…

汽车软件开发之敏捷开发

一、前言 目前汽车电子产品&#xff0c;特别是汽车几大域控&#xff08;如&#xff1a;智能座舱、智能驾驶、智能网联、车身控制&#xff09;市场竞争激烈&#xff0c;消费者对汽车的需求逐渐多元化和个性化&#xff0c;用户对座舱和智驾产品的要求也越来越高。他们不仅要求产…