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

在这里插入图片描述

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

从零开始使用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.xdnf.cn/news/144532.html

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

相关文章

内核是如何接收网络包的

1、数据如何从网卡到网络协议栈 1.1内核收包的过程 1、数据帧从外部网络到达网卡 2、网卡把数据帧从自己的缓存DMA(拷贝到)和内核共有的RingBuffer上 3、网卡发出硬中断通知CPU 4、CPU响应硬中断&#xff0c;简单处理后发出软中断 5、k’softirqd线程处理软中断&#xff0c;调…

ATGM331C-5T杭州中科微BDS/GNSS全星座定位授时模块应用领域

ATGM331C-5T 系列模块全部支持辅助 GNSS &#xff08;AGNSS&#xff09;功能&#xff1b;支持精确秒脉冲输出&#xff0c;脉冲上升沿与 UTC 时间对齐。 产品选型&#xff1a; 性能指标&#xff1a; 出色的定位导航功能&#xff0c;支持 BDS/GPS 卫星导航系统的单系统授时&#…

基于SpringBoot+Vue+MySQL的电子产品手机商城系统

系统展示 用户前台界面 管理员后台界面 系统背景 随着科技的飞速发展&#xff0c;电子商务已成为现代生活不可或缺的一部分。尤其是电子产品领域&#xff0c;手机作为人们日常生活中必不可少的通讯与娱乐工具&#xff0c;其市场需求持续增长。为了满足广大消费者的购物需求&…

ssrf攻击fastcgi复现及环境搭建

目录 一、环境 二、开始操作 一、环境 网上自己找vulhub-master.zip&#xff0c;我这里没用docker&#xff0c;本地自己搭建的 二、开始操作 很明显的ssrf漏洞 很明显我们之前的协议file dict 很明显9000端口是开放的 我们还是用gopherus生成一下 我们环境机上很明显有PEAR…

git报错:无法读取远程分支 ‘origin‘ does not appear to be a git repository

问题分析 push上传的时候本地分支和远程分支断开连接 所以重新链接即可 排查问题 1. 查看是否有分支&#xff0c;检查分支是否正确 git branch -v 2. 查看连接是否断开&#xff0c;断开无内容展示 查看远程仓库详细信息&#xff0c;可看到仓库地址 git remote -v 解决…

华为OD机试 - 打印机队列 - 优先队列(Python/JS/C/C++ 2024 E卷 200分)

华为OD机试 2024E卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试真题&#xff08;Python/JS/C/C&#xff09;》。 刷的越多&#xff0c;抽中的概率越大&#xff0c;私信哪吒&#xff0c;备注华为OD&#xff0c;加入华为OD刷题交流群&#xff0c;…

数据恢复免费版工具大比拼,哪款才是你的“救星”?

在数字时代&#xff0c;数据的重要性不言而喻&#xff1b;如果不幸遇到了数据丢失的问题&#xff0c;不要慌张&#xff0c;我这里有四款免费的数据恢复工具推荐给你&#xff1b;希望能帮助你找到最适合自己的那款“救星”&#xff01; 一、福昕数据恢复 直通车&#xff08;粘…

避免高额开发费用,如何轻松开发类似喜马拉雅的听书平台?

近年来&#xff0c;随着移动互联网的飞速发展&#xff0c;音频市场迅猛增长&#xff0c;像喜马拉雅这样的听书APP已经成为很多人生活中的重要娱乐和学习工具&#xff0c;吸引了大量用户。不过&#xff0c;很多创业者在尝试开发类似APP时&#xff0c;往往遇到开发费用高、时间长…

【已解决】Uncaught RangeError: Maximum depth reached

【已解决】Uncaught RangeError: Maximum depth reached 在JavaScript编程中&#xff0c;Uncaught RangeError: Maximum depth reached 是一个常见的错误&#xff0c;通常与递归调用深度过大有关。递归是一种编程技巧&#xff0c;它允许函数直接或间接地调用自身。然而&#xf…

oracle行转列函数 wm_concat

wm_concat()函数是oracle中独有的,mysql中有一个group_concat()函数。 这两个函数的作用是相同的&#xff0c;它们的功能是&#xff1a;实现行转列功能&#xff08;就是多行整合成一列&#xff0c;例如一个工单里有多个项目&#xff0c;工单号是一样的&#xff0c;则可以把多个…

