TCP半关闭状态分析和skynet对半关闭状态的支持

news2025/7/10 6:50:26

TCP半关闭状态分析

  • 一、背景
  • 二、TCP四次挥手流程
  • 三、发送FIN包的场景
  • 四、skynet 网络封装支持半关闭状态
    • 4.1、连接的建立
    • 4.2、连接断开
    • 4.3、消息到达
    • 4.4、消息发送完毕
  • 五、测试skynet对半关闭的支持
    • 5.1、测试直接关闭进程
    • 5.2、测试关闭读端
    • 5.2、测试关闭写端
  • 总结
  • 后言

一、背景

TCP四次挥手中的半关闭状态是否需要解决,依赖于使用场景,大多数场景不解决也不会有影响,但有些场景(特别是游戏服务器)还是需要关注这个半关闭状态的。
所谓半关闭,就是只关闭读端或写端;半关闭状态是接收到FIN包并返回ACK包时 到 发送FIN包前的状态。

对半关闭状态进行了解决的有JAVA的netty、skynet开源框架。

大多数网络连接程序在read=0时即调用close()关闭TCP连接;但是,在read=0到调用close()之间,可能还有很多数据需要发送(send),如果read=0时即调用close()那么对端就收不到这些数据了。

对TCP三次握手和四次挥手不了解的,可以查阅前面的文章《Linux网络设计之TCP网络协议栈》。

二、TCP四次挥手流程

需要四次挥手的原因:希望将FIN包控制权交给应用程序去处理,以便处理断开连接的善后工作,即对端可能还有数据要发送。
tcp_4_end_sch
四次挥手流程:

  1. 服务器收到FIN包,自动回复ACK包,进入CLOSE_WAIT状态。
  2. 此时TCP协议栈会为FIN包插入一个文件描述符EOF到内核态网络读缓冲区。
  3. 应用程序通过read=0来感知这个FIN包。
  4. 如果此时应用程序有数据要发送,则发送数据后调用关闭连接操作(发送FIN包到对端)。
  5. 双端关闭都需要一个FIN和一个ACK流程。
Client Server 四次挥手 FIN 进入FIN_WAIT_1状态 ACK 进入CLOSE_WAIT状态 进入FIN_WAIT_2状态 发送数据 FIN 进入LAST_ACK状态 ACK 进入TIME_WAIT状态 完成断开 alt [close] Client Server

三、发送FIN包的场景

  1. 关闭读端:调用shutdown(fd,SHUT_RD)。
  2. 关闭写端:调用shutdown(fd,SHUT_WR);半关闭连接,仍旧接收数据,继而等待对方关闭。
  3. 关闭双端:调用close(fd) 或者shoutdown(fd,SHUT_RDWR)。
  4. 关闭进程:内核协议栈会自动发送FIN包。

如果客户端想进入半关闭状态,需要关闭写端,服务端会把自己的读端关闭,从而进入半关闭状态。

如果客户端是关闭读端或者把读写端都关闭了(调用close(fd) 或者shoutdown(fd,SHUT_RDWR))或者是关闭了进程,那么会进入快速关闭流程:

Client Server 四次挥手 FIN 进入FIN_WAIT_1状态 ACK 进入CLOSE_WAIT状态 进入FIN_WAIT_2状态 FIN 进入LAST_ACK状态 RST 进入CLOSE状态,完成断开 完成断开 alt [close] Client Server

接受到对端回复的FIN包之后回复RST包,直接进入CLOSE状态,而没有TIME_WAIT状态,这是解决有大量TIME_WAIT现象的常用方法

思考:四次挥手中,为什么存在TIME_WAIT状态?
因为ACK不会重传,防止没有LAST_ACK或LAST_ACK丢失,对端没有收到LAST_ACK而一直重发FIN包。一直重发已经不存在的socket,可能会对新建立的连接造成干扰。

四、skynet 网络封装支持半关闭状态

