技术标签: 协议栈 领居协议 邻居协议 邻居状态 linux内核协议栈
目录
4.1 首包数据发送 ip_finish_output2()
5.3 定时器超时处理 neigh_timer_handler()
邻居项的状态机机制是通用邻居层最重要的内容,主要是处理邻居项中状态的改变,其中包括几个邻居状态的定时器机制,以及邻居项的更新,solicit请求的发送等。对于通用邻居项的状态机,主要有如下几个状态:
其中,处于如下状态的邻居项,都会启动一个定时器:
#define NUD_IN_TIMER (NUD_INCOMPLETE|NUD_REACHABLE|NUD_DELAY|NUD_PROBE)
而处于如下状态的邻居项,我们认为邻居项是可达的:
#define NUD_CONNECTED (NUD_PERMANENT|NUD_NOARP|NUD_REACHABLE)
对于处于 NUD_PERMANENT、NUD_NOARP 状态的邻居项,是不会进行邻居项的状态的转换的,
从上面2个宏定义我们知道,处于NUD_REACHABLE状态的邻居项, 肯定会进行状态的状态的转变。因为处于该状态的邻居项,要启动一个定时器,那如果定时器超时后,邻居项一直没有被使用,则邻居项的状态就会转变。
对于上面3种创建邻居项,其初始状态以及状态的变化会有所不同。
疑问:因为创建的邻居项的状态为NUD_NONE,而NUD_NONE也不处于定时器状态,那么处于NUD_NONE状态的邻居项,是如何将邻居项的状态转变为NUD_INCOMPLETE的呢?对于处于NUD_STALE状态的邻居项,又是如何实现邻居项状态的转变的呢?
在上面的第 1 点中,我只是说邻居项从NUD_NONE转变为NUD_INCOMPLETE,却没有说明这个转换是如何进行的。其转变过程大致如下(此处以ipv4为例):当有数据包要发送时,首先是查找路由表,确定目的地址可达。在这个查找的过程中,若还没有与该目的地址对应的邻居项,则会创建一个邻居项,并与查找到的路由缓存相关联,此时邻居项的状态还是NUD_NONE。对于ipv4来说,接着就会执行ip_output,然后就会调用到ip_finish_output2,接着就会调用到neighbour->output,而在neighbour->output里就会调用到__neigh_event_send判断数据包是否可以直接发送出去,如果此时邻居项的状态为NUD_NONE,则会将邻居项的状态设置为NUD_INCOMPLETE,并将要发送的数据包缓存到邻居项的队列中。而处于NUD_INCOMPLETE状态的邻居项的状态转变会有定时器处理函数来实现。对于处于NUD_STALE状态的邻居项,有两个条件实现状态的转变:
1、对于NUD_INCOMPLETE,当本机发送完arp 请求包后,还未收到应答时,即会进入该状态。 进入该状态,即会启动定时器,如果在定时器到期后,还没有收到应答时:如果没有到达最大发包上限时,即会重新进行发送请求报文;如果超过最大发包上限还没有收到应答,则会将状态设置为NUD_FAILED
2、对于收到可到达性确认后,即会进入NUD_REACHABLE,当进入NUD_REACHABLE状态。当进入NUD_REACHABLE后,即会启动一个定时器,当定时器到时前,该邻居协议没有被使用过,就会将邻居项的状态转换为NUD_STALE
3、对于进入NUD_STALE状态的邻居项,即会启动一个定时器。如果在定时器到时前,有数据需要发送,则直接将数据包发送出去,并将状态设置为NUD_DELAY;如果在定时器到时,没有数据需要发送,且该邻居项的引用计数为1,则会通过垃圾回收机制,释放该邻居项对应的缓存
4、处于NUD_DELAY状态的邻居项,如果在定时器到时后,没有收到可到达性确认,则会进入NUD_PROBE状态;如果在定时器到达之前,收到可到达性确认,则会进入NUD_REACHABLE (在该状态下的邻居项不会发送solicit请求,而只是等待可到达性应答。主要包括对以前的solicit请求的应答或者收到一个对于本设备以前发送的一个数据包的应答)
5、处于NUD_PROBE状态的邻居项,会发送arp solicit请求(ipv4、ipv6则是NS请求),并启动一个定时器。如果在定时器到时前,收到可到达性确认,则进入NUD_REACHABLE;如果在定时器到时后,没有收到可到达性确认:
a)没有超过最大发包次数时,则继续发送solicit请求,并启动定时器
b)如果超过最大发包次数,则将邻居项状态设置为NUD_FAILED
下图是邻居项的状态转换逻辑图,通过上面的描述和下面的逻辑图,能够很好的理解邻居项的状态机机制。
当第一包数据要发送出去,我们还不知道目的三层地址对应的二层地址或者下一跳网关对应的二层地址,对邻居项的创建(我们以ipv4为例)。在ip_finish_output2() 接口中先是获取网关(下一跳)的三层地址,根据取得三层地址(即领居地址)获取领居表项,获取不到则用网关地址创建一个。核心就是通过下一跳网关地址 nexthop 和 net_dev 为关键字查找一个neighbour项:
若查找到,则直接调用 dst_neigh_output() 发送数据。
若没有查找到,则调用neigh_create() 创建一个邻居表项并加入到arp_table的邻居表项链表中。
static inline int ip_finish_output2(struct sk_buff *skb)
{
struct dst_entry *dst = skb_dst(skb);
struct rtable *rt = (struct rtable *)dst;
struct net_device *dev = dst->dev;
unsigned int hh_len = LL_RESERVED_SPACE(dev);
struct neighbour *neigh;
u32 nexthop;
...
rcu_read_lock_bh();
nexthop = (__force u32) rt_nexthop(rt, ip_hdr(skb)->daddr);
neigh = __ipv4_neigh_lookup_noref(dev, nexthop);
if (unlikely(!neigh))
neigh = __neigh_create(&arp_tbl, &nexthop, dev, false);
if (!IS_ERR(neigh)) {
int res = dst_neigh_output(dst, neigh, skb);
rcu_read_unlock_bh();
return res;
}
rcu_read_unlock_bh();
net_dbg_ratelimited("%s: No header cache and no neighbour!\n",
__func__);
kfree_skb(skb);
return -EINVAL;
}
static inline void dst_confirm(struct dst_entry *dst)
{
dst->pending_confirm = 1;
}
static inline int dst_neigh_output(struct dst_entry *dst, struct neighbour *n,
struct sk_buff *skb)
{
const struct hh_cache *hh;
...
hh = &n->hh;
if ((n->nud_state & NUD_CONNECTED) && hh->hh_len)
return neigh_hh_output(hh, skb);
else
return n->output(n, skb);//调用 neigh_resolve_output()
}
执行完上面邻居项的创建以后,后面neigh_resolve_output会间接调用函数neigh_event_send(),实现邻居项状态从 NUD_NONE 到 NUD_INCOMPLETTE 状态的改变。
在arp的接收处理函数arp_rcv里,在对arp包的头部信息进行检查以及防火墙规则检查以后,对于允许接收的arp包,则会调用arp_process进行后续的处理,而在arp_process中,对于接收到的arp请求报文后,则会调用 neigh_event_ns() 进行邻居项的查找与创建功能,对于新创建的邻居项,则会调用函数 neigh_update() 更新邻居项的状态。
函数neigh_event_ns() 的逻辑流程还是比较简单的,主要是将邻居项的状态设置为 NUD_STALE
struct neighbour *neigh_event_ns(struct neigh_table *tbl,
u8 *lladdr, void *saddr,
struct net_device *dev)
{
//找不到就创建一个neigh,状态设置成 NUD_NONE
struct neighbour *neigh = __neigh_lookup(tbl, saddr, dev,
lladdr || !dev->addr_len);
if (neigh)
neigh_update(neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_OVERRIDE);
return neigh;
}
对于第三种创建邻居项的执行流程,是通过netlink消息机制实现的,最终会调用函数neigh_add实现邻居项的创建。对于新创建的邻居项,则会调用函数neigh_update或者neigh_event_send实现邻居项状态的更新。
通过对于第 4 节中三种情况下邻居项的创建流程,我们发现会调用函数neigh_update、neigh_event_send进行邻居项状态的更新,其实对于处于定时器状态的邻居项,会通过定时器超时处理函数实现邻居项状态的转变,下面我们分析一下这3个函数的处理流程。
该函数的功能:邻居项的更新,主要是更新二层地址与邻居项的状态,并会根据邻居项的状态,选择相对应的输出函数。
该函数被调用的情形有:
/* Generic update routine.
-- lladdr is new lladdr or NULL, if it is not supplied.
-- new is new state.
-- flags
NEIGH_UPDATE_F_OVERRIDE allows to override existing lladdr,
if it is different.
NEIGH_UPDATE_F_WEAK_OVERRIDE will suspect existing "connected"
lladdr instead of overriding it
if it is different.
It also allows to retain current state
if lladdr is unchanged.
NEIGH_UPDATE_F_ADMIN means that the change is administrative.
NEIGH_UPDATE_F_OVERRIDE_ISROUTER allows to override existing
NTF_ROUTER flag.
NEIGH_UPDATE_F_ISROUTER indicates if the neighbour is known as
a router.
Caller MUST hold reference count on the entry.
*/
int neigh_update(struct neighbour *neigh, const u8 *lladdr, u8 new,
u32 flags)
{
u8 old;
int err;
int notify = 0;
struct net_device *dev;
int update_isrouter = 0;
write_lock_bh(&neigh->lock);
dev = neigh->dev;
old = neigh->nud_state;
err = -EPERM;
//对于邻居项的原状态为NOARP或者PERMANENT,且不是admin发送的请求,则直接返回
if (!(flags & NEIGH_UPDATE_F_ADMIN) &&
(old & (NUD_NOARP | NUD_PERMANENT)))
goto out;
/*
当邻居项状态不是有效状态时(即是NUD_INCOMPLET、NUD_NONE、NUD_FAILED):
1、删除该邻居项的定时器。
2、对于原状态是 CONNECT 而新状态不是有效态时,则调用 neigh_suspect() 将邻居项的
输出函数设置为通用输出函数。
3、如果原状态是 NUD_INCOMPLETE 或者 NUD_PROBE,且新状态为 NUD_FAILED 时,则调用
neigh_invalidate() 发送错误报告,并发送通知信息,函数返回。
*/
if (!(new & NUD_VALID)) {
neigh_del_timer(neigh);
if (old & NUD_CONNECTED)
neigh_suspect(neigh);
neigh->nud_state = new;
err = 0;
notify = old & NUD_VALID;
if ((old & (NUD_INCOMPLETE | NUD_PROBE)) &&
(new & NUD_FAILED)) {
neigh_invalidate(neigh);
notify = 1;
}
goto out;
}
/*
1、对于设备二层地址长度为0的情形,则不需要更新二层地址,直接使用neigh->ha
2、原状态为有效的,且要更改的地址与邻居项存储的地址相同,则无需更改
3、原状态为无效,且要更改的地址也是无效,则是逻辑错误,函数直接返回
4、原状态有效,且要更改的地址无效时,则先将地址设置为邻居项的地址. ???
5、其他情况下不更改传进来的二层地址。
即:原状态有效,且修改的地址与原邻居项地址不同
原状态无效,且修改的地址有效时
*/
/* Compare new lladdr with cached one */
if (!dev->addr_len) {
/* First case: device needs no address. */
lladdr = neigh->ha;
} else if (lladdr) {
/* The second case: if something is already cached
and a new address is proposed:
- compare new & old
- if they are different, check override flag
*/
if ((old & NUD_VALID) &&
!memcmp(lladdr, neigh->ha, dev->addr_len))
lladdr = neigh->ha;
} else {
/* No address is supplied; if we know something,
use it, otherwise discard the request.
*/
err = -EINVAL;
if (!(old & NUD_VALID))
goto out;
lladdr = neigh->ha;
}
//邻居项的新状态是CONNECT时,更新connect时间
if (new & NUD_CONNECTED)
neigh->confirmed = jiffies;
//更新update时间
neigh->updated = jiffies;
/* If entry was valid and address is not changed,
do not change entry state, if new one is STALE.
*/
err = 0;
update_isrouter = flags & NEIGH_UPDATE_F_OVERRIDE_ISROUTER;
/*
1、原状态有效,且不允许覆盖原来值时,且二层地址不同,且原状态为
CONNECT时,则不更新邻居项的二层地址,而只是将状态设置为STALE
(这是在二层地址不同时,不修改二层地址的最后一个条件)
2、原状态有效,且二层地址不变,且新状态为STALE时,则不改邻居项的状态
*/
if (old & NUD_VALID) {
if (lladdr != neigh->ha && !(flags & NEIGH_UPDATE_F_OVERRIDE)) {
update_isrouter = 0;
if ((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) &&
(old & NUD_CONNECTED)) {
lladdr = neigh->ha;
new = NUD_STALE;
} else
goto out;
} else {
if (lladdr == neigh->ha && new == NUD_STALE &&
((flags & NEIGH_UPDATE_F_WEAK_OVERRIDE) ||
(old & NUD_CONNECTED))
)
new = old;
}
}
/*
当邻居项的新旧状态不同时,则会删除定时器。
若新状态是 NUD_TIMER,则重新添加定时器。(其中对于定时器的超时时间,如果新状态为NUD_REACHABLE,则将超时时间设置为reachable_time,否则将定时器的超时时间设置为当前时间)
设置邻居项的状态为新状态
*/
if (new != old) {
neigh_del_timer(neigh);
if (new & NUD_IN_TIMER)
neigh_add_timer(neigh, (jiffies +
((new & NUD_REACHABLE) ?
neigh->parms->reachable_time :
0)));
neigh->nud_state = new;
}
/*
如果邻居项的二层地址不同,则更新邻居项里的二层地址,并调用neigh_update_hhs,
更新与该邻居项相关联的所有二层头部缓存。如果新状态不是CONNECT状态,
则将confirm时间设置为比当前时间早2*base_reachable_time.
根据邻居项的不同更新邻居项的输出函数:
当为NUD_CONNECTED,则调用neigh_connect将邻居项的输出函数设置为快速输出函数
当为非NUD_CONNECTED,则调用neigh_suspect将邻居项的输出函数设置为通用输出函数
*/
if (lladdr != neigh->ha) {
write_seqlock(&neigh->ha_lock);
memcpy(&neigh->ha, lladdr, dev->addr_len);
write_sequnlock(&neigh->ha_lock);
neigh_update_hhs(neigh);
if (!(new & NUD_CONNECTED))
neigh->confirmed = jiffies -
(neigh->parms->base_reachable_time << 1);
notify = 1;
}
if (new == old)
goto out;
if (new & NUD_CONNECTED)
neigh_connect(neigh);
else
neigh_suspect(neigh);
if (!(old & NUD_VALID)) {
struct sk_buff *skb;
/* Again: avoid dead loop if something went wrong */
while (neigh->nud_state & NUD_VALID &&
(skb = __skb_dequeue(&neigh->arp_queue)) != NULL) {
struct dst_entry *dst = skb_dst(skb);
struct neighbour *n2, *n1 = neigh;
write_unlock_bh(&neigh->lock);
rcu_read_lock();
/* Why not just use 'neigh' as-is? The problem is that
* things such as shaper, eql, and sch_teql can end up
* using alternative, different, neigh objects to output
* the packet in the output path. So what we need to do
* here is re-lookup the top-level neigh in the path so
* we can reinject the packet there.
*/
n2 = NULL;
if (dst) {
n2 = dst_neigh_lookup_skb(dst, skb);
if (n2)
n1 = n2;
}
n1->output(n1, skb);
if (n2)
neigh_release(n2);
rcu_read_unlock();
write_lock_bh(&neigh->lock);
}
skb_queue_purge(&neigh->arp_queue);
neigh->arp_queue_len_bytes = 0;
}
out:
if (update_isrouter) {
neigh->flags = (flags & NEIGH_UPDATE_F_ISROUTER) ?
(neigh->flags | NTF_ROUTER) :
(neigh->flags & ~NTF_ROUTER);
}
write_unlock_bh(&neigh->lock);
if (notify)
neigh_update_notify(neigh);
return err;
}
1、对于connect、delay、probe状态的邻居项,返回0
2、当状态为NUD_NONE时:
3、当状态为STALE时:
4、对于处于INCOMPLETE状态的邻居项
static inline int neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
unsigned long now = jiffies;
if (neigh->used != now)
neigh->used = now;
if (!(neigh->nud_state&(NUD_CONNECTED|NUD_DELAY|NUD_PROBE)))
return __neigh_event_send(neigh, skb);
return 0;
}
int __neigh_event_send(struct neighbour *neigh, struct sk_buff *skb)
{
int rc;
bool immediate_probe = false;
write_lock_bh(&neigh->lock);
rc = 0;
if (neigh->nud_state & (NUD_CONNECTED | NUD_DELAY | NUD_PROBE))
goto out_unlock_bh;
if (!(neigh->nud_state & (NUD_STALE | NUD_INCOMPLETE))) {
if (neigh->parms->mcast_probes + neigh->parms->app_probes) {
unsigned long next, now = jiffies;
atomic_set(&neigh->probes, neigh->parms->ucast_probes);
neigh->nud_state = NUD_INCOMPLETE;
neigh->updated = now;
next = now + max(neigh->parms->retrans_time, HZ/2);
neigh_add_timer(neigh, next);
immediate_probe = true;
} else {
neigh->nud_state = NUD_FAILED;
neigh->updated = jiffies;
write_unlock_bh(&neigh->lock);
kfree_skb(skb);
return 1;
}
} else if (neigh->nud_state & NUD_STALE) {
neigh_dbg(2, "neigh %p is delayed\n", neigh);
neigh->nud_state = NUD_DELAY;
neigh->updated = jiffies;
neigh_add_timer(neigh,
jiffies + neigh->parms->delay_probe_time);
}
if (neigh->nud_state == NUD_INCOMPLETE) {
if (skb) {
while (neigh->arp_queue_len_bytes + skb->truesize >
neigh->parms->queue_len_bytes) {
struct sk_buff *buff;
buff = __skb_dequeue(&neigh->arp_queue);
if (!buff)
break;
neigh->arp_queue_len_bytes -= buff->truesize;
kfree_skb(buff);
NEIGH_CACHE_STAT_INC(neigh->tbl, unres_discards);
}
skb_dst_force(skb);
__skb_queue_tail(&neigh->arp_queue, skb);
neigh->arp_queue_len_bytes += skb->truesize;
}
rc = 1;
}
out_unlock_bh:
if (immediate_probe)
neigh_probe(neigh);
else
write_unlock(&neigh->lock);
local_bh_enable();
return rc;
}
EXPORT_SYMBOL(__neigh_event_send);
通过上面的分析,我们知道对于这几个邻居项状态的转变,最主要的就是需要定时器的那几个邻居项的状态,所以我们下面分析邻居项的定时器的处理函数,对于这几个邻居项的状态,内核使用的是同一个定时器,只不过设置的超时时间不同罢了。
该函数的功能:邻居项最主要的定时器超时处理函数,实现了诸多邻居项状态的转换以及邻居项 solicit 请求相关的函数
1、如果邻居项的当前状态不属于NUD_IN_TIMER,则函数返回。
2、对于处于reach状态的邻居项:
3、对于Delay状态的邻居项:
4、对于probe与incomplete状态的邻居项:
5、对于probe与incomplete状态的邻居项:
/* Called when a timer expires for a neighbour entry. */
static void neigh_timer_handler(unsigned long arg)
{
unsigned long now, next;
struct neighbour *neigh = (struct neighbour *)arg;
unsigned int state;
int notify = 0;
write_lock(&neigh->lock);
state = neigh->nud_state;
now = jiffies;
next = now + HZ;
if (!(state & NUD_IN_TIMER))
goto out;
if (state & NUD_REACHABLE) {
if (time_before_eq(now,
neigh->confirmed + neigh->parms->reachable_time)) {
neigh_dbg(2, "neigh %p is still alive\n", neigh);
next = neigh->confirmed + neigh->parms->reachable_time;
} else if (time_before_eq(now,
neigh->used + neigh->parms->delay_probe_time)) {
neigh_dbg(2, "neigh %p is delayed\n", neigh);
neigh->nud_state = NUD_DELAY;
neigh->updated = jiffies;
neigh_suspect(neigh);
next = now + neigh->parms->delay_probe_time;
} else {
neigh_dbg(2, "neigh %p is suspected\n", neigh);
neigh->nud_state = NUD_STALE;
neigh->updated = jiffies;
neigh_suspect(neigh);
notify = 1;
}
} else if (state & NUD_DELAY) {
if (time_before_eq(now,
neigh->confirmed + neigh->parms->delay_probe_time)) {
neigh_dbg(2, "neigh %p is now reachable\n", neigh);
neigh->nud_state = NUD_REACHABLE;
neigh->updated = jiffies;
neigh_connect(neigh);
notify = 1;
next = neigh->confirmed + neigh->parms->reachable_time;
} else {
neigh_dbg(2, "neigh %p is probed\n", neigh);
neigh->nud_state = NUD_PROBE;
neigh->updated = jiffies;
atomic_set(&neigh->probes, 0);
next = now + neigh->parms->retrans_time;
}
} else {
/* NUD_PROBE|NUD_INCOMPLETE */
next = now + neigh->parms->retrans_time;
}
if ((neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) &&
atomic_read(&neigh->probes) >= neigh_max_probes(neigh)) {
neigh->nud_state = NUD_FAILED;
notify = 1;
neigh_invalidate(neigh);
}
if (neigh->nud_state & NUD_IN_TIMER) {
if (time_before(next, jiffies + HZ/2))
next = jiffies + HZ/2;
if (!mod_timer(&neigh->timer, next))
neigh_hold(neigh);
}
if (neigh->nud_state & (NUD_INCOMPLETE | NUD_PROBE)) {
neigh_probe(neigh);
} else {
out:
write_unlock(&neigh->lock);
}
if (notify)
neigh_update_notify(neigh);
neigh_release(neigh);
}
在申请邻居项的内存函数neigh_alloc()里,会创建该定时器,并会将定时器的超时处理函数设置为 neigh_timer_handler。
至此,大致分析完了通用邻居项的工作流程,通过这次认真的阅读通用邻居层的代码机制,对于以后编程应该会有影响。通过分析该子层,对于一个比较好的子层代码:
文章浏览阅读1k次。通过使用ajax方法跨域请求是浏览器所不允许的,浏览器出于安全考虑是禁止的。警告信息如下:不过jQuery对跨域问题也有解决方案,使用jsonp的方式解决,方法如下:$.ajax({ async:false, url: 'http://www.mysite.com/demo.do', // 跨域URL ty..._nginx不停的xhr
文章浏览阅读2k次。关于在 Oracle 中配置 extproc 以访问 ST_Geometry,也就是我们所说的 使用空间SQL 的方法,官方文档链接如下。http://desktop.arcgis.com/zh-cn/arcmap/latest/manage-data/gdbs-in-oracle/configure-oracle-extproc.htm其实简单总结一下,主要就分为以下几个步骤。..._extproc
文章浏览阅读1.5w次。linux下没有上面的两个函数,需要使用函数 mbstowcs和wcstombsmbstowcs将多字节编码转换为宽字节编码wcstombs将宽字节编码转换为多字节编码这两个函数,转换过程中受到系统编码类型的影响,需要通过设置来设定转换前和转换后的编码类型。通过函数setlocale进行系统编码的设置。linux下输入命名locale -a查看系统支持的编码_linux c++ gbk->utf8
文章浏览阅读750次。今天准备从生产库向测试库进行数据导入,结果在imp导入的时候遇到“ IMP-00009:导出文件异常结束” 错误,google一下,发现可能有如下原因导致imp的数据太大,没有写buffer和commit两个数据库字符集不同从低版本exp的dmp文件,向高版本imp导出的dmp文件出错传输dmp文件时,文件损坏解决办法:imp时指定..._imp-00009导出文件异常结束
文章浏览阅读143次。当下是一个大数据的时代,各个行业都离不开数据的支持。因此,网络爬虫就应运而生。网络爬虫当下最为火热的是Python,Python开发爬虫相对简单,而且功能库相当完善,力压众多开发语言。本次教程我们爬取前程无忧的招聘信息来分析Python程序员需要掌握那些编程技术。首先在谷歌浏览器打开前程无忧的首页,按F12打开浏览器的开发者工具。浏览器开发者工具是用于捕捉网站的请求信息,通过分析请求信息可以了解请..._初级python程序员能力要求
文章浏览阅读7.6k次,点赞2次,收藏6次。@Service标注的bean,类名:ABDemoService查看源码后发现,原来是经过一个特殊处理:当类的名字是以两个或以上的大写字母开头的话,bean的名字会与类名保持一致public class AnnotationBeanNameGenerator implements BeanNameGenerator { private static final String C..._@service beanname
文章浏览阅读6.9w次,点赞73次,收藏463次。1.前序创建#include<stdio.h>#include<string.h>#include<stdlib.h>#include<malloc.h>#include<iostream>#include<stack>#include<queue>using namespace std;typed_二叉树的建立
文章浏览阅读7.1k次。在Asp.net上使用Excel导出功能,如果文件名出现中文,便会以乱码视之。 解决方法: fileName = HttpUtility.UrlEncode(fileName, System.Text.Encoding.UTF8);_asp.net utf8 导出中文字符乱码
文章浏览阅读2.1k次,点赞4次,收藏23次。第一次实验 词法分析实验报告设计思想词法分析的主要任务是根据文法的词汇表以及对应约定的编码进行一定的识别,找出文件中所有的合法的单词,并给出一定的信息作为最后的结果,用于后续语法分析程序的使用;本实验针对 PL/0 语言 的文法、词汇表编写一个词法分析程序,对于每个单词根据词汇表输出: (单词种类, 单词的值) 二元对。词汇表:种别编码单词符号助记符0beginb..._对pl/0作以下修改扩充。增加单词
文章浏览阅读773次。我在使用adb.exe时遇到了麻烦.我想使用与bash相同的adb.exe shell提示符,所以我决定更改默认的bash二进制文件(当然二进制文件是交叉编译的,一切都很完美)更改bash二进制文件遵循以下顺序> adb remount> adb push bash / system / bin /> adb shell> cd / system / bin> chm..._adb shell mv 权限
文章浏览阅读6.8k次,点赞12次,收藏125次。1. 单目相机标定引言相机标定已经研究多年,标定的算法可以分为基于摄影测量的标定和自标定。其中,应用最为广泛的还是张正友标定法。这是一种简单灵活、高鲁棒性、低成本的相机标定算法。仅需要一台相机和一块平面标定板构建相机标定系统,在标定过程中,相机拍摄多个角度下(至少两个角度,推荐10~20个角度)的标定板图像(相机和标定板都可以移动),即可对相机的内外参数进行标定。下面介绍张氏标定法(以下也这么称呼)的原理。原理相机模型和单应矩阵相机标定,就是对相机的内外参数进行计算的过程,从而得到物体到图像的投影_相机-投影仪标定
文章浏览阅读2.2k次。文章目录Wayland 架构Wayland 渲染Wayland的 硬件支持简 述: 翻译一篇关于和 wayland 有关的技术文章, 其英文标题为Wayland Architecture .Wayland 架构若是想要更好的理解 Wayland 架构及其与 X (X11 or X Window System) 结构;一种很好的方法是将事件从输入设备就开始跟踪, 查看期间所有的屏幕上出现的变化。这就是我们现在对 X 的理解。 内核是从一个输入设备中获取一个事件,并通过 evdev 输入_wayland