ozon买家网址是什么,跨境电商ozon买家网址

在网购的世界里&#xff0c;每一个平台都像是一座宝藏岛&#xff0c;等待着我们去探索、去发现。而提到跨境电商&#xff0c;不得不提的一个名字就是Ozon&#xff0c;它对于许多跨境购物爱好者来说&#xff0c;简直就是打开国际购物大门的金钥匙。那么&#xff0c;今天咱们就来…

Win11|Win10电脑如何卸载软件?这3种方法帮你卸载烦人的程序。

引言 最近有粉丝问我,电脑不小心被家里的小伙子装了一大堆游戏软件,附带了很多垃圾软件,导致电脑卡顿、蓝屏异常。怎么把这些软件卸载呢?下面我将为大家分享3种卸载Windows电脑程序的方法: 解决办法: 方法1. 通过“设置”卸载: 1. 点击桌面左下角的“开始”菜单,然…

log4j2线程级动态日志级别

详见 参考 着重说明&#xff1a; DynamicThresholdFilter&#xff1a; 配置长这样&#xff1a;配置解释链接 <DynamicThresholdFilter key"logLevel" defaultThreshold"ERROR" onMatch"ACCEPT" onMismatch"DENY"><KeyVa…

企业网络安全关键:防御措施和应急响应

感谢浪浪云支持发布 浪浪云活动链接 &#xff1a;https://langlangy.cn/?i8afa52 文章目录 什么是网络安全常见的网络安全威胁病毒和恶意软件网络钓鱼拒绝服务攻击中间人攻击社会工程学 基本的网络安全措施强密码策略双因素认证安装和更新防病毒软件定期备份 高级的网络安全方…

【树莓派】利用socket改善树莓派3B运行YOLO运力不够

前言一、版本一树莓派&#xff08;客户端&#xff09;PC端&#xff08;服务端&#xff09; 总结 前言 如标题所示。 目标是树莓派作为客户端只负责捕捉画面&#xff0c;PC端运行yolo识别。 一、版本一 树莓派&#xff08;客户端&#xff09; import cv2 import socket impor…

智能办公新纪元:AI优秘圈引领未来工作方式

随着人工智能技术的不断进步&#xff0c;它已经开始渗透到我们工作与生活的每一个角落。在这一背景下&#xff0c;AI优秘圈以其创新的智能办公解决方案&#xff0c;正在重新定义企业的工作方式。本文将探讨AI优秘圈如何利用AI技术提升工作效率&#xff0c;降低成本&#xff0c;…

使用cmake配置c++环境(C++配置环境的“心路历程”)

最开始&#xff0c;接触c的时候&#xff0c;摆在面前的难题就是配置环境。 我也尝试了很多办法&#xff0c;故写下这篇文章&#xff0c;记录一下。 目录 方法一&#xff08;我最开始尝试的&#xff09;方法二&#xff08;我也试过&#xff09;方法三&#xff08;复制vs中配置好…

揭秘线程安全:HashMap 的四大实用策略

这篇文章&#xff0c;我们聊聊线程安全使用 HashMap 的四种技巧。 1 方法内部&#xff1a;每个线程使用单独的 HashMap 如下图&#xff0c;tomcat 接收到到请求后&#xff0c;依次调用控制器 Controller、服务层 Service 、数据库访问层的相关方法。 每次访问服务层方法 serv…

【数据结构-差分】【hard】力扣995. K 连续位的最小翻转次数

给定一个二进制数组 nums 和一个整数 k 。 k位翻转 就是从 nums 中选择一个长度为 k 的 子数组 &#xff0c;同时把子数组中的每一个 0 都改成 1 &#xff0c;把子数组中的每一个 1 都改成 0 。 返回数组中不存在 0 所需的最小 k位翻转 次数。如果不可能&#xff0c;则返回 -…

C++ 第三讲:内存管理

C 第三讲&#xff1a;内存管理 1.C内存分布2.内存管理方式2.1C语言内存管理方式2.2C内存管理方式2.2.1new\delete操作内置类型2.2.2new\delete操作自定义类型 3.operator new与operator delete函数4.new和delete实现原理4.1内置类型4.2自定义类型 5.定位new5.1内存池的基本了解…