注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4

在分析connect()系统调用时,我们已经发送SYN报文,所以服务端就需要作出回应了。我们依然只分析TCP层的操作。SYN报文到达TCP层由tcp_v4_rcv()接管。

int tcp_v4_rcv(struct sk_buff *skb)
{const struct iphdr *iph;const struct tcphdr *th;struct sock *sk;int ret;struct net *net = dev_net(skb->dev);
...//checksum检查,其实也就是完整性校验if (skb_checksum_init(skb, IPPROTO_TCP, inet_compute_pseudo))goto csum_error;th = tcp_hdr(skb);//获取TCP头部iph = ip_hdr(skb);//获取ip头部TCP_SKB_CB(skb)->seq = ntohl(th->seq);TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +skb->len - th->doff * 4);TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th);TCP_SKB_CB(skb)->tcp_tw_isn = 0;TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);TCP_SKB_CB(skb)->sacked  = 0;//根据报文的源和目的地址在established哈希表以及listen哈希表中查找连接//对于正要建立的连接,返回的就是listen哈希表的连接sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);if (!sk)goto no_tcp_socket;process://如果此时socket状态处于time_wait,那就进入对应的处理流程中if (sk->sk_state == TCP_TIME_WAIT)goto do_time_wait;
...th = (const struct tcphdr *)skb->data;iph = ip_hdr(skb);sk_mark_napi_id(sk, skb);//记录napi的idskb->dev = NULL;bh_lock_sock_nested(sk);tcp_sk(sk)->segs_in += max_t(u16, 1, skb_shinfo(skb)->gso_segs);ret = 0;if (!sock_owned_by_user(sk)) {//如果sk没有被用户锁定,即没在使用//检查是否需要先进入prequeue队列if (!tcp_prequeue(sk, skb))ret = tcp_v4_do_rcv(sk, skb);//进入到主处理函数//如果用户正在使用,则数据包进入backlog中//不太理解的是为什么limit入参是sk_rcvbuf和sk_sndbuf之和} else if (unlikely(sk_add_backlog(sk, skb,sk->sk_rcvbuf + sk->sk_sndbuf))) {bh_unlock_sock(sk);NET_INC_STATS_BH(net, LINUX_MIB_TCPBACKLOGDROP);goto discard_and_relse;}bh_unlock_sock(sk);sock_put(sk);return ret;
...do_time_wait:if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {inet_twsk_put(inet_twsk(sk));goto discard_it;}if (skb->len < (th->doff << 2)) {inet_twsk_put(inet_twsk(sk));goto bad_packet;}if (tcp_checksum_complete(skb)) {inet_twsk_put(inet_twsk(sk));goto csum_error;}//处理在time_wait状态收到报文的情况switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {case TCP_TW_SYN: {struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),&tcp_hashinfo,iph->saddr, th->source,iph->daddr, th->dest,inet_iif(skb));if (sk2) {inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);inet_twsk_put(inet_twsk(sk));sk = sk2;goto process;}/* Fall through to ACK */}case TCP_TW_ACK:tcp_v4_timewait_ack(sk, skb);break;case TCP_TW_RST:tcp_v4_send_reset(sk, skb);inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);inet_twsk_put(inet_twsk(sk));goto discard_it;case TCP_TW_SUCCESS:;}goto discard_it;
}

接收到SYN包后要查看下该报文是否之前已建立的连接,通过__inet_lookup_skb()查找是否有匹配的连接。

