当前位置: 首页 > news >正文

网络编程中容易踩的坑罗列,谨记!

1、TCP没考虑粘包分包

        TCP是面向连接的可靠协议,TCP是流式协议,创建TCP套接字的类型为SOCK_STREAM

        int sockfd = socket(AF_INET, SOCK_STREAM, 0);很多同学面试时对书上的话背诵如流,在实际TCP编程中却没有处理粘包和分包的代码,以为TCP也和UDP一样,客户端每send一次,服务端就会recv一次,在本机上测试可能也没有出现问题,一旦到了线上发生粘包和分包的情况就会导致逻辑出错甚至程序崩溃。

解决方案:

        1、使用流式解析器保存当前状态,如http-parser就使用了流式解析;

        2、使用缓存push接收到的数据,判断接收到完整的一帧数据再pop取出进行处理;

Tips:

        在libhv中可通过hio_set_unpack设置拆包规则,支持固定包长、分隔符、头部长度字段三种常见的拆包方式,调用该接口设置拆包规则后,内部会根据拆包规则处理粘包与分包,保证回调上来的是完整的一包数据,大大节省了上层处理粘包与分包的成本。

2、UDP没考虑丢包

        在一些追求低延时的场景,为了避免TCP三次握手,我们会考虑使用UDP协议,但是却忽略了系统对丢包的容忍度,没考虑到某个关键包丢失带来的影响,没有重传重组机制。

解决方案:

        结合FEC、KCP、UDT、QUIC等手段增强可靠性;

Tips:

        libhv计划陆续集成FEC、KCP、UDT、QUIC等开源实现,欢迎有志之士加入;

3、长连接没考虑应用层心跳

        TCP连接不是指真的有一条物理的连接,而是通信双方靠状态来记录维持的,从客户端发起SYN请求开始,状态就开始有序转换了。如果不发包,我们也就无法感知对方是否掉线,虽然TCP协议本身有keepalive机制,但是默认的间隔时间特别久,也无法携带其它信息,所以发送应用层心跳是非常有必要的,能快速感知掉线以便做出通知和处理,也能及时关闭fd,释放相关资源,以节省开销。

解决方案:

        使用定时器发送心跳包,多长时间或者多少次没有收到回应便断开连接;

Tips:

        在libhv中可通过hio_set_heartbeat设置心跳;

4、大数据没考虑分片和流量控制

        见过有人直接将几十M、上百M甚至几G的文件直接读到内存进行发送,试问你家内存TB级别的吗,经的起这么消耗,另外不做发送速率控制和流量控制,可能会导致网络拥塞。

解决方案:

        循环从磁盘读取少量数据到内存再发送,并做好流量控制;

Tips:

        libhv中大文件的发送示例可参考examples/httpd里的largeFileHandler;

5、客户端没考虑断线重连

        网络哪没有个掉线的时候,如果没有断线重连机制,将会严重影响用户体验,试想你正在打游戏,突然掉线了,不给你自动重连,必须重新启动应用程序,是不是很影响心情。

Tips:

        在libhv中可通过TcpClient::setReconnect设置重连延时策略;

6、外网没考虑加密通信

        在外网环境不使用SSL/TLS加密通信,就犹如一个人在大街上裸奔,没有丝毫隐私可言,安全系数为0。

解决方案:

        1、集成openssl、gnutls、mbedtls等SSL/TLS加密通信库;

        2、在网关处使用SSL代理,如使用nginx做反向代理服务;

Tips:

        在libhv中集成了openssl、gnutls、mbedtls等SSL/TLS加密通信库,打开WITH_OPENSSL、WITH_GNUTLS、WITH_MBEDTLS选项编译,通过hio_enable_ssl即可开启SSL/TLS加密通信。

7、没有处理SIGPIPE

        当向已经收到RST的socket执行写操作时,内核就会向进程发送一个SIGPIPE信号,该信号的默认行为是终止进程。通常的做法是忽略该信号。

        signal(SIGPIPE, SIG_IGN);

8、大小端字节序问题

        计算机硬件有两种存储数据的方式:大端字节序和小端字节序。网络通信中我们一般使用大端字节序,如果我们不按照对应的字节序来编码解码,就会得到错误的值。

9、多线程发送乱序问题

        TCP虽然保证重传重组,但是我们自己要保证发送数据的有序性,特别是多线程发送时,即使加锁我们也无法保证哪个线程先发送,除非每个发送的包都是独立完整的一包,不分先后顺序,否则就可能引发乱序问题。

解决方案:

        通常不建议多线程发送,而是由一个线程来负责发送。

