Linux--TCP编程--0216 17
观前提示:本篇博文的一些接口需要前几篇博文实现的
- 线程池的实现
Liunx--线程池的实现--0208 09_Gosolo!的博客-CSDN博客
- 线程池的单例模式
Linux--线程安全的单例模式--自旋锁--0211_Gosolo!的博客-CSDN博客
1.TCP编程需要用的接口
创建 socket 文件描述符
int socket(int domain, int type, int protocol);
type 给成 SOCK_STREAM 表示是流式套接
listensock=socket(AF_INET,SOCK_STREAM,0);
开始监听socket
int listen(int socket, int backlog);
TCP是面向连接的,listen其实也是一个套接字,不过他的用途在于建立连接,而不真正提供服务。类似拉人的,提供服务的是服务员。
接收请求
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
相当于拉客的和服务员进行了交接,返回值是真正提供服务的套接字(fd)
建立连接
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
在正式通信之前,需要先建立连接。客户端需要连接到连接。
2.TCP编程的框架
2.1 服务端 TcpServer的框架 tcp_server.hpp
class TcpServer
{
private:const static int gbacklog = 20; //后面再说public:TcpServer(uint16_t port, std::string ip="0.0.0.0"):_port(port),_ip(ip),listensock(-1){}void initServer(){}void Start(){}~TcpServer(){}
private:uint16_t _port;std::string _ip;int listensock;//listensock套接字仅用于建立连接
};
头文件在这里一次性给出
#pragma once#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <pthread.h>
#include <ctype.h>//这几个头文件在线程池里面 其中Task.hpp会在用时给出
#include "ThreadPool/log.hpp"
#include "ThreadPool/ThreadPool.hpp"
#include "ThreadPool/Task.hpp"
2.1.2 TcpServer的调用 tcp_server.cc
#include "tcp_server.hpp"
#include <memory>static void usage(std::string proc)
{std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}// ./tcp_server port
int main(int argc, char *argv[])
{if(argc != 2){usage(argv[0]);exit(1);}uint16_t port = atoi(argv[1]);std::unique_ptr<TcpServer> svr(new TcpServer(port));svr->initServer();svr->Start();return 0;
}
2.2 客户端 TcpClient的实现 tcp_client.cc
网络间通信首先需要 struct sockaddr_in 结构体,这里定义一个对象 server
结构对象有了需要对他进行初始化(建议先全部置零,避免出现一些bug)