static inline struct sock *__inet_lookup_skb(struct inet_hashinfo *hashinfo,struct sk_buff *skb,const __be16 sport,const __be16 dport)
{//sk_buff结构体里有一个变量指向sock,即skb->sk//但是对于尚未建立连接的skb来说,其sk变量为空,因此会走进__inet_lookup()struct sock *sk = skb_steal_sock(skb);const struct iphdr *iph = ip_hdr(skb);if (sk)return sk;elsereturn __inet_lookup(dev_net(skb_dst(skb)->dev), hashinfo,iph->saddr, sport,iph->daddr, dport, inet_iif(skb));
}
static inline struct sock *__inet_lookup(struct net *net,struct inet_hashinfo *hashinfo,const __be32 saddr, const __be16 sport,const __be32 daddr, const __be16 dport,const int dif)
{u16 hnum = ntohs(dport);//查找established哈希表struct sock *sk = __inet_lookup_established(net, hashinfo,saddr, sport, daddr, hnum, dif);//查找listen哈希表return sk ? : __inet_lookup_listener(net, hashinfo, saddr, sport,daddr, hnum, dif);
}

最终会在listen哈希表中找到该连接,也就是服务端的监听socket。

之后如果当前这个监听socket没有被使用,就会进入prequeue队列中处理,但是由于这是SYN报文,还没有进程接收数据,所以不会进入prequeue的真正处理中。

bool tcp_prequeue(struct sock *sk, struct sk_buff *skb)
{struct tcp_sock *tp = tcp_sk(sk);//如果设置了/proc/sys/net/ipv4/tcp_low_latency(低时延)参数,默认为0//或者用户还没有调用接收函数接收数据,那么不使用prequeue队列//ucopy.task会在接收数据函数recvmsg()中设置为接收数据的当前进程//所以对于第一个SYN报文,会从以下分支返回if (sysctl_tcp_low_latency || !tp->ucopy.task)return false;if (skb->len <= tcp_hdrlen(skb) &&skb_queue_len(&tp->ucopy.prequeue) == 0)return false;if (likely(sk->sk_rx_dst))skb_dst_drop(skb);elseskb_dst_force_safe(skb);//加入到prequeue队列尾部__skb_queue_tail(&tp->ucopy.prequeue, skb);tp->ucopy.memory += skb->truesize;//如果prequeue队列长度大于socket连接的接收缓冲区,//将prequeue中的数据报文转移到receive_queue中if (tp->ucopy.memory > sk->sk_rcvbuf) {struct sk_buff *skb1;BUG_ON(sock_owned_by_user(sk));//从prequeue中摘链while ((skb1 = __skb_dequeue(&tp->ucopy.prequeue)) != NULL) {sk_backlog_rcv(sk, skb1);//放入backlog中NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPREQUEUEDROPPED);}tp->ucopy.memory = 0;//如果prequeue中有报文了,那么唤醒睡眠的进程来收取报文} else if (skb_queue_len(&tp->ucopy.prequeue) == 1) {//唤醒sk上睡眠的进程,这里只唤醒其中一个,避免惊群现象//至于怎么唤醒,选择哪个唤醒,暂未研究wake_up_interruptible_sync_poll(sk_sleep(sk),POLLIN | POLLRDNORM | POLLRDBAND);//没有ACK需要发送,重置延时ACK定时器if (!inet_csk_ack_scheduled(sk))inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,(3 * tcp_rto_min(sk)) / 4,TCP_RTO_MAX);}return true;
}

既然不会进入到prequeue队列中,那就进入tcp_v4_do_rcv()的处理,这是个主要的报文处理函数。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{struct sock *rsk;
...//SYN报文走的是这里if (sk->sk_state == TCP_LISTEN) {//查找对应的半连接状态的socketstruct sock *nsk = tcp_v4_hnd_req(sk, skb);if (!nsk)goto discard;//可知,对于SYN报文,返回的还是入参sk,即nsk=skif (nsk != sk) {sock_rps_save_rxhash(nsk, skb);if (tcp_child_process(sk, nsk, skb)) {rsk = nsk;goto reset;}return 0;}} elsesock_rps_save_rxhash(sk, skb);//这里是除ESTABLISHED and TIME_WAIT状态外报文的归宿。。。if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {rsk = sk;goto reset;}return 0;
...
}