Tips:

        libhv中的hio_write、hio_close是多线程安全的,这可以让网络IO事件循环线程里接收数据、拆包组包、反序列化后放入队列,消费者线程从队列里取出数据、处理后发送响应和关闭连接,变得更加简单。

10、串包问题

        串包即将本将发送给A的数据发送给了B。通常发生在服务器用fd1接受A的请求,A掉线后,B再上线了,POSIX标准要求每次打开文件的时候必须是要当前最小可以的文件描述符,于是又将fd1分配给了B,如果你继续使用fd1给A发送数据就会发送到了B。

解决方案:

        该问题发生的根本原因是使用fd作为了设备的标示,应该建立某种机制来确认socket句柄是否是你想发送的那一个,例如设备连接后通过登录验证携带uuid来唯一标示该设备。

        

11、server端业务进程响应心跳超时被监控进程kill,导致数据或者逻辑异常

        我们的后台框架采用的是proxy,worker模型,proxy处理连接和会话,worker处理业务,proxy和worker之间通过共享内存队列进行通信,并有监控进程扫描proxy和worker的运行情况。管理进程会定时向worker发起心跳查询,防止业务进程挂起。业务worker的心跳默认是60s,如果任务处理超过60s没有回复心跳,该进程会被认为异常,被监控进程kill后重启。review代码,没有发现sleep或者耗时操作,初步判定是网络连接异常导致的超时,检查了客户端连接代码,果然是没有使用带超时的连接,导致超时被监控进程kill

        分析到这里,应该是找到原因了,但是疑问来了:默认连接超时是多久,该怎样设置连接超时,如果是非阻塞的socket该怎么做?

先自行回顾下三次握手的过程。

connect函数的接口并没有设置超时时间,那么默认的超时机制是什么情况

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

侯捷说源码面前,了无秘密,还是看协议栈源码。

先从系统调用开始,inet_stream_connect是connect调用的socket层实现

代码在内核文件 net/ipv4/af_inet.c

int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,                          int addr_len, int flags){        struct sock *sk = sock->sk;        int err;        long timeo;        ...        timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);        ...}

从代码可以看到,connect只是完成发送syn的过程,后续的两次握手由协议栈完成。如果是非阻塞方式,返回的错误码是EINPROGRESS,超时时间在连接前设置,设置连接超时和设置发送超时是一样的 。

timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);

设置超时的实现​​​​​​​

static inline long sock_sndtimeo(const struct sock *sk, bool noblock){    return noblock ? 0 : sk->sk_sndtimeo;}

如果没有设置发送超时,那么默认的机制是什么?继续看代码,传输层的具体实现是调用tcp_v4_connect(),中间过程忽略,最后调用构造syn并发送的接口是tcp_connect()

代码在文件 net/ipv4/tcp_output.c

从代码可以看到发送后会启用重传定时器,直到应答或者超时,每次重传的超时时间采用指数退避的方式。具体实现是这两个函数​​​​​​​

tcp_write_timeout()retransmits_timed_out()

如果发送syn超时没有响应,重传次数sysctl_tcp_sys_retries,这个值是在tcp的系统参数设置,使用 sysctl 查看,默认设置为5​​​​​​​

sysctl net.ipv4.tcp_syn_retriesnet.ipv4.tcp_syn_retries = 5

至此,tcp的connect的机制已经很清楚了,如果设置了超时,当syn_retries重传syn次数的累计时间大于超时,那么在超时后返回,否则在syn_retries重传累计时间后返回。

为了验证以上逻辑,使用telnet 和tcpdump进行验证​​​​​​​

telnet 192.168.128.254 10086 Trying 192.168.128.254... telnet: Unable to connect to remote host: Connection timed out

耗时:63.128s

tcpdump跟踪如下:​​​​​​​

00:37:25.986061 IP 192.168.128.131.46640 > 192.168.128.254.10086: Flags [S], seq 3312153031, win 1460000:37:26.983700 IP 192.168.128.131.46640 > 192.168.128.254.10086: Flags [S], seq 3312153031, win 1460000:37:28.987752 IP 192.168.128.131.46640 > 192.168.128.254.10086: Flags [S], seq 3312153031, win 1460000:37:32.995936 IP 192.168.128.131.46640 > 192.168.128.254.10086: Flags [S], seq 3312153031, win 1460000:37:41.012194 IP 192.168.128.131.46640 > 192.168.128.254.10086: Flags [S], seq 3312153031, win 1460000:37:57.059795 IP 192.168.128.131.46640 > 192.168.128.254.10086: Flags [S], seq 3312153031, win 14600