skynet 网络层支持半关闭状态。skynet 采用 reactor 网络编程模型;reactor 网络模型是一种异步事件模型。
linux_epoll
通过 epoll_ctl 设置 struct epoll_event 中 data.ptr =(struct socket *)ud; 来完成 fd 与 actor绑定。skynet 通过 socket.start(fd, func) 来完成 actor 与 fd 的绑定。

4.1、连接的建立

客户端与服务端建立连接处理:建立成功的标识是 listenfd,有读事件触发。
服务端与第三方服务建立连接:建立成功的标识是 connect 返回的fd,有可写事件触发。

4.2、连接断开

skynet 网络层支持半关闭状态。

(1)读写端都关闭,epoll的事件EPOLLHUP 读写段都关闭:
(socket_epoll.h)

static int 
sp_wait(int efd, struct event *e, int max) {
	struct epoll_event ev[max];
	int n = epoll_wait(efd , ev, max, -1);
	int i;
	for (i=0;i<n;i++) {
		e[i].s = ev[i].data.ptr;
		unsigned flag = ev[i].events;
		e[i].write = (flag & EPOLLOUT) != 0;
		e[i].read = (flag & EPOLLIN) != 0;
		e[i].error = (flag & EPOLLERR) != 0;
		e[i].eof = (flag & EPOLLHUP) != 0;
	}

	return n;
}

(socket_server.c)

// 处理:直接关闭并向 actor 返回事件 SOCKET_CLOSE
if (e->eof) {
	// For epoll (at least), FIN packets are exchanged both ways.
	// See: https://stackoverflow.com/questions/52976152/tcp-when-is-epollhup-generated
	int halfclose = halfclose_read(s);
	force_close(ss, s, &l, result);
	 // 如果前面因为关闭读端已经发送SOCKET_CLOSE,在这里避免重复 SOCKET_CLOSE
	if (!halfclose) {
		return SOCKET_CLOSE;
	}
}

(2)检测读端关闭:
(socket_server.c)

// return -1 (ignore) when error
static int
forward_message_tcp(struct socket_server *ss, struct socket *s, struct socket_lock *l, struct socket_message * result) {
	int sz = s->p.size;
	char * buffer = MALLOC(sz);
	int n = (int)read(s->fd, buffer, sz);
	if (n<0) {
		FREE(buffer);
		switch(errno) {
		case EINTR:
		case AGAIN_WOULDBLOCK:
			break;
		default:
			return report_error(s, result, strerror(errno));
		}
		return -1;
	}
	if (n==0) {
		FREE(buffer);
		if (s->closing) {	// 如果该连接的 socket 已经关闭
			// Rare case : if s->closing is true, reading event is disable, and SOCKET_CLOSE is raised.
			if (nomore_sending_data(s)) {
				force_close(ss,s,l,result);
			}
			return -1;
		}
		int t = ATOM_LOAD(&s->type);
		if (t == SOCKET_TYPE_HALFCLOSE_READ) {	// 如果已经处理过读端关闭
			// Rare case : Already shutdown read.
			return -1;
		}
		if (t == SOCKET_TYPE_HALFCLOSE_WRITE) {	// 如果之前已经处理过写端关闭,则直接 close
			// Remote shutdown read (write error) before.
			force_close(ss,s,l,result);
		} else {
			close_read(ss, s, result);
		}
		return SOCKET_CLOSE;
	}

	if (halfclose_read(s)) {
		// discard recv data (Rare case : if socket is HALFCLOSE_READ, reading event is disable.)
		FREE(buffer);
		return -1;
	}

	stat_read(ss,s,n);

	result->opaque = s->opaque;
	result->id = s->id;
	result->ud = n;
	result->data = buffer;

	if (n == sz) {
		s->p.size *= 2;
		return SOCKET_MORE;
	} else if (sz > MIN_READ_BUFFER && n*2 < sz) {
		s->p.size /= 2;
	}

	return SOCKET_DATA;
}

(3)检测写端关闭:write < 0 && errno == EPIPE,能检测对端读端关闭。
(socket_server.c)

