上一节我们讲了 Socket 在 TCP 和 UDP 场景下的调用流程。这一节,我们就沿着这个流程到内核里面一探究竟,看看在内核里面,都创建了哪些数据结构,做了哪些事情。

解析 socket 函数

我们从 Socket 系统调用开始。

SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{int retval;struct socket *sock;int flags;
......if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;retval = sock_create(family, type, protocol, &sock);
......retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
......return retval;
}

这里面的代码比较容易看懂,Socket 系统调用会调用 sock_create 创建一个 struct socket 结构,然后通过 sock_map_fd 和文件描述符对应起来。

在创建 Socket 的时候,有三个参数。

一个是family,表示地址族。不是所有的 Socket 都要通过 IP 进行通信,还有其他的通信方式。例如,下面的定义中,domain sockets 就是通过本地文件进行通信的,不需要 IP 地址。只不过,通过 IP 地址只是最常用的模式,所以我们这里着重分析这种模式。

#define AF_UNIX 1/* Unix domain sockets */
#define AF_INET 2/* Internet IP Protocol */

第二个参数是type,也即 Socket 的类型。类型是比较少的。

第三个参数是protocol,是协议。协议数目是比较多的,也就是说,多个协议会属于同一种类型。

常用的 Socket 类型有三种,分别是 SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW。

enum sock_type {
SOCK_STREAM = 1,
SOCK_DGRAM = 2,
SOCK_RAW = 3,
......
}

SOCK_STREAM 是面向数据流的,协议 IPPROTO_TCP 属于这种类型。SOCK_DGRAM 是面向数据报的,协议 IPPROTO_UDP 属于这种类型。如果在内核里面看的话,IPPROTO_ICMP 也属于这种类型。SOCK_RAW 是原始的 IP 包,IPPROTO_IP 属于这种类型。

这一节,我们重点看 SOCK_STREAM 类型和 IPPROTO_TCP 协议。

为了管理 family、type、protocol 这三个分类层次,内核会创建对应的数据结构。

接下来,我们打开 sock_create 函数看一下。它会调用 __sock_create。

int __sock_create(struct net *net, int family, int type, int protocol,struct socket **res, int kern)
{int err;struct socket *sock;const struct net_proto_family *pf;
......sock = sock_alloc();
......sock->type = type;
......pf = rcu_dereference(net_families[family]);
......err = pf->create(net, sock, protocol, kern);
......*res = sock;return 0;
}

这里先是分配了一个 struct socket 结构。接下来我们要用到 family 参数。这里有一个 net_families 数组,我们可以以 family 参数为下标,找到对应的 struct net_proto_family。

/* Supported address families. */
#define AF_UNSPEC	0
#define AF_UNIX		1	/* Unix domain sockets 		*/
#define AF_LOCAL	1	/* POSIX name for AF_UNIX	*/
#define AF_INET		2	/* Internet IP Protocol 	*/
......
#define AF_INET6	10	/* IP version 6			*/
......
#define AF_MPLS		28	/* MPLS */
......
#define AF_MAX		44	/* For now.. */
#define NPROTO		AF_MAXstruct net_proto_family __rcu *net_families[NPROTO] __read_mostly;

我们可以找到 net_families 的定义。每一个地址族在这个数组里面都有一项,里面的内容是 net_proto_family。每一种地址族都有自己的 net_proto_family,IP 地址族的 net_proto_family 定义如下,里面最重要的就是,create 函数指向 inet_create。

//net/ipv4/af_inet.c
static const struct net_proto_family inet_family_ops = {.family = PF_INET,.create = inet_create,// 这个用于 socket 系统调用创建
......
}

我们回到函数 __sock_create。接下来,在这里面,这个 inet_create 会被调用。

static int inet_create(struct net *net, struct socket *sock, int protocol, int kern)
{struct sock *sk;struct inet_protosw *answer;struct inet_sock *inet;struct proto *answer_prot;unsigned char answer_flags;int try_loading_module = 0;int err;/* Look for the requested type/protocol pair. */
lookup_protocol:list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {err = 0;/* Check the non-wild match. */if (protocol == answer->protocol) {if (protocol != IPPROTO_IP)break;} else {/* Check for the two wild cases. */if (IPPROTO_IP == protocol) {protocol = answer->protocol;break;}if (IPPROTO_IP == answer->protocol)break;}err = -EPROTONOSUPPORT;}
......sock->ops = answer->ops;answer_prot = answer->prot;answer_flags = answer->flags;
......sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot, kern);
......inet = inet_sk(sk);inet->nodefrag = 0;if (SOCK_RAW == sock->type) {inet->inet_num = protocol;if (IPPROTO_RAW == protocol)inet->hdrincl = 1;}inet->inet_id = 0;sock_init_data(sock, sk);sk->sk_destruct	   = inet_sock_destruct;sk->sk_protocol	   = protocol;sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;inet->uc_ttl	= -1;inet->mc_loop	= 1;inet->mc_ttl	= 1;inet->mc_all	= 1;inet->mc_index	= 0;inet->mc_list	= NULL;inet->rcv_tos	= 0;if (inet->inet_num) {inet->inet_sport = htons(inet->inet_num);/* Add to protocol hash chains. */err = sk->sk_prot->hash(sk);}if (sk->sk_prot->init) {err = sk->sk_prot->init(sk);}
......
}