半连接状态socket通过tcp_v4_hnd_req()查找。

static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb)
{struct tcphdr *th = tcp_hdr(skb);const struct iphdr *iph = ip_hdr(skb);struct sock *nsk;struct request_sock **prev;/* Find possible connection requests. *///查找半连接队列,对于SYN报文肯定找不到struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,iph->saddr, iph->daddr);if (req)return tcp_check_req(sk, skb, req, prev, false);//再一次查找established哈希表,以防在此期间重传过SYN报文且建立了连接//对于SYN报文这里也是返回空的nsk = inet_lookup_established(sock_net(sk), &tcp_hashinfo, iph->saddr,th->source, iph->daddr, th->dest, inet_iif(skb));if (nsk) {if (nsk->sk_state != TCP_TIME_WAIT) {bh_lock_sock(nsk);return nsk;}inet_twsk_put(inet_twsk(nsk));return NULL;}#ifdef CONFIG_SYN_COOKIESif (!th->syn)sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
#endif//所以最终返回的还是原来的连接,即该函数对于SYN报文啥都没做return sk;
}

接下来就是进入tcp_rcv_state_process()处理,这个函数处理绝大多数状态的报文处理。

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,const struct tcphdr *th, unsigned int len)
{struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);struct request_sock *req;int queued = 0;bool acceptable;u32 synack_stamp;tp->rx_opt.saw_tstamp = 0;switch (sk->sk_state) {case TCP_CLOSE:goto discard;case TCP_LISTEN:
...if (th->syn) {//LISTEN状态收到SYN报文if (th->fin)goto discard;//这里其实就是将请求放入半连接队列,必要时启动SYNACK定时器if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)return 1;kfree_skb(skb);return 0;}goto discard;}
...
}

加入半连接队列通过icsk->icsk_af_ops->conn_request操作。我们知道icsk->icsk_af_ops指向ipv4_specific

const struct inet_connection_sock_af_ops ipv4_specific = {.queue_xmit    = ip_queue_xmit,.send_check    = tcp_v4_send_check,.rebuild_header    = inet_sk_rebuild_header,.sk_rx_dst_set     = inet_sk_rx_dst_set,.conn_request      = tcp_v4_conn_request,
...
};

所以加入半连接的操作就是由tcp_v4_conn_request()操刀。

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{/* Never answer to SYNs send to broadcast or multicast */if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))goto drop;return tcp_conn_request(&tcp_request_sock_ops,&tcp_request_sock_ipv4_ops, sk, skb);
drop:NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);return 0;
}