static int
send_list_tcp(struct socket_server *ss, struct socket *s, struct wb_list *list, struct socket_lock *l, struct socket_message *result) {
	while (list->head) {
		struct write_buffer * tmp = list->head;
		for (;;) {
			ssize_t sz = write(s->fd, tmp->ptr, tmp->sz);
			if (sz < 0) {
				switch(errno) {
				case EINTR:
					continue;
				case AGAIN_WOULDBLOCK:
					return -1;
				}
				return close_write(ss, s, l, result);
			}
			stat_write(ss,s,(int)sz);
			s->wb_size -= sz;
			if (sz != tmp->sz) {
				tmp->ptr += sz;
				tmp->sz -= sz;
				return -1;
			}
			break;
		}
		list->head = tmp->next;
		write_buffer_free(ss,tmp);
	}
	list->tail = NULL;

	return -1;
}

4.3、消息到达

单线程读。读策略:

int n = read(fd, buf, sz);

sz 初始值为 64,根据从网络接收数据情况,动态调整 sz 的大小。
(socket_server.c)

// return -1 (ignore) when error
static int
forward_message_tcp(struct socket_server *ss, struct socket *s, struct socket_lock *l, struct socket_message * result) {
	int sz = s->p.size;
	char * buffer = MALLOC(sz);
	int n = (int)read(s->fd, buffer, sz);
	if (n<0) {
		FREE(buffer);
		switch(errno) {
		case EINTR:
		case AGAIN_WOULDBLOCK:
			break;
		default:
			return report_error(s, result, strerror(errno));
		}
		return -1;
	}
	if (n==0) {
		FREE(buffer);
		if (s->closing) {
			// Rare case : if s->closing is true, reading event is disable, and SOCKET_CLOSE is raised.
			if (nomore_sending_data(s)) {
				force_close(ss,s,l,result);
			}
			return -1;
		}
		int t = ATOM_LOAD(&s->type);
		if (t == SOCKET_TYPE_HALFCLOSE_READ) {
			// Rare case : Already shutdown read.
			return -1;
		}
		if (t == SOCKET_TYPE_HALFCLOSE_WRITE) {
			// Remote shutdown read (write error) before.
			force_close(ss,s,l,result);
		} else {
			close_read(ss, s, result);
		}
		return SOCKET_CLOSE;
	}

	if (halfclose_read(s)) {
		// discard recv data (Rare case : if socket is HALFCLOSE_READ, reading event is disable.)
		FREE(buffer);
		return -1;
	}

	stat_read(ss,s,n);

	result->opaque = s->opaque;
	result->id = s->id;
	result->ud = n;
	result->data = buffer;

	if (n == sz) {
		s->p.size *= 2;
		return SOCKET_MORE;
	} else if (sz > MIN_READ_BUFFER && n*2 < sz) {
		s->p.size /= 2;
	}

	return SOCKET_DATA;
}

4.4、消息发送完毕

多线程写。同一个 fd 可以在不同的 actor 中发送数据。skynet底层通过加锁确保数据正确发送到 socket 的写缓冲区。