如果目标ip不可达,在5次重试后返回,总耗时63.128s,从时间戳可以看到重传syn的时间是采用指数退避的方式,分别为 1,2,4,8,16,32

如果目标ip可达,只是没有对应监听端口,在一次重试后,对端机器直接发送了reset标志,连接结束,耗时只要1s多,tcpdump跟踪如下:​​​​​​

00:52:21.776637 IP 192.168.128.131.58497 > 192.168.128.1.10086: Flags [S], seq 2415778508, win 1460000:52:22.775693 IP 192.168.128.131.58497 > 192.168.128.1.10086: Flags [S], seq 2415778508, win 1460000:52:22.799371 IP 192.168.128.1.10086 > 192.168.128.131.58497: Flags [R.], seq 19086327, ack 2415778509, win 64240

telnet: Unable to connect to remote host: Connection refused

因此,要设置连接超时其实有两种方法,如果是非阻塞方式,按照Stevens的建议

        1、设置socket为非阻塞

        2、根据connect返回值检查连接是否建立

        3、调用select

        4、检查超时

        5、检查socket状态是可读还是可写

        如果是阻塞方式,根据之前的源码分析,只要在连接前设置socket的发送超时即可

int connect_with_timeout(){    ...    struct timeval timeo = {1, 0};    socklen_t len = sizeof(timeo);    fd = socket(AF_INET, SOCK_STREAM, 0);    setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len);    int ret=connect(fd, (struct sockaddr*)&addr, sizeof(addr))    ...}

12、在接入层调用后端多个逻辑服务时,某一个后端的服务异常,导致接入层不能处理新的请求

查看机器的log,发现某个后端服务异常,代码使用了短连接请求后端服务,并在失败时自动重试,创建socket时发生错误。查看机器的网络状态,发现有大量的TIME_WAIT状态。

统计机器的TIME_WAIT状态数量有几个命令,最简单的是 cat /proc/net/sockstat​​​​​​​

sockets: used 2861TCP: inuse 603 orphan 0 tw 19 alloc 795 mem 339UDP: inuse 985 mem 557

如果要查看更详细的状态统计,可以使用netstat 或者ss 加 awk 来处理

netstat -ant |awk '{if(NR>1)++s[$NF]} END {for(k in s) print k,s[k]}' ss -ant |awk '{if(NR>1)++s[$1]} END {for(k in s) print k,s[k]}'

推荐使用ss命令,当socket数量很大的时候,ss会快很多。

因此,原因很清楚了,是短连接在后端服务异常时大量产生的TIME_WAIT状态导致创建文件描述符失败,不能处理请求。

这种情况通常的处理建议是打开tcp_tw_recycle 或者tcp_tw_reuse 选项,那么是否有效,还会不会有什么坑?

TIME_WAIT是在连接断开时产生,先看下连接断开的过程:

        上面就是常说的连接断开四次挥手的过程,TIME_WAIT出现在主动断开连接方,那它存在的意义是什么呢?

stevens在unix网络编程里边讲到有两点:

        1、保证TCP连接关闭的可靠性。如果最终发送的ACK丢失,被动关闭的一端会重传最终的FIN包,如果执行主动关闭的一端没有维护这个连接的状态信息,会发送RST包响应,导致连接不正常关闭。

        2、允许老的重复分组在网络中消逝。假设在一个连接关闭后,发起建立连接的一端(客户端)立即重用原来的端口、IP地址和服务端建立新的连接。老的连接上的分组可能在新的连接建立后到达服务端,TCP必须防止来自某个连接的老的重复分组在连接终止后再现,从而被误解为同一个连接的化身。要实现这种功能,TCP不能给处于TIME_WAIT状态的连接启动新的连接。

        TIME_WAIT的时长通常定义成2*MSL,MSL表示报文在网络上存在的最长时间,如果超过这个时间,报文将被丢弃。linux下TIME_WAIT被定义在tcp.h中,时间是60s,除非重新编译内核,否则不能修改。​​​​​​​

``` /* how long to wait to destroy TIME-WAIT state, about 60 seconds*/ #define TCP_TIMEWAIT_LEN (60*HZ) ```

        如果每秒有1000个请求,在60秒内产生的TIME_WAIT就有60000个,要控制或者减少TIME_WAIT的数量,协议栈提供了tcp_tw_recycle、tcp_tw_reuse、tcp_max_tw_buckets这几个选项,下面逐一分析。

tcp_tw_recycle

        linux协议栈实现的时候提供了一种快速回收TIME_WAIT状态的机制,不用等待2MSL的时间,只要等待一个重传的时间即可回收,在idc内部,这个时间极短,可能不到1ms。但是新建立的连接可能存在风险:

        1、如果之前的FIN延迟到达,新连接会被reset

        2、如果之前发出的包延时后到达对端,会造成干扰

