Linux SYN报文接收及发送SYNACK报文
注:本文分析基于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;
}
从这个冲突判断上看有这么几个条件:
- 同个ip的此次通信和上次通信间隔时间在60s(刚好等于time_wait状态默认持续时间)内
- 上次通信时间大于此次通信时间,或者没有开时间戳选项
所以,这样看来在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层处理,然后通过网卡发送出去。
我们大概总结一下总体流程:
- 根据SYN报文查找到服务端的监听socket;
- 查找是否有对应的半连接socket(第一个SYN报文肯定是没有的);
- 接着检查半连接队列和全连接队列是否满了,满了就丢弃报文;
- 然后就是创建和初始化一个request_sock,表示这个请求;
- 检查该请求是否和同IP的上个连接冲突;
- 一起OK的情况下,发送SYNACK报文;
- 报文发送完,请求入半连接队列,开启SYNACK定时器。
不过,有个问题不知大家有没有发现,为什么在接收到SYN报文后socket的状态没有改变,变成SYN_RECV呢?理论上收到SYN报文后就应该进入SYN_RECV的,至少从netstat命令查看是这样,书上也都是这么说的,但是代码里确实没有这么做,不知道是怎么回事?虽然我后面知道其实是在服务端接收到第三次握手报文后才会进入SYN_RECV转态,然后转为ESTABLISHED。不解不解。。。
Linux SYN报文接收及发送SYNACK报文相关推荐
- ue4打包安卓发送udp报文_内核udp报文截取、修改和发送
本文档的Copyleft归necofang所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途. msn : necofang@hotmail.com 近来做一个产 ...
- TCP/IP传输层协议实现 - TCP接收窗口/发送窗口/通告窗口(lwip)
1.tcp通告窗口/接收窗口/发送窗口 接收端有一个接收窗口大小,接收端只能接收这么多数据,接收窗口的数据需要被上层接收后才释放更大接收空间,才可以接收更多数据:接收窗口之前的数据已经被接收,再次接收 ...
- MQTT协议-报文分析及网络客户端报文测试(MQTT报文连接阿里云上传数据+订阅数据)
文章目录 一.本文章所涉及到的内容 二.感性认识MQTT协议 三.准备信息 (一)工具获取 (二)获取信息 1.获取三元组信息 2.获取发布topic和订阅topic 3.客户端ID,用户名,哈希加密 ...
- linux网络报文接收发送浅析_Docker容器网络-基础篇
Docker的技术依赖于Linux内核的虚拟化技术的发展,Docker使用到的网络技术有Network Namespace.Veth设备对.Iptables/Netfilter.网桥.路由等.接下来, ...
- 【can接口卡、LCANTest使用,接收、发送、分析、记录、回放CAN报文】
LPCI-252通用型PCI接口CAN卡,具有2路 CAN通道和一路PCI接口,插到电脑的PCI卡槽上,快速扩展出2路CAN通道.CAN接口采用金升阳电源模块和信号隔离芯片实现2500V DC电气隔离 ...
- delphi tclientsocket接收不到返回数据_RS—485中教你主站发送报文结构、从站返回报文结构?系列11...
作者:马乐 1.主站发送报文结构 大家可以看到我之前写的文章中的程序都是没有什么具体功能的,都是两个站点之间互相传递数据,这些数据我们只是看看是否可以正常接收发送,数据本身是没有任何含义的.很明显在实 ...
- Linux 下C/C++实现发送ICMP和ICMPv6(报文分析)
当终端系统无法到达目的地的IP数据包时,为了方便获取诊断信息.一种称为Internet控制消息协议(ICMP)的特殊协议与IP结合使用,以提供与IP协议层配置和IP数据包处理相关的诊断和控制信息.主要 ...
- java socket发送定长报文_java使用Socket类接收和发送数据
网络应用分为客户端和服务端两部分,而Socket类是负责处理客户端通信的Java类.通过这个类可以连接到指定IP或域名的服务器上,并且可以和服务器互相发送和接受数据.在本文及后面的数篇文章中将详细讨论 ...
- TCP实现之:TCP报文接收
TCP实现之:TCP报文接收 本章节讲述了内核TCP协议层快速收报的流程,包括从IP层将报文传递给TCP层,一直到用户调用系统调用收到报文数据的过程.之所以说是快速收报过程,是因为本文暂不分析异常网络 ...
最新文章
- Java/Android基础-02
- springboot中使用redis详解
- content type 介绍
- 如何在客户端清除fileUpLoad控件的文件路径
- 转:基于科大讯飞语音API语音识别开发详解
- 【loj3056】【hnoi2019】多边形
- rest_framework-序列化-总结完结篇
- linux chmod 命令理解
- hbase下载安装与配置
- 【机器学习中的矩阵求导】(五)矩阵对矩阵求导
- 八皇后问题 (25分)
- 怎么删除feed php,怎样关闭或删除WordPress程序默认的RSS feed功能
- Lab、RGB、CMY、HSV、HSL
- android手机改电视,DIY让手机变成万能电视遥控器 手机万能遥控器设置方法
- 光猫、路由器、交换机、wifi通俗释义
- 字符串是否为空(isEmpty和isBlank的区别)
- 微信摇一摇php,微信“摇一摇”功能是怎么实现的?
- 丰巢科技面试题(2019年JAVA)
- 求500以内的10个最大素数及其和,并分别输出这10个最大素数及其和。
- 【上海官方2019年】垃圾分类宣传资料
热门文章
- LINUX下 ssdp 实现
- DC电源口实物VCC引脚和GND引脚
- 10+编程语言实现云笔记
- 做好每周工作总结很重要
- 小观插值逼近的龙格现象
- java base64转图片
- hashcat跑握手包笔记
- FileNotFoundException open failed: XXXXXXX EPERM (Operation not permitted)的坑
- Autodesk Inventor: Presentations Autodesk Inventor 教程之Presentations Lynda课程中文字幕
- 帅到没朋友 (20 分)