tcp_v4_conn_request()对tcp_conn_request()做了一个简单的封装。

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)
{struct tcp_options_received tmp_opt;struct request_sock *req;struct tcp_sock *tp = tcp_sk(sk);struct dst_entry *dst = NULL;__u32 isn = TCP_SKB_CB(skb)->tcp_tw_isn;bool want_cookie = false, fastopen;struct flowi fl;struct tcp_fastopen_cookie foc = { .len = -1 };int err;//如果开启了syncookies选项,/proc/sys/net/ipv4/if ((sysctl_tcp_syncookies == 2 ||//或者此时半连接队列已经满了//同时isn不是由tcp_timewait_state_process()函数选择//那么判断是否需要发送syncookieinet_csk_reqsk_queue_is_full(sk)) && !isn) {want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);//不需要发送syncookies就直接丢弃报文if (!want_cookie)goto drop;}//如果全连接队列满了,同时半连接队列里尚未重传过的SYN报文个数大于1//那么就直接丢弃报文if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) {NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);goto drop;}//都没问题的话,那就分配一个request_sock,表示一个请求//这个内存分配是从tcp的slab中分配的req = inet_reqsk_alloc(rsk_ops);if (!req)goto drop;inet_rsk(req)->ireq_family = sk->sk_family;//af_ops即为tcp_request_sock_ipv4_ops,这个结构体比较重要,请留意tcp_rsk(req)->af_specific = af_ops;tcp_clear_options(&tmp_opt);tmp_opt.mss_clamp = af_ops->mss_clamp;tmp_opt.user_mss  = tp->rx_opt.user_mss;//分析该请求的tcp各个选项,比如时间戳、窗口大小、快速开启等选项tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc);if (want_cookie && !tmp_opt.saw_tstamp)tcp_clear_options(&tmp_opt);tmp_opt.tstamp_ok = tmp_opt.saw_tstamp;//记录时间戳选项开启情况//将刚才分析的请求的TCP选项记录到刚刚分配的request_sock中,即req中tcp_openreq_init(req, &tmp_opt, skb);af_ops->init_req(req, sk, skb);if (security_inet_conn_request(sk, skb, req))goto drop_and_free;//如果不需要发送syncookies//同时isn不是由tcp_timewait_state_process()函数选择if (!want_cookie && !isn) {//如果开启了time_wait状态连接快速回收//即设置/proc/sys/net/ipv4/tcp_tw_recycleif (tcp_death_row.sysctl_tw_recycle) {bool strict;//查找路由dst = af_ops->route_req(sk, &fl, req, &strict);if (dst && strict &&//主要用于判断是否会和该IP的旧连接冲突//这里就涉及到nat环境下丢包的问题!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;}}/* Kill the following clause, if you dislike this way. *///如果没有开启syncookies选项else if (!sysctl_tcp_syncookies &&//同时,半连接队列长度已经大于syn backlog队列的3/4(sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <(sysctl_max_syn_backlog >> 2)) &&//并且当前连接和旧连接有冲突!tcp_peer_is_proven(req, dst, false, tmp_opt.saw_tstamp)) {//很有可能遭受synflood 攻击pr_drop_req(req, ntohs(tcp_hdr(skb)->source), rsk_ops->family);goto drop_and_release;}//生成随机报文序列号isn = af_ops->init_seq(skb);}if (!dst) {dst = af_ops->route_req(sk, &fl, req, NULL);if (!dst)goto drop_and_free;}tcp_ecn_create_request(req, skb, sk, dst);//如果要发送syncookies,那就发送if (want_cookie) {isn = cookie_init_sequence(af_ops, sk, skb, &req->mss);req->cookie_ts = tmp_opt.tstamp_ok;if (!tmp_opt.tstamp_ok)inet_rsk(req)->ecn_ok = 0;}tcp_rsk(req)->snt_isn = isn;tcp_openreq_init_rwin(req, sk, dst);fastopen = !want_cookie && tcp_try_fastopen(sk, skb, req, &foc, dst);//这里便是调用tcp_v4_send_synack()发送SYNACK报文了err = af_ops->send_synack(sk, dst, &fl, req,skb_get_queue_mapping(skb), &foc);if (!fastopen) {if (err || want_cookie)goto drop_and_free;tcp_rsk(req)->listener = NULL;//发送报文后将该请求加入半连接队列,同时启动SYNACK定时器//调用inet_csk_reqsk_queue_hash_add()完成上述操作af_ops->queue_hash_add(sk, req, TCP_TIMEOUT_INIT);}return 0;
...
}

要加入半连接队列首先要创建一个request_sock,用于表示客户端发起的请求,然后是做一些初始化,其中req->ts_recent后续会用到多次,这个变量表示的就是对端发送报文的时间(前提是对端开启了时间戳选项)。