tcp协议栈设计的时候是如何处理这些风险呢,代码如下:​​​​​​​

int tcp_conn_request(struct request_sock_ops *rsk_ops,                     const struct tcp_request_sock_ops *af_ops,                     struct sock *sk, struct sk_buff *skb){    ...    if (!want_cookie && !isn) {        if (tcp_death_row.sysctl_tw_recycle) {            bool strict;             dst = af_ops->route_req(sk, &fl, req, &strict);             if (dst && strict &&                !tcp_peer_is_proven(req, dst, true,tmp_opt.saw_tstamp)) {                    NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);                    goto drop_and_release;            }        }    }        ...}

在tcp_tw_recycle模式下,判断是无效连接的条件是:

        1、来自对端的tcp syn请求携带时间戳

        2、本机在MSL时间内接收过来自同一台ip机器的tcp数据

        3、新连接的时间戳小于上次tcp数据的时间戳

        以上条件满足时,连接请求会被拒绝,使用netstat -s |grep timestamp 有如下记录

……packets rejects in established connections because of timestamp

        因此,在启用了tcp_tw_recycle的情况下,TIME_WAIT时间内(60s),同一源ip主机syn请求中的timestamp必须是递增的,连接才能被接受。

        这个看起来很完美,同一个主机的timestamp的一定是递增的,但是NAT环境就悲剧了,NAT下,多个主机映射到同一个或几个对外IP,NAT设备只修改源地址和端口,timestamp不做修改,不能保证来自NAT机器多个主机间连接请求的timestamp是递增的,时间戳小的请求都会被拒绝。

tcp_tw_reuse

TIME_WAIT的重用只满足一定的条件下,处于TIME_WAIT状态的socket连接可以被新请求的syn使用。条件如下:

        1、新请求的sequence要大于TIME_WAIT连接的最后的sequence

        2、如果启用了tcp的timestamp选项,syn请求的时间戳要大于TIME_WAIT连接最后接收数据的时间戳

这个选项没有太大的意义,满足这个条件的情形并不多,并不能减少TIME_WAIT的数量。

tcp_max_tw_buckets:这个选项其实没有什么可说的,就是设置系统允许的最大TIME_WAIT数量,如果超过这个量,就不再出现TIME_WAIT,直接close​​​​​​​

struct inet_timewait_sock *inet_twsk_alloc(const struct sock *sk,                                           struct inet_timewait_death_row *dr,                                           const int state){    struct inet_timewait_sock *tw;    if (atomic_read(&dr->tw_count) >= dr->sysctl_max_tw_buckets)            return NULL;    ...}

        这种方式对TIME_WAIT数量控制简单粗暴,但是效果也比较明显。但是问题和tcp_tw_recycle类似,新连接也可能被对端重传的FIN reset。

        总结:控制TIME_WAIT的选项都存在一些问题,最好慎用。最好的方式是维持正常的TIME_WAIT状态,通过连接池的方式复用连接,减少TIME_WAIT出现的数量。如果要使用tcp_tw_recycle,一定要确保没有NAT设备接入,如果是只有client场景的机器,可以使用tcp_tw_reuse或增大net.ipv4.ip_local_port_range范围来发起更多的连接。

13、使用spp实现简单的web服务器,压测时短连接功能正常,但是采用keeplive模式,大约有40ms的时延

抓包分析发现,server端连续发送了两个小于mss的包,第一个包发出后,经过大约40ms才确认,第二个包才发出。检查代码,回包时,先回了http的包头,再回复http的body,命中nagle算法和delayed ack的应用场景,后一个包延时发出,但是为什么短连接正常,长连接有问题?

wikipedia对nagle算法的描述:

https://en.wikipedia.org/wiki/Nagle%27s_algorithm

算法实现:​​​​​​​

if there is new data to send  if the window size >= MSS and available data is >= MSS    send complete MSS segment now  else    if there is unconfirmed data still in the pipe      enqueue data in the buffer until an acknowledge is received    else      send data immediately    end if  end ifend if

xshell或telnet这样的应用,每次键盘输入发送包含一个字符的包,却要耗费40字节的包头(tcp头加ip头),为了改进这种情况,Nagle算法的做法是先把第一个小包发送出去,后面的小包都缓存起来,直到收到前一个数据段的ack,或者缓存数据长度已经达到mss大小才发送,代码文件在 net/ipv4/tcp_output.c​​​​​​​