int 
socket_server_send(struct socket_server *ss, struct socket_sendbuffer *buf) {
	int id = buf->id;
	struct socket * s = &ss->slot[HASH_ID(id)];
	if (socket_invalid(s, id) || s->closing) {
		free_buffer(ss, buf);
		return -1;
	}

	struct socket_lock l;
	socket_lock_init(s, &l);
	
	// 确保能在fd上写数据,连接可用状态,检测 socket 线程是否在对该fd操作
	if (can_direct_write(s,id) && socket_trylock(&l)) {
		// may be we can send directly, double check
		if (can_direct_write(s,id)) {	// 双检测
			// send directly
			struct send_object so;
			send_object_init_from_sendbuffer(ss, &so, buf);
			ssize_t n;
			if (s->protocol == PROTOCOL_TCP) {
				// 尝试在 work 线程直接写,如果n >0,则写成功
				n = write(s->fd, so.buffer, so.sz);
			} else {
				union sockaddr_all sa;
				socklen_t sasz = udp_socket_address(s, s->p.udp_address, &sa);
				if (sasz == 0) {
					skynet_error(NULL, "socket-server : set udp (%d) address first.", id);
					socket_unlock(&l);
					so.free_func((void *)buf->buffer);
					return -1;
				}
				n = sendto(s->fd, so.buffer, so.sz, 0, &sa.s, sasz);
			}
			if (n<0) {
				// ignore error, let socket thread try again
				 // 如果失败,不要在 work 线程中处理异常,所有网络异常在 socket 线程中处理
				n = 0;
			}
			stat_write(ss,s,n);
			if (n == so.sz) {
				// write done
				socket_unlock(&l);
				so.free_func((void *)buf->buffer);
				return 0;
			}
			// write failed, put buffer into s->dw_* , and let socket thread send it. see send_buffer()
			s->dw_buffer = clone_buffer(buf, &s->dw_size);
			s->dw_offset = n;

			socket_unlock(&l);

			struct request_package request;
			request.u.send.id = id;
			request.u.send.sz = 0;
			request.u.send.buffer = NULL;
			 // 如果写失败,可能写缓冲区满,或被中断打断,直接交由 socket 线程去重试。
			 // 这里通过 pipe 来与 socket 线程通信。
			// let socket thread enable write event
			send_request(ss, &request, 'W', sizeof(request.u.send));

			return 0;
		}
		socket_unlock(&l);
	}

	inc_sending_ref(s, id);

	struct request_package request;
	request.u.send.id = id;
	request.u.send.buffer = clone_buffer(buf, &request.u.send.sz);

	send_request(ss, &request, 'D', sizeof(request.u.send));
	return 0;
}

注意:在 work 线程中调用的。如果work线程写失败,可能写缓冲区满,或被中断打断,直接交由 socket 线程去重试。这里通过 pipe 来与 socket 线程通信。

五、测试skynet对半关闭的支持

main.lua

local skynet = require "skynet"
local socket = require "skynet.socket"

skynet.start(function ()
    local listenfd = socket.listen("0.0.0.0", 8888)
    socket.start(listenfd, function (clientfd, addr)
        print("receive a client:", clientfd, addr)
        socket.start(clientfd)
        while true do
            local data = socket.readline(clientfd, "\n")
            if not data then -- read = 0
                -- for i=1,10 do
                    socket.write(clientfd, "FINAL\n")
                -- end
                socket.close(clientfd)
                print("closed", clientfd)
                return
            end
            if data == "quit" then
                print("recv quit", clientfd)
                socket.close(clientfd)
                return
            else
                print("S recv:", data)
                socket.write(clientfd, "=> "..data.."\n")
            end
        end
    end)
    
end)

client.lua

package.cpath = "./skynet/luaclib/?.so"
package.path = "./skynet/lualib/?.lua;./?.lua"

if _VERSION ~= "Lua 5.4" then
	error "Use lua 5.4"
end

local socket = require "client.socket"

local fd = assert(socket.connect("127.0.0.1", 8888))

local function send_package(pack)
	socket.send(fd, pack .. "\n")
end

local function unpack_package(text)
	local size = #text
	if size < 1 then
		return nil, text
	end
    local pos = text:find("\n", 1, true)
	if not pos then
		return nil, text
	end

	return text:sub(1,pos-1), text:sub(pos+1)
end

local function recv_package(last)
	local result
	result, last = unpack_package(last)
	if result then
		return result, last
	end
	local r = socket.recv(fd)
	if not r then
		return nil, last
	end
	if r == "" then
		error "Server closed"
	end
	return unpack_package(last .. r)
end

local last = ""

local function dispatch_package()
	while true do
		local v
		v, last = recv_package(last)
		if not v then
			break
		end
        print(v)
	end
end