static inline void tcp_openreq_init(struct request_sock *req,struct tcp_options_received *rx_opt,struct sk_buff *skb)
{struct inet_request_sock *ireq = inet_rsk(req);req->rcv_wnd = 0;       /* So that tcp_send_synack() knows! */req->cookie_ts = 0;tcp_rsk(req)->rcv_isn = TCP_SKB_CB(skb)->seq;tcp_rsk(req)->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;tcp_rsk(req)->snt_synack = tcp_time_stamp;tcp_rsk(req)->last_oow_ack_time = 0;req->mss = rx_opt->mss_clamp;//如果对端开启时间戳,那么记录下这个时间,也就是对方发送SYN报文的时间req->ts_recent = rx_opt->saw_tstamp ? rx_opt->rcv_tsval : 0;ireq->tstamp_ok = rx_opt->tstamp_ok;//时间戳开启标志ireq->sack_ok = rx_opt->sack_ok;ireq->snd_wscale = rx_opt->snd_wscale;ireq->wscale_ok = rx_opt->wscale_ok;ireq->acked = 0;ireq->ecn_ok = 0;ireq->ir_rmt_port = tcp_hdr(skb)->source;//目的端口,也就是当前服务端的监听端口ireq->ir_num = ntohs(tcp_hdr(skb)->dest);
}

初始化完这个请求后就要看下这个请求是否有问题。主要检查的就是看是否会和当前ip的上次通讯有冲突。该操作通过tcp_peer_is_proven()检查。

#define TCP_PAWS_MSL    60      /* Per-host timestamps are invalidated* after this time. It should be equal* (or greater than) TCP_TIMEWAIT_LEN* to provide reliability equal to one* provided by timewait state.*/
#define TCP_PAWS_WINDOW 1       /* Replay window for per-host* timestamps. It must be less than* minimal timewait lifetime.*/
bool tcp_peer_is_proven(struct request_sock *req, struct dst_entry *dst,bool paws_check, bool timestamps)
{struct tcp_metrics_block *tm;bool ret;if (!dst)return false;rcu_read_lock();tm = __tcp_get_metrics_req(req, dst);if (paws_check) {//如果当前ip的上次tcp通讯发生在60s内if (tm && (u32)get_seconds() - tm->tcpm_ts_stamp < TCP_PAWS_MSL &&//同时当前ip上次tcp通信的时间戳大于本次tcp,或者没有开启时间戳开关//从这里看,快速回收打开选项就很容易导致nat环境丢包((s32)(tm->tcpm_ts - req->ts_recent) > TCP_PAWS_WINDOW ||!timestamps))ret = false;elseret = true;} else {if (tm && tcp_metric_get(tm, TCP_METRIC_RTT) && tm->tcpm_ts_stamp)ret = true;elseret = false;}rcu_read_unlock();return ret;
}

从这个冲突判断上看有这么几个条件:

  1. 同个ip的此次通信和上次通信间隔时间在60s(刚好等于time_wait状态默认持续时间)内
  2. 上次通信时间大于此次通信时间,或者没有开时间戳选项

所以,这样看来在nat环境下就很容易有问题。nat环境下的机器时间可能不统一,也就有可能出现某个机器先发的报文时间比较靠前,后面其他机器发的报文时间比较靠后,那个这个报文就会被丢弃,也就经常出现nat环境下有些机器无法连接网络的问题。

对于这种情况呢,我们注意到这是在快速回收选项开启的前提下才会检查,所以只要把快速回收选项tcp_tw_recycle关闭即可。因此nat环境最好不要打开这个选项。

一切OK的情况下,接着就该发送SYNACK报文了,通过af_ops->send_synack()。上面我们说过af_ops即为tcp_request_sock_ipv4_ops

static const struct tcp_request_sock_ops tcp_request_sock_ipv4_ops = {....route_req  =   tcp_v4_route_req,.init_seq   =   tcp_v4_init_sequence,.send_synack    =   tcp_v4_send_synack,.queue_hash_add =   inet_csk_reqsk_queue_hash_add,
};

因此,SYNACK报文就是通过tcp_v4_send_synack()发送的。

static int tcp_v4_send_synack(struct sock *sk, struct dst_entry *dst,struct flowi *fl,struct request_sock *req,u16 queue_mapping,struct tcp_fastopen_cookie *foc)
{const struct inet_request_sock *ireq = inet_rsk(req);struct flowi4 fl4;int err = -1;struct sk_buff * skb;//获取路由if (!dst && (dst = inet_csk_route_req(sk, &fl4, req)) == NULL)return -1;//准备synack报文,该报文使用的是用户的send buffer内存skb = tcp_make_synack(sk, dst, req, foc);if (skb) {__tcp_v4_send_check(skb, ireq->ir_loc_addr, ireq->ir_rmt_addr);skb_set_queue_mapping(skb, queue_mapping);//传到IP层继续处理,组建ip头,然后发送报文err = ip_build_and_send_pkt(skb, sk, ireq->ir_loc_addr,ireq->ir_rmt_addr,ireq->opt);err = net_xmit_eval(err);}return err;
}

发送完SYNACK报文,接着就是将该连接放入半连接队列了,同时启动我们的SYNACK定时器。这一动作通过af_ops->queue_hash_add实现,由上面结构体可知,也就是调用inet_csk_reqsk_queue_hash_add()

void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,unsigned long timeout)
{struct inet_connection_sock *icsk = inet_csk(sk);struct listen_sock *lopt = icsk->icsk_accept_queue.listen_opt;const u32 h = inet_synq_hash(inet_rsk(req)->ir_rmt_addr,inet_rsk(req)->ir_rmt_port,lopt->hash_rnd, lopt->nr_table_entries);//添加到半连接队列reqsk_queue_hash_req(&icsk->icsk_accept_queue, h, req, timeout);//更新半连接队列统计信息,同时开启SYNACK定时器inet_csk_reqsk_queue_added(sk, timeout);
}