static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,                           int push_one, gfp_t gfp){    ...    if (tso_segs == 1) {        if (unlikely(!tcp_nagle_test(tp, skb, mss_now,(tcp_skb_is_last(sk, skb) ?nonagle : TCP_NAGLE_PUSH))))            break;    } else {        if (!push_one && tcp_tso_should_defer(sk, skb, &is_cwnd_limited,max_segs))            break;    }    ...}

具体判断是否启用nagle算法的逻辑:

/* Return false, if packet can be sent now without violation Nagle's rules: * 1. It is full sized. (provided by caller in %partial bool) * 2. Or it contains FIN. (already checked by caller) * 3. Or TCP_CORK is not set, and TCP_NODELAY is set. * 4. Or TCP_CORK is not set, and all sent packets are ACKed. *    With Minshall's modification: all sent small packets are ACKed. */static bool tcp_nagle_check(bool partial, const struct tcp_sock *tp,                            int nonagle){    return partial &&            ((nonagle & TCP_NAGLE_CORK) ||             (!nonagle && tp->packets_out && tcp_minshall_check(tp)));}

根据上面代码,nagle算法生效的条件是:

        1、当前发送的包小于mss

        2、启用TCP_NAGLE_CORK 并禁用TCP_NODELAY,或者启用TCP_NODELAY,有需要发送的数据以及还未ack的数据包

        明确nagle算法的条件后,问题来了,为什么上一个包的ack是经过了40ms才返回,正常情况应该就是一个rtt的时间,同一idc的rtt时延小于1ms,怎么会有40ms延时呢?

        其实这个涉及到了tcp协议的另外一个机制:延迟确认delayed ack

        tcp发送ack有两种方式:quick ack 和 delayed ack

        quick ack:收到数据包后,立即发送ACK给对端。

        delayed ack:收到数据包后,不会立即发送ACK,而是启动延时确认定时器,在此期间:

                1. 本端有数据包要发送给对端。就在发送数据包的时候捎带上此ACK。

                2. 本端没有数据包要发送,定时器超时后发送ACK给对端。

        根据算法的描述可以看到:nagle算法和delayed ack都是为了减少小数据包在网路中传输的数量,优化网络性能。

        delayed ack的具体实现在代码文件net/ipv4/tcp_input.c

检查是否需要发送ack:

static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible){    struct tcp_sock *tp = tcp_sk(sk);         /* More than one full frame received... */    if (((tp->rcv_nxt - tp->rcv_wup) > inet_csk(sk)->icsk_ack.rcv_mss &&         /* ... and right edge of window advances far enough.          * (tcp_recvmsg() will send ACK otherwise). Or...          */         __tcp_select_window(sk) >= tp->rcv_wnd) ||        /* We ACK each frame or... */        tcp_in_quickack_mode(sk) ||        /* We have out of order data. */        (ofo_possible && skb_peek(&tp->out_of_order_queue))) {            /* Then ack it now */            tcp_send_ack(sk);    } else {            /* Else, send delayed ack. */            tcp_send_delayed_ack(sk);    }}

满足下列条件之一,需要立即发送ack,否则进入延迟确认模式:

        1、收到大于mss的包且有能力接收数据

        2、满足快速确认模式

        3、有乱序的数据,需要对端重传

检查是否快速确认模式:

/* Send ACKs quickly, if "quick" count is not exhausted * and the session is not interactive. */static inline int tcp_in_quickack_mode(const struct sock *sk){         const struct inet_connection_sock *icsk = inet_csk(sk);         return icsk->icsk_ack.quick && !icsk->icsk_ack.pingpong;}

快速确认模式的初始化:

static void tcp_incr_quickack(struct sock *sk){        struct inet_connection_sock *icsk = inet_csk(sk);        unsigned int quickacks = tcp_sk(sk)->rcv_wnd / (2 * icsk->icsk_ack.rcv_mss);         if (quickacks == 0)                quickacks = 2;        if (quickacks > icsk->icsk_ack.quick)                icsk->icsk_ack.quick = min(quickacks, TCP_MAX_QUICKACKS);} static void tcp_enter_quickack_mode(struct sock *sk){        struct inet_connection_sock *icsk = inet_csk(sk);        tcp_incr_quickack(sk);        icsk->icsk_ack.pingpong = 0;        icsk->icsk_ack.ato = TCP_ATO_MIN;}

        socket有一个pingpong属性来表明当前会话是否交互模式,如果是,会使用延迟确认机制,这个值是动态计算的。

        有数据要发送时,如果当前时间与最近接受数据包的时间间隔小于ato(40ms,min(rtt,200ms)),则进入pingpong模式。因此,一旦有数据交互后,很快就切换到pingpong模式。

        综上,在长连接的模式下,会话很快进入pingpong模式,server端先回了一个http头的小包,client收到数据准备回复ack时进入延时确认机制,server端继续发送http body也是一个小包,nagle算法生效,需要等前一个包的ack到达或者发送的数据大于mss时,数据才会发送,等40ms后,前一个包的延时ack到达,http的body内容才发送出去。

        至此,问题已经真相大白,但是有个疑问,为什么短连接的时候,server端也是连续写入了两个小包,为什么没有触发nagle算法和delayed ack 呢?玄机在这里:

