`
simohayha
  • 浏览: 1403590 次
  • 性别: Icon_minigender_1
  • 来自: 火星
社区版块
存档分类
最新评论

tcp connection setup的实现(二)

阅读更多
首先来看下内核如何处理3次握手的半连接队列和accept队列(其实也就是server端的三次握手的状态变换).而半连接队列和accept队列在内核如何表示,我们上次已经介绍过了,这里就不介绍了.


首先我们知道当3层的数据包到达之后会调用4层的协议handle,tcp的话就是tcp_v4_rcv.如何调用可以看我前面的blog:

而在tcp_v4_rcv中,则最终会调用tcp_v4_do_rcv来处理输入数据包.在看tcp_v4_do_rcv之前,我们先来看在tcp_v4_rcv中,内核如何通过4元组(目的,源端口和地址)来查找对应得sock对象.

在分析之前,我们要知道,当一对tcp连接3次握手完毕后,内核将会重新new一个socket,这个socket中的大部分域都是与主socket相同的.而把这个新的socket的状态设置为established,而主socket的状态依旧为listen状态.

而通过前面的blog分析,我们也知道在inet_hashinfo中将处于listening状态的socket和处于TCP_ESTABLISHED与TCP_CLOSE之间的状态的socket是分开的,一个是ehash,一个是listening_hash.因此通过对应的4元组查找socket也是分开在这两个hash链表中操作的.

内核是通过调用__inet_lookup来查找socket的:


///在tcp_v4_rcv中的代码片段.
sk = __inet_lookup(net, &tcp_hashinfo, iph->saddr,
			th->source, iph->daddr, th->dest, 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);
	struct sock *sk = __inet_lookup_established(net, hashinfo,
				saddr, sport, daddr, hnum, dif);

	return sk ? : __inet_lookup_listener(net, hashinfo, daddr, hnum, dif);
}


tcp_hashinfo我们前面也已经分析过了,包含了所有tcp所用到的hash信息,比如socket,port等等.这里的查找其实就是在tcp_hashinfo中(其实是它的域ehash或者listening_hash)查找相应的socket.

我们可以看到内核在这里进行了两次查找,首先是在established状态的socket中查找,处于established状态,说明3次握手已经完成,因此这个socket可以通过简单的4元组hash在hashinfo的ehash中查找.

而当在__inet_lookup_established中没有找到时,则将会__inet_lookup_listener中查找.也就是在处于listening状态的socket中查找(这里主要是通过daddr也就是目的地址来进行匹配).


当找到对应的socket以后就会进入数据包的处理,也就是进入tcp_v4_do_rcv函数.


int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
	struct sock *rsk;
..................................................

///如果为TCP_ESTABLISHED状态,则进入相关处理
	if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */
		TCP_CHECK_TIMER(sk);
		if (tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len)) {
			rsk = sk;
			goto reset;
		}
		TCP_CHECK_TIMER(sk);
		return 0;
	}

///进行包头的合法性校验.
	if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb))
		goto csum_err;
///进入TCP_LISTEN状态.
	if (sk->sk_state == TCP_LISTEN) {
		struct sock *nsk = tcp_v4_hnd_req(sk, skb);
		if (!nsk)
			goto discard;

		if (nsk != sk) {
			if (tcp_child_process(sk, nsk, skb)) {
				rsk = nsk;
				goto reset;
			}
			return 0;
		}
	}

	TCP_CHECK_TIMER(sk);
///进入其他状态的处理.除了ESTABLISHED和TIME_WAIT状态.
	if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
		rsk = sk;
		goto reset;
	}
	TCP_CHECK_TIMER(sk);
	return 0;
......................................................................
}


可以看到当进来之后,会通过判断socket的不同状态来进入不同的处理.这里其实就分了3种状态,TCP_ESTABLISHED,TCP_LISTEN和剩余的的状态.

我们这里先不分析TCP_ESTABLISHED.

我们先来看当第一个syn分解到达后,内核会做怎么样处理.首先它会进入tcp_v4_hnd_req函数,这个函数我们后面会处理,这里只需要知道当为第一个syn分节时,它会返回当前socket.因此此时nsk == sk,所以我们进入tcp_rcv_state_process函数,这个函数处理除了ESTABLISHED和TIME_WAIT状态之外的所有状态.

我们这里只看他的listen状态处理,后面的话也是遇到一个状态,我们看一个状态的处理:


int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
			  struct tcphdr *th, unsigned len)
{
	struct tcp_sock *tp = tcp_sk(sk);
///取得对应的inet_connection_sock .
	struct inet_connection_sock *icsk = inet_csk(sk);
	int queued = 0;
	tp->rx_opt.saw_tstamp = 0;

	switch (sk->sk_state) {
	case TCP_LISTEN:
///当为ack分节,则返回1,而对应内核会发送一个rst给对端.
		if (th->ack)
			return 1;
///如果是rst,则忽略这个分组.
		if (th->rst)
			goto discard;
///是syn分组,因此调用对应的虚函数conn_request,而这个函数在tcpv4中被初始化为tcp_v4_conn_request.
		if (th->syn) {
			if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)
				return 1;
			kfree_skb(skb);
			return 0;
		}
		goto discard;
............................................................
}


可以看到最终会调用tcp_v4_conn_request来处理syn分组,我们接下来就来看这个函数的实现.

先来看几个相关的函数,第一个是reqsk_queue_is_full,他来判断半连接队列是否已满.其实实现很简单,就是判断qlen和max_qlen_log的大小:

static inline int reqsk_queue_is_full(const struct request_sock_queue *queue)
{
	return queue->listen_opt->qlen >> queue->listen_opt->max_qlen_log;
}


第二个是sk_acceptq_is_full,它用来判断accept队列是否已满.这个也是很简单,比较当前的队列大小sk_ack_backlog与最大的队列大小sk_max_ack_backlog.

static inline int sk_acceptq_is_full(struct sock *sk)
{
	return sk->sk_ack_backlog > sk->sk_max_ack_backlog;
}


最后一个是tcp_openreq_init,它用来新建一个inet_request_sock,我们知道每次一个syn到达后,我们都会新建一个inet_request_sock,并加入到半连接队列.

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;
	req->mss = rx_opt->mss_clamp;
	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->rmt_port = tcp_hdr(skb)->source;
}



接下来来看tcp_v4_conn_request的实现,
int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
	struct inet_request_sock *ireq;
	struct tcp_options_received tmp_opt;
	struct request_sock *req;
	__be32 saddr = ip_hdr(skb)->saddr;
	__be32 daddr = ip_hdr(skb)->daddr;
///这个名字实在是无语,when具体表示什么不太理解,只是知道它是用来计算rtt的.
	__u32 isn = TCP_SKB_CB(skb)->when;
	struct dst_entry *dst = NULL;
#ifdef CONFIG_SYN_COOKIES
	int want_cookie = 0;
#else
#define want_cookie 0 /* Argh, why doesn't gcc optimize this :( */
#endif

///如果是广播或者多播,则丢掉这个包.
	if (skb->rtable->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
		goto drop;

///判断半连接队列是否已经满掉.如果满掉并且处于非timewait状态,则丢掉这个包(如果设置了SYN Cookie则会继续进行,因为SYN Cookie不需要新分配半连接队列,详细的SYN Cookie请google)
	if (inet_csk_reqsk_queue_is_full(sk) && !isn) {
#ifdef CONFIG_SYN_COOKIES
		if (sysctl_tcp_syncookies) {
			want_cookie = 1;
		} else
#endif
		goto drop;
	}
///如果accept队列已满,并且qlen_young大于一就丢掉这个包,这里qlen_young大于一表示在syn队列中已经有足够多的(这里不包括重传的syn)请求了.
	if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1)
		goto drop;
	req = inet_reqsk_alloc(&tcp_request_sock_ops);
	if (!req)
		goto drop;
...................................................

///对tmp_opt进行初始化,而tcp_options_received中包含了tcp的一些选项信息(比如mss,窗口扩大因子等等)
	tcp_clear_options(&tmp_opt);
	tmp_opt.mss_clamp = 536;
	tmp_opt.user_mss  = tcp_sk(sk)->rx_opt.user_mss;

///对对端的tcp_options_received进行解析,并对本端得tcp_options_received进行初始化.
	tcp_parse_options(skb, &tmp_opt, 0);

.......................................................
///这里对新的req进行初始化.

	tcp_openreq_init(req, &tmp_opt, skb);
...............................................

///这里将tcp_options_received保存到req中.
	ireq->opt = tcp_v4_save_options(sk, skb);
	if (!want_cookie)
		TCP_ECN_create_request(req, tcp_hdr(skb));

	if (want_cookie) {
#ifdef CONFIG_SYN_COOKIES
		syn_flood_warning(skb);
		req->cookie_ts = tmp_opt.tstamp_ok;
#endif
		isn = cookie_v4_init_sequence(sk, skb, &req->mss);
	}else if (!isn) {
.............................................
///计算当前一个合适的isn,并返回.
		isn = tcp_v4_init_sequence(skb);
	}

///赋值发送给对端的isn
	tcp_rsk(req)->snt_isn = isn;

///发送syn和ack(如果设置了want_cookie则不会将这个req链接到半连接队列中.
	if (__tcp_v4_send_synack(sk, req, dst) || want_cookie)
		goto drop_and_free;

///将这个req链接到半连接队列中.
	inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
	return 0;

drop_and_release:
	dst_release(dst);
drop_and_free:
	reqsk_free(req);
drop:
	return 0;
}



而tcp_v4_hnd_req的主要工作是在半连接队列中看是否存在当前的socket,如果存在则说明这个有可能是最终的ack包,因此将会做一系列的合法性校验(比如重传,rst,syn等等),最终确定这个是ack后会调用对应的新建socket的虚函数syn_recv_sock.


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;
///通过socket,查找对应request_sock
	struct request_sock *req = inet_csk_search_req(sk, &prev, th->source,
						       iph->saddr, iph->daddr);
	if (req)
///如果存在则进入req的相关处理.
		return tcp_check_req(sk, skb, req, prev);

///不存在,则通过inet_lookup_established查找.这是因为有可能当我们进入这个函数之前,socket的状态被改变了,也就是这个socket的状态已经不是listen了.

	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) {
///非tw状态返回新的socket.
			bh_lock_sock(nsk);
			return nsk;
		}
///如果是timewait状态则返回空.
		inet_twsk_put(inet_twsk(nsk));
		return NULL;
	}

#ifdef CONFIG_SYN_COOKIES
	if (!th->rst && !th->syn && th->ack)
		sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
#endif
	return sk;
}



tcp_check_req最主要工作就是调用虚函数,新建一个socket,并返回.

先来看几个相关的函数,第一个是inet_csk_reqsk_queue_unlink,它主要用来从半连接队列unlink掉一个元素.:

static inline void inet_csk_reqsk_queue_unlink(struct sock *sk,
					       struct request_sock *req,
					       struct request_sock **prev)
{
	reqsk_queue_unlink(&inet_csk(sk)->icsk_accept_queue, req, prev);
}

static inline void reqsk_queue_unlink(struct request_sock_queue *queue,
				      struct request_sock *req,
				      struct request_sock **prev_req)
{
	write_lock(&queue->syn_wait_lock);
///处理链表.
	*prev_req = req->dl_next;
	write_unlock(&queue->syn_wait_lock);
}


第二个是inet_csk_reqsk_queue_removed,它主要用来修改对应的qlen和qlen_young的值.


static inline void inet_csk_reqsk_queue_removed(struct sock *sk,
						struct request_sock *req)
{
	if (reqsk_queue_removed(&inet_csk(sk)->icsk_accept_queue, req) == 0)
		inet_csk_delete_keepalive_timer(sk);
}

static inline int reqsk_queue_removed(struct request_sock_queue *queue,
				      struct request_sock *req)
{
	struct listen_sock *lopt = queue->listen_opt;
///如果重传数为0则说明没有重传过,因此qlen_young跟着也减一.
	if (req->retrans == 0)
		--lopt->qlen_young;

	return --lopt->qlen;
}


最后是inet_csk_reqsk_queue_add,它用来把新的req加入到accept队列中.


static inline void inet_csk_reqsk_queue_add(struct sock *sk,
					    struct request_sock *req,
					    struct sock *child)
{
	reqsk_queue_add(&inet_csk(sk)->icsk_accept_queue, req, sk, child);
}


static inline void reqsk_queue_add(struct request_sock_queue *queue,
				   struct request_sock *req,
				   struct sock *parent,
				   struct sock *child)
{
	req->sk = child;
	sk_acceptq_added(parent);
///可以看到刚好就是request_sock_queue的rskq_accept_head与rskq_accept_tail保存accept队列.
	if (queue->rskq_accept_head == NULL)
		queue->rskq_accept_head = req;
	else
		queue->rskq_accept_tail->dl_next = req;

	queue->rskq_accept_tail = req;
	req->dl_next = NULL;
}


然后再来看tcp_check_req的实现.
struct sock *tcp_check_req(struct sock *sk,struct sk_buff *skb,
			   struct request_sock *req,
			   struct request_sock **prev)
{
	const struct tcphdr *th = tcp_hdr(skb);
	__be32 flg = tcp_flag_word(th) & (TCP_FLAG_RST|TCP_FLAG_SYN|TCP_FLAG_ACK);
	int paws_reject = 0;
	struct tcp_options_received tmp_opt;
	struct sock *child;

	tmp_opt.saw_tstamp = 0;
......................................
///如果只有rst和syn域则发送一个rst给对端.
if (flg & (TCP_FLAG_RST|TCP_FLAG_SYN)) {
		TCP_INC_STATS_BH(sock_net(sk), TCP_MIB_ATTEMPTFAILS);
		goto embryonic_reset;
	}

///如果是重传的syn,则重新发送syn和ack分组.
	if (TCP_SKB_CB(skb)->seq == tcp_rsk(req)->rcv_isn &&
	    flg == TCP_FLAG_SYN &&
	    !paws_reject) {
		req->rsk_ops->rtx_syn_ack(sk, req);
		return NULL;
	}

	..........................................

///确定有设置ack分节.
	if (!(flg & TCP_FLAG_ACK))
		return NULL;

///这里主要处理TCP_DEFER_ACCEPT被设置的情况,如果它被设置,则丢掉这个包.(这是因为TCP_DEFER_ACCEPT会等待数据真正发过来才处理的,而不是最后一个ack发过来就处理)
	if (inet_csk(sk)->icsk_accept_queue.rskq_defer_accept &&
	    TCP_SKB_CB(skb)->end_seq == tcp_rsk(req)->rcv_isn + 1) {
		inet_rsk(req)->acked = 1;
		return NULL;
	}

///可以创建一个新的socket了.返回一个包含新创建的socket的request结构.
	child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL);
	if (child == NULL)
		goto listen_overflow;
..................................
#endif
///创建成功,则在request_sock_queue的listen_opt中unlink掉这个req.也就是从半连接队列中删除这个req.
	inet_csk_reqsk_queue_unlink(sk, req, prev);
///修改对应的 qlen和qlen_young的值.
	inet_csk_reqsk_queue_removed(sk, req);
///最后加入到accept队列中.这里注意最终是将新的socket赋值给对应的req.
	inet_csk_reqsk_queue_add(sk, req, child);
	return child;

listen_overflow:
	if (!sysctl_tcp_abort_on_overflow) {
		inet_rsk(req)->acked = 1;
		return NULL;
	}

embryonic_reset:
	NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_EMBRYONICRSTS);
	if (!(flg & TCP_FLAG_RST))
		req->rsk_ops->send_reset(sk, skb);

	inet_csk_reqsk_queue_drop(sk, req, prev);
	return NULL;
}



最后我们来看内核如何创建一个新的socket,tcp 协议使用tcp_v4_syn_recv_sock来实现,它做的其实很简单就是新建一个socket,并且设置状态为TCP_SYN_RECV(在inet_csk_clone中),父socket继续处于listen状态,然后对新的socket进行一些赋值,然后对一些定时器进行初始化.这里定时器我们全部都略过了,以后会专门来分析tcp中的定时器.


最后从tcp_v4_hnd_req中返回,判断是否与父socket相等,然后调用tcp_child_process函数:

这个函数主要是完成最终的三次握手,将子socket设置为TCP_ESTABLISHED然后根据条件唤醒被accept阻塞的主socket:

int tcp_child_process(struct sock *parent, struct sock *child,
		      struct sk_buff *skb)
{
	int ret = 0;
	int state = child->sk_state;

	if (!sock_owned_by_user(child)) {
///完成最终的三次握手.
		ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb),
					    skb->len);
		/* Wakeup parent, send SIGIO */
		if (state == TCP_SYN_RECV && child->sk_state != state)
///唤醒阻塞的主socket.
			parent->sk_data_ready(parent, 0);
	} else {
		/* Alas, it is possible again, because we do lookup
		 * in main socket hash table and lock on listening
		 * socket does not protect us more.
		 */
		sk_add_backlog(child, skb);
	}

	bh_unlock_sock(child);
	sock_put(child);
	return ret;
}


最后来分析下在tcp_rcv_state_process中的处理当前的TCP_SYN_RECV状态,它主要是为将要到来的数据传输做一些准备,设置一些相关域.:


case TCP_SYN_RECV:
			if (acceptable) {
				tp->copied_seq = tp->rcv_nxt;
				smp_mb();
///设置状态为TCP_ESTABLISHED.
				tcp_set_state(sk, TCP_ESTABLISHED);
				sk->sk_state_change(sk);

///这里的wake应该是针对epoll这类的
				if (sk->sk_socket)
					sk_wake_async(sk,
						      SOCK_WAKE_IO, POLL_OUT);

///设置期望接收的isn号,也就是第一个字节的序列和窗口大小.
				tp->snd_una = TCP_SKB_CB(skb)->ack_seq;
				tp->snd_wnd = ntohs(th->window) <<
					      tp->rx_opt.snd_wscale;
				tcp_init_wl(tp, TCP_SKB_CB(skb)->ack_seq,
					    TCP_SKB_CB(skb)->seq);

.........................................................................
			break;





0
0
分享到:
评论

相关推荐

    FANUC机器人MODBUS TCP通信相关设置步骤与参数.docx

    FANUC机器人的MODBUS TCP通信功能需要使用特定的软件配置,包括A05B-2600-R800 MODBUS TCP Connection或A05B-2600-R581 MODBUS TCP。 三、MODBUS TCP功能设置 MODBUS TCP功能设置包括机器人侧IP地址设置、MODBUS ...

    Python Socket实现简单TCP Server/client功能示例

    print('Failed to setup socket connection') tcp_client.close() if __name__ == '__main__': start_tcp_client('10.20.0.20', 12345) ``` **4.2 关键步骤解析** - **创建Socket对象**:与Server端类似,使用`...

    Windows TCP 通信学习笔记

    10. **Connection Setup and Teardown**: 应用程序可以在接受连接请求前获取对方信息,也可以在连接断开时交换数据,提供了更为灵活的连接管理方式。 11. **Graceful Shutdown, Linger Options, and Socket Closure...

    esp32ModbusTCP:ESP32的Modbus客户端

    ESP32 Modbus TCP是基于Arduino环境的一种通信协议实现,主要针对Microcontroller ESP32,使其能够作为Modbus主设备(Master)通过TCP/IP网络连接到Modbus从设备(Slave)。在本文中,我们将深入探讨ESP32如何利用...

    Microtek中晶高档扫描仪产品如何实现网络共享扫描.pdf

    最后,启动完毕后,可以进行扫描仪的找寻工作,点击”TCP/IP Scanner Setup…”按钮,选中“Enable TCP/IP scanning”,然后点击“Test connection”按钮,得到连接成功的结果。 四、 服务器端驱动软件的使用 在...

    Miracast技术原理及实现方案.docx

    4. WFD Connection Setup(WFD 连接) 5. WFD Capability Negotiation(WFD 能力协商) 6. WFD Session Establishment(WFD 会话建立) 7. User Input Back Channel Setup (Optional)(UIBC 反向控制) 8. Link ...

    MidtermExam_吴先_13000128171

    Question 1.4 highlights the three-way handshake required to establish a TCP connection, which typically takes two round-trip times (RTTs). Persistent and non-persistent connections refer to how ...

    modjn:Netty用Java实现Modbus

    使用Netty 4.x的Java中的Modbus TCP客户端/服务器实现 当前实现的Modbus功能 读线圈| 0x01 读取离散输入| 0x02 阅读保存寄存器| 0x03 读取输入寄存器| 0x04 写单线圈| 0x05 写单寄存器| 0x06 写多个线圈| 0x0F...

    JMS与Spring之二(用message listener container异步收发消息)

    JMS与Spring之二(用message listener container异步收发消息) 在本文中,我们将探讨使用 message listener container 在 Spring 框架中异步收发 JMS 消息的方法。Message listener container 是 Spring 提供的一...

    PICO2000品科监控卡软件

    PICO2000监控软件,含客户端软件 **********************************...** Please refer to Section 4 of The installation guide for the detailed information about the TCP/IP setup for both SERVER and CLIENT.

    计算机网络英文课件:Chapter6 The Transport Layer.ppt

    The data link layer is more involved in establishing connections within a subnet, requiring explicit addressing and more complex connection setup, and it manages in/out-buffers on a more granular ...

    dcmtk程序包简介

    4. dcmnet:实现了DICOM网络协议,包括DIMSE(DICOM Message Service Element)和ACSE(Application Entity Connection Setup Protocol)。 5. dcmimage:更高级别的图像处理库,提供图像显示和处理功能。 6. ...

    win 3.11 for workgroup tcpip支持

    mechanism is used only with TCP (connection-oriented traffic). Therefore, utilities like PING will only use the first default gateway. Notice that t his only applies to IP datagrams that have to be ...

    routeros常用命令详解

    ip firewall命令用于映射端口,例如用户可以输入"ip firewall add action=nat protocol=tcp dst-address=212.12.*.*/32 : 80 to-dst-address=192.168.0.198"以映射端口80到本地的192.168.0.198上。 七、print命令 ...

    SecureBridge v6.2.3 for Delphi & BCB Full Source

    It protects any TCP traffic using SSH or SSL secure transport layer protocols, that provide authentication for both client and server, strong data encryption, and data integrity verification....

    SecureBridge v5.5.1 Full Source for XE5

    SecureBridge is a set of network security and data protection solutions that can protect any TCP traffic using SSH or SSL secure transport layer protocols that provide authentication for both client ...

    dcmtk在PACS开发中的应用 工作列表 测试文件

    5. DICOM网络协议:在工作列表测试中,开发者需要了解DICOM网络层的基本概念,如TCP/IP、ACSE(Application Entity Connection Setup)和PDU(Protocol Data Unit)。 6. 数据存储和检索:除了工作列表查询外,还...

    iperf-3.9-win64.zip

    最新版本iperf,官网下载直接编译,适合win10系统。... --connect-timeout # timeout for control connection setup (ms) -b, --bitrate #[KMG][/#] target bitrate in bits/sec (0 for unlimited)

    modbus-poll-快速入门到精通.doc

    4. 显示设置:默认显示方式为Signed(16进制无符号二进制),可通过"Display"菜单选择不同的数值显示方式,并可设置地址格式为PLC地址或协议地址。 5. 连接:通过"Connection" -&gt; "Connect.. F3"进行串口或TCP/IP...

    WCDMA KPI监控和优化指导书

    4.5.4 UE收到RRC Connection Setup消息没有发出RRC Setup Complete消息 41 4.5.5 UE发出RRC Setup Complete消息RNC没有收到 41 4.6 鉴权问题分析 41 4.6.1 MAC Failure 41 4.6.2 Sync Failure 42 4.7 安全模式问题...

Global site tag (gtag.js) - Google Analytics