在 inet_create 中,我们先会看到一个循环 list_for_each_entry_rcu。在这里,第二个参数 type 开始起作用。因为循环查看的是 inetsw[sock->type]。

这里的 inetsw 也是一个数组,type 作为下标,里面的内容是 struct inet_protosw,是协议,也即 inetsw 数组对于每个类型有一项,这一项里面是属于这个类型的协议。

static struct list_head inetsw[SOCK_MAX];static int __init inet_init(void)
{
....../* Register the socket-side information for inet_create. */for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)INIT_LIST_HEAD(r);for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)inet_register_protosw(q);
......
}

inetsw 数组是在系统初始化的时候初始化的,就像下面代码里面实现的一样。

首先,一个循环会将 inetsw 数组的每一项,都初始化为一个链表。咱们前面说了,一个 type 类型会包含多个 protocol,因而我们需要一个链表。接下来一个循环,是将 inetsw_array 注册到 inetsw 数组里面去。inetsw_array 的定义如下,这个数组里面的内容很重要,后面会用到它们。

static struct inet_protosw inetsw_array[] =
{{.type =       SOCK_STREAM,.protocol =   IPPROTO_TCP,.prot =       &tcp_prot,.ops =        &inet_stream_ops,.flags =      INET_PROTOSW_PERMANENT |INET_PROTOSW_ICSK,},{.type =       SOCK_DGRAM,.protocol =   IPPROTO_UDP,.prot =       &udp_prot,.ops =        &inet_dgram_ops,.flags =      INET_PROTOSW_PERMANENT,},{.type =       SOCK_DGRAM,.protocol =   IPPROTO_ICMP,.prot =       &ping_prot,.ops =        &inet_sockraw_ops,.flags =      INET_PROTOSW_REUSE,},{.type =       SOCK_RAW,.protocol =   IPPROTO_IP,	/* wild card */.prot =       &raw_prot,.ops =        &inet_sockraw_ops,.flags =      INET_PROTOSW_REUSE,}
}

我们回到 inet_create 的 list_for_each_entry_rcu 循环中。到这里就好理解了,这是在 inetsw 数组中,根据 type 找到属于这个类型的列表,然后依次比较列表中的 struct inet_protosw 的 protocol 是不是用户指定的 protocol;如果是,就得到了符合用户指定的 family->type->protocol 的 struct inet_protosw *answer 对象。

接下来,struct socket *sock 的 ops 成员变量,被赋值为 answer 的 ops。对于 TCP 来讲,就是 inet_stream_ops。后面任何用户对于这个 socket 的操作,都是通过 inet_stream_ops 进行的。

接下来,我们创建一个 struct sock *sk 对象。这里比较让人困惑。socket 和 sock 看起来几乎一样,容易让人混淆,这里需要说明一下,socket 是用于负责对上给用户提供接口,并且和文件系统关联。而 sock,负责向下对接内核网络协议栈。

在 sk_alloc 函数中,struct inet_protosw *answer 结构的 tcp_prot 赋值给了 struct sock *sk 的 sk_prot 成员。tcp_prot 的定义如下,里面定义了很多的函数,都是 sock 之下内核协议栈的动作。

struct proto tcp_prot = {.name			= "TCP",.owner			= THIS_MODULE,.close			= tcp_close,.connect		= tcp_v4_connect,.disconnect		= tcp_disconnect,.accept			= inet_csk_accept,.ioctl			= tcp_ioctl,.init			= tcp_v4_init_sock,.destroy		= tcp_v4_destroy_sock,.shutdown		= tcp_shutdown,.setsockopt		= tcp_setsockopt,.getsockopt		= tcp_getsockopt,.keepalive		= tcp_set_keepalive,.recvmsg		= tcp_recvmsg,.sendmsg		= tcp_sendmsg,.sendpage		= tcp_sendpage,.backlog_rcv		= tcp_v4_do_rcv,.release_cb		= tcp_release_cb,.hash			= inet_hash,.get_port		= inet_csk_get_port,
......
}

在 inet_create 函数中,接下来创建一个 struct inet_sock 结构,这个结构一开始就是 struct sock,然后扩展了一些其他的信息,剩下的代码就填充这些信息。这一幕我们会经常看到,将一个结构放在另一个结构的开始位置,然后扩展一些成员,通过对于指针的强制类型转换,来访问这些成员。