有关SYNACK定时器的介绍,可以参看TCP SYNACK定时器梳理

至此,TCP层的处理就算是结束了,后面进入IP层处理,然后通过网卡发送出去。

我们大概总结一下总体流程:

  1. 根据SYN报文查找到服务端的监听socket;
  2. 查找是否有对应的半连接socket(第一个SYN报文肯定是没有的);
  3. 接着检查半连接队列和全连接队列是否满了,满了就丢弃报文;
  4. 然后就是创建和初始化一个request_sock,表示这个请求;
  5. 检查该请求是否和同IP的上个连接冲突;
  6. 一起OK的情况下,发送SYNACK报文;
  7. 报文发送完,请求入半连接队列,开启SYNACK定时器。

不过,有个问题不知大家有没有发现,为什么在接收到SYN报文后socket的状态没有改变,变成SYN_RECV呢?理论上收到SYN报文后就应该进入SYN_RECV的,至少从netstat命令查看是这样,书上也都是这么说的,但是代码里确实没有这么做,不知道是怎么回事?虽然我后面知道其实是在服务端接收到第三次握手报文后才会进入SYN_RECV转态,然后转为ESTABLISHED。不解不解。。。

Linux SYN报文接收及发送SYNACK报文相关推荐

  1. ue4打包安卓发送udp报文_内核udp报文截取、修改和发送

    本文档的Copyleft归necofang所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途. msn : necofang@hotmail.com 近来做一个产 ...

  2. TCP/IP传输层协议实现 - TCP接收窗口/发送窗口/通告窗口(lwip)

    1.tcp通告窗口/接收窗口/发送窗口 接收端有一个接收窗口大小,接收端只能接收这么多数据,接收窗口的数据需要被上层接收后才释放更大接收空间,才可以接收更多数据:接收窗口之前的数据已经被接收,再次接收 ...

  3. MQTT协议-报文分析及网络客户端报文测试(MQTT报文连接阿里云上传数据+订阅数据)

    文章目录 一.本文章所涉及到的内容 二.感性认识MQTT协议 三.准备信息 (一)工具获取 (二)获取信息 1.获取三元组信息 2.获取发布topic和订阅topic 3.客户端ID,用户名,哈希加密 ...

  4. linux网络报文接收发送浅析_Docker容器网络-基础篇

    Docker的技术依赖于Linux内核的虚拟化技术的发展,Docker使用到的网络技术有Network Namespace.Veth设备对.Iptables/Netfilter.网桥.路由等.接下来, ...

  5. 【can接口卡、LCANTest使用,接收、发送、分析、记录、回放CAN报文】

    LPCI-252通用型PCI接口CAN卡,具有2路 CAN通道和一路PCI接口,插到电脑的PCI卡槽上,快速扩展出2路CAN通道.CAN接口采用金升阳电源模块和信号隔离芯片实现2500V DC电气隔离 ...

  6. delphi tclientsocket接收不到返回数据_RS—485中教你主站发送报文结构、从站返回报文结构?系列11...

    作者:马乐 1.主站发送报文结构 大家可以看到我之前写的文章中的程序都是没有什么具体功能的,都是两个站点之间互相传递数据,这些数据我们只是看看是否可以正常接收发送,数据本身是没有任何含义的.很明显在实 ...

  7. Linux 下C/C++实现发送ICMP和ICMPv6(报文分析)

    当终端系统无法到达目的地的IP数据包时,为了方便获取诊断信息.一种称为Internet控制消息协议(ICMP)的特殊协议与IP结合使用,以提供与IP协议层配置和IP数据包处理相关的诊断和控制信息.主要 ...

  8. java socket发送定长报文_java使用Socket类接收和发送数据

    网络应用分为客户端和服务端两部分,而Socket类是负责处理客户端通信的Java类.通过这个类可以连接到指定IP或域名的服务器上,并且可以和服务器互相发送和接受数据.在本文及后面的数篇文章中将详细讨论 ...

  9. TCP实现之:TCP报文接收

    TCP实现之:TCP报文接收 本章节讲述了内核TCP协议层快速收报的流程,包括从IP层将报文传递给TCP层,一直到用户调用系统调用收到报文数据的过程.之所以说是快速收报过程,是因为本文暂不分析异常网络 ...