/* There is something which you must keep in mind when you analyze the * behavior of the tp->ato delayed ack timeout interval.  When a * connection starts up, we want to ack as quickly as possible.  The * problem is that "good" TCP's do slow start at the beginning of data * transmission.  The means that until we send the first few ACK's the * sender will sit on his end and only queue most of his data, because * he can only send snd_cwnd unacked packets at any given time.  For * each ACK we send, he increments snd_cwnd and transmits more of his * queue.  -DaveM */static void tcp_event_data_recv(struct sock *sk, struct sk_buff *skb){    ...}

tcp_event_data_recv函数的注释解释的很清楚:

        连接刚启动的时候,拥塞算法使用的是慢启动,必须尽快发送ack,发送方才可能尽快增大发送窗口,发送更多的数据,所以在首次收包的时候,启用了快速确认模式,pingpong模式的值为0,而短连接只有一次业务数据的收发,后边连接就关闭了,nagle算法和delayed ack并没有生效。

        针对问题描述的情形,解决方案是把http头和http body合并后发送,就不会有问题了,因为接收方收到完整数据后会重新发起新的请求,这时候会把上一个包的ack附带发回,发送方就不用等40ms的超时了。

        如果发送方确实存在有多个小包要分别发送,并使用长连接的情况,最好是禁用nagle算法,其实这也是主流的做法,nginx在keeplive模式下就禁用了nagle算法。

相关文章:

网络编程中容易踩的坑罗列,谨记!

1、TCP没考虑粘包分包 TCP是面向连接的可靠协议,TCP是流式协议,创建TCP套接字的类型为SOCK_STREAM int sockfd socket(AF_INET, SOCK_STREAM, 0);很多同学面试时对书上的话背诵如流,在实际TCP编程中却没有处理粘包和分包的代码,以…...

SD-WAN:推动企业网络优化与发展

近年来,软件定义广域网(SD-WAN)逐渐成为众多企业的首选网络解决方案。这背后的原因是什么?接下来我们将深入探讨这一趋势。 在快速发展的通信技术领域,企业对高效、灵活且可扩展的网络架构需求愈发迫切。随着数据流量的…...

[MyBatis-Plus]扩展功能详解

代码生成 使用MP的步骤是非常固定的几步操作 基于插件, 可以快速的生成基础性的代码 安装插件安装完成后重启IEDA连接数据库 mp是数据库的名字?serverTimezoneUTC 是修复mysql时区, 不加会报错 生成代码 TablePrefix选项是用于去除表名的前缀, 比如根据tb_user表生成实体类U…...

循序渐进丨MogDB 5.0 远程访问 MogDB/Oracle 数据库的简便方法(使用@符号)

概述 早期的 MogDB 就提供了Postgres_fdw、Oracle_fdw、MySQL_fdw3个插件,用于远程访问 MogDB/Oracle/MySQL数据库。 旧的版本中,访问远程数据库的表,需要显式创建外部表,而在 MogDB 5.0当中,这种用法得到了简化&…...

大模型训练触达「瓶颈」,基座模型厂商还有必要坚持预训练吗?

进入2024年来,中国大模型行业从狂奔进入到了“长跑阶段”。无论是在技术侧,还是在产业侧,行业内都产生了更多新的思考。 从技术发展上看,在算力受限的情况下,中国基座模型的研发能力在全球范围内处在什么身位、如何追赶…...

media3 exoplayer 扩展解码库在这里 take it , please !

Media3 ExoPlayer 扩展解码库介绍 请注意,本文讨论的是 Media3 ExoPlayer 而不是 Google ExoPlayer2。详细参考:Media3 ExoPlayer 迁移指南 文章最后提供了已经编译好的AAR文件,直接引用即可!!! 为什么选…...

在Xshell中查看日志文件详情

学习总结 1、掌握 JAVA入门到进阶知识(持续写作中……) 2、学会Oracle数据库入门到入土用法(创作中……) 3、手把手教你开发炫酷的vbs脚本制作(完善中……) 4、牛逼哄哄的 IDEA编程利器技巧(编写中……) 5、面经吐血整理的 面试技…...