socket 的创建至此结束。

解析 bind 函数

接下来,我们来看 bind。

SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{struct socket *sock;struct sockaddr_storage address;int err, fput_needed;sock = sockfd_lookup_light(fd, &err, &fput_needed);if (sock) {err = move_addr_to_kernel(umyaddr, addrlen, &address);if (err >= 0) {err = sock->ops->bind(sock,(struct sockaddr *)&address, addrlen);}fput_light(sock->file, fput_needed);}return err;
}

在 bind 中,sockfd_lookup_light 会根据 fd 文件描述符,找到 struct socket 结构。然后将 sockaddr 从用户态拷贝到内核态,然后调用 struct socket 结构里面 ops 的 bind 函数。根据前面创建 socket 的时候的设定,调用的是 inet_stream_ops 的 bind 函数,也即调用 inet_bind。

int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)
{struct sockaddr_in *addr = (struct sockaddr_in *)uaddr;struct sock *sk = sock->sk;struct inet_sock *inet = inet_sk(sk);struct net *net = sock_net(sk);unsigned short snum;
......snum = ntohs(addr->sin_port);
......inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr;/* Make sure we are allowed to bind here. */if ((snum || !inet->bind_address_no_port) &&sk->sk_prot->get_port(sk, snum)) {
......}inet->inet_sport = htons(inet->inet_num);inet->inet_daddr = 0;inet->inet_dport = 0;sk_dst_reset(sk);
}

bind 里面会调用 sk_prot 的 get_port 函数,也即 inet_csk_get_port 来检查端口是否冲突,是否可以绑定。如果允许,则会设置 struct inet_sock 的本方的地址 inet_saddr 和本方的端口 inet_sport,对方的地址 inet_daddr 和对方的端口 inet_dport 都初始化为 0。

bind 的逻辑相对比较简单,就到这里了。

解析 listen 函数

接下来我们来看 listen。

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{struct socket *sock;int err, fput_needed;int somaxconn;sock = sockfd_lookup_light(fd, &err, &fput_needed);if (sock) {somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;if ((unsigned int)backlog > somaxconn)backlog = somaxconn;err = sock->ops->listen(sock, backlog);fput_light(sock->file, fput_needed);}return err;
}

在 listen 中,我们还是通过 sockfd_lookup_light,根据 fd 文件描述符,找到 struct socket 结构。接着,我们调用 struct socket 结构里面 ops 的 listen 函数。根据前面创建 socket 的时候的设定,调用的是 inet_stream_ops 的 listen 函数,也即调用 inet_listen。

int inet_listen(struct socket *sock, int backlog)
{struct sock *sk = sock->sk;unsigned char old_state;int err;old_state = sk->sk_state;/* Really, if the socket is already in listen state* we can only allow the backlog to be adjusted.*/if (old_state != TCP_LISTEN) {err = inet_csk_listen_start(sk, backlog);}sk->sk_max_ack_backlog = backlog;
}

如果这个 socket 还不在 TCP_LISTEN 状态,会调用 inet_csk_listen_start 进入监听状态。

int inet_csk_listen_start(struct sock *sk, int backlog)
{struct inet_connection_sock *icsk = inet_csk(sk);struct inet_sock *inet = inet_sk(sk);int err = -EADDRINUSE;reqsk_queue_alloc(&icsk->icsk_accept_queue);sk->sk_max_ack_backlog = backlog;sk->sk_ack_backlog = 0;inet_csk_delack_init(sk);sk_state_store(sk, TCP_LISTEN);if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
......}
......
}

这里面建立了一个新的结构 inet_connection_sock,这个结构一开始是 struct inet_sock,inet_csk 其实做了一次强制类型转换,扩大了结构,看到了吧,又是这个套路。

struct inet_connection_sock 结构比较复杂。如果打开它,你能看到处于各种状态的队列,各种超时时间、拥塞控制等字眼。我们说 TCP 是面向连接的,就是客户端和服务端都是有一个结构维护连接的状态,就是指这个结构。我们这里先不详细分析里面的变量,因为太多了,后面我们遇到一个分析一个。

首先,我们遇到的是 icsk_accept_queue。它是干什么的呢?

在 TCP 的状态里面,有一个 listen 状态,当调用 listen 函数之后,就会进入这个状态,虽然我们写程序的时候,一般要等待服务端调用 accept 后,等待在哪里的时候,让客户端就发起连接。其实服务端一旦处于 listen 状态,不用 accept,客户端也能发起连接。其实 TCP 的状态中,没有一个是否被 accept 的状态,那 accept 函数的作用是什么呢?

在内核中,为每个 Socket 维护两个队列。一个是已经建立了连接的队列,这时候连接三次握手已经完毕,处于 established 状态;一个是还没有完全建立连接的队列,这个时候三次握手还没完成,处于 syn_rcvd 的状态。

