`
wqtn22
  • 浏览: 102031 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

gen_tcp的close与delay_send交叉问题

 
阅读更多

铁血的同学遇到这样一个问题,与之前4399同学遇到的问题类似,当初以为是erlang:ports/0的快照问题,仔细分析后发现另有玄机。

以下是问题描述:


>> 我使用的erlang版本是R15B03,进行socket处理的时候,如果在客户端到服务端有大概1000多个连接的时候(同时有较多的数据在发送),同时关闭所有客户端,

>> 这时在使用ports()查询出的端口中,有一些对其port_info/1返回的是undefined,

>> 并且这些undefined端口一样会占用端口数量,(出现这种情况的时候socket的控制进程确认已经都结束了)

>> 并且出现了这种undefined的端口,在使用init:stop()方式结束节点的时候就不能正常结束,只能使用halt(Status,

>> [{flush,false}])才行

>> 我尝试服务端再次调用或者不调用gen_tcp:close/1, erlang:port_close/1都不起作用

>>

>> 我socket的设置是这样的:

>> -define(TCP_OPTIONS, [binary, {packet, 0}, {active, false},

>> {reuseaddr, true}, {nodelay, false}, {delay_send, true},

>> {send_timeout, 5000}, {keepalive, false}, {exit_on_close, true}]).

>> socket处理这块使用的是rabbitmq的框架



初步分析认为场景是这样的:

若干tcp port上有大量数据发送时,关闭这些port,造成erlang:ports/0erlang:port_info/1得到的port状态不一致,此时跟踪这两个函数如下:


erlang:ports/0

BIF_RETTYPE ports_0(BIF_ALIST_0)

{

    Eterm res = NIL;

    Eterm* port_buf = erts_alloc(ERTS_ALC_T_TMP,

                                      sizeof(Eterm)*erts_max_ports);

    Eterm* pp = port_buf;

    Eterm* dead_ports;

    int alive, dead;

    Uint32 next_ss;

    int i;

 

    erts_smp_mtx_lock(&ports_snapshot_mtx); /* One snapshot at a time */

 

    erts_smp_atomic_set_nob(&erts_dead_ports_ptr,

                                (erts_aint_t) (port_buf + erts_max_ports));

 

    next_ss = erts_smp_atomic32_inc_read_relb(&erts_ports_snapshot);

 

    for (i = erts_max_ports-1; i >= 0; i--) {

         Port* prt = &erts_port[i];

         erts_smp_port_state_lock(prt);

         if (!(prt->status & ERTS_PORT_SFLGS_DEAD)

             && prt->snapshot != next_ss) {

             ASSERT(prt->snapshot == next_ss - 1);

             *pp++ = prt->id;                

             prt->snapshot = next_ss; /* Consumed by this snapshot */

         }

         erts_smp_port_state_unlock(prt);

    }

 

    dead_ports = (Eterm*)erts_smp_atomic_xchg_nob(&erts_dead_ports_ptr,

                                                          (erts_aint_t) NULL);

    erts_smp_mtx_unlock(&ports_snapshot_mtx);

 

    ASSERT(pp <= dead_ports);

 

    alive = pp - port_buf;

    dead = port_buf + erts_max_ports - dead_ports;

 

    ASSERT((alive+dead) <= erts_max_ports);

 

    if (alive+dead > 0) {

         erts_aint_t i;

         Eterm *hp = HAlloc(BIF_P, (alive+dead)*2);

 

         for (i = 0; i < alive; i++) {

             res = CONS(hp, port_buf[i], res);        

             hp += 2;

         }

         for (i = 0; i < dead; i++) {

             res = CONS(hp, dead_ports[i], res);

             hp += 2;

         }

    }

 

    erts_free(ERTS_ALC_T_TMP, port_buf);

 

    BIF_RET(res);

}



erlang:ports/0这个函数将当前系统中处于非ERTS_PORT_SFLGS_DEAD状态的port快照组成一个列表返回,得到当前所有的活动port,这里有如下几点值得注意:


1.ERTS_PORT_SFLGS_DEAD状态由下列状态组成:

ERTS_PORT_SFLG_FREE|

ERTS_PORT_SFLG_FREE_SCHEDULED | 

ERTS_PORT_SFLG_INITIALIZING;

2.若在调用erlang:ports/0进行统计的期间内,某个port退出,则这个port也将位于erlang:ports/0的返回列表内,但这些port在下次调用erlang:ports/0时将不会再次出现。


erlang:port_info/1

BIF_RETTYPE

port_info_1(BIF_ALIST_1)

{

    Process* p = BIF_P;

    Eterm pid = BIF_ARG_1;

    static Eterm keys[] = {

         am_name,

         am_links,

         am_id,

         am_connected,

         am_input,

         am_output,

         am_os_pid

    };

    Eterm items[ASIZE(keys)];

    Eterm result = NIL;

    Eterm reg_name;

    Eterm* hp;

    Uint need;

    int i;

 

    for (i = 0; i < ASIZE(keys); i++) {

         Eterm item;

 

         item = port_info(p, pid, keys[i]);

         if (is_non_value(item)) {

             return THE_NON_VALUE;

         }

         if (item == am_undefined) {

             return am_undefined;

         }

         items[i] = item;

    }

    reg_name = port_info(p, pid, am_registered_name);

 

    need = 2*ASIZE(keys);

    if (is_tuple(reg_name)) {

         need += 2;

    }

    hp = HAlloc(p, need);

    for (i = ASIZE(keys) - 1; i >= 0; i--) {

         result = CONS(hp, items[i], result);

         hp += 2;

    }

    if (is_tuple(reg_name)) {

         result = CONS(hp, reg_name, result);

    }

 

    return result;

}



static BIF_RETTYPE port_info(Process* p, Eterm portid, Eterm item)

{

    BIF_RETTYPE ret;

    Port *prt;

    Eterm res;

    Eterm* hp;

    int count;

 

    if (is_internal_port(portid))

                  prt = erts_id2port(portid, p, ERTS_PROC_LOCK_MAIN);

    else if (is_atom(portid))

                  erts_whereis_name(p, ERTS_PROC_LOCK_MAIN,

                              portid, NULL, 0, 0, &prt);

    else if (is_external_port(portid)

              && external_port_dist_entry(portid) == erts_this_dist_entry)

                  BIF_RET(am_undefined);

    else

                  BIF_ERROR(p, BADARG);

 

    if (!prt)

                  BIF_RET(am_undefined);

}

#define erts_id2port(ID, P, PL) \

  erts_id2port_sflgs((ID), (P), (PL), ERTS_PORT_SFLGS_INVALID_LOOKUP)

 

 ERTS_GLB_INLINE Port*

erts_id2port_sflgs(Eterm id, Process *c_p, ErtsProcLocks c_p_locks, Uint32 sflgs)

{

#ifdef ERTS_SMP

    int no_proc_locks = !c_p || !c_p_locks;

#endif

    Port *prt;

 

    if (is_not_internal_port(id))

         return NULL;

 

    prt = &erts_port[internal_port_index(id)];

 

    erts_smp_port_state_lock(prt);

    if (ERTS_INVALID_PORT_OPT(prt, id, sflgs)) {

         erts_smp_port_state_unlock(prt);

         prt = NULL;

    }

#ifdef ERTS_SMP

    else {

         erts_smp_atomic_inc_nob(&prt->refc);

         erts_smp_port_state_unlock(prt);

 

         if (no_proc_locks)

             erts_smp_mtx_lock(prt->lock);

         else if (erts_smp_mtx_trylock(prt->lock) == EBUSY) {

             /* Unlock process locks, and acquire locks in lock order... */

             erts_smp_proc_unlock(c_p, c_p_locks);

             erts_smp_mtx_lock(prt->lock);

             erts_smp_proc_lock(c_p, c_p_locks);

         }

 

         /* The id may not have changed... */

         ERTS_SMP_LC_ASSERT(prt->id == id);

         /* ... but status may have... */

         if (prt->status & sflgs) {

             erts_smp_port_unlock(prt); /* Also decrements refc... */

             prt = NULL;

         }

    }

#endif

 

    return prt;

}



erlang:port_info/1这个函数仅将当前系统中处于非ERTS_PORT_SFLGS_INVALID_LOOKUP状态的portport_info返回,,这里有如下几点值得注意: 


1.ERTS_PORT_SFLGS_DEAD状态由下列状态组成:

ERTS_PORT_SFLG_FREE |

ERTS_PORT_SFLG_FREE_SCHEDULED |

ERTS_PORT_SFLG_INITIALIZING |

ERTS_PORT_SFLG_INVALID |

ERTS_PORT_SFLG_CLOSING;

2.与erlang:ports/0的区别在于,除了前三个状态,erlang:port_info/1也不会将处于ERTS_PORT_SFLG_INVALID或ERTS_PORT_SFLG_CLOSING状态的port_info返回,其中ERTS_PORT_SFLG_INVALID不会真正赋予port,而ERTS_PORT_SFLG_CLOSING可以被赋予port。


ERTS_PORT_SFLG_CLOSING状态是当前这个场景的问题核心,若一个port处于ERTS_PORT_SFLG_CLOSING状态,而不处于ERTS_PORT_SFLGS_DEAD | ERTS_PORT_SFLG_FREE_SCHEDULED | ERTS_PORT_SFLG_INITIALIZING状态,则它将出现在erlang:ports/0的列表中,同时在erlang:port_info/1的结果中返回undefined

 

对于portERTS_PORT_SFLG_CLOSING状态,由这个函数进行设置:


void erts_do_exit_port(Port *p, Eterm from, Eterm reason)

{

   ErtsLink *lnk;

   Eterm rreason;

 

   ERTS_SMP_CHK_NO_PROC_LOCKS;

   ERTS_SMP_LC_ASSERT(erts_lc_is_port_locked(p));

 

   rreason = (reason == am_kill) ? am_killed : reason;

 

   if ((p->status & (ERTS_PORT_SFLGS_DEAD

                        | ERTS_PORT_SFLG_EXITING

                        | ERTS_PORT_SFLG_IMMORTAL))

       || ((reason == am_normal) &&

            ((from != p->connected) && (from != p->id)))) {

      return;

   }

 

   if (IS_TRACED_FL(p, F_TRACE_PORTS)) {

     trace_port(p, am_closed, reason);

   }

 

   erts_trace_check_exiting(p->id);

   set_busy_port((ErlDrvPort)internal_port_index(p->id), 0);

 

   if (p->reg != NULL)

       (void) erts_unregister_name(NULL, 0, p, p->reg->name);

 

   erts_port_status_bor_set(p, ERTS_PORT_SFLG_EXITING);

 

   {

       SweepContext sc = {p->id, rreason};

       lnk = p->nlinks;

       p->nlinks = NULL;

       erts_sweep_links(lnk, &sweep_one_link, &sc);

   }

   DRV_MONITOR_LOCK_PDL(p);

   {

       ErtsMonitor *moni = p->monitors;

       p->monitors = NULL;

       erts_sweep_monitors(moni, &sweep_one_monitor, NULL);

   }

   DRV_MONITOR_UNLOCK_PDL(p);

 

   if ((p->status & ERTS_PORT_SFLG_DISTRIBUTION) && p->dist_entry) {

       erts_do_net_exits(p->dist_entry, rreason);

       erts_deref_dist_entry(p->dist_entry);

       p->dist_entry = NULL;

       erts_port_status_band_set(p, ~ERTS_PORT_SFLG_DISTRIBUTION);

   }

      

   if ((reason != am_kill) && !is_port_ioq_empty(p)) {

       erts_port_status_bandor_set(p,

                                        ~ERTS_PORT_SFLG_EXITING, /* must turn it off */

                                        ERTS_PORT_SFLG_CLOSING);

      flush_port(p);

   }

   else {

       terminate_port(p);

   }

}



这里进行一项检查,即对于一个非kill掉的port,若其发送队列不为空,则将port的状态加入ERTS_PORT_SFLG_CLOSING,同时flushport,即调用port driverflush回调。

调用erts_do_exit_port这个函数的位置各不相同,但其基本目的在于关闭一个port

 

推测server设置了delay_send标志,在调用gen_tcp:close/1时,发送缓冲中仍然有若干数据未发送完,导致server不能正常关闭port,而使得port的中间状态ERTS_PORT_SFLG_CLOSING被暴露出来,结合当前问题,设计如下场景,意图重现该问题:


1.同时启动一个tcp client和一个tcp server;

2.client与server均为被动接收;

3.server缓冲发送,设置{delay_send,true}标志;

4.server一次发送大量数据,而client仅接收其中的一小部分,server的发送缓冲被占用而无法发出数据,然后调用gen_tcp:close关闭port;

5.实验场景与霸爷博客http://blog.yufeng.info/archives/1489

的这篇文章相似,实验代码也是在其上进行的改动,同时hack了一部分prim_inet的代码。


主要流程如下:


1.server启动

2.server listen创建监听套接字port,设置选项为{active, false}等;

3.server accept等待;

4.client启动

5.client connect,设置选项为{active,false}等;

6.server accept接受,创建已连接套接字port,设置选项为[{exit_on_close, true}, {delay_send,true}]等;

7.server 将port controlling_process给一个新进程,在这个新进程内进行控制;

8.server控制进程等待client请求;

9.client send请求”start”;

10.client等待server返回结果;

11.server recv client的”start”请求,send 1M数据到client;

12.client recv 1K数据

13.client send请求”bang”,通知server进行后续触发动作;

14.client 进行退出动作,不同的退出动作将产生不同的结果,包括如下退出动作:csr/csw/cc/cn/cr,这些动作稍后将进行解释;

15.server recv client的”bang”请求,通过inet:getstat(S, [send_pend])获取当前发送缓冲的长度;

16.server进行退出动作,不同的退出动作将产生不同的结果,包括如下退出动作:ssr/ssw/sc/scc/sn/su,这些动作稍后将进行解释;

17.server进行其它辅助工作,主要为接收最后一条tcp错误消息。


client的退出动作解释如下:

csrshutdown read

cswshutdown write

ccclose

cndo nothing

crrecv after 20 seconds

 

server的退出动作解释如下:

ssrshutdown read

sswshutdown write

scclose

sccclose with hacking gen_tcp:close/1hackinggen_tcp:close/1在延迟清空发送队列时,将无限发送直至成功,而非原来的5s内发送队列不变动则强行关闭port

sndo nothing

suunlink



这些不同动作的组合结果如下:

 

csr

csw

cc

cn

cr

ssr

server正常,port随控制进程退出而关闭;

client因未调用closeport未释放,但可通过erlang:port_close释放

csr+ ssr

server正常,portclient提前关闭而关闭;client亦正常关闭port

csr+ ssr

server正常,port随控制进程退出而关闭;client因未调用closeport未释放,但可通过erlang:port_close释放,client最后的recv无法收到数据

ssw

csr+ ssr

csr+ ssr

cc+ssr

cn+ssr

cr+ssr

sc

serverport的发送缓冲仍然有数据,但提前关闭该port,出现了erlang:ports/0erlang:port_info/1不一致的情形,同时port无法再通过erlang:port_close/1释放;client因未调用closeport未释放,但可通过erlang:port_close释放;client调用erlang:port_close/1释放关闭port后,serverport也随之释放

csr+sc

server正常,portclient提前关闭而关闭;client亦正常关闭port

csr+sc

csr+sc,另外,client最后的recv无法收到数据

scc

serverport的发送缓冲仍然有数据,hacking后的erlang:port_info/1导致控制进程不断循环进行清空发送缓冲区的动作,从而不会出现erlang:ports/0erlang:port_info/1不一致的情形client因未调用closeport未释放,但可通过erlang:port_close释放;client调用erlang:port_close/1释放关闭port后,server的控制进程退出循环,port也随之释放

csr+scc

server正常,portclient提前关闭而关闭;client亦正常关闭port

csr+scc

csr+scc,另外,client最后的recv将接收到数据,同时令server的控制进程正常退出循环,port也随之释放

sn

csr+ ssr

csr+ ssr

cc+ssr

csr+ ssr

cr+ssr

su

server提前unlinkport,导致port仍然遗留,控制进程却已经退出,遗留port可以通过erlang:port_close释放;client因未调用closeport未释放,但可通过erlang:port_close释放

csr+ su

server提前unlinkport,导致port仍然遗留,控制进程却已经退出,遗留port可以通过erlang:port_close释放;client正常关闭port

csr+ su

server提前unlinkport,导致port仍然遗留,控制进程却已经退出,遗留port可以通过erlang:port_close释放;client因未调用closeport未释放,但可通过erlang:port_close释放,client最后的recv无法收到数据

 


综上所述,serverportdelay_send选项控制下,若发送缓冲有数据却强行close,此时若clientclose而仅仅是shutdown,则serverport将不能释放,并出现erlang:ports/0erlang:port_info/1不一致的情形,解决的办法是server也进行shutdown,并令控制进程退出或client进行close

 


分析gen_tcp:close/1的代码如下:

gen_tcp.erl

close(S) ->

inet:tcp_close(S).

 

inet.erl

tcp_close(S) when is_port(S) ->

    %% if exit_on_close is set we must force a close even if remotely closed!!!

    prim_inet:close(S),

receive {tcp_closed, S} -> ok after 0 -> ok end.

 

prim_inet.erl

close(S) when is_port(S) ->

    unlink(S),               %% avoid getting {'EXIT', S, Reason}

    case subscribe(S, [subs_empty_out_q]) of

         {ok, [{subs_empty_out_q,N}]} when N > 0 ->

             close_pend_loop(S, N);   %% wait for pending output to be sent

         _ ->

             catch erlang:port_close(S),

             ok

    end.

 

close_pend_loop(S, N) ->

    receive

         {empty_out_q,S} ->

             catch erlang:port_close(S), ok

    after ?INET_CLOSE_TIMEOUT ->

             case getstat(S, [send_pend]) of

                {ok, [{send_pend,N1}]} ->

                    if N1 =:= N -> catch erlang:port_close(S), ok;

                       true -> close_pend_loop(S, N1)

                    end;

                   _ ->

                       catch erlang:port_close(S), ok

             end

end.


gen_tcp:close/1所作的工作包括:


1.unlink掉port与当前进程的关系,port成为无主port;

2.将调用进程作为port的一个订阅进程,通过subscribe函数订阅port的empty_out_q消息,这个消息仅在port的发送缓冲被清空时,由虚拟机投递给订阅进程(也即当前的调用进程);

3.循环等待empty_out_q消息的到达,或者5秒超时,若empty_out_q消息到达,则正常关闭port即可,若超时,则进入超时处理流程;

4.超时后,通过getstat(S, [send_pend])检查port的发送缓冲是否出现了变化,若未变化,则表明port的发送缓冲在过去的5秒内没能将任何数据发送出去,因此推测将来也不可能再将数据发送出去,因此强行关闭port,若出现了变化,则继续循环等待,直到port的发送缓冲清空;

5.强行关闭port将导致调用前述的erts_do_exit_port函数,在port的发送缓冲未清空的场景下,这个函数将设置port的状态为ERTS_PORT_SFLG_CLOSING,导致调用erlang:ports/0与erlang:port_info/1观察到了port的中间状态,得到不一致的结果;

6.这个问题不是资源泄露,而是发送缓冲这种设计机制导致的,port在close前必须将发送缓冲的数据全部推送到客户端,而客户端如果既不recv,也不close,而是shutdown半关闭或不作为,则导致server的port仍然被占用,此时连接仍然存在,只是难于被观察到,通过netstat可以看到client套接字处于FIN_WAIT2,而server套接字处于CLOSING,符合半关闭的状态。


gen_tcp:shutdown/2的经历和gen_tcp:close/1类似,霸爷已经在
http://blog.yufeng.info/archives/1489
一文中有了详细的分析,与gen_tcp:close/1不同的是gen_tcp:shutdown/2不会强行调用erlang:port_close/1关闭port,也就不会令中间状态暴露出来,当进程最终退出时,虚拟机帮助回收资源,进行真正的port关闭。


附件是测试代码。

分享到:
评论

相关推荐

    毕设单片机实战项目基于esp8266的高考倒计时.zip

    【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    毕设工坊:专注于计算机毕业设计项目的交流与资源共享平台,涵盖各类技术文档、代码示例及实战经验分享,助力学子顺利完成学业挑战

    毕设工坊:专注于计算机毕业设计项目的交流与资源共享平台,涵盖各类技术文档、代码示例及实战经验分享,助力学子顺利完成学业挑战。

    【window 可视化nvm管理node版本 nvm-desktop】

    【window 可视化nvm管理node版本 nvm-desktop】

    《基于YOLOv8的玉器识别系统》(包含源码、完整数据集、可视化界面、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计.zip

    资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。

    (源码)基于microbit编程语言的mymicrobit扩展插件项目.zip

    # 基于microbit编程语言的mymicrobit扩展插件项目 ## 项目简介 这是一个基于microbit编程语言的mymicrobit扩展插件项目。该项目旨在提供额外的功能和特性,以扩展microbit编程环境。通过此插件,用户可以轻松地在MakeCode环境中进行编程,实现对micro:bit设备的更多控制和功能实现。 ## 项目的主要特性和功能 1. 扩展性提供了丰富的积木块和代码库,允许用户轻松实现复杂的编程逻辑和功能扩展。 2. 图形化编程支持通过积木块形式的图形化编程,降低编程门槛,方便初学者快速上手。 3. 实时预览提供了积木块的实时预览功能,方便用户直观地了解代码块的逻辑和功能。 4. 与MakeCode无缝集成可以直接在MakeCode环境中导入和使用,无需额外的配置和安装。 ## 安装使用步骤

    毕设单片机实战项目基于ESP8266的局域网图片刷新显示系统.zip

    【项目资源】: 单片机项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    《基于YOLOv8的印章分析系统》(包含源码、完整数据集、可视化界面、部署教程)简单部署即可运行。功能完善、操作简单,适合毕设或课程设计.zip

    资源内项目源码是来自个人的毕业设计,代码都测试ok,包含源码、数据集、可视化页面和部署说明,可产生核心指标曲线图、混淆矩阵、F1分数曲线、精确率-召回率曲线、验证集预测结果、标签分布图。都是运行成功后才上传资源,毕设答辩评审绝对信服的保底85分以上,放心下载使用,拿来就能用。包含源码、数据集、可视化页面和部署说明一站式服务,拿来就能用的绝对好资源!!! 项目备注 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、大作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.txt文件,仅供学习参考, 切勿用于商业用途。

    p111基于django的企业员工管理系统.zip

    项目资源包含:可运行源码+sql文件 适用人群:学习不同技术领域的小白或进阶学习者;可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 项目具有较高的学习借鉴价值,也可拿来修改、二次开发。 有任何使用上的问题,欢迎随时与博主沟通,博主看到后会第一时间及时解答。 开发语言:Python 框架:django Python版本:python3.8 数据库:mysql 5.7 数据库工具:Navicat 开发软件:PyCharm 浏览器:谷歌浏览器

    第三章-局域网-思维导图

    第三章-局域网-思维导图

    机械工程PT300机械故障仿真测试台:高校教学与科研用精密振动分析及故障诊断实验系统了您提供的规范

    内容概要:PT300机械故障综合模拟实验台由瓦仑尼安教学设备有限公司生产,旨在帮助用户深入了解振动特征知识及复杂转子振动频谱分析,实现精密振动分析和精准故障诊断。该实验台能模拟轴承故障、不平衡、不对中、设备松动、转子摩擦等多种机械故障现象,可进行不同转速下的轴承故障频率识别、转子静动平衡模拟试验、设备启停机测试等实验。设备采用高效节能ABB三相交流电动机,配备高精度转速控制和测量模块,确保运行稳定。此外,实验台还设有透明防震安全罩和互锁开关,保障实验安全。; 适合人群:高校师生、科研人员等需要学习或研究机械故障诊断相关理论知识和实践技能的人群。; 使用场景及目标:①用于高校等教育机构的教学,辅助学生理解机械故障诊断的理论知识和实践技能;②满足科研人员进行机械故障诊断算法验证、故障特征分析等科研需求。; 其他说明:PT300机械故障综合模拟实验台的每个部件均经过高精度加工,确保在不同振动状态下稳定运行。用户可根据期望分析特定部件的故障特征。设备尺寸为735mm(长)×310mm(宽)×350mm(高),保修一年,且提供免费操作指导服务。

    Android毕设实战项目基于Android+Django+sqlit3开发.zip

    【项目资源】: 适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    【光子晶体模拟】基于COMSOL弱形式PDE的三维光子晶体能带结构计算与优化:电磁场切向连续性处理及带隙分析系统设计使用COMSOL

    内容概要:本文详细介绍了使用COMSOL Multiphysics的弱形式接口对三维光子晶体进行数值模拟的方法和技巧。文章通过具体的代码示例,解释了如何构建光子晶体的介电常数分布、设置弱形式PDE、处理电磁场切向连续性、应用Floquet周期边界条件以及特征值求解等关键步骤。特别强调了弱形式接口相比传统物理场接口的优势,如灵活性和对复杂边界的处理能力。文中还分享了一些实用的经验和注意事项,如布洛赫边界条件的实现、特征值求解器参数的优化配置以及网格划分的技巧。 适合人群:具备一定电磁学和数值模拟基础的研究人员或工程师,尤其是对光子晶体仿真感兴趣的读者。 使用场景及目标:①理解并掌握COMSOL弱形式接口在光子晶体仿真中的应用;②学习如何通过弱形式设置处理复杂的电磁场问题;③提高对光子晶体能带结构和带隙特性的认识;④掌握特征值求解和网格划分的最佳实践。 阅读建议:由于本文涉及较多的具体代码和物理概念,建议读者在阅读过程中结合COMSOL软件进行实际操作,同时查阅相关电磁理论书籍以加深理解。此外,对于文中提到的一些具体参数设置和技巧,可以通过尝试不同的配置来巩固所学知识。

    (源码)基于Arduino平台的INSPTComputacion2项目.zip

    # 基于Arduino平台的INSPTComputacion2项目 ## 项目简介 INSPTComputacion2是一个基于Arduino平台的开发项目。该项目旨在通过Arduino的硬件和软件能力,实现一系列计算和交互功能。通过此项目,用户可以体验到Arduino在嵌入式系统、物联网和微控制器等领域的强大功能。 ## 项目的主要特性和功能 该项目的主要特性和功能包括但不限于以下几点 1. 嵌入式系统开发利用Arduino的硬件资源,开发嵌入式系统应用。 2. 物联网应用实现Arduino与物联网技术的结合,进行数据采集、传输和控制。 3. 交互设计通过Arduino实现人机交互,如按钮控制、LED显示等。 4. 数据处理利用Arduino进行数据处理和分析,如温度、湿度等环境数据的采集和处理。 ## 安装使用步骤 以下是在已下载本项目源码文件后的安装使用步骤 1. 确保已安装Arduino IDE软件。

    毕业设计物联网实战项目基于云且连接 Internet 的新式应用程序。 可用于建立Web应用、 IoT物联网、移动后端等。.zip

    【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    毕业设计物联网实战项目基于touchgfx,调度基于freertos.zip

    【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

    Python数据结构-学习笔记

    Python数据结构-学习笔记

    电影在线购票系统(springboot+ssm+vue+mysql)含万字系统详细说明文档

    该系统展示了一个电影在线购票系统的结构图,系统分为前台和后台两部分。前台包括首页、影院信息、电影信息、电影资讯和个人中心等模块,主要面向普通用户,提供电影浏览、选座购票、个人账户管理等功能。后台部分由管理员通过后台模块进行操作,包括系统首页、用户管理、场次管理、时间段管理、影院信息管理、电影分类管理、电影信息管理、订单管理和个人中心等模块,用于系统的维护和管理,如用户信息维护、电影和影院信息更新、订单处理等。整个系统旨在为用户提供便捷的在线购票体验,同时确保后台管理的高效和有序。

    5G NR射频基本指标一致性自测用例

    5G NR射频一致性自测用例 由于NR射频测试场景太多,全指标自动化跑测用时太久,本表格选取了主要场景的关键指标,以便于射频工程师对产品的NR射频指标进行初步摸底。 表格每一页概况: 1、NR TDD摸底指标N77/78/79/41 2、NR FDD摸底指标N1/3//5/8/28 附录1、各频段带宽所支持的SCS 附录2、各带宽SCS的RB配置 附录3、灵敏度测试的RB配置 附录4、摸底测试信道查询 附录5、各频段频率范围

    毕业设计物联网实战项目基于STM32、ESP8266、EMQX和Android的智能家居系统.zip

    【项目资源】: 物联网项目适用于从基础到高级的各种项目,特别是在性能要求较高的场景中,比如操作系统开发、嵌入式编程和底层系统编程。如果您是初学者,可以从简单的控制台程序开始练习;如果是进阶开发者,可以尝试涉及硬件或网络的项目。 【项目质量】: 所有源码都经过严格测试,可以直接运行。 功能在确认正常工作后才上传。 【适用人群】: 适用于希望学习不同技术领域的小白或进阶学习者。 可作为毕设项目、课程设计、大作业、工程实训或初期项目立项。 【附加价值】: 项目具有较高的学习借鉴价值,也可直接拿来修改复刻。 对于有一定基础或热衷于研究的人来说,可以在这些基础代码上进行修改和扩展,实现其他功能。 【沟通交流】: 有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 鼓励下载和使用,并欢迎大家互相学习,共同进步。 # 注意 1. 本资源仅用于开源学习和技术交流。不可商用等,一切后果由使用者承担。 2. 部分字体以及插图等来自网络,若是侵权请联系删除。

Global site tag (gtag.js) - Google Analytics