【Linux】TCP网络编程
目录
V1_Echo_Server
V2_Echo_Server多进程版本
V3_Echo_Server多线程版本
V3-1_多线程远程命令执行
V4_Echo_Server线程池版本
V1_Echo_Server
TcpServer的上层调用如下,和UdpServer几乎一样:

而在InitServer中,大部分也和UDP那里一样,不同的是使用socket时第二个参数是SOCK_STREAM。


除了创建socket和bind外,还有第三步,因为tcp是面向连接的,tcp需要未来不断地能够做到获取连接,需要将server套接字设为listen状态,以便随时等待被获取连接,

其中backlog一般设为较小的数字,比如4、8等。

此时,server处于listen状态,等待别人随时来连接自己,listen就比如饭馆老板一天随时等待客人来吃饭。然后,我们可以添加一个_isrunning的成员变量,以表明服务器的运行状态,初始化为false。
在server处于listen状态后,因为tcp是需要连接的,需要使用accept函数来获取连接:

其中,第一个参数是server的套接字,后两个参数是用来得到是谁来连接server。关键在于accept的返回值:

我们看到accept的返回值竟然是一个文件描述符,这就让我们有点蒙圈了。因为在之前写udp代码时,只有一个文件描述符,那么此时我们难免有这样两个疑问:
- return fd是什么?
- return fd 和 _sockfd的关系
我们来将一个小故事,比如你和你的朋友去杭州西湖玩,在那里附近有很多饭馆,有一家叫西湖鱼庄,这家店雇了张三在店外面拉客,正好你在饭点碰到这家饭馆,就被拉了进去吃饭,张三带着你们进了饭店门口,然后张三喊来客人了,出来个人招呼客人,然后李四就出来招呼你们了。然后,张三又去店外面继续拉客,过了不久,张三又拉来了几个客人,到了店里喊又来客人了,出来个人招呼,此时王五出来招呼这几个客人,张三又跑出去继续拉客。在这个过程中,张三不给客人提供服务,只负责拉客。这个西湖鱼庄就是服务器,一个个客户就是一个个连接,而张三就是类成员_sockfd,李四、王五就相当于accept的返回值return fd,这个返回值来给连接提供服务,_sockfd就是用来协助accept获取新连接。把这个只负责获取连接的_sockfd叫做listensockfd(监听套接字)。

把成员变量改为_listensockfd。
如果张三拉客失败,也就是accept的返回值为0,那会怎么样呢?张三当然会继续拉客。


在提供服务时,由于udp是面向数据报,udp只能用recvfrom和sendto这样和网络强相关的接口,而tcp是面向字节流。之前我们学过C/C++的文件流以及管道的字节流,这些都是“流”,实际上它们都是一个东西,Linux下一切皆文件,所以网络、管道等都是文件,所以只要符合相同的流的特性,tcp这里的字节流的读取就相当于文件读取,也就是可以使用read/write进行读取。当使用read进行读取时,表明读取客户端结束(文件中表示读到文件结尾,这点有区别)。

在客户端这里,也是首先创建套接字,然后不需要显式bind,但是一定要有自己的IP和port,所以需要隐式bind,OS会用自己的IP和随机端口号去bind sockfd。客户端也不需要监听,没人回来连接客户端。server在等连接,所以客户端需要发起连接,使用connect调用,

那什么时候进行自动bind呢?在创建连接成功时就会bind!client的代码如下:
int main(int argc, char* argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << "server_ip server_port" << std::endl;exit(0);}std::string server_ip = argv[1];uint16_t server_port = std::stoi(argv[2]);//1.创建socketint sockfd = ::socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){std::cerr << "create socket error" << std::endl;exit(1);}//2.connectstruct sockaddr_in server;memset(&server, 0 , sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(server_port);::inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr.s_addr);int n = ::connect(sockfd, (struct sockaddr*)&server, sizeof(server));if(n < 0){std::cerr << "connect socket error\n" << std::endl;exit(2);}while(true){std::string message;std::cout << "Enter# ";std::getline(std::cin, message);write(sockfd, message.c_str(), message.size());char echo_buffer[1024];int n = ::read(sockfd, echo_buffer, sizeof(echo_buffer)-1);if(n > 0){echo_buffer[n] = 0;std::cout << echo_buffer << std::endl;}else{break;}}::close(sockfd);return 0;
}
我们编译运行这份代码,当启动第一个客户端时,发现可以正常echo:

然后我们再启动第二个客户端,发现服务器没有和第二个客户端建立连接,也没有echo,

只有把第一个客户端退出后,服务器才能和第二个客户端建立连接,服务器才能echo第二个客户端,

因此,我们发现这版客户端代码没有并发处理能力,一次只能处理一个客户端,这时因为主线程一直在Service内部在运行:

所以,为了解决以上服务器端不能并发处理的问题,
V2_Echo_Server多进程版本
因此,我们在处理Service时,通过创建子进程来处理:

父子进程都要有独立的文件描述符表,而子进程的文件描述符表是从父进程那里拷贝来的,注定了父子进程指向了同样的文件,所以子进程肯定能看见创建的创建的sockfd(代码是共享的,数据以写时拷贝的方式各自私有一份),也就是说,父进程打开了多少个文件,子进程可以看到并且能访问。父进程创建的listensockfd是3文件描述符,子进程创建的sockfd是4号文件描述符,子进程从父进程拷贝了文件描述符表,所以和父进程指向同一个文件。因为子进程不关心3,只关心4,这里的建议是让子进程关闭listensockfd,只保留sockfd。同时要求父进程关闭sockfd,只保留listensockfd,这里是要求,如果父进程不关sockfd,相当于4号文件描述符一直被占用,如果再有客户端来连接服务器,只能使用5号文件描述符来处理,导致父进程的文件描述符一直在被打开而从来没有被关闭,文件描述符的本质就是数组的下标,数组下标肯定是有限个,这就导致了文件描述符泄漏的问题。
所以,我们期望的是父进程把自己该做的做完,然后去回到accept,继续等待被连接。而子进程去执行if(id ==0)内部的代码,这样就能做到服务器采用多进程的方式并发处理连接,

可是,父进程在waitpid时采用的是0(阻塞式等待),所以我们刚才想的理想过程不会发生,子进程在处理任务期间,父进程会阻塞等待,这不是还是一次只能处理一个连接吗?!那怎么解决呢?我们在学习信号的时候,子进程在退出时,会向父进程发送SIGCHID信号,如果对SIGCHID进程ingore,那父进程就不需要等子进程退出了,只负责连接就行了,这种方式是可行的也是最推荐的。

此外,我们还可以这样做:

在子进程中再创建子进程,也就是孙子进程。if(fork() > 0)exit(0)让子进程直接退了,直接留下孙子进程。子进程返回了,父进程就能等待成功然后返回了。当孙子进程处理完后,就会变成孤儿进程,被系统领养,就不用再关心这个孙子进程了。但是这不是最好方案,最好方案就是上面那种。
V3_Echo_Server多线程版本

创建新线程,主线程会等待新线程,这还是串行运行,不能实现并发访问。为此,我们想到之前学过线程分离,不再让主线程等待新线程,而是让新线程分离,

那用于执行任务的文件描述符sockfd怎么交给新线程呢?我们知道,新线程和主线程是共享同一张文件描述符表的,这里绝对不能让主线程和新线程关闭自己不用的套接字fd,也不需要了。我们把Execute函数设置为了static属性,不能访问类内方法,不能访问类内的Service方法,为此,我们创建一个内部类ThreadData:


V3-1_多线程远程命令执行
由远程发过来命令行字符串,server对命令行字符串进行执行,把执行结果返回给远程。建立Command.hpp头文件,

我们进行网络的读取,不仅仅可以使用read/write接口,还可以使用recv/send这一对接口,这两个接口不能用来读取udp,只能读取tcp,是面向字节流的读取。


recv/send的flags默认设为0。Command类的设计如下,HandlerCommand函数用于处理客户端传来的字符串,通过Excute函数来把传入的字符串做解释,

那在Excute拿到待解释的命令行字符串后,怎么解释这个字符串呢?我们可以使用popen函数调用:

popen内部会建立一个管道文件,然后创建子进程,执行对应的command命令,内部来帮我们做命令行解析,解析后的内容放到管道文件中,返回FILE*,让我们以文件的方式读取管道。换句话说,未来只需要命令字符串传给popen就可以了,像读文件一样把结果读出来。第二个参数type是"r"/"w"/"a"。通过pclose把对应的管道文件关闭。
class Command
{
public:Command(){_safe_command.insert("ls");_safe_command.insert("touch");_safe_command.insert("pwd");_safe_command.insert("whoami");_safe_command.insert("which"); }~Command(){}bool CheckSafe(const std::string& cmdstr){for(auto e : _safe_command){if(strncmp(e.c_str(), cmdstr.c_str(), e.size()) == 0){return true;}}return false;}std::string Excute(const std::string& cmdstr){if(!CheckSafe(cmdstr)) return "unsafe";FILE* fp = popen(cmdstr.c_str(), "r");std::string result;if(fp){char line[1024];while(fgets(line, sizeof(line), fp)){result += line;}return result;}return "excute error";}void HandlerCommand(int sockfd, InetAddr addr){while (true){char commandbuff[1024];ssize_t n = ::recv(sockfd, commandbuff, sizeof(commandbuff) - 1, 0); // TODOif (n > 0){commandbuff[n] = 0;LOG(INFO, "get command from client %s, command : %s\n", addr.AddrStr(), commandbuff); std::string result = Excute(commandbuff);::send(sockfd, result.c_str(), result.size(),0);}else if (n == 0){LOG(INFO, "client %s quit\n", addr.AddrStr().c_str());break;}else{LOG(ERROR, "read error: %s quit\n", addr.AddrStr().c_str());}}}
private:std::set<std::string> _safe_command;
};
运行结果如下:

实际上,我们打开Xshell,实际上是打开了一个客户端,在Xshell上输入命令,其实是将命令发送到远端,去请求服务器上的一个长启动的服务,把命令行字符串交给它,由它执行并推送给客户端执行结果。所以,我们所谓的命令执行就是推送到远端。
V4_Echo_Server线程池版本
实际上,这种Service长服务不太适合用线程池,因为线程池中的线程是有上限的,每个线程一直被占用。这次的线程池版本只是一个示例,未来还是要使用V2版本的多线程。创建任务类型task_t,这是线程池中任务的类型,
using func_t = std::function<void()>;
然后构建任务,放到线程池中去处理:

总结一下tcp,就是通过listensocket套接字去获取连接,把新连接和客户端地址交给别人去处理,可以多并发地去处理。
相关文章:
【Linux】TCP网络编程
目录 V1_Echo_Server V2_Echo_Server多进程版本 V3_Echo_Server多线程版本 V3-1_多线程远程命令执行 V4_Echo_Server线程池版本 V1_Echo_Server TcpServer的上层调用如下,和UdpServer几乎一样: 而在InitServer中,大部分也和UDP那里一样&…...
排序学习整理(2)
上集回顾 排序学习整理(1)-CSDN博客 2.3 交换排序 交换排序的基本思想是:根据序列中两个记录键值的比较结果,交换这两个记录在序列中的位置。 特点: 通过比较和交换操作,将键值较大的记录逐步移动到序列…...
AI蛋白质设计与人工智能药物设计
AI蛋白质设计与人工智能药物设计 AI蛋白质设计 一、蛋白质相关的深度学习简介 1.基础概念 1.1.机器学习简介:从手写数字识别到大语言模型 1.2.蛋白质结构预测与设计回顾 1.3.Linux简介 1.4.代码环境:VS code和Jupyter notebook* 1.5.Python关键概…...
IOS ARKit进行图像识别
先讲一下基础控涧,资源的话可以留言,抽空我把它传到GitHub上,这里没写收积分,竟然充值才能下载,我下载也要充值,牛! ARSCNView 可以理解画布或者场景 1 配置 ARWorldTrackingConfiguration AR追…...
初级数据结构——二叉搜索树
目录 前言一、定义二、基本操作三、时间复杂度分析四、变体五、动态图解六、代码模版七、经典例题[1.——700. 二叉搜索树中的搜索](https://leetcode.cn/problems/search-in-a-binary-search-tree/)代码题解 [2.——938. 二叉搜索树的范围和](https://leetcode.cn/problems/ra…...
C++设计模式之组合模式中如何实现同一层部件的有序性
在组合模式中,为了实现同一层上部件的有序性,可以采取以下几种设计方法: 1. 使用有序集合 使用有序集合(如 std::list、std::vector 或其他有序容器)来存储和管理子部件。这种方法可以确保子部件按照特定顺序排列&am…...
duxapp RN 端使用AppUpgrade 进行版本更新
版本更新包含了组件和工具的组合 注册 下面这是 duxcms 入口文件检查更新的注册方法,注册的同时会检查更新 import {request,updateApp,userConfig } from ./utils// 检查app更新 setTimeout(async () > {if (process.env.TARO_ENV rn) {// eslint-disable-n…...
【计网】自定义序列化反序列化(三) —— 实现网络版计算器【下】
🌎实现网络版计算器【下】 本次序列化与反序列化所用到的代码,Tcp服务自定义序列化反序列化实现网络版计算器。 文章目录: 实实现网络版计算器【下】 客户端实现 基于守护进程的改写 🚀客户端实现 在这之前,…...
神经网络中的优化方法(一)
目录 摘要Abstract1. 与纯优化的区别1.1 经验风险最小化1.2 代理损失函数1.3 批量算法和小批量算法 2. 神经网络中优化的挑战2.1 病态2.2 局部极小值2.3 高原、鞍点和其他平坦区域2.4 悬崖和梯度爆炸2.5 长期依赖2.6 非精确梯度2.7 局部和全局结构间的弱对应 3. 基本算法3.1 随…...
Linux 计算机网络基础概念
目录 0.前言 1.计算机网络背景 1.1 独立模式 1.2 网络互联 1.3 局域网(Local Area Network,LAN) 1.4 广域网(Wide Area Network,WAN) 2.协议 2.1什么是协议 2.2协议分层和软件分层 2.3 OSI七层网络模型 2.3…...
qt QGraphicsEllipseItem详解
1、概述 QGraphicsEllipseItem是Qt框架中QGraphicsItem的一个子类,它提供了一个可以添加到QGraphicsScene中的椭圆项。QGraphicsEllipseItem表示一个带有填充和轮廓的椭圆,也可以用于表示椭圆段(通过startAngle()和spanAngle()方法ÿ…...
Python websocket
router.websocket(/chat/{flow_id}) 接口代码,并了解其工作流程、涉及的组件以及如何基于此实现你的新 WebSocket 接口。以下内容将分为几个部分进行讲解: 接口整体概述代码逐行解析关键组件和依赖关系如何基于此实现新功能示例:创建一个新的…...
【MySQL-5】MySQL的内置函数
目录 1. 整体学习的思维导图 2. 日期函数 编辑 2.1 current_date() 2.2 current_time() 2.3 current_timestamp() 2.4 date(datetime) 2.5 now() 2.6 date_add() 2.7 date_sub() 2.8 datediff() 2.9 案例 2.9.1 创建一个出生日期登记簿 2.9.2 创建一个留言版 3…...
深度学习笔记之BERT(三)RoBERTa
深度学习笔记之RoBERTa 引言回顾:BERT的预训练策略RoBERTa训练过程分析静态掩码与动态掩码的比较模型输入模式与下一句预测使用大批量进行训练使用Byte-pair Encoding作为子词词元化算法更大的数据集和更多的训练步骤 RoBERTa配置 引言 本节将介绍一种基于 BERT \t…...
C++知识点总结(59):背包型动态规划
背包型动态规划 一、背包 dp1. 01 背包(限量)2. 完全背包(不限量)3. 口诀 二、例题1. 和是质数的子集数2. 黄金的太阳3. 负数子集和4. NASA的⻝物计划 一、背包 dp 1. 01 背包(限量) 假如有这几个物品&am…...
C++:反向迭代器的实现
反向迭代器的实现与 stack 、queue 相似,是通过适配器模式实现的。通过传入不同类型的迭代器来实现其反向迭代器。 正向迭代器中,begin() 指向第一个位置,end() 指向最后一个位置的下一个位置。 代码实现: template<class I…...
webGL入门教程_04vec3、vec4 和齐次坐标总结
vec3、vec4 和齐次坐标总结 1. vec3 和 vec4 1.1 什么是 vec3 和 vec4? vec3: GLSL 中的三维向量类型,包含 3 个浮点数:(x, y, z)。常用于表示三维坐标、RGB 颜色、法线、方向等。 vec4: GLSL 中的四维向量类型&…...
uniapp中父组件数组更新后与页面渲染数组不一致实战记录
简单描述一下业务场景方便理解: 商品设置功能,支持添加多组商品(点击添加按钮进行增加).可以对任意商品进行删除(点击减少按钮对选中的商品设置进行删除). 问题: 正常添加操作后,对已添加的任意商品删除后,控制台打印数组正常.但是与页面显示不一致.已上图为例,选中尾…...
优化 Conda 下载速度:详细的代理配置和网络管理策略
优化 Conda 下载速度:详细的代理配置和网络管理策略 为了彻底解决使用 Conda 下载 PyTorch 时遇到的速度问题,并确保下载过程稳定可靠,这需要一个详细、综合的技术方案。让我们更深入地分析问题原因,然后详尽地解释采取的解决策略…...
服务器遭受DDoS攻击后如何恢复运行?
当服务器遭受 DDoS(分布式拒绝服务)攻击 后,恢复运行需要快速采取应急措施来缓解攻击影响,并在恢复后加强防护以减少未来攻击的风险。以下是详细的分步指南: 一、应急处理步骤 1. 确认服务器是否正在遭受 DDoS 攻击 …...
51c自动驾驶~合集58
我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留,CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制(CCA-Attention),…...
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
文章目录 前言限流限制并发的实际理解限流令牌桶代码实现结果分析令牌桶lua的模拟实现原理总结: 滑动窗口代码实现结果分析lua脚本原理解析 限并发分布式信号量代码实现结果分析lua脚本实现原理 双注解去实现限流 并发结果分析: 实际业务去理解体会统一注…...
工业自动化时代的精准装配革新:迁移科技3D视觉系统如何重塑机器人定位装配
AI3D视觉的工业赋能者 迁移科技成立于2017年,作为行业领先的3D工业相机及视觉系统供应商,累计完成数亿元融资。其核心技术覆盖硬件设计、算法优化及软件集成,通过稳定、易用、高回报的AI3D视觉系统,为汽车、新能源、金属制造等行…...
【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
智能分布式爬虫的数据处理流水线优化:基于深度强化学习的数据质量控制
在数字化浪潮席卷全球的今天,数据已成为企业和研究机构的核心资产。智能分布式爬虫作为高效的数据采集工具,在大规模数据获取中发挥着关键作用。然而,传统的数据处理流水线在面对复杂多变的网络环境和海量异构数据时,常出现数据质…...
rnn判断string中第一次出现a的下标
# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...