服务端调用 accept 函数,其实是在第一个队列中拿出一个已经完成的连接进行处理。如果还没有完成就阻塞等待。这里的 icsk_accept_queue 就是第一个队列。

初始化完之后,将 TCP 的状态设置为 TCP_LISTEN,再次调用 get_port 判断端口是否冲突。

至此,listen 的逻辑就结束了。

解析 accept 函数

接下来,我们解析服务端调用 accept。

SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,int __user *, upeer_addrlen)
{return sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
}SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,int __user *, upeer_addrlen, int, flags)
{struct socket *sock, *newsock;struct file *newfile;int err, len, newfd, fput_needed;struct sockaddr_storage address;
......sock = sockfd_lookup_light(fd, &err, &fput_needed);newsock = sock_alloc();newsock->type = sock->type;newsock->ops = sock->ops;newfd = get_unused_fd_flags(flags);newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);err = sock->ops->accept(sock, newsock, sock->file->f_flags, false);if (upeer_sockaddr) {if (newsock->ops->getname(newsock, (struct sockaddr *)&address, &len, 2) < 0) {}err = move_addr_to_user(&address,len, upeer_sockaddr, upeer_addrlen);}fd_install(newfd, newfile);
......
}

accept 函数的实现,印证了 socket 的原理中说的那样,原来的 socket 是监听 socket,这里我们会找到原来的 struct socket,并基于它去创建一个新的 newsock。这才是连接 socket。除此之外,我们还会创建一个新的 struct file 和 fd,并关联到 socket。

这里面还会调用 struct socket 的 sock->ops->accept,也即会调用 inet_stream_ops 的 accept 函数,也即 inet_accept。

int inet_accept(struct socket *sock, struct socket *newsock, int flags, bool kern)
{struct sock *sk1 = sock->sk;int err = -EINVAL;struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err, kern);sock_rps_record_flow(sk2);sock_graft(sk2, newsock);newsock->state = SS_CONNECTED;
}

inet_accept 会调用 struct sock 的 sk1->sk_prot->accept,也即 tcp_prot 的 accept 函数,inet_csk_accept 函数。

/** This will accept the next outstanding connection.*/
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)
{struct inet_connection_sock *icsk = inet_csk(sk);struct request_sock_queue *queue = &icsk->icsk_accept_queue;struct request_sock *req;struct sock *newsk;int error;if (sk->sk_state != TCP_LISTEN)goto out_err;/* Find already established connection */if (reqsk_queue_empty(queue)) {long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);error = inet_csk_wait_for_connect(sk, timeo);}req = reqsk_queue_remove(queue, sk);newsk = req->sk;
......
}/** Wait for an incoming connection, avoid race conditions. This must be called* with the socket locked.*/
static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{struct inet_connection_sock *icsk = inet_csk(sk);DEFINE_WAIT(wait);int err;for (;;) {prepare_to_wait_exclusive(sk_sleep(sk), &wait,TASK_INTERRUPTIBLE);release_sock(sk);if (reqsk_queue_empty(&icsk->icsk_accept_queue))timeo = schedule_timeout(timeo);sched_annotate_sleep();lock_sock(sk);err = 0;if (!reqsk_queue_empty(&icsk->icsk_accept_queue))break;err = -EINVAL;if (sk->sk_state != TCP_LISTEN)break;err = sock_intr_errno(timeo);if (signal_pending(current))break;err = -EAGAIN;if (!timeo)break;}finish_wait(sk_sleep(sk), &wait);return err;
}

inet_csk_accept 的实现,印证了上面我们讲的两个队列的逻辑。如果 icsk_accept_queue 为空,则调用 inet_csk_wait_for_connect 进行等待;等待的时候,调用 schedule_timeout,让出 CPU,并且将进程状态设置为 TASK_INTERRUPTIBLE。

如果再次 CPU 醒来,我们会接着判断 icsk_accept_queue 是否为空,同时也会调用 signal_pending 看有没有信号可以处理。一旦 icsk_accept_queue 不为空,就从 inet_csk_wait_for_connect 中返回,在队列中取出一个 struct sock 对象赋值给 newsk。

解析 connect 函数

什么情况下,icsk_accept_queue 才不为空呢?当然是三次握手结束才可以。接下来我们来分析三次握手的过程。

三次握手一般是由客户端调用 connect 发起。

SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,int, addrlen)
{struct socket *sock;struct sockaddr_storage address;int err, fput_needed;sock = sockfd_lookup_light(fd, &err, &fput_needed);err = move_addr_to_kernel(uservaddr, addrlen, &address);err = sock->ops->connect(sock, (struct sockaddr *)&address, addrlen, sock->file->f_flags);
}

 connect 函数的实现一开始你应该很眼熟,还是通过 sockfd_lookup_light,根据 fd 文件描述符,找到 struct socket 结构。接着,我们会调用 struct socket 结构里面 ops 的 connect 函数,根据前面创建 socket 的时候的设定,调用 inet_stream_ops 的 connect 函数,也即调用 inet_stream_connect。

