UDP send 出现大量“Resource temporarily unavailable”

背景

        最近排查用户现场环境,查看日志出现大量的“send: Resource temporarily unavailable”错误,UDP设置NO_BLOCK模式,send又发生在进程上下文,并且还设置了SO_SNDBUF 为8M,在此情况下为什么还会出现发送队列满的情况,由此产生几个问题?

  • send 在什么情况下会报“Resource temporarily unavailable”错误?
  • send工作在进程上下文中,发送的SKB报文在什么时机进行释放?
  • /proc/net/softnet_stat 第三列数不停的再增加?
  • SO_SNDBUF的设置真的生效了么,netstat -napu 接收队列并没有达到阈值8M?
内核源码分析

  • send 在什么情况下会报“Resource temporarily unavailable”错误?

 我们跟踪kernel 源码,用户态调用系统调用send,内核跟踪路径

/* trace-cmd record -p function_graph -g __sys_sendto  */
__sys_sendto() {
    __sock_sendmsg() {
        inet_sendmsg() {
            udp_sendmsg() {
                /* 查找路由,经历了策略路由 */
                ip_route_output_flow() {
                    ip_route_output_key_hash() {
                        ip_route_output_key_hash_rcu() {
                            __fib_lookup() {
                                fib4_rule_match();
                                fib4_rule_action() {
                                    fib_get_table();
                                    fib_table_lookup();
                                }
                                fib4_rule_suppress();
                            }
                        }
                    }
                }
            }
            /* 生成skb 结构 */
            ip_make_skb() {
                __ip_append_data.isra.0() {
                    sock_alloc_send_skb() {
                        /* 这个函数特别分析 */
                        sock_alloc_send_pskb() {
                            alloc_skb_with_frags() {
                                __alloc_skb() {
                                    skb_set_owner_w();
                                }
                            }
                        }
                    }
                }
            }
            udp_send_skb.isra.0() {
                ip_send_skb() {
                    ip_local_out() {
                        __ip_local_out() {
                            /* 进入netfilter 框架 */
                            nf_hook_slow() {
                                ipv4_conntrack_local() {
                                    nf_conntrack_in() {
                                    }
                                }
                            }
                            ip_output() {
                                nf_hook_slow() {
                                    iptable_mangle_hook() {
                                        ipt_do_table() {
                                        }
                                    }
                                    nf_nat_ipv4_out() {
                                        nf_nat_ipv4_fn() {
                                        }
                                    }
                                    ipv4_confirm() {
                                        nf_confirm() {
                                        }
                                    }
                                }
                                /* IP 层处理完毕 */
                                ip_finish_output() {
                                    __ip_finish_output() {
                                        ip_finish_output2() {
                                            dev_queue_xmit() {
                                                __dev_queue_xmit() {
                                                    netdev_core_pick_tx() {
                                                        netdev_pick_tx() {
                                                        }
                                                    }
                                                }
                                                _raw_spin_lock();
                                                sch_direct_xmit() {
                                                }
                                                /* 发送到网卡,这里使用的vmxnet3 虚拟网卡 */
                                                _raw_spin_lock();
                                                dev_hard_start_xmit() {
                                                    vmxnet3_xmit_frame() {
                                                        vmxnet3_tq_xmit.isra.0() {
                                                            _raw_spin_lock_irqsave();
                                                            vmxnet3_map_pkt.isra.0();
                                                            _raw_spin_unlock_irqrestore();
                                                        }
                                                    }
                                                }
                                            }
                                            __qdisc_run() {
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

 根据调用栈信息看到整个sendto,处于一个进程上下文中,经历了socket层,ip层,netfilter, qdisc, driver 层,我们特别注意到sock_alloc_send_pskb() 函数

struct sk_buff *sock_alloc_send_pskb(struct sock *sk, unsigned long header_len,
				     unsigned long data_len, int noblock,
				     int *errcode, int max_page_order)
{
	struct sk_buff *skb;
	long timeo;
	int err;

    /* 这里我们设置的noblock, timeo=0 */
	timeo = sock_sndtimeo(sk, noblock);
	for (;;) {
		err = sock_error(sk);
		if (err != 0)
			goto failure;

		err = -EPIPE;
		if (sk->sk_shutdown & SEND_SHUTDOWN)
			goto failure;

        /*
            static inline int sk_wmem_alloc_get(const struct sock *sk)
            {
	            return refcount_read(&sk->sk_wmem_alloc) - 1;
            }
            
            sk->sk_wmem_alloc 和 sk->sk_sndbuf 进行对比,当大于时进行等待或者返回
            -EAGAIN, 
        */
		if (sk_wmem_alloc_get(sk) < READ_ONCE(sk->sk_sndbuf))
			break;

        /* alloc wmem(write memory) 大于sk->sk_sndbuf,并且timeo 为0,返回-EAGIN*/ 
		sk_set_bit(SOCKWQ_ASYNC_NOSPACE, sk);
		set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
		err = -EAGAIN;
		if (!timeo)
			goto failure;
		if (signal_pending(current))
			goto interrupted;
		timeo = sock_wait_for_wmem(sk, timeo);
	}
	skb = alloc_skb_with_frags(header_len, data_len, max_page_order,
				   errcode, sk->sk_allocation);
    /*
        skb_set_owner_w() {
            skb->destructor = sock_wfree;
            /* 成功申请写内存后,增加sk_wmem_alloc 变量 */
            refcount_add(skb->truesize, &sk->sk_wmem_alloc);
        }
    */
	if (skb)
		skb_set_owner_w(skb, sk);
	return skb;

interrupted:
	err = sock_intr_errno(timeo);
failure:
	*errcode = err;
	return NULL;
}
void skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
{
    /* 设置skb 为孤儿状态 */
	skb_orphan(skb);
	skb->sk = sk;
#ifdef CONFIG_INET
	if (unlikely(!sk_fullsock(sk))) {
		skb->destructor = sock_edemux;
		sock_hold(sk);
		return;
	}
#endif
    /* 注意这个回调函数,sock_wfree() 什么时机调用?*/
	skb->destructor = sock_wfree;
	skb_set_hash_from_sk(skb, sk);
	/*
	 * We used to take a refcount on sk, but following operation
	 * is enough to guarantee sk_free() wont free this sock until
	 * all in-flight packets are completed
	 */
    /* 注释已经解释很清楚了,我们释放内存,直到报文处理完成 */
	refcount_add(skb->truesize, &sk->sk_wmem_alloc);
}
void sock_wfree(struct sk_buff *skb)
{
	struct sock *sk = skb->sk;
	unsigned int len = skb->truesize;

	if (!sock_flag(sk, SOCK_USE_WRITE_QUEUE)) {
		/*
		 * Keep a reference on sk_wmem_alloc, this will be released
		 * after sk_write_space() call
		 */
		WARN_ON(refcount_sub_and_test(len - 1, &sk->sk_wmem_alloc));
		sk->sk_write_space(sk);
		len = 1;
	}
	/*
	 * if sk_wmem_alloc reaches 0, we must finish what sk_free()
	 * could not do because of in-flight packets
	 */
	if (refcount_sub_and_test(len, &sk->sk_wmem_alloc))
		__sk_free(sk);
}

 到这里我们可以看到,当我们申请sk_wmem_alloc大于我们设置的sk_sndbuf时,系统调用直接返回给我们-EAGAIN错误码,sk_wmem_alloc 什么时机增加和减少?整个发送的进程上下文暂未找到减少的流程,从代码可以看出,在什么时机调用sock_wfree(),就是更新sk_wmem_alloc。

  • send工作在进程上下文中,发送的SKB报文在什么时机进行释放?
# bpftrace -e 'kprobe:sock_wfree {printf ("%s\n", kstack());}'
    sock_wfree+1
    skb_release_all+19
    kfree_skb+50
    __dev_kfree_skb_any+59
    vmxnet3_tq_tx_complete+254
    vmxnet3_poll_rx_only+121
    net_rx_action+322
    __softirqentry_text_start+209
    irq_exit+174
    do_IRQ+90
    ret_from_intr+0
    native_safe_halt+14
    arch_cpu_idle+21
    default_idle_call+35
    do_idle+507
    cpu_startup_entry+32
    start_secondary+376
    secondary_startup_64+164

这里可以看到,在中断上下文中do_IRQ() 触发了软中net_rx_action(),调用了vmxnet3_poll_rx_only() 和 vmxnet3_tq_tx_complete() 中kfree_skb() 函数回调 sock_wfree(),其发送过skb后,并没有立刻释放skb,借用飞哥《理解了实现再谈性能》的一张图第6,7步骤。这部分内容书中有详细的介绍,可以详细看下此书。

 至此我们也可以理解为什么send() 时会报-EAGAIN,因为发送申请内存和释放内存是异步操作的,当CPU比较繁忙,send发送比回收快时,很快将sk_sndbuf 占满,会发生EAGAIN情况,这里需要注意 /proc/net/softnet_stat 文件的第三列 的数据变化。

  • /proc/net/softnet_stat 第三列数不停的再增加?
static __latent_entropy void net_rx_action(struct softirq_action *h)
{
	struct softnet_data *sd = this_cpu_ptr(&softnet_data);
	unsigned long time_limit = jiffies +
		usecs_to_jiffies(netdev_budget_usecs);
	int budget = netdev_budget;
	LIST_HEAD(list);
	LIST_HEAD(repoll);

	local_irq_disable();
	list_splice_init(&sd->poll_list, &list);
	local_irq_enable();

	for (;;) {
		struct napi_struct *n;

		if (list_empty(&list)) {
			if (!sd_has_rps_ipi_waiting(sd) && list_empty(&repoll))
				goto out;
			break;
		}

		n = list_first_entry(&list, struct napi_struct, poll_list);
		budget -= napi_poll(n, &repoll);

		/* If softirq window is exhausted then punt.
		 * Allow this to run for 2 jiffies since which will allow
		 * an average latency of 1.5/HZ.
		 */
        /*
            这里影响单词softirq 处理报文个数有两个参数决定:
           netdev_budget 和 netdev_budget_usecs
        */
		if (unlikely(budget <= 0 ||
			     time_after_eq(jiffies, time_limit))) {
            /* percpu squeeze 对应 /proc/net/softnet_stat 第三列 */
			sd->time_squeeze++;
			break;
		}
	}

	local_irq_disable();

	list_splice_tail_init(&sd->poll_list, &list);
	list_splice_tail(&repoll, &list);
	list_splice(&list, &sd->poll_list);
	if (!list_empty(&sd->poll_list))
		__raise_softirq_irqoff(NET_RX_SOFTIRQ);

	net_rps_action_and_irq_enable(sd);
out:
	__kfree_skb_flush();
}

 第三列的变化是取值于time_squeeze变量,也就表明单次软中处理已经无法处理完,需要唤醒ksoftirqd 内核线程协助处理,此时未处理的软中断事务,比如释放skb将由内核线程ksoftirqd处理,这将延后到内核线程ksoftirqd 何时得到调度,在我们客户现场环境是关闭内核抢占的。

我们在看下处理软中断函数

asmlinkage __visible void do_softirq(void)
{
	__u32 pending;
	unsigned long flags;

	if (in_interrupt())
		return;

	local_irq_save(flags);

	pending = local_softirq_pending();

    /* 当 ksofirqd 已经启动的时候,我们不会再继续调用do_softirq,注释也能 明确说明*/
	if (pending && !ksoftirqd_running(pending))
		do_softirq_own_stack();

	local_irq_restore(flags);
}

/*
 * If ksoftirqd is scheduled, we do not want to process pending softirqs
 * right now. Let ksoftirqd handle this at its own rate, to get fairness,
 * unless we're doing some of the synchronous softirqs.
 */
#define SOFTIRQ_NOW_MASK ((1 << HI_SOFTIRQ) | (1 << TASKLET_SOFTIRQ))
static bool ksoftirqd_running(unsigned long pending)
{
	struct task_struct *tsk = __this_cpu_read(ksoftirqd);

	if (pending & SOFTIRQ_NOW_MASK)
		return false;
	return tsk && (tsk->state == TASK_RUNNING) &&
		!__kthread_should_park(tsk);
}

看代码逻辑,当有新的软中断事件发生时,会检测ksoftirqd的运行情况,已经是运行状态时,不会调用do_softirq(),对应网卡软中断事件来说就是不处理发送后的SKB回收,由ksoftirqd 来处理,这样就加剧了SKB回收的工作,增加send 出现EAGAIN的可能性。

  • SO_SNDBUF的设置真的生效了么,netstat -napu 接收队列并没有达到阈值8M?

明明我们程序设置了8M的snd_buf,但是netstat -napu 确始终未发现超过8M的大小,这个需要从源码分析

int sock_setsockopt(struct socket *sock, int level, int optname,
		    char __user *optval, unsigned int optlen)
{

	switch (optname) {
	case SO_SNDBUF:
		/* Don't error on this BSD doesn't and if you think
		 * about it this is right. Otherwise apps have to
		 * play 'guess the biggest size' games. RCVBUF/SNDBUF
		 * are treated in BSD as hints
		 */
		val = min_t(u32, val, sysctl_wmem_max);
set_sndbuf:
		/* Ensure val * 2 fits into an int, to prevent max_t()
		 * from treating it as a negative value.
		 */
		val = min_t(int, val, INT_MAX / 2);
		sk->sk_userlocks |= SOCK_SNDBUF_LOCK;
		WRITE_ONCE(sk->sk_sndbuf,
			   max_t(int, val * 2, SOCK_MIN_SNDBUF));
		/* Wake up sending tasks if we upped the value. */
		sk->sk_write_space(sk);
		break;

	case SO_SNDBUFFORCE:
		if (!capable(CAP_NET_ADMIN)) {
			ret = -EPERM;
			break;
		}

		/* No negative values (to prevent underflow, as val will be
		 * multiplied by 2).
		 */
		if (val < 0)
			val = 0;
		goto set_sndbuf;

	case SO_RCVBUF:
		/* Don't error on this BSD doesn't and if you think
		 * about it this is right. Otherwise apps have to
		 * play 'guess the biggest size' games. RCVBUF/SNDBUF
		 * are treated in BSD as hints
		 */
		val = min_t(u32, val, sysctl_rmem_max);
set_rcvbuf:
		/* Ensure val * 2 fits into an int, to prevent max_t()
		 * from treating it as a negative value.
		 */
		val = min_t(int, val, INT_MAX / 2);
		sk->sk_userlocks |= SOCK_RCVBUF_LOCK;
		/*
		 * We double it on the way in to account for
		 * "struct sk_buff" etc. overhead.   Applications
		 * assume that the SO_RCVBUF setting they make will
		 * allow that much actual data to be received on that
		 * socket.
		 *
		 * Applications are unaware that "struct sk_buff" and
		 * other overheads allocate from the receive buffer
		 * during socket buffer allocation.
		 *
		 * And after considering the possible alternatives,
		 * returning the value we actually used in getsockopt
		 * is the most desirable behavior.
		 */
		WRITE_ONCE(sk->sk_rcvbuf,
			   max_t(int, val * 2, SOCK_MIN_RCVBUF));
		break;

	case SO_RCVBUFFORCE:
		if (!capable(CAP_NET_ADMIN)) {
			ret = -EPERM;
			break;
		}

		/* No negative values (to prevent underflow, as val will be
		 * multiplied by 2).
		 */
		if (val < 0)
			val = 0;
		goto set_rcvbuf;
}

 从代码可以看出当我们使用SO_SNDBUF时候,会强制 min_t(u32, val, sysctl_wmem_max)取小,也就是上限为sysctl_wmem_max的大小,查看系统cat /proc/sys/net/core/wmem_max,最大为229376,也就是说我们应用层所有设置SO_SNDBUF都有这个问题,此时查看了man 手册有这段话

       SO_SNDBUF
              Sets  or  gets the maximum socket send buffer in bytes.  The kernel doubles this value
              (to allow space for bookkeeping overhead) when it is set using setsockopt(2), and this
              doubled  value  is  returned  by  getsockopt(2).   The  default  value  is  set by the
              /proc/sys/net/core/wmem_default file and the maximum  allowed  value  is  set  by  the
              /proc/sys/net/core/wmem_max  file.   The  minimum  (doubled)  value for this option is
              2048.

       SO_SNDBUFFORCE (since Linux 2.6.14)
              Using this socket option, a privileged (CAP_NET_ADMIN) process can  perform  the  same
              task as SO_SNDBUF, but the wmem_max limit can be overridden.

 这里已经给出了答案,当我们使用SO_SNDBUF时,会有wmem_max 的上限,如果我们想改变限制,就使用SO_SNDBUFFORCE强制更新写缓存区大小。

解决方案

之前的领导有句话,“发现问题,并解决问题”,根据上面分析为缓解send 出现EAGIN的情况,有以下优化方案

  1. 通过设置setsockopt 使用SO_SNDBUFFORCE类型,强制修改系统限制
  2. 适当增加 netdev_budget 和 netdev_budget_usecs 数值,增加单次软中断处理能力
  3. 优化应用软件,出现这种情况应用软件100%占用CPU,导致ksoftirqd 处理变慢
总结

至此,我们已经分析开头所说困惑,一个小小的“Resource temporarily unavailable”错误,背后蕴藏着太多技术细节,如果得过且过将来必成后患。

工作中遇到的每个小问题,背后都蕴藏着大量知识,只有平时多积累总结,才能游刃有余解决所面对的问题。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

iOS —— 初识KVO

iOS —— 初始KVO KVO的基础1. KVO概念2. KVO使用步骤注册KVO监听实现KVO监听销毁KVO监听 3. KVO基本用法4. KVO传值禁止KVO的方法 注意事项&#xff1a; KVO的基础 1. KVO概念 KVO是一种开发模式&#xff0c;它的全称是Key-Value Observing (观察者模式) 是苹果Fundation框架…

蓝桥备赛——DFS

废话不多说&#xff0c;先上题 对应代码如下&#xff1a; def dfs(x,y):global numfor i in range(0,4):dir[(-1,0),(0,-1),(1,0),(0,1)]nx,nyxdir[i][0] ,ydir[i][1]if nx<0 or nx>hx or ny <0 or ny>wy: continueif mp[nx][ny]*:num1print("%d:%s->%d%…

ROS 2边学边练(3)-- 何为节点(nodes)

在接触节点这个概念之前&#xff0c;我们先来看看下面这张动态图&#xff0c;更方便我们理解一些概念和交互过程。 &#xff08;相信大家的英文基础哈&#xff09; 概念 如上图所示&#xff0c;这里面其实涉及到了三个概念&#xff08;功能&#xff09;&#xff0c;分别是节点…

深入解析Spring MVC: 原理、流程【面试版】

什么是SpringMV? 1.是一个基于MVC的web框架&#xff1b; 2.是spring的一个模块&#xff0c;是spring的子容器&#xff0c;子容器可以拿父容器的东西&#xff0c;但是反过来不可&#xff1b; 2.SpringMVC的前端控制器是DispatcherServlet&#xff0c;用于分发请求。使开发变…

009——服务器开发环境搭建及开发方法(上)

目录 一、环境搭建 1.1网络环境 1.2 文件传输环境搭建 1.2.1 nfs环境 1.2.2 tftp环境 1.3 源码环境搭建 1.4 代码托管 1.5 配置交叉编译工具链 二、 开发方式 2.1 内核、设备树、驱动 make mrproper make 100ask_imx6ull_mini_defconfig​编辑 make zImage -j4 m…

Kubernetes Gateway API 介绍

Kubernetes Gateway API 诞生背景 在 kubernetes 中&#xff0c;流量的治理主要分为两个部分&#xff1a; 南北向流量东西向流量 南北向流量&#xff08;NORTH-SOUTH traffic&#xff09; 在计算机网络中&#xff0c;南北向流量通常指数据流量从一个**内部网络&#xff08;…

结构数列演化中的分枝

假设一个6*6的平面&#xff0c;这个平面的行和列可以自由的变换。 已知一个4点的结构数列顺序为 9 1 10 6 16 14 5 15 8 12 11 13 7 2 4 3 让这个数列按照4-5-4的方式演化 得到顺序为 1 9 1 10 6 16 14 5 15 8 12 11 13 7 2 4 3 2 16 6 9…

无需插件就能实现异构数据库的互联互通?(powershell妙用)

前两天在DBA群里有大佬分享了利用Oracle Database Gateway&#xff08;透明网关&#xff09;实现sqlserver和oracle 的数据交互&#xff0c;这里让我想到前些年写的一些powershell脚本用来做sqlserver和oracle的数据交互&#xff0c;powershell是windows自带的一个脚本工具&…

红队笔记8-CTF5打靶流程-CMS漏洞-多用户信息泄露(vulnhub)

目录 开头: 1.主机发现和端口扫描&#xff1a; 2.80端口-NanoCMS哈希密码信息泄露-后台getshell 3.提权-用户过多信息泄露 4.总结&#xff1a; 开头: 学习的视频是哔哩哔哩红队笔记&#xff1a; 「红队笔记」靶机精讲&#xff1a;LAMPSecurityCTF5 - 标准攻击链&#xff…

图论-最短路

一、不存在负权边-dijkstra算法 dijkstra算法适用于这样一类问题&#xff1a; 从起点 start 到所有其他节点的最短路径。 其实求解最短路径最暴力的方法就是使用bfs广搜一下&#xff0c;但是要一次求得所有点的最短距离我们不可能循环n次&#xff0c;这样复杂度太高&#xf…

vue.js——学习计划表

1&#xff09;准备工作 ①打开D:\vue\chapter02\ learning_schedule 目录&#xff0c;找到 index.html 文件。 在文件中引 入BootStrap 样式文件&#xff0c;具体代码如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8&qu…

vivado 手动布线

手动路由 手动路由允许您为网络选择特定的路由资源。这给了你对信号将要采用的路由路径的完全控制。手动路由不调用route_design。路线在路线数据库中直接更新。当您想精确控制网络的延迟时&#xff0c;可能需要使用手动路由。对于例如&#xff0c;假设有一个源同步接口&#…

面试题--3.18

1. http与https的区别&#xff0c;以及https的认证过程及加密算法 &#xff1f; 区别&#xff1a; https协议需要到CA申请证书&#xff0c;一般免费证书较少&#xff0c;因而需要一定费用。 http是超文本传输协议&#xff0c;信息是明文传输&#xff0c;https则是具有安全性…

大型语言模型:技术回顾

公众号&#xff1a;Halo 咯咯&#xff0c;欢迎关注~ 简介 很难说自然语言处理&#xff08;NLP&#xff09;的旅程是什么时候开始的。根据维基百科的文章《自然语言处理的历史》[1]&#xff0c;它可能始于 17 世纪&#xff0c;当时莱布尼茨和笛卡尔试图理解不同语言中单词之间的…

让人担心的软件生态

shigen坚持更新文章的博客写手&#xff0c;擅长Java、python、vue、shell等编程语言和各种应用程序、脚本的开发。记录成长&#xff0c;分享认知&#xff0c;留住感动。 个人IP&#xff1a;shigen 其实很久之前shigen就想写这样的一篇文章&#xff0c;思考现在的软件生态和我们…

c语言数据结构(9)——插入排序、希尔排序

欢迎来到博主的专栏——C语言数据结构 博主ID&#xff1a;代码小豪 文章目录 排序插入排序希尔排序 排序 现在有N个数据的序列&#xff0c;其对应的序列号为[r1 ,r2 ……rn];将该序列对应的数据[k1 ,k2 ……kn]排成满足递减或递减的序列的操作称为排序 插入排序 玩过斗地主…

tomcat配置静态资源后无法正常访问

目录 一、场景二、配置三、访问异常四、排查五、原因六、解决 一、场景 1、将前端文件存在到指定目录 2、在tomcat配置静态资源 3、配置后无法正常访问到前端文件 二、配置 1、tomcat配置 2、静态资源 三、访问异常 四、排查 可以ping通&#xff0c;但是访问不了3080端口 …

探究WordPress受欢迎的原因及其org和com的区别

在当今互联网时代&#xff0c;WordPress已经成为了建立网站的首选工具之一&#xff0c;其受欢迎程度远远超出了其他竞争对手。那么&#xff0c;为什么WordPress如此受欢迎呢&#xff1f;让我们一起探究一下。 首先&#xff0c;WordPress是一个开源项目&#xff0c;这意味着任何…

【UEditorPlus】后端配置项没有正常加载,上传插件不能正常使用

解决办法&#xff1a; 1、找到UEditorPlus的根目录&#xff0c;修改 ueditor.all.js 文件 搜索&#xff1a;isJsonp utils.isCrossDomainUrl(configUrl); 更改为&#xff1a;isJsonp false; 2、重新运行前端即可正常使用 如果出现依旧不行&#xff0c;请关闭服务&#xff…

如何选择适合自己的办公空间

说到办公地点的选择&#xff0c;其实就跟挑衣服似的&#xff0c;得看场合、看需求&#xff0c;还得看个人喜好。有的人喜欢自由自在&#xff0c;有的人则需要稳定和私密。所以&#xff0c;咱们来看看哪些朋友更适合哪种办公环境。 适合共享办公室的&#xff1a; 刚起步的小公司…