while true do
	dispatch_package()
	local cmd = socket.readstdin()
	if cmd then
		if cmd == "quit" then
			send_package("quit")
        elseif cmd == "shut_r" then
            -- send_package("shut_r")
            socket.shutdown(fd, "r")
            -- send_package("shut_r")
        elseif cmd == "shut_w" then
            -- send_package("shut_w")
            send_package("shut_w1")
            send_package("shut_w2")
            send_package("shut_w3")
            send_package("shut_w4")
            socket.shutdown(fd, "w")
		else
			send_package(cmd)
		end
	else
		socket.usleep(100)
	end
end

config

thread=4
logger=nil
harbor=0
start="main" -- 启动第一个服务
lua_path="./skynet/lualib/?.lua;".."./skynet/lualib/?/init.lua;".."./lualib/?.lua;"
luaservice="./skynet/service/?.lua;./app/?.lua"
lualoader="./skynet/lualib/loader.lua"
cpath="./skynet/cservice/?.so"
lua_cpath="./skynet/luaclib/?.so"

Makefile

SKYNET_PATH?=./skynet

all:
	cd $(SKYNET_PATH) && $(MAKE) PLAT='linux'

clean:
	cd $(SKYNET_PATH) && $(MAKE) clean

项目结构:

-- mytest
----+ skynet 
----| app
-------- main.lua
-------- client.lua
----+ config
----+ Makefile

编译和运行:

make
./skynet/skynet config

启动client.lua的方式:

./skynet/3rd/lua/lua ./app/client.lua

5.1、测试直接关闭进程

启动client.lua,然后CTL+C直接关闭进程。

./skynet/3rd/lua/lua ./app/client.lua
  1. 此时内核协议栈会收到一个FIN包,内核协议栈的读缓冲区插入EOF,并触发读事件。
  2. skynet开始读数据,读到EOF(即read()==0);开始关闭读事件和读端。
  3. 然后进入main.lua的while循环,尝试发送消息;但是消息是无法发送出去了,因为客户端进程已经关闭,资源已经被释放,协议栈会把其丢弃。
  4. 注意,读数据、写数据、业务逻辑都在线程池执行,socket网络线程只负责监听触发事件;socket网络线程和线程池通过pipeline消息交互,epoll可以监听这个消息,然后注册读、写事件。
socket_server.c
skynet_socket.c
skynet_start.c
close_read()
forward_message_tcp()
socket_server_poll()
forward_message()
skynet_socket_poll()
wakeup
thread_socket()

5.2、测试关闭读端

启动client.lua,输入shut_r命令。

$ ./skynet/3rd/lua/lua ./app/client.lua 
shut_r
  1. 关闭读端,会走RST流程,立即进入CLOSE状态,服务端会报出“Connection reset by peer”错误。
  2. 服务端关闭写端,但仍然能接受到客户端发送的数据。
  3. 接受完数据后才调用关闭读端,结束连接。
receive a client:       4       127.0.0.1:35384
S recv: shut_r
S recv: shut_r2
closed  4
[:00000008] socket: error on 4 Connection reset by peer

5.2、测试关闭写端

启动client.lua,输入shut_w命令。

$ ./skynet/3rd/lua/lua ./app/client.lua 
shut_w
  1. 关闭写端,服务器会关闭读端。
  2. 在完全关闭连接之前,服务器还可以发送数据到客户端。

server端:

receive a client:       5       127.0.0.1:35486
S recv: shut_w1
S recv: shut_w2
S recv: shut_w3
S recv: shut_w4
closed  5

client端:

shut_w
SHUTDOWN 3 1
=> shut_w1
=> shut_w2
=> shut_w3
=> shut_w4
FINAL

总结

ACK包是不会重发的,如果ACK包丢失了,那么发送端在一定时间内接受不到数据就会重发FIN包,一般三次FIN包都没有收到ACK就会直接进入close。

后言

本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux系统提升感兴趣的读者,可以点击链接查看详细的服务:C/C++服务器开发 。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/109159.html

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

相关文章

经典设计模式总则

Design pattern 设计模式背景-概念、面向对象六大原则、设计模式分类、二十三中常用设计模式即创建型模式、结构型模式、行为型模式 1、设计模式的背景、概念及其必要性 1.1、设计模式的背景 设计模式最初并不是应用于软件设计领域&#xff0c;而是被用于建筑领域的设计中。 …