/**	Connect to a remote host. There is regrettably still a little*	TCP 'magic' in here.*/
int __inet_stream_connect(struct socket *sock, struct sockaddr *uaddr,int addr_len, int flags, int is_sendmsg)
{struct sock *sk = sock->sk;int err;long timeo;switch (sock->state) {
......case SS_UNCONNECTED:err = -EISCONN;if (sk->sk_state != TCP_CLOSE)goto out;err = sk->sk_prot->connect(sk, uaddr, addr_len);sock->state = SS_CONNECTING;break;}timeo = sock_sndtimeo(sk, flags & O_NONBLOCK);if ((1 << sk->sk_state) & (TCPF_SYN_SENT | TCPF_SYN_RECV)) {
......if (!timeo || !inet_wait_for_connect(sk, timeo, writebias))goto out;err = sock_intr_errno(timeo);if (signal_pending(current))goto out;}sock->state = SS_CONNECTED;
}

在 tcp_v4_connect 函数中,ip_route_connect 其实是做一个路由的选择。为什么呢?因为三次握手马上就要发送一个 SYN 包了,这就要凑齐源地址、源端口、目标地址、目标端口。目标地址和目标端口是服务端的,已经知道源端口是客户端随机分配的,源地址应该用哪一个呢?这时候要选择一条路由,看从哪个网卡出去,就应该填写哪个网卡的 IP 地址。

接下来,在发送 SYN 之前,我们先将客户端 socket 的状态设置为 TCP_SYN_SENT。然后初始化 TCP 的 seq num,也即 write_seq,然后调用 tcp_connect 进行发送。

/* Build a SYN and send it off. */
int tcp_connect(struct sock *sk)
{struct tcp_sock *tp = tcp_sk(sk);struct sk_buff *buff;int err;
......tcp_connect_init(sk);
......buff = sk_stream_alloc_skb(sk, 0, sk->sk_allocation, true);
......tcp_init_nondata_skb(buff, tp->write_seq++, TCPHDR_SYN);tcp_mstamp_refresh(tp);tp->retrans_stamp = tcp_time_stamp(tp);tcp_connect_queue_skb(sk, buff);tcp_ecn_send_syn(sk, buff);/* Send off SYN; include data in Fast Open. */err = tp->fastopen_req ? tcp_send_syn_data(sk, buff) :tcp_transmit_skb(sk, buff, 1, sk->sk_allocation);
......tp->snd_nxt = tp->write_seq;tp->pushed_seq = tp->write_seq;buff = tcp_send_head(sk);if (unlikely(buff)) {tp->snd_nxt	= TCP_SKB_CB(buff)->seq;tp->pushed_seq	= TCP_SKB_CB(buff)->seq;}
....../* Timer for repeating the SYN until an answer. */inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS,inet_csk(sk)->icsk_rto, TCP_RTO_MAX);return 0;
}

在 tcp_connect 中,有一个新的结构 struct tcp_sock,如果打开他,你会发现他是 struct inet_connection_sock 的一个扩展,struct inet_connection_sock 在 struct tcp_sock 开头的位置,通过强制类型转换访问,故伎重演又一次。

struct tcp_sock 里面维护了更多的 TCP 的状态,咱们同样是遇到了再分析。

接下来 tcp_init_nondata_skb 初始化一个 SYN 包,tcp_transmit_skb 将 SYN 包发送出去,inet_csk_reset_xmit_timer 设置了一个 timer,如果 SYN 发送不成功,则再次发送。

发送网络包的过程,我们放到下一节讲解。这里我们姑且认为 SYN 已经发送出去了。

我们回到 __inet_stream_connect 函数,在调用 sk->sk_prot->connect 之后,inet_wait_for_connect 会一直等待客户端收到服务端的 ACK。而我们知道,服务端在 accept 之后,也是在等待中。

网络包是如何接收的呢?对于解析的详细过程,我们会在下下节讲解,这里为了解析三次握手,我们简单的看网络包接收到 TCP 层做的部分事情。

static struct net_protocol tcp_protocol = {.early_demux	=	tcp_v4_early_demux,.early_demux_handler =  tcp_v4_early_demux,.handler	=	tcp_v4_rcv,.err_handler	=	tcp_v4_err,.no_policy	=	1,.netns_ok	=	1,.icmp_strict_tag_validation = 1,
}