深入理解计算机系统--计算机系统漫游

对于一段最基础代码的文件hello.c&#xff0c;解释程序的运行 #include <stdio.h>int main() {printf ( "Hello, world\n") ;return 0; }1.1、信息就是位上下文 源程序是由值 0 和 1 组成的位&#xff08;比特&#xff09;序列&#xff0c;8 个位被组织成一组…...

哪些指标可以用来评估精益生产现场管理和改善的效果?

在探讨如何评估精益生产现场管理和改善效果时&#xff0c;我们需要关注一系列关键指标&#xff0c;这些指标不仅反映了生产流程的效率&#xff0c;还体现了质量和客户满意度的提升。详细如天行健企业管理咨询公司下文所述&#xff1a; 1. 生产效率 每小时生产数量&#xff08;…...

在 Linux 系统上安装免费杀毒软件

选择合适的免费杀毒软件 Linux 上流行的免费杀毒软件&#xff1a; . ClamAV&#xff1a;最为知名的开源免费杀毒软件&#xff0c;支持多种 Linux 发行版。它可以扫描病毒、恶意软件以及 Windows 系统上的威胁。 Sophos Antivirus for Linux&#xff1a;虽然是商业软件&#xff…...

第 7 章:Vue UI 组件库

1. PC 端常用 UI 组件库 Element UI&#xff1a;https://element.eleme.cnIView UI&#xff1a;https://www.iviewui.com 2. 移动端常用 UI 组件库 Vant&#xff1a;https://youzan.github.io/vantCube UI&#xff1a;https://didi.github.io/cube-uiMint UI&#xff1a;htt…...

【SQL】SQL用户管理和权限

&#x1f384; 管理用户 &#x1f4e2; 用来管理数据库用户、控制数据库的访 问权限 ⭐ 查询用户 &#x1f4e2; mysql数据库中的user表中,存放了当前数据库中所有的用户和用户的权限 use mysql; select * from user; &#x1f4e2; 其中Host代表当前用户访问的主机, 如果为…...

STM32应用详解(5)USART串口初始化

文章目录 一、USART初始化二、代码说明1.原理图2.main函数3.USART串口初始化函数4.代码整体结构 三、USART串口初始化总结 一、USART初始化 所谓的对USART进行初始化&#xff0c;就是对USART固件库函数的调用&#xff0c;来完成串口(USART)的设置&#xff0c;比如设置波特率、…...

渗透实战 JS文件怎么利用

1.前言 关于JS在渗透测试中的关键作用&#xff0c;想必不用过多强调&#xff0c;在互联网上也有许多从JS中找到敏感信息从而拿下关键系统的案例。大部分师傅喜欢使用findsomething之类的浏览器插件&#xff0c;也有使用诸如Unexpected.information以及APIFinder之类的Burp插件…...

啥是CTF?新手如何入门CTF?

CTF是啥 CTF 是 Capture The Flag 的简称&#xff0c;中文咱们叫夺旗赛&#xff0c;其本意是西方的一种传统运动。在比赛上两军会互相争夺旗帜&#xff0c;当有一方的旗帜已被敌军夺取&#xff0c;就代表了那一方的战败。在信息安全领域的 CTF 是说&#xff0c;通过各种攻击手法…...

解决python多环境冲突问题

解决Python多环境冲突问题&#xff0c;以下是一些详细的解决方法&#xff1a; 1. 使用虚拟环境 虚拟环境允许你为每个项目创建独立的Python环境&#xff0c;每个环境可以有自己的库和依赖。常用的工具包括venv、virtualenv和pipenv。 使用 venv venv 是Python 3.3及以上版本…...

Aatrox-Bert-VITS2部署指南

一、模型介绍 【AI 剑魔 ①】在线语音合成&#xff08;Bert-Vits2&#xff09;&#xff0c;将输入文字转化成暗裔剑魔亚托克斯音色的音频输出。 作者&#xff1a;Xz 乔希 https://space.bilibili.com/5859321 声音归属&#xff1a;Riot Games《英雄联盟》暗裔剑魔亚托克斯 …...

计算不停歇,百度沧海数据湖存储加速方案 2.0 设计和实践

本文整理自百度云智峰会 2024 —— 云原生论坛的同名演讲。 今天给大家介绍下百度沧海存储团队在数据湖加速方面的工作进展情况。 数据湖这个概念&#xff0c;从 2012 年产生到现在已经有十余年的时间&#xff0c;每家公司对它内涵的解读都不太一样。但是数据湖的主要存储底座…...