圣诞节送哪款电容笔合适?平价电容笔排行

随着技术的发展&#xff0c;各种品牌的电容笔也随之出现。一款出色的电容笔可以极大地提升我们的工作效率&#xff0c;并改善我们的学习方式。就目前的技术而言&#xff0c;平替电容笔无论从质量还是性能上都是物有所值的&#xff0c;其表现与苹果的原装电容笔相差无几。下面就…

谈谈vue的路由守卫和keep-alive后生命周期

目录 &#x1f53d; Vue-Router的懒加载如何实现 1、方案一&#xff1a;箭头函数import 2、方案二&#xff1a;箭头函数require 3、方案三&#xff1a;箭头函数require.ensure &#x1f53d; 如何定义动态路由 param方式 query方式 &#x1f53d; Vue-Router导航守卫 …

美颜sdk背景扭曲修复算法的实现流程

目前&#xff0c;美颜sdk在对人像进行美型美体等编辑处理的时候&#xff0c;想要保证背景不受影响&#xff0c;是比较困难的&#xff0c;如果需要在手机端上进行处理&#xff0c;难度更大&#xff0c;主要有以下几点&#xff1a; 一、难点分析 1、拍摄背景多变&#xff0c;背景…

反向迭代器reverse_iterator模拟实现

准备工作 相同的命名空间可以分割在不同的文件中,编译器最后都会合成在同一个命名空间下。我们的reverse_iterator是个适配器&#xff0c;为什么叫适配器&#xff0c;是因为它需用正向迭代器做适配。简言之&#xff0c;反向迭代器通过正向迭代器做实例化会减少很多冗余且方便很…

基于Fragstats的土地利用景观格局分析

景观格局及相关软件介绍 Fragstats界面与数据格式 数据准备&#xff1a;ArcGIS软件操作 数据准备&#xff1a;数据结构及变换 数据准备&#xff1a;数据投影及变换 数据准备&#xff1a;数据采集与编辑 数据准备&#xff1a;数据获取及处理 土地利用统计分析 Fragstats…

微服务(一) —— 概念

目录1. 什么是微服务2. springcloud3. 服务提供者、服务消费者1. 什么是微服务 微服务&#xff1a; 分布式架构的一种。 服务集群&#xff1a;将一个功能复杂的项目拆分成许多个独立的项目&#xff08;称为服务&#xff0c;每部分完成一定的功能&#xff09;&#xff0c;并进…

继承、多态、组合(Java系列5)

目录 前言&#xff1a; 1.继承 1.1继承的概念 1.2继承的语法 1.3父类成员访问 1.4super关键字 1.5super和this 1.6继承关系的执行顺序 1.7继承方式 1.8final关键字 2.继承与组合 3.多态 3.1多态的概念 3.2多态实现的条件 4.重写 4.1重写的概念 4.2方法重写的规…

前端基础(十五)_多栏布局(两列自适应布局、圣杯布局---三列布局、双飞翼布局--三列布局、等高布局)

什么是自适应&#xff1f; 自适应&#xff1a;让同一个页面自动适应不同大小的设备&#xff0c;从而解决为不同设备提供不同版本页面的问题。 自适应布局&#xff1a;解决在不同大小的设备上呈现相同网页的问题 两列自适应布局 1、Html结构中–左右两个盒子&#xff1b; 2、…

UT斯达康MC8638S-高安-S905-河北联通-破解刷机线刷固件包

UT斯达康MC8638S-高安-S905-河北联通-破解刷机线刷固件包 固件特点&#xff1a; 1、修改dns&#xff0c;三网通用&#xff1b; 2、开放原厂固件屏蔽的市场安装和u盘安装apk&#xff1b; 3、无开机广告&#xff0c;无系统更新&#xff0c;不在被强制升级&#xff1b; 4、大…

远离不恰当的运动方式,缤跃酒店满足大众对专业化、品质化健身场所的需求!