最新文章

  1. Java/Android基础-02
  2. springboot中使用redis详解
  3. content type 介绍
  4. 如何在客户端清除fileUpLoad控件的文件路径
  5. 转:基于科大讯飞语音API语音识别开发详解
  6. 【loj3056】【hnoi2019】多边形
  7. rest_framework-序列化-总结完结篇
  8. linux chmod 命令理解
  9. hbase下载安装与配置
  10. 【机器学习中的矩阵求导】(五)矩阵对矩阵求导
  11. 八皇后问题 (25分)
  12. 怎么删除feed php,怎样关闭或删除WordPress程序默认的RSS feed功能
  13. Lab、RGB、CMY、HSV、HSL
  14. android手机改电视,DIY让手机变成万能电视遥控器 手机万能遥控器设置方法
  15. 光猫、路由器、交换机、wifi通俗释义
  16. 字符串是否为空(isEmpty和isBlank的区别)
  17. 微信摇一摇php,微信“摇一摇”功能是怎么实现的?
  18. 丰巢科技面试题(2019年JAVA)
  19. 求500以内的10个最大素数及其和,并分别输出这10个最大素数及其和。
  20. 【上海官方2019年】垃圾分类宣传资料

热门文章

  1. LINUX下 ssdp 实现
  2. DC电源口实物VCC引脚和GND引脚
  3. 10+编程语言实现云笔记
  4. 做好每周工作总结很重要
  5. 小观插值逼近的龙格现象
  6. java base64转图片
  7. hashcat跑握手包笔记
  8. FileNotFoundException open failed: XXXXXXX EPERM (Operation not permitted)的坑
  9. Autodesk Inventor: Presentations Autodesk Inventor 教程之Presentations Lynda课程中文字幕
  10. 帅到没朋友 (20 分)