`
simohayha
  • 浏览: 1386530 次
  • 性别: 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
分享到:
评论

相关推荐

    modjn:Netty用Java实现Modbus

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

    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.

    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 ...

    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 ...

    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)

    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 安全模式问题...

    S7A驱动720版本

    - The setup program created a wrong e-doc link in Windows start menu. Now the proper link to S7A.CHM will be installed - From the S7A power tool it wasn't possible to open the online help file. ...

    CMU_Application_Testing_with_CMU200.pdf

    Although the option of a live internet connection is supported for each of the network simulations, this document describes a test setup which, instead, uses a "Server PC" to provide the various ...

    计算机网络第六版答案

    22. Five generic tasks are error control, flow control, segmentation and reassembly, multiplexing, and connection setup. Yes, these tasks can be duplicated at different layers. For example, error ...

    ezyfox-server-android-client:ezyfox-server-android-client

    1.创建一个TCP客户端 val clients = EzyClients .getInstance() val client = clients.newClient(config) 2.设置客户端 val setup = client.setup() setup.addEventHandler( EzyEventType . CONNECTION_SUCCESS , ...

    pySTR4500:带有 SimPLEX 的 STR4500 GPSSBAS 模拟器的实用程序

    带有 SimPLEX over TCP 的 STR4500 GPS/SBAS 模拟器的实用程序。 开发构建和设置 希望, sudo pip install ElementTree --allow-external ElementTree --allow-unverified ElementTree python setup.py install ...

    Windows MicroXP 0.82[Microsoft Windows XP SP3原版加工成的微型XP系统,=99.9%个完整XP]

    TCP/IP NetBIOS Helper Telephony Windows Audio Windows Installer Wireless Zero Configuration All these services are enabled except "rint Spooler" which you will need to enable in "services.msc". To ...

    Linux Networking Subsystem

    2.1 Function do basic setup() . . . . . . . . . . . . . . . . . . . . 3 2.2 Function sock init() . . . . . . . . . . . . . . . . . . . . . . . . 5 2.2.1 Function sk init() . . . . . . . . . . . . . . ...

    基恩士条码枪

    Click Properties, then double click [internet protocol V4 (TCP/IP)], enable “use static IP address”. And set its IP address as below image. Click ok. 17 | Confidential © 2013 Cognex Corporation FTP...

    SIP - Understanding the Session Initiation Protocol, 2nd Ed - 1459

    2.5.2 TCP Transport 40 2.5.3 TLS Transport 40 2.5.4 SCTP Transport 41 References 42 3 SIP Clients and Servers 43 3.1 SIP User Agents 43 3.2 Presence Agents 44 3.3 Back-to-Back User Agents 45 3.4 SIP ...

    VB编程资源大全(英文源码 网络)

    You can send messages with a client/server type setup.&lt;END&gt;&lt;br&gt;57 , al40.zip Apparently, if you use AOL to connect to the Internet and you do not touch it for 45 minutes it will timeout and drop ...

    a project model for the FreeBSD Project.7z

    A project model for the FreeBSD Project Niklas Saers ... ...Table of Contents Foreword 1 Overview 2 Definitions ...3-1....3-2....4-1....4-2....4-3....5-1....6-1....6-2....6-3....6-4....6-5....6-6....6-7....6-8....6-9....8-1....Up until now, the FreeBSD ...

    BURNINTEST--硬件检测工具

    - A network connection and the TCP/IP networking software installed for the Network Tests Pro version only: - A serial port loop back plug for the serial port test. - A parallel port loop back plug...

    python3.6.5参考手册 chm

    Python参考手册,官方正式版参考手册,chm版。以下摘取部分内容:Navigation index modules | next | Python » 3.6.5 Documentation » Python Documentation contents What’s New in Python ...

Global site tag (gtag.js) - Google Analytics