2022年&#xff0c;各大新闻平台关于“横纹肌溶解综合征”的新闻报道屡见不鲜&#xff0c;横纹肌溶解是一种因肌肉组织严重受损导致的综合征&#xff0c;严重的可能会出现急性肾损伤、心律失常&#xff0c;甚至死亡。探究原因&#xff0c;这些患者多是由于运动过量或不当被送入…

深度解读|NebulaGraph x 阿里云计算巢,云上构建超大规模图数据库

近期&#xff0c;杭州悦数科技有限公司与阿里云计算巢达成合作&#xff0c;NebulaGraph 作为首款图数据库产品正式入驻阿里云计算巢&#xff0c;为用户带来了云端一键部署企业级图数据库集群的全新体验。同时&#xff0c;该服务集成了多款 NebulaGraph 周边可视化图数据库管理工…

python-面向对象

目录 面向对象 封装 继承 重写 重载 多态 单下划线、双下划线、头尾双下划线说明&#xff1a; 面向对象 类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。类变量&#xff1a;类变量在整个实例化的对…

ENSP防火墙进入web登陆界面

步骤 新建拓扑【选择USG6000V】然后导入USG6000V得镜像包进入到防火墙的CLI界面 账户与密码 账户&#xff1a;admin 密码Admin123&#xff08;密码输入不会显示&#xff09; 输入正确账户密码后会提醒修改密码输入 y 回车后提醒如下&#xff1a; 输入旧密码 输入新密码&…

基于禁忌搜索的TSP问题求解仿真输出路线规划图和收敛曲线

目录 1.算法描述 2.仿真效果预览 3.MATLAB核心程序 4.完整MATLAB 1.算法描述 禁忌搜索&#xff08;Tabu Search或Taboo Search&#xff0c;简称TS&#xff09;是对局部搜索&#xff08;LS&#xff09;的一种扩展&#xff0c;是一种全局寻优算法&#xff0c;其特点是采用禁忌…

践行者访谈实录:你真的了解CMMI吗?

2022年12月21日晚8点&#xff0c;我参与了《践行者》访谈节目&#xff0c;历时2小时&#xff0c;就CMMI有关的话题和主持人徐东伟老师&#xff0c;和热心的听众进行了在线交流。节目结束后&#xff0c;禅道公司的小朋友们整理了文字记录如下。 相信大家对CMMI的认知或多或少地…

Android自定义ViewGroup的布局,往往都是从流式布局开始

前言 前面几篇我们简单的复习了一下自定义 View 的测量与绘制&#xff0c;并且回顾了常见的一些事件的处理方式。 那么如果我们想自定义 ViewGroup 的话&#xff0c;它和自定义View又有什么区别呢&#xff1f;其实我们把 ViewGroup 当做 View 来用的话也不是不可以。但是既然…

端到端网络全链路监控方案

结构日渐复杂&#xff0c;设备类型、设备数量逐渐增加&#xff0c;设备间的连接关系随之复杂化&#xff0c;同时随着无线网络的发展&#xff0c;网络中的连接关系逐渐去“线”化&#xff0c;如何可观、高效的对网络间复杂的连接关系进行监控和管理&#xff0c;成为用户不可忽视…

2022年最好用的五款设备管理软件

工厂是典型的设备密集型组织&#xff0c;设备固定资产具有数量多、种类多、使用周期长、使用地点分散等特征。如果依然在使用传统的手工记录数据、手工巡检、纸质维保、电话维修的方式&#xff0c;势必给企业带来损失。 设备是众多企业经营中支出的主要组成部分&#xff0c;在…

(二十)Vue之非单文件组件

文章目录基本使用一、如何定义一个组件&#xff1f;二、如何注册组件&#xff1f;三、如何使用组件&#xff1f;演示程序普通Vue程序单文件组件程序局部注册全局注册几个注意点1.关于组件名2.关于组件标签3.一个简写方式组件的嵌套使用关于VueComponent一个重要的内置关系&…