我们通过 struct net_protocol 结构中的 handler 进行接收,调用的函数是 tcp_v4_rcv。接下来的调用链为 tcp_v4_rcv->tcp_v4_do_rcv->tcp_rcv_state_process。tcp_rcv_state_process,顾名思义,是用来处理接收一个网络包后引起状态变化的。

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);const struct tcphdr *th = tcp_hdr(skb);struct request_sock *req;int queued = 0;bool acceptable;switch (sk->sk_state) {
......case TCP_LISTEN:
......if (th->syn) {acceptable = icsk->icsk_af_ops->conn_request(sk, skb) >= 0;if (!acceptable)return 1;consume_skb(skb);return 0;}
......
}

目前服务端是处于 TCP_LISTEN 状态的,而且发过来的包是 SYN,因而就有了上面的代码,调用 icsk->icsk_af_ops->conn_request 函数。struct inet_connection_sock 对应的操作是 inet_connection_sock_af_ops,按照下面的定义,其实调用的是 tcp_v4_conn_request。

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,.syn_recv_sock     = tcp_v4_syn_recv_sock,.net_header_len    = sizeof(struct iphdr),.setsockopt        = ip_setsockopt,.getsockopt        = ip_getsockopt,.addr2sockaddr     = inet_csk_addr2sockaddr,.sockaddr_len      = sizeof(struct sockaddr_in),.mtu_reduced       = tcp_v4_mtu_reduced,
};

tcp_v4_conn_request 会调用 tcp_conn_request,这个函数也比较长,里面调用了 send_synack,但实际调用的是 tcp_v4_send_synack。具体发送的过程我们不去管它,看注释我们能知道,这是收到了 SYN 后,回复一个 SYN-ACK,回复完毕后,服务端处于 TCP_SYN_RECV。

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)
{
......
af_ops->send_synack(sk, dst, &fl, req, &foc,!want_cookie ? TCP_SYNACK_NORMAL :TCP_SYNACK_COOKIE);
......
}/**	Send a SYN-ACK after having received a SYN.*/
static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst,struct flowi *fl,struct request_sock *req,struct tcp_fastopen_cookie *foc,enum tcp_synack_type synack_type)
{......}

这个时候,轮到客户端接收网络包了。都是 TCP 协议栈,所以过程和服务端没有太多区别,还是会走到 tcp_rcv_state_process 函数的,只不过由于客户端目前处于 TCP_SYN_SENT 状态,就进入了下面的代码分支。

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);const struct tcphdr *th = tcp_hdr(skb);struct request_sock *req;int queued = 0;bool acceptable;switch (sk->sk_state) {
......case TCP_SYN_SENT:tp->rx_opt.saw_tstamp = 0;tcp_mstamp_refresh(tp);queued = tcp_rcv_synsent_state_process(sk, skb, th);if (queued >= 0)return queued;/* Do step6 onward by hand. */tcp_urg(sk, skb, th);__kfree_skb(skb);tcp_data_snd_check(sk);return 0;}
......
}

tcp_rcv_synsent_state_process 会调用 tcp_send_ack,发送一个 ACK-ACK,发送后客户端处于 TCP_ESTABLISHED 状态。

又轮到服务端接收网络包了,我们还是归 tcp_rcv_state_process 函数处理。由于服务端目前处于状态 TCP_SYN_RECV 状态,因而又走了另外的分支。当收到这个网络包的时候,服务端也处于 TCP_ESTABLISHED 状态,三次握手结束。

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb)
{struct tcp_sock *tp = tcp_sk(sk);struct inet_connection_sock *icsk = inet_csk(sk);const struct tcphdr *th = tcp_hdr(skb);struct request_sock *req;int queued = 0;bool acceptable;
......switch (sk->sk_state) {case TCP_SYN_RECV:if (req) {inet_csk(sk)->icsk_retransmits = 0;reqsk_fastopen_remove(sk, req, false);} else {/* Make sure socket is routed, for correct metrics. */icsk->icsk_af_ops->rebuild_header(sk);tcp_call_bpf(sk, BPF_SOCK_OPS_PASSIVE_ESTABLISHED_CB);tcp_init_congestion_control(sk);tcp_mtup_init(sk);tp->copied_seq = tp->rcv_nxt;tcp_init_buffer_space(sk);}smp_mb();tcp_set_state(sk, TCP_ESTABLISHED);sk->sk_state_change(sk);if (sk->sk_socket)sk_wake_async(sk, SOCK_WAKE_IO, POLL_OUT);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)->seq);break;
......
}

总结时刻

这一节除了网络包的接收和发送,其他的系统调用我们都分析到了。可以看出来,它们有一个统一的数据结构和流程。具体如下图所示:

首先,Socket 系统调用会有三级参数 family、type、protocal,通过这三级参数,分别在 net_proto_family 表中找到 type 链表,在 type 链表中找到 protocal 对应的操作。这个操作分为两层,对于 TCP 协议来讲,第一层是 inet_stream_ops 层,第二层是 tcp_prot 层。

