2025/12/31 22:13:17
网站建设
项目流程
北京公司公示在哪个网站,网站代码输入完成之后要怎么做,百度搜自己的网站,学生登录注册入口通过前面的大致框架udptcp的学习#xff0c;我们已经大致了解了整个网络是如何搭建的
本篇章将通过网络的视角重新认识一下我们经常使用的软件xshell
目录
通过网络重识shell
编写一个自己的守护进程
总结 通过网络重识shell
我们重新回顾之前的一个小点#xff0c;再次…通过前面的大致框架udptcp的学习我们已经大致了解了整个网络是如何搭建的本篇章将通过网络的视角重新认识一下我们经常使用的软件xshell目录通过网络重识shell编写一个自己的守护进程总结通过网络重识shell我们重新回顾之前的一个小点再次理解xshell我们在腾讯或者阿里云上租用的云服务器只要启动起来就默认启动一个SSH服务程序通常是sshdsshd是一个守护进程后台运行它是默认监听一个22端口的xshell是一个本地程序xshell支持ssh协议所以通过xshell连接云服务器就是公网ip端口访问本质相当于两个进程互相通信看到同一份资源网络资源ssh协议是应用层协议这里可以看到我的xshell上面需要配置连接哪个公网ip协议使用的是SSH端口号是221sshd主进程持续监听 22 端口等待连接请求Xshell 发起 TCP 连接请求目标是云服务器公网 IP:222云平台 NAT 网关将公网 IP 请求转发到服务器内网 IP:22sshd主进程接收到连接-3sshd主进程调用fork()创建sshd子进程继承主进程的网络连接-4sshd子进程与 Xshell 进行SSH 加密握手1. 协商加密算法如 AES2. 身份验证密码 / 密钥校验3. 建立加密通信通道完成协议的初始化后续协议进程好加密解密Xshell 响应加密握手发送账号密码 / 密钥进行验证5验证通过后sshd子进程调用 fork后exec()系统调用- 替换sshd的子进程的子进程的程序镜像为用户默认 Shell如bash- 进程权限从root降为登录用户权限如ubuntuXshell 显示服务器的终端提示符如ubuntuserver:~$连接建立完成执行了三次握手会话本质就是一个客户端主进程sshd 时刻 listen来一个客户端就建立三次握手三次握手完之后accept获取然后创建一个新的sockfd给这个客户端和云服务器通信然后fork()sshd的子进程继承了父进程sshd的文件描述符继承完之后父进程sshd就会删掉这个accept上来的fdsshd主进程不需要处理数据传输保留fd会浪费系统资源这里不明白的看我的Linux系统编程章节子进程进行身份验证等等功能验证完后初始化SSH加密协议的一些字段后续作为协议层处理协议的加密解密创建子进程exec(bash)替换程序sshd的子进程和bash通过管道进行通信sshd的子进程直接操控sockfd的接收和发送缓冲区加密和解密后续通过管道让bash拿到的数据是明文的只担心处理命令的逻辑即可bash会修改标准输入输出关联到这个管道。比如命令ls执行客户端与云服务器之间经过这次建立好了连接后续就是客户端和sshd子进程进行通信ls执行然后经过SSH协议加密加密之后传输到了sockfd此时sshd子进程通过sockfd读取到数据然后解密解密之后写进管道bash直接从标准输入的接收缓冲区因为之前重定向了拿到解密的ls之后fork创建bash子进程exec(ls)程序替换执行ls返回结果给bashbash把结果经过管道交给sshd子进程加密放到标准输出的发送缓冲区客户端的接收缓冲区收到后进行解密然后呈现在标准输出屏幕注意外部命令通过 forkexec 执行内置命令如 cd、export直接在 Bash 主进程执行再来一个客户端就在创建一个子进程在程序替换成bash程序重复执行上述流程这期间来一个lsvimsleep我们都叫作业bash 对 “当前会话中启动的进程 / 进程组” 的统称前台任务占用 bash 的标准输入 / 输出管道必须等它执行完bash 才能接收新命令比如vim、ls后台任务不占用 bash 的标准输入在后台运行bash 可立即接收新命令比如sleep 100 。一个会话同一时间只有一个前台任务可以有多个后台本质就是你只有一个标准输入标准输出来一个任务bash就forkexec来执行jobs查询作业号ctrlz暂停任务fg把后台任务切换到前台bg把暂停任务切换到后台先执行sleep然后vimvim的时候ctrlz暂停前台任务后面jobs查询可以看到状态一个sleep后台一直再跑前台暂停了此时fg作业号又回到了vim的界面说明此时回到前台任务注意如果关闭终端断开连接此时所有的任务都会中断守护进程是脱离终端、在后台长期运行的系统级进程完全独立于 SSH/Shell 会话 —— 哪怕关闭所有 SSH 连接守护进程依然会运行这是它和普通后台任务sleep 100 最核心的区别并且不受bash控制系统进程控制PID1自成会话自成进程组普通后台任务关闭终端直接就结束了生命周期和bash同步注意创建的守护进程不能是组长比如bash他去forkexec(ls)那这个ls的组长是bash那后面bash不能单独拉出来成为守护进程等假设如果允许那ls究竟是跟谁bash成为了新的组的组长ls要过去吗还是属于原来的组如果两个都可以那信号发送的话是两个都发送所以这里不允许组长再去创建守护进程编写一个自己的守护进程setsid()作用创建新会话脱离原终端控制成为新会话首进程核心脱离终端依赖由于我们一开始通过./test允许程序这个交给bash去创建子进程然后进行exec(test)允许我们可以在其中调用setsid使其脱离bash的管理成为守护进程注意不能是组长即可编写守护进程的时候需要忽略异常的信号守护进程的核心特性是 “脱离终端 / SSH 会话”但内核仍可能向其发送终端相关信号比如误判进程关联终端忽略这些信号能杜绝 “无意义的终止”守护进程是 “系统级后台进程”如sshd/nginx其生命周期应由显式指令kill/systemctl stop管理而非随机的异常信号基本是忽略SIGHUPSIGPIPE等忽略信号的系统调用接口signal()/proc/此目录是系统进程级目录一个伪文件系统实际并不在硬盘当中存储是内核映射出来的方便你通过文件接口访问内核数据打开之后进入一个进程看里面有个cwd指向进程当前工作目录Current Working Directory的符号链接对于我们之前别写的程序为什么open不存在时创建是在当前目录下因为cwd中记录了一个路径会使用这个路劲创建这个进程可执行路径发送更改你可选或者不可选都行使用chdir函数/dev/null这个是 Linux 系统中的 **/dev/null设备文件 **它是一个特殊的 “空设备”核心作用是接收任何写入的数据并直接丢弃读取它则返回空。这是一个字符设备文件和磁盘文件、目录的类型不同是内核提供的虚拟设备我们后面编写的守护进程是后台进程跟终端没关系所以我们需要把标准输入输出重定向到这个设备相当于丢弃如果不重定向直接关闭有些printf函数都是默认打印到0/1/2的可能会阻塞进程分配时会从低到高所以read可能会占用0后面你输入输出函数往里面写就不对了数据污染思路第一步先忽略掉一些影响守护进程的信号第二步fork创建子进程虽然说你的进程的组长是bash可以直接setsid自成组长风险a. 在你setsid之前仍属于bash子进程bash可能误发信号让你再调用setsid之前挂掉可以先fork然后直接exit父进程此时fork之后的子进程就可以避免信号残留风险b. 有时候不是让bash启动而是系统直接启动此时你进程就是组长那调用setsid就会失败只有fork出来的子进程能够保证什么时候都不会是组长c. 直接 setsid ()你的进程会成为 “新会话的首进程”若代码中误操作打开/dev/tty比如日志输出到终端这个设备不是dev/null如果是首进程打开时内核会强制分配一个终端如果不是首进程会打开失败内核会让该进程重新关联终端违背守护进程 “脱离终端” 的核心诉求总结两个fork能够规避所有情况 第一次 forksetsid () 让子进程成为会话首进程第二次 fork () 让孙子进程成为 “非会话首进程”—— 即使打开/dev/tty也不会关联终端彻底杜绝风险。第一次fork是防止组长第二次fork是防止成为会话首进程一般来说生成环境是两次fork当然如果你能保证代码不关联dev设备你也可以一次第三步守护进程是脱离终端的关闭或者重定向以前进程默认打开的文件第四步可选进程执行路径发送改变总结本次编写我们使用两次fork重定向#include iostream #include signal.h #include sys/types.h #include unistd.h #include errno.h #include cstring #include sys/stat.h #include fcntl.h // 编写成一个函数或者类进行管理但好像也不需要管理啥后面可以自己改 #define DEV /dev/null void daemon(const char*pathnullptr) { // 第一步屏蔽信号 signal(SIGHUP, SIG_IGN); // 忽略SIGHUP信号 signal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE信号 // 第二步创建子进程 pid_t pid fork(); if (pid 0){ std::cout fork error: errno strerror(errno) std::endl; exit(1); // 退出 } if (pid 0){ // 此时是父进程直接关掉 exit(0); } // 走到这里绝对是创建出来的子进程 if (setsid() 0) { // 创建失败 std::cout setsid error: errno strerror(errno) std::endl; exit(1); } // 这里创建成功我们选择两次次fork pid fork(); if(pid0){ std::cout fork error: errno strerror(errno) std::endl; } if(pid0){ // 父进程直接退出 exit(0); } //这里绝对不是首进程避免终端tty的影响 // 第三步把关联的文件描述符关闭 int fd open(DEV, O_RDWR); if(fd0){ //打开成功重定向到这个设备 dup2(fd, 0);//先关闭0然后让0指向fd对应的设备 dup2(fd, 1); dup2(fd, 2); close(fd); } else{ //打开失败直接关掉 close(0); close(1); close(2); } if(path){ //要求修改路径 chdir(path); } }使用ps可以查询进程相关信息可以看到本次守护进程时第一条第二条是我们执行ps这条命令第一条中它的ppid是1说明是孤儿进程已经被内核接管了PID和PGID不是同一个说明不是组长并且终端没有打印消息说明以后台守护进程一直再运行TTY是?已经说明是脱离终端了重定向到当前文件下然后查看当前文件是否有helloworld没问题直接重定向过去了总结辨析shell、ssh、bash呢Shell是「操作系统的命令解释器总称」Bash是Shell的一种最常用SSH是「远程通信协议 / 工具」它依赖Bash/Shell完成远程命令的解析和执行。如果你在云服务器本地直接敲命令那就是由bash直接执行你的命令解析你的命令如果你是远端那就是通过SSH协议加密后传输到远端远端解析后给bash执行Shell是 “人机交互的命令解释器抽象层”Bash是它最常用的具体实现SSH是 “加密的远程通信工具”它通过调用远程服务器的Bash/Shell把 “本地输入的命令” 转化为 “远程内核的执行动作”。为什么bash需要创建子进程去执行命令呢Bash 作为「命令解释器」需要保证自身 “不被替换、不退出、能持续接收新命令”—— 如果 Bash 直接执行命令用exec替换自身执行完命令后 Bash 就会消失无法继续交互而 fork 子进程执行命令既能完成命令执行又能保留 Bash 主进程的 “控制权”。如果直接exec替身每个命令的执行可能修改「工作目录、环境变量、文件描述符」等如果 Bash 直接执行这些修改会污染自身Bash 是 “公司老板”外部命令是 “员工”—— 老板不会自己去干活否则公司没人管而是派员工子进程去干干完后员工离职老板继续管公司内置命令是 “老板自己的事”如调整公司地址、制定规则—— 必须老板自己做交给员工做没用。至此关于shell的所有认知我们进行了重构后续的学习会更加清晰sshd 本身就是典型的守护进程其运行机制与我们手动开发的守护进程一致 —— 脱离终端、后台长期运行、由 PID1 进程接管