vue2项目 实现上边两个下拉框,下边一个输入框 输入框内显示的值为[“第一个下拉框选中值“ -- “第二个下拉框选中的值“]

效果: 思路: 采用vue中 [computed:] 派生属性的方式实现联动效果,上边两个切换时,下边的跟随变动 demo代码: <template><div><!-- 第一个下拉框 --><select v-model"firstValue"><option v-for"option in options" :key&q…...

el-radio 点击报错 Element with focus: inputAncestor with aria-hidden....

一、序言 浏览器版本影响的问题&#xff08;与代码无关&#xff0c;可能是web或浏览器相关协议更新导致&#xff09;&#xff0c;不影响功能的使用. 翻译&#xff1a;元素上的Blocked aria-hidden&#xff0c;因为刚刚接收焦点的元素不能对辅助技术用户隐藏。避免在焦点元素或…...

集成平台,互联互通平台,企业大数据平台建设方案,技术方案(Word原件 )

企业集成平台建设方案及重点难点攻坚 基础支撑平台主要承担系统总体架构与各个应用子系统的交互&#xff0c;第三方系统与总体架构的交互。需要满足内部业务在该平台的基础上&#xff0c;实现平台对于子系统的可扩展性。基于以上分析对基础支撑平台&#xff0c;提出了以下要求&…...

宠物用品交易网站开发:SpringBoot技术详解

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常…...

解构OpenAI swarm:利用Cursor进行框架分析与示例运行

解构OpenAI SWARM:利用Cursor进行框架分析与示例运行 1. 引言 在AI技术日新月异的今天,OpenAI再次为我们带来了惊喜。SWARM框架作为其最新研究成果,正在开创多智能体协作的新纪元。本文将带您深入探索这一框架,通过Cursor工具进行代码分析,并手把手教您安装运行SWARM。无论您…...

基于springboot的秦皇岛旅游景点管理系统 设计与实现

博主介绍&#xff1a;专注于Java&#xff08;springboot ssm 等开发框架&#xff09; vue .net php phython node.js uniapp 微信小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不…...

uniapp展示本地swf格式文件,实现交互

概览 uniapp打包的Android项目实现本地swf格式文件的展示&#xff0c;并且能够进行交互 需求分析 1、因为是打包的Android项目展示本地的swf文件&#xff0c;首先需要拿到这个本地的swf文件路径 2、如何在uniapp的vue页面中展示swf&#xff0c;因为没有直接展示swf文件的标…...

ZYNQ:流水灯实验

实验目的 PL_LED0 和 PL_LED1 连接到 ZYNQ 的 PL 端&#xff0c;PL_LED0 和 PL_LED1循环往复产生流水灯的效果&#xff0c;流水间隔时间为 0.5s。 原理图 程序设计 本次实验是需要实现两个LED的循环熄灭点亮&#xff0c;时间间隔是0.5S,对时间间隔的控制使用计数器来完成。本…...

StratoVirt中vCPU拓扑(SMP)配置与实现的深度解析

tratoVirt作为计算产业中面向云数据中心的企业级虚拟化平台&#xff0c;通过一套统一的架构支持虚拟机、容器和Serverless三种场景。它不仅在轻量低噪、软硬协同和Rust语言级安全等方面具备关键技术竞争优势&#xff0c;还预留了接口和设计来支持更多特性&#xff0c;并向着标准…...

Xml 相关注解使用

XmlRootElement XmlAccessorType(XmlAccessType.FIELD) 在 Java 中&#xff0c;XmlRootElement 和 XmlAccessorType 是用于 JAXB&#xff08;Java Architecture for XML Binding&#xff09;库的注解。它们帮助开发人员将 Java 对象映射到 XML 格式&#xff0c;反之亦然。下面对…...

本地时间与时区时间转化(以Helpdesk和BPI Challenge 2012为例)

数据集&#xff1a;Helpdesk 数据来源&#xff1a;https://data.4tu.nl/datasets/94ee26c8-78f6-4387-b32b-f028f2103a2c/1 描述问题&#xff1a;此数据三列属性皆为object&#xff0c;此为本地时间&#xff0c;只需关注时间格式的变化。 经过格式转化&#xff0c; 数据集&am…...

Golang | Leetcode Golang题解之第482题秘钥格式化

题目&#xff1a; 题解&#xff1a; func licenseKeyFormatting(s string, k int) string {ans : []byte{}for i, cnt : len(s)-1, 0; i > 0; i-- {if s[i] ! - {ans append(ans, byte(unicode.ToUpper(rune(s[i]))))cntif cnt%k 0 {ans append(ans, -)}}}if len(ans) &…...