- server 有三个地方需要做初始化,sin_family、sin_port、sin_addr.s_addr
server.sin_family=AF_INET; //跟申请套接字传入的参数一样即可
server.sin_port=htos(serverport); //记得从本地转为网络字节序
server.sin_addr.s_addr=inet_addr(serverip,c_str());
- connect()接口 客户端需要连接到服务上
connect(sock, (struct sockaddr *)&server, sizeof(server) );
- send() 向服务端发送消息
ssize_t s = send(sock, line.c_str(), line.size(), 0);
- recv() 接收服务器发来的消息
recv(sock, buffer, sizeof(buffer) - 1, 0);
- close() 关闭套接字
close(sock);
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>void usage(std::string proc)
{std::cout << "\nUsage: " << proc << " serverIp serverPort\n"<< std::endl;
}// ./tcp_client targetIp targetPort
int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(1);}std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);int sock = 0;while (true) // TODO{sock = socket(AF_INET, SOCK_STREAM, 0);if (sock < 0){std::cerr << "socket error" << std::endl;exit(2);}// client 要不要bind呢?不需要显示的bind,但是一定是需要port// 需要让os自动进行port选择// 连接别人的能力!struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0){std::cerr << "connect error" << std::endl;exit(3); // TODO}std::cout << "connect success" << std::endl;std::cout << "请输入# ";std::string line;std::getline(std::cin, line);if (line == "quit"){close(sock);break;}ssize_t s = send(sock, line.c_str(), line.size(), 0);if (s > 0){char buffer[1024];ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);if (s > 0){buffer[s] = 0;std::cout << "server 回显# " << buffer << std::endl;close(sock);}else if (s == 0){close(sock);}}else{close(sock);}}return 0;
}
3. 服务端的实现
3.1 TcpServer::initServer()
- 初始化,首先需要建立一个监听套接字listensock,这个监听套接字的作用是建立连接。
listensock=socket(AF_INET,SOCK_STREAM,0);
我们是网络之间进行通信,所以需要借助 struct sockaddr_in 类型的对象,local,创建出来之后最好先清空一下,保证不会出现一些奇怪的问题。
- local 有三个地方需要做初始化,sin_family、sin_port、sin_addr.s_addr
local.sin_family=AF_INET; //跟申请套接字传入的参数一样即可
local.sin_port=htos(_port); //记得从本地转为网络字节序
local.sin_addr.s_addr=_ip=="0.0.0.0"?INADDR_ANY:inet_addr(_ip.c_str());
- 接着和udp一样,进行绑定操作
//注意 虽然使用struct sockaddr_in 结构体类型 但是接口中的参数依然是 struct sockaddr*
bind(listensock, (struct sockaddr *)&local, sizeof(local);
- (tcp新增)建立连接
listen(listensock, gbacklog);//这个gbacklog以后再谈
3.1.1 完整代码
class TcpServer
{
private:const static int gbacklog = 20; //后面再说public:TcpServer(uint16_t port, std::string ip="0.0.0.0"):_port(port),_ip(ip),listensock(-1){}void initServer(){listensock=socket(AF_INET,SOCK_STREAM,0);if(listensock<0){logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));exit(2);}logMessage(NORMAL, "create socket success, listensock: %d", listensock);struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family=AF_INET;local.sin_port=htons(_port);//local.sin_addr.s_addr=_ip.empty()?INADDR_ANY:inet_addr(_ip.c_str());local.sin_addr.s_addr=_ip=="0.0.0.0"?INADDR_ANY:inet_addr(_ip.c_str());//inet_pton(AF_INET, _ip.c_str(), &local.sin_addr);if (bind(listensock, (struct sockaddr *)&local, sizeof(local)) < 0){logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));exit(3);}if(listen(listensock, gbacklog)<0){logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));exit(4);}logMessage(NORMAL, "init server success");}
}
3.2 TcpServer::Start()
要想服务端程序开始运行,首先需要有人来提供服务,记得listensock的作用仅是建立连接吗,那谁来做具体的服务呢?
使用accept接口的返回值!
首先,网络通信需要struct sockaddr_in结构体,先创建一个,struct sockaddr_in src;,len就是这个结构体的长度。
int servicesock=accept(listensock,(struct sockaddr*)&src,&len);
连接成功时候,就可以从我们创建的src中获取端口号、ip信息了。
uint16_t client_port=ntohs(src.sin_port);
std::string client_ip=inet_ntoa(src.sin_addr);
接下来就可以开始进行服务了,自定义让做什么。这里让服务端去做这个任务
service(servicesock, client_ip, client_port);
static void service(int sock, const std::string &clientip, const uint16_t &clientport)
{//echo serverchar buffer[1024];while(true){// read && write 可以直接被使用!ssize_t s = read(sock, buffer, sizeof(buffer)-1);if(s > 0){buffer[s] = 0; //将发过来的数据当做字符串std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;}else if(s == 0) //对端关闭连接{logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);break;}else{ // logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));break;}write(sock, buffer, strlen(buffer));}close(sock);
}
3.2.1 完整代码——单进程阻塞循环版
void Start(){while(true){struct sockaddr_in src;socklen_t len=sizeof(src);//建立连接int servicesock=accept(listensock,(struct sockaddr*)&src,&len);if(servicesock < 0){logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));continue;}uint16_t client_port=ntohs(src.sin_port);std::string client_ip=inet_ntoa(src.sin_addr);logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",servicesock, client_ip.c_str(), client_port);//版本1————单进程阻塞循环版service(servicesock, client_ip, client_port);}

问题在于,一次只能够处理一个进程。因为我们调用的函数service是一个死循环函数,如果一个客户端没有终止访问,其他客户端都不能正常来使用。
3.2.2 完整代码——多进程带信号屏蔽版
那我使用多进程来解决这个问题,可是使用多进程也有问题,我创建了子进程,那我是不是要等待子进程结束啊?如果我使用阻塞等待那和上面有什么本质区别呢?
注:使用非阻塞等待成本很大,可以但不建议。
所以我们还需要用到信号,当子进程结束后,会给父进程发SIGCHLD信号!
如果我们主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态。
void Start(){ signal(SIGCHLD, SIG_IGN); while(true){struct sockaddr_in src;socklen_t len=sizeof(src);//建立连接int servicesock=accept(listensock,(struct sockaddr*)&src,&len);if(servicesock < 0){logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));continue;}uint16_t client_port=ntohs(src.sin_port);std::string client_ip=inet_ntoa(src.sin_addr);logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",servicesock, client_ip.c_str(), client_port);pid_t id = fork();assert(id != -1);if(id == 0){close(listensock);//关闭不需要的文件描述符service(servicesock, client_ip, client_port);exit(0); }//关闭父进程不需要的文件描述符 不关闭会导致父进程可用的文件描述符越来越少close(servicesock);}}

3.3.3 完整代码——多进程版
能不能不屏蔽SIGCHLD信号呢?那我们就需要有人等待子进程,让谁等呢?
让bash领养,让bash等!
void Start(){while(true){//...跟上面一样//版本3————多进程孤儿进程版// 利用孤儿进程被系统回收pid_t id=fork();if(id==0){close(listensock);if(fork()>0) {//子进程本身 exit(0);}//子进程的子进程service(servicesock, client_ip, client_port);exit(0);}waitpid(id,nullptr,0);//由于子进程创建子进程后立即退出 所以父进程不会阻塞close(servicesock);}}

3.3.4 完整代码——多线程版
相较于使用多进程,多线程的开销明显小。
class ThreadData
{
public:int _sock;std::string _ip;uint16_t _port;
};class TcpServer
{
private:const static int gbacklog = 20; //后面再说//设置的回调函数 必须是static的 不然会多一个this指针static void *threadRoutine(void *args){pthread_detach(pthread_self());ThreadData *td = static_cast<ThreadData *>(args);service(td->_sock, td->_ip, td->_port);delete td;return nullptr;}void Start(){while(true){//...//版本4————多线程版本//因为创建一个进程的代价还是比较大的,创建一个线程相对简便//不使用在栈上创建是为了保证线程安全 不会被覆盖 发生拷贝ThreadData *td=new ThreadData(); td->_sock=servicesock;td->_ip=client_ip;td->_port=client_port;pthread_t tid ;//如果不join 一定会造成内存泄漏 可以在threadRoutine中设置等待pthread_create(&tid,nullptr,threadRoutine,td); }}};

3.3.5 完整代码——线程池版本
线程的创建也是一笔开销,能省就省
线程池版本的服务函数
//线程池版本的服务函数
static void service(int sock, const std::string &clientip,const uint16_t &clientport, const std::string &thread_name)
{// echo server// 同时在线10人// 所以,我们一般服务器进程业务处理,如果是从连上,到断开,要一直保持这个链接, 长连接char buffer[1024];while (true){// read && write 可以直接被使用!ssize_t s = read(sock, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0; // 将发过来的数据当做字符串std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;}else if (s == 0) // 对端关闭连接{logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);break;}else{ //logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));break;}write(sock, buffer, strlen(buffer));}close(sock);
}
class TcpServer
{
public:TcpServer(uint16_t port, std::string ip="0.0.0.0"):_port(port),_ip(ip),listensock(-1), _threadpool_ptr(ThreadPool<Task>::getThreadPool()){}void Start(){//引入线程池_threadpool_ptr->run();//signal(SIGCHLD, SIG_IGN); // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态while(true){//...略//线程池版本Task t(servicesock, client_ip, client_port, service);//Task t(servicesock, client_ip, client_port, dictOnline);_threadpool_ptr->pushTask(t);}}~TcpServer(){}
private:uint16_t _port;std::string _ip;int listensock;//仅用于建立连接//定义一个线程池先std::unique_ptr<ThreadPool<Task>> _threadpool_ptr;
};
pushTask
#pragma once#include <iostream>
#include <string>
#include <functional>
#include "log.hpp"//typedef std::function<void (int , const std::string &, const uint16_t &)> func_t;
using func_t = std::function<void (int , const std::string &, const uint16_t &, const std::string &)>;class Task
{
public:Task(){}Task(int sock, const std::string ip, uint16_t port, func_t func): _sock(sock), _ip(ip), _port(port), _func(func){}void operator ()(const std::string &name){_func(_sock, _ip, _port, name);}
public:int _sock;std::string _ip;uint16_t _port;// int type;func_t _func;
};

相关文章:
Linux--TCP编程--0216 17
观前提示:本篇博文的一些接口需要前几篇博文实现的 线程池的实现Liunx--线程池的实现--0208 09_Gosolo!的博客-CSDN博客 线程池的单例模式Linux--线程安全的单例模式--自旋锁--0211_Gosolo!的博客-CSDN博客 1.TCP编程需要用的接口 创建 sock…...
关于设计模式的记录
############### 先弄清楚类模型的关系 ############### 万物的抽象关系 ############### 1.组合 composition实菱形 实线 无填充箭头整体与部分的关系同生共死代码体现:成员变量如:生命体与器官,http请求(请求行,请求…...
Lambda-常见的函数式接口
如果需要使用Lambda接口,就必须要有一个函数式接口 函数式接口是有且仅有一个抽象方法的接口, 对应的注解是FunctionalInterface Java中内置的常见函数式接口如下: 1.Runnable/ Callable /*** The <code>Runnable</code> interface should be implem…...
P1196 [NOI2002] 银河英雄传说 带权并查集
[NOI2002] 银河英雄传说 题目背景 公元 580158015801 年,地球居民迁至金牛座 α\alphaα 第二行星,在那里发表银河联邦创立宣言,同年改元为宇宙历元年,并开始向银河系深处拓展。 宇宙历 799799799 年,银河系的两大军…...
【项目实战】快来入门Groovy的基础语法吧
一、Groovy是什么? 1.1 与Java语言的关系 下一代的Java 语言,增强Java平台的唯一的脚本语言跟java一样,它也运行在 JVM 中。支持Java平台,无缝的集成了Java 的类和库;Groovy是一种运行在JVM上的动态语言,跑在JVM中的另一种语言编译后的.groovy也是以class的形式出现的。1…...
Mybatis中的动态SQL
Mybatis中的动态SQL 当存在多条件查询的SQL时,当用户某个条件的属性没有写时,就会存在问题,在test中则不能很好的运行 所以Mybatis提出了动态SQL。 即判断用户是否输入了某个属性 动态SQL中的一些问题 方法一 这个里的and是为了确保if条…...
VUE常用API
1.$set数据变了,视图没变 this.$set(targe,key,value)2.$nextTick:返回参数[函数]。是一个异步的,功能获得更新后DOM$nextTick(callback){return Promise.resolve().then(()>{callback();}) }3.$refs获取dom4.$el获取当前组件根…...
25 openEuler管理网络-使用nmcli命令配置ip
文章目录25 openEuler管理网络-使用nmcli命令配置ip25.1 nmcli介绍25.2 设备管理25.2.1 连接到设备25.2.2 断开设备连接25.3 设置网络连接25.3.1 配置动态IP连接25.3.1.1 配置IP25.3.1.2 激活连接并检查状态25.3.2 配置静态IP连接25.3.2.1 配置IP25.3.2.2 激活连接并检查状态25…...
如何安装和使用A-ops工具?
一、pip配置 1.配置信任域 pip3 config set global.trusted-host mirrors.tools.huawei.com2.配置pip源的url地址pip3 config set global.index-url http://mirrors.tools.huawei.com/pypi/simple 二、npm安装及配置 npm -v检测系统有无安装npm,如果没有的话需要配置ope…...
MySql数据库环境部署
MySql基础与Sql数据库概述基础环境的建立MYSQL数据库的连接方法MySql的默认数据库数据库端口号数据库概述 数据库(DataBase,DB)∶存储在磁带、磁盘、光盘或其他外存介质上、按定结构组织在一起的相关数据的集合。数据库管理系统〈DataBase Management S…...
极品笔记,阿里P7爆款《K8s+Jenkins》技术笔记,职场必备
前些日子从阿里的朋友那里取得这两份K8sJenkins的爆款技术笔记:《K8S(kubernetes)学习指南》《Jenkins持续集成从入门到精通》,非常高质量的干货,我立马收藏! 而今天咱们文章的主角就是这非常之干货的技术笔记:K8SJenk…...
数据结构:各种排序方法的综合比较
排序方法的选用应视具体场合而定。一般情况下考虑的原则有:(1)待排序的记录个数 n;(2)记录本身的大小;(3)关键字的分布情况:(4)对排序稳定性的要求等。 1.时间性能 (1) 按平均的时间性能来分,有三类排序方法: 时间复杂度为 O(nlogn)的方法有:快速排序、堆排序和归并排序,其中…...
【设计模式】 策略模式介绍及C代码实现
【设计模式】 策略模式介绍及C代码实现 背景 在软件构建过程中,某些对象使用的算法可能多种多样,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂,而且有时候支持不使用的算法也是一个性能负担。 如何…...
【数据库】第二章 关系数据库
第二章 关系数据库 2.1关系数据结构及形式化定义 关系 域(domain) :域是一组具有相同数据类型的值的集合,可以取值的个数叫基数 笛卡尔积 :一个记录叫做一个元组(tuple),元组中每一个属性值,叫一个分量 基数&…...
oracle和mysql的分页
oracle的分页:rownum 注意:: 对 ROWNUM 只能使用 < 或 <, 用 、 >、 > 都不能返回任何数据。 rownum是对结果集的编序排列,始终是从1开始,所以rownum直接使用时不允许使用>、> 所以当查询中间部分的信息时&…...
深拷贝与浅拷贝的理解
浅拷贝的理解浅拷贝的话只会拷贝基本数据类型,例如像string、Number等这些,类似:Object、Array 这类的话拷贝的就是对象的一个指针(通俗来讲就是拷贝一个引用地址,指向的是一个内存同一份数据),也就是说当拷贝的对象数…...
Shell变量
一、变量分类 根据作用域分三种 (一)只在函数内有效,叫局部变量 (二)只在当前shell进程中有效,叫做全局变量 (三)在当前shell进程与子进程中都有效,叫做环境变量 shell进…...
Android 8请求权限时弹窗BUG
弹窗BUG 应用使用requestPermissions申请权限时,系统会弹出一个选择窗口,可进行允许或拒绝, 此窗口中有一个”不再询问“的选择框, ”拒绝”及“允许”的按钮。 遇到一个Bug,单点击“不再询问”,“允许”这个按钮会变…...
路漫漫:网络空间的监管趋势
网络空间是“以相互依存的网络基础设施为基本架构,以代码、信息与数据的流动为环境,人类利用信息通讯技术与应用开展活动,并与其他空间高度融合与互动的空间”。随着信息化技术的发展,网络空间日益演绎成为与现实人类生存空间并存…...
洛谷 P1208 [USACO1.3]混合牛奶 Mixing Milk
最后水一篇水题题解(实在太水了) # [USACO1.3]混合牛奶 Mixing Milk ## 题目描述 由于乳制品产业利润很低,所以降低原材料(牛奶)价格就变得十分重要。帮助 Marry 乳业找到最优的牛奶采购方案。 Marry 乳业从一些奶农手…...
未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?
编辑:陈萍萍的公主一点人工一点智能 未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战,在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...
Spark 之 入门讲解详细版(1)
1、简介 1.1 Spark简介 Spark是加州大学伯克利分校AMP实验室(Algorithms, Machines, and People Lab)开发通用内存并行计算框架。Spark在2013年6月进入Apache成为孵化项目,8个月后成为Apache顶级项目,速度之快足见过人之处&…...
【Zephyr 系列 10】实战项目:打造一个蓝牙传感器终端 + 网关系统(完整架构与全栈实现)
🧠关键词:Zephyr、BLE、终端、网关、广播、连接、传感器、数据采集、低功耗、系统集成 📌目标读者:希望基于 Zephyr 构建 BLE 系统架构、实现终端与网关协作、具备产品交付能力的开发者 📊篇幅字数:约 5200 字 ✨ 项目总览 在物联网实际项目中,**“终端 + 网关”**是…...
SpringCloudGateway 自定义局部过滤器
场景: 将所有请求转化为同一路径请求(方便穿网配置)在请求头内标识原来路径,然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
力扣热题100 k个一组反转链表题解
题目: 代码: func reverseKGroup(head *ListNode, k int) *ListNode {cur : headfor i : 0; i < k; i {if cur nil {return head}cur cur.Next}newHead : reverse(head, cur)head.Next reverseKGroup(cur, k)return newHead }func reverse(start, end *ListNode) *ListN…...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