于是,接下来的系统调用规律就都一样了:

  • bind 第一层调用 inet_stream_ops 的 inet_bind 函数,第二层调用 tcp_prot 的 inet_csk_get_port 函数;
  • listen 第一层调用 inet_stream_ops 的 inet_listen 函数,第二层调用 tcp_prot 的 inet_csk_get_port 函数;
  • accept 第一层调用 inet_stream_ops 的 inet_accept 函数,第二层调用 tcp_prot 的 inet_csk_accept 函数;
  • connect 第一层调用 inet_stream_ops 的 inet_stream_connect 函数,第二层调用 tcp_prot 的 tcp_v4_connect 函数。

课堂练习

TCP 的三次握手协议非常重要,请你务必跟着代码走读一遍。另外我们这里重点关注了 TCP 的场景,请走读代码的时候,也看一下 UDP 是如何实现各层的函数的。

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 【jQuery】计算行高

    思路&#xff1a; 总高度 / 行高 行数 结果&#xff1a; <div id"box">文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字哦文字…...

    2024/4/25 7:49:47
  2. 城市统计年鉴数据查询

    城市统计年鉴查询可以去 https://www.yearbookchina.com...

    2024/4/26 6:40:03
  3. echarts 散点图

    echarts 散点图...

    2024/4/24 13:31:04
  4. 在JAVA考证之前复习遇到的典型问题小结

    HashSet|HashMap比较&#xff1a;底层数据结构为哈希表&#xff1b; HashMap存储键值对&#xff0c;键唯一&#xff0c;而HashSet仅仅存储对象&#xff0c;对象唯一&#xff1b; HashMap使用唯一的键来获取对象&#xff0c;速度相对较快。 HashSet集合方法&#xff1a; add(O…...

    2024/4/15 5:53:16
  5. wContour-tracingContourLines生成等值线-java.lang.ArrayIndexOutOfBoundsException-数组越界问题

    //nc数据生成等值线数据public static List<Polygon> nc2EquiSurface(Map ncData, double[] dataInterval) throws IOException {// String geojsonpogylon "";List<PolyLine> cPolylineList new ArrayList<PolyLine>();List<Polygon> cPo…...

    2024/4/27 0:29:55
  6. ArcPy抽查实验的代码

    ArcPy实验代码抽查 小提醒 如果您是直接复制代码运行&#xff0c;请注意查看以下提醒&#xff1a; 1.本代码运行时&#xff0c;需要输入对应资源所在的绝对路径&#xff0c;均只需输入至...\Chp7\Ex1,如&#xff1a;D:\桌面\ArcPy实验代码抽查\Chp7\Ex12.在运行代码之前&…...

    2024/4/25 23:52:49
  7. 剑指offer——第十三天

    剑指offer——第十二天第一题&#xff1a;剑指 Offer 21. 调整数组顺序使奇数位于偶数前面问题描述&#xff1a;思路代码第二题&#xff1a;剑指 Offer 57. 和为s的两个数字问题描述&#xff1a;思路代码第三题&#xff1a;剑指 Offer 58 - I. 翻转单词顺序问题描述思路代码第一…...

    2024/4/19 8:33:58
  8. LeetCode 58.最后一个单词的长度

    题目 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中最后一个单词的长度。单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 代码 int lengthOfLastWord(char * s){int a 0;for (int i strlen(s) - 1; i >…...

    2024/4/24 16:15:01
  9. 安装PowerDesigner16使用mysql8.0的ODBC导入数据库模型

    参考 https://blog.csdn.net/zjcjava/article/details/103232871...

    2024/4/26 19:47:00
  10. Linux命令行与shell脚本编程大全

    ...

    2024/4/19 8:53:57
  11. EXCEL常用技巧速查(自用资源型--游客老爷们请略过)

    一.冻结列表和分割视窗 教程直通车 1.冻结列表 应用于想要将&#xff0c;某几列或者某几行始终显示在页面上。如将上面的标题栏固定来确定每一列的数据代表什么。 2.分割 应用于想要将excel中处于不同行且跨度较大的数据进行对比观察时。 二&#xff0c;格式化为表格…...

    2024/4/20 17:20:42
  12. RobotFramework--API高级网页跳转小练习

    ** 一.58同城小案例 ** 1.今天我们来试试&#xff0c;在ride中&#xff0c;是怎么实现网页跳转的。 我们先来找一个小案例来试试。 案例&#xff1a;58租房:http://bj.58.com 登录58同城>>点击租房>>选择区域>>选择租金>>进行筛选 Get Window Handle…...

    2024/4/19 6:09:30
  13. Linux 中新下载的FireFox(火狐浏览器)无法运行的解决方法

    在Linux系统中想要用FireFox火狐浏览器时电脑卡死&#xff0c;无奈将该浏览器卸载移除&#xff0c;又在应用商店重新下载了火狐浏览器&#xff0c;结果新下载好的浏览器点击运行时出现以下的情况&#xff1a; 几经探索后找到解决方法&#xff1a; 1.在linux桌面单击右键打开终…...

    2024/4/26 10:30:02
  14. http协议与https协议区别

    HTTP&#xff1a;&#xff08;超文本传输协议&#xff09;是互联网上应用最为广泛的一种网络协议&#xff0c;是一个客户端和服务器端请求和应答的标准&#xff08;TCP&#xff09;&#xff0c;用于从WWW服务器传输超文本到本地浏览器的传输协议&#xff0c;它可以使浏览器更加…...

    2024/4/20 13:36:23
  15. 非零段划分

    非零段划分  非零段划分函数为代码中的find #include<stdio.h> #include<windows.h> int find(int b[],int n) {int i,start,end,sum0;start1,endn;while(b[start]0&&end!start) /*去掉数组的首部0*/start;while(b[end]0&&end!start) /*去掉…...

    2024/4/15 5:53:11
  16. 【Linux】1.基本命令

    1. useradd &#xff0c; passwd 用来创建普通用户&#xff1a;useradd &#xff0c; passwd ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210519203641690.png) 前面的root表示当前登录到Linux操作系统的是root用户&#xff1b; root用户在Linux操作系统中是管理员…...

    2024/4/25 1:42:40
  17. 通过多种方法实现对四个整数从小到大排序

    题目描述&#xff1a; 将四个整数进行从小到大的顺序排列 样例输入门&#xff1a; 5 3 4 2 样例输出&#xff1a; 2 3 4 5 方式一&#xff1a;冒泡排序 比较相邻的元素。如果第一个比第二个大&#xff0c;就交换他们两个。对每一对相邻元素做同样的工作&#xff0c;执行完…...

    2024/4/17 10:40:49
  18. 数据结构与算法实验2——排序算法

    1.实验内容 用任意一种排序方式给出n个整数按升序排序后的结果&#xff0c;满足以下要求&#xff1a; 1.不得使用与实验相关的STL&#xff1b; 2.需使用类模版(template<class T>)&#xff1b; 3.需定义排序类&#xff0c;封装各排序方法&#xff1b; 4.排序数据需使…...

    2024/4/26 21:04:09
  19. win10家庭版开启hyper-v

    1新建txt文档&#xff0c;输入以下内容&#xff1a; pushd "%~dp0"dir /b %SystemRoot%\servicing\Packages\*Hyper-V*.mum >hyper-v.txtfor /f %%i in (findstr /i . hyper-v.txt 2^>nul) do dism /online /norestart /add-package:"%SystemRoot%\servi…...

    2024/4/21 0:02:48
  20. UE4 C++入门之路4-PostInitProperties函数详解(设置属性默认值的四种方法)

    PostInitProperties函数详解前言设置属性默认值的四种方法一 声明时赋值二 构造函数赋值三 构造函数初始化列表四 PostInitProperties前言 也许在工作或者学习中&#xff0c;我们会遇到PostInitProperties函数&#xff0c;本文旨在讲清楚PostInitProperties函数的作用以及一些…...

    2024/4/19 13:42:14

最新文章

  1. Python 中的下划线变量

    Python 中的下划线变量 _name: 这是一种约定俗成的方式&#xff0c;表示这是一个私有变量&#xff0c;意味着它应该在类的内部使用&#xff0c;而不应该直接从外部访问。但实际上&#xff0c;Python并没有严格的私有化机制&#xff0c;这只是一种约定&#xff0c;因为外部仍然可…...

    2024/4/27 3:26:22
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. [C++][算法基础]模拟队列(数组)

    实现一个队列&#xff0c;队列初始为空&#xff0c;支持四种操作&#xff1a; push x – 向队尾插入一个数 x&#xff1b;pop – 从队头弹出一个数&#xff1b;empty – 判断队列是否为空&#xff1b;query – 查询队头元素。 现在要对队列进行 M 个操作&#xff0c;其中的每…...

    2024/4/22 21:35:57
  4. 17、Lua 文件 I-O

    Lua 文件 I/O Lua 文件 I/O简单模式完全模式 Lua 文件 I/O LuaI/O 库用于读取和处理文件。分为简单模式&#xff08;和C一样&#xff09;、完全模式。 简单模式&#xff08;simple model&#xff09;拥有一个当前输入文件和一个当前输出文件&#xff0c;并且提供针对这些文件…...

    2024/4/23 6:22:15
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/26 18:09:39
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/26 20:12:18
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/26 23:05:52
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/25 18:39:23
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/25 18:39:22
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/25 18:39:22
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/26 21:56:58
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/25 16:48:44
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/26 16:00:35
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/25 18:39:16
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/26 22:01:59
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/25 2:10:52
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/25 18:39:00
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/26 19:46:12
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/25 18:38:58
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/25 18:38:57
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57