参考资料

<<linux内核网络栈源代码情景分析>>

socket常用函数继续调用分析

根据socket提供的常用库函数,socket、read和write等函数,继续往下一层分析tcp/ip协议的执行过程,本文分析的函数大部分位于af_inet.c文件中,该层大部分逻辑还是将调用tcp.c中的逻辑处理,作为了一个过渡层,待到tcp.c文件内容时,得到的细节会慢慢深入。

socket的执行过程

执行过程中有sock_socket函数,sock_bind函数等,本文就接着上一篇文章内容继续分析

sock_socket调用的net_create函数

主要的函数逻辑如下:

/**  Create an inet socket.**    FIXME: Gcc would generate much better code if we set the parameters*    up in in-memory structure order. Gcc68K even more so* *  用于创建一个套接字对应的sock结构并对其进行初始化*/static int inet_create(struct socket *sock, int protocol)
{struct sock *sk;struct proto *prot;int err;sk = (struct sock *) kmalloc(sizeof(*sk), GFP_KERNEL);  // 申请内存if (sk == NULL) return(-ENOBUFS);sk->num = 0;sk->reuse = 0;switch(sock->type)                                      // 根据传入的套接字进行赋值{case SOCK_STREAM:                                   // 流式套接字使用tcp协议操作函数case SOCK_SEQPACKET:if (protocol && protocol != IPPROTO_TCP)        // 检查protocl是否与IPPROTO_TCP相同 如果不同则报错返回{kfree_s((void *)sk, sizeof(*sk));return(-EPROTONOSUPPORT);}protocol = IPPROTO_TCP;                      // 设置协议/* TCP_NO_CHECK设置为1 表示对于tcp协议默认使用校验  */sk->no_check = TCP_NO_CHECK;prot = &tcp_prot;                                // 设置协议操作函数集break;case SOCK_DGRAM:if (protocol && protocol != IPPROTO_UDP)         // 设置UDP协议操作集{kfree_s((void *)sk, sizeof(*sk));return(-EPROTONOSUPPORT);}protocol = IPPROTO_UDP;sk->no_check = UDP_NO_CHECK;prot=&udp_prot;                               // 设置UDP操作集break;case SOCK_RAW:                                         // 原始套接字操作集if (!suser()) {kfree_s((void *)sk, sizeof(*sk));return(-EPERM);}if (!protocol) {kfree_s((void *)sk, sizeof(*sk));return(-EPROTONOSUPPORT);}prot = &raw_prot;                                // 设置套接字操作函数集sk->reuse = 1;sk->no_check = 0;    /** Doesn't matter no checksum is* performed anyway.*/sk->num = protocol;                      // protocol则为端口号break;case SOCK_PACKET:                                 // 包套接字if (!suser()) {kfree_s((void *)sk, sizeof(*sk));return(-EPERM);}if (!protocol) {kfree_s((void *)sk, sizeof(*sk));return(-EPROTONOSUPPORT);}prot = &packet_prot;                         // 设置流式套接字操作集sk->reuse = 1;sk->no_check = 0;    /* Doesn't matter no checksum is* performed anyway.*/sk->num = protocol;break;default:kfree_s((void *)sk, sizeof(*sk));            // 都没有则报错return(-ESOCKTNOSUPPORT);}sk->socket = sock;                                   // 建立socket对应的sock,socket先创建成功
#ifdef CONFIG_TCP_NAGLE_OFF                             // 是否定义了nagle算法 如果定义则初始化为0 否则为1sk->nonagle = 1;
#else    sk->nonagle = 0;
#endif  sk->type = sock->type;                               // 设置type类型sk->stamp.tv_sec=0;sk->protocol = protocol;      // 设置协议sk->wmem_alloc = 0;sk->rmem_alloc = 0;sk->sndbuf = SK_WMEM_MAX;  // 最大发送缓冲区sk->rcvbuf = SK_RMEM_MAX; // 最大接受缓冲区sk->pair = NULL;sk->opt = NULL;sk->write_seq = 0;sk->acked_seq = 0;sk->copied_seq = 0;sk->fin_seq = 0;sk->urg_seq = 0;sk->urg_data = 0;sk->proc = 0;sk->rtt = 0;              /*TCP_WRITE_TIME << 3;*/sk->rto = TCP_TIMEOUT_INIT;       /*TCP_WRITE_TIME*/sk->mdev = 0;sk->backoff = 0;sk->packets_out = 0;sk->cong_window = 1;      /* start with only sending one packet at a time. * tcp进入满启动阶段  */sk->cong_count = 0;sk->ssthresh = 0;sk->max_window = 0;sk->urginline = 0;sk->intr = 0;sk->linger = 0;sk->destroy = 0;sk->priority = 1;sk->shutdown = 0;sk->keepopen = 0;sk->zapped = 0;sk->done = 0;sk->ack_backlog = 0;sk->window = 0;sk->bytes_rcv = 0;sk->state = TCP_CLOSE;   // 由于还未进行连接,状态设置为TCP_CLOSEsk->dead = 0;sk->ack_timed = 0;sk->partial = NULL;sk->user_mss = 0;sk->debug = 0;/* 设置最大可暂缓应答的字节数  *//* this is how many unacked bytes we will accept for this socket.  */sk->max_unacked = 2048; /* needs to be at most 2 full packets. *//* how many packets we should send before forcing an ack. if this is set to zero it is the same as sk->delay_acks = 0 */sk->max_ack_backlog = 0;sk->inuse = 0;sk->delay_acks = 0;skb_queue_head_init(&sk->write_queue);skb_queue_head_init(&sk->receive_queue);sk->mtu = 576;     // mtu设置为576字节sk->prot = prot;  // 设置协议操作集函数sk->sleep = sock->wait;sk->daddr = 0;sk->saddr = 0 /* ip_my_addr() */;sk->err = 0;sk->next = NULL;sk->pair = NULL;sk->send_tail = NULL;sk->send_head = NULL;sk->timeout = 0;sk->broadcast = 0;sk->localroute = 0;init_timer(&sk->timer);init_timer(&sk->retransmit_timer);sk->timer.data = (unsigned long)sk;sk->timer.function = &net_timer;skb_queue_head_init(&sk->back_log);sk->blog = 0;sock->data =(void *) sk;sk->dummy_th.doff = sizeof(sk->dummy_th)/4;  // 设置tcp首部信息sk->dummy_th.res1=0;sk->dummy_th.res2=0;sk->dummy_th.urg_ptr = 0;sk->dummy_th.fin = 0;sk->dummy_th.syn = 0;sk->dummy_th.rst = 0;sk->dummy_th.psh = 0;sk->dummy_th.ack = 0;sk->dummy_th.urg = 0;sk->dummy_th.dest = 0;sk->ip_tos=0;sk->ip_ttl=64;
#ifdef CONFIG_IP_MULTICASTsk->ip_mc_loop=1;sk->ip_mc_ttl=1;*sk->ip_mc_name=0;sk->ip_mc_list=NULL;
#endifsk->state_change = def_callback1;             // 设置回调函数sk->data_ready = def_callback2;sk->write_space = def_callback3;sk->error_report = def_callback1;if (sk->num)                                    // 如果已经设置端口号{/** It assumes that any protocol which allows* the user to assign a number at socket* creation time automatically* shares.*/put_sock(sk->num, sk);                      // 添加到sock列表中sk->dummy_th.source = ntohs(sk->num);       // 在tcp首部信息中添加本地端口信息}if (sk->prot->init)                              // 如果协议有初始化方法{err = sk->prot->init(sk);                  // 调用该协议的初始化方法if (err != 0)                                // 如果出错{destroy_sock(sk);                       // 销毁该skreturn(err);}}return(0);                                        // 初始化完成
}

该函数的主要工作就是传入sock_socket中创建的socket,并将在inet_create函数中创建的sock结构进行互相关联,然后再初始化sock,初始化sock的过程就是根据传入不同的协议来进行初始化,并选择不同的操作集函数,如果是tcp协议就选择tcp_prot作为sock的prot后续的操作都会通过sock->prot函数集来操作,在设置初始化完成之后,如果检查到有协议注册的init函数就调用该函数进行注册。

sock_bind调用的inet_bind函数

主要的函数逻辑如下:

/* this needs to be changed to disallowthe rebinding of sockets.   What errorshould it return?  该函数进行地址绑定 */static int inet_bind(struct socket *sock, struct sockaddr *uaddr,int addr_len)
{struct sockaddr_in *addr=(struct sockaddr_in *)uaddr;struct sock *sk=(struct sock *)sock->data, *sk2;unsigned short snum = 0 /* Stoopid compiler.. this IS ok */;int chk_addr_ret;/* check this error. */if (sk->state != TCP_CLOSE)return(-EIO);if(addr_len<sizeof(struct sockaddr_in))return -EINVAL;if(sock->type != SOCK_RAW)         // 是否是原始套接字类型{/* 检查是否时Row类型 如果端口不为空则报错 */if (sk->num != 0) return(-EINVAL);snum = ntohs(addr->sin_port);        // 获取绑定中的端口号/** We can't just leave the socket bound wherever it is, it might* be bound to a privileged port. However, since there seems to* be a bug here, we will leave it if the port is not privileged.*/if (snum == 0)                      // 如果没有则分配一个{snum = get_new_socknum(sk->prot, 0);}if (snum < PROT_SOCK && !suser())      // 如果小于1024 并且不是超级用户则报错return(-EACCES);}chk_addr_ret = ip_chk_addr(addr->sin_addr.s_addr);      // 检查ip地址是否为一个本地地址if (addr->sin_addr.s_addr != 0 && chk_addr_ret != IS_MYADDR && chk_addr_ret != IS_MULTICAST) // 如果指定的地址不是本地地址也不是一个多播地址则报错return(-EADDRNOTAVAIL);    /* Source address MUST be ours! */if (chk_addr_ret || addr->sin_addr.s_addr == 0)     // 如果没有指定地址,则系统自动分配一个本地地址sk->saddr = addr->sin_addr.s_addr;if(sock->type != SOCK_RAW)                          // 如果不是原始套接字{/* Make sure we are allowed to bind here. */cli();                                                 // 关闭中断for(sk2 = sk->prot->sock_array[snum & (SOCK_ARRAY_SIZE -1)];      // 根据端口号找到对应的sock队列sk2 != NULL; sk2 = sk2->next) {/* should be below! */if (sk2->num != snum)                                          // 如果端口不相同则循环下一个continue;if (!sk->reuse)                                             // 检查是否设置了端口复用参数{sti();                                                 // 如果没有设置则返回报错return(-EADDRINUSE);}if (sk2->num != snum) continue;      /* more than one */if (sk2->saddr != sk->saddr)                                  // 检查地址continue;    /* socket per slot ! -FB */if (!sk2->reuse || sk2->state==TCP_LISTEN)               // 如果sock的状态为TCP_LISTEN则表示该套接字为一个服务端 服务端不可使用地址复用{sti();return(-EADDRINUSE);}}sti();remove_sock(sk);                                                 // 删除该skput_sock(snum, sk);                                             // 加入该端口和sksk->dummy_th.source = ntohs(sk->num);                             // 设置tcp头部的本地端口信息sk->daddr = 0;                                                     // 设置远端地址sk->dummy_th.dest = 0;}return(0);
}

该函数主要是完成本地地址绑定,本地地址绑定包括ip地址和端口号两个部分,如果在绑定过程中没有指定地址和端口号,则系统就会进行自动分配,由于原始套接字的实现不同,故在该函数的处理过程中将处理tcp等协议的远端地址的设置与绑定,主要绑定在tcp协议的头部。

sock_listen调用的inet_listen函数

该函数如下:

/**  Move a socket into listening state.*/static int inet_listen(struct socket *sock, int backlog)
{struct sock *sk = (struct sock *) sock->data;                      // 获取sock结构if(inet_autobind(sk)!=0)                                            // 检查是否有未绑定的端口号,设置source,设置端口号return -EAGAIN;/* We might as well re use these. */ /** note that the backlog is "unsigned char", so truncate it* somewhere. We might as well truncate it to what everybody* else does..*/if ((unsigned) backlog > 128)                                        // 如果设置的队列长度大于128则设置为128backlog = 128;sk->max_ack_backlog = backlog;                                       // 设置最大队列数if (sk->state != TCP_LISTEN)                                      // 如果sock的状态不是TCP_LISTEN改为TCP_LISTEN{sk->ack_backlog = 0;                                           // ack_backlog设置为0sk->state = TCP_LISTEN;                                       // 设置为TCP_LISTEN状态}return(0);
}

该函数主要是将检查传入的链接大小,不能超过最大128,如果超过则设置为128,并更改当前连接的监听状态为TCP_LISTEN。该函数实现较为简单。

sock_send函数调用的inet_send和sock_recv函数调用的inet_recv函数

inet_send函数主要就是调用了协议层的发送函数;

static int inet_send(struct socket *sock, void *ubuf, int size, int noblock, unsigned flags)
{struct sock *sk = (struct sock *) sock->data;if (sk->shutdown & SEND_SHUTDOWN) {send_sig(SIGPIPE, current, 1);      //发送信号return(-EPIPE);}if(sk->err)return inet_error(sk);/* We may need to bind the socket. */if(inet_autobind(sk)!=0)return(-EAGAIN);return(sk->prot->write(sk, (unsigned char *) ubuf, size, noblock, flags));    // 调用inet的操作函数集操作
}

直接调用了sk对应的prot函数集来发送数据。

inet_recv函数主要就是接受函数,接受底层往上传入的数据;

static int inet_recv(struct socket *sock, void *ubuf, int size, int noblock,unsigned flags)
{/* BSD explicitly states these are the same - so we do it this way to be sure */return inet_recvfrom(sock,ubuf,size,noblock,flags,NULL,NULL);
}/**    The assorted BSD I/O operations*    读取sock中的数据*/static int inet_recvfrom(struct socket *sock, void *ubuf, int size, int noblock, unsigned flags, struct sockaddr *sin, int *addr_len )
{struct sock *sk = (struct sock *) sock->data;if (sk->prot->recvfrom == NULL) return(-EOPNOTSUPP);if(sk->err)return inet_error(sk);/* We may need to bind the socket. */if(inet_autobind(sk)!=0)return(-EAGAIN);return(sk->prot->recvfrom(sk, (unsigned char *) ubuf, size, noblock, flags,(struct sockaddr_in*)sin, addr_len));          // 调用sock的函数操作集获取数据
}

这两个函数都是通过协议函数集来操作的,例如tcp协议对应的tcp_prot函数,直接调用对应的函数操作。

sock_connect对应的inet_connect函数

该函数如下;

/**  Connect to a remote host. There is regrettably still a little*  TCP 'magic' in here.**    调用方式: sock_connect -> inet_connect*   完成套接字连接操作*/static int inet_connect(struct socket *sock, struct sockaddr * uaddr,int addr_len, int flags)
{struct sock *sk=(struct sock *)sock->data;             // 获取sockint err;sock->conn = NULL;if (sock->state == SS_CONNECTING && tcp_connected(sk->state))        // 如果该套接字状态是连接状态则返回{sock->state = SS_CONNECTED;/* Connection completing after a connect/EINPROGRESS/select/connect */return 0;  /* Rock and roll */}if (sock->state == SS_CONNECTING && sk->protocol == IPPROTO_TCP && (flags & O_NONBLOCK)) {/* 如果套接字处于建立连接阶段并且套接字使用了TCP协议,并且使用了非阻塞选项则立刻返回连接正在进行的错误*/if (sk->err != 0){err=sk->err;sk->err=0;return -err;}return -EALREADY;   /* Connecting is currently in progress */}/* 如果sock连接字状态不是正在连接中 */if (sock->state != SS_CONNECTING) {/* We may need to bind the socket. */if(inet_autobind(sk)!=0)                     // 检查自动绑定端口return(-EAGAIN);if (sk->prot->connect == NULL)           // 检查协议是否有该操作函数return(-EOPNOTSUPP);/* * TCP ¸发送SYN 并将状态更改为sk 状态 TCP_SYN_SENT*/err = sk->prot->connect(sk, (struct sockaddr_in *)uaddr, addr_len);        // 调用协议函数的connect函数if (err < 0)                                                                  // 如果连接出错则报错return(err);sock->state = SS_CONNECTING;                                                // 更新状态为正在连接中}// 如果sk状态大于TCP_FIN_WAIT2 但是sock状态为正在连接中 则状态可能为TCP_TIME_WAIT TCP_CLOSE TCP_CLOSE_WAITif (sk->state > TCP_FIN_WAIT2 && sock->state==SS_CONNECTING){sock->state=SS_UNCONNECTED;                   // 此时重置该状态并返回连接过程中的错误cli();err=sk->err;sk->err=0;sti();return -err;}if (sk->state != TCP_ESTABLISHED &&(flags & O_NONBLOCK))    // 如果连接过程中不是建立完成,并且不是阻塞状态return(-EINPROGRESS);                                   // 返回错误cli(); /* avoid the race condition */while(sk->state == TCP_SYN_SENT || sk->state == TCP_SYN_RECV)         // 状态为TCP_SYN_SEND 或者 TCP_SYN_RECV{interruptible_sleep_on(sk->sleep);                                // 等待if (current->signal & ~current->blocked)                         // 如果出错则返回错误{sti();return(-ERESTARTSYS);}/* This fixes a nasty in the tcp/ip code. There is a hideous hassle withicmp error packets wanting to close a tcp or udp socket. */if(sk->err && sk->protocol == IPPROTO_TCP)                      // 检查错误 并且协议为TCP{sti();sock->state = SS_UNCONNECTED;                                // 设置为未连接err = -sk->err;                                            // 返回错误sk->err=0;return err; /* set by tcp_err() */}}sti();                                                                 // 使能中断sock->state = SS_CONNECTED;                                      // 更新状态为连接成功if (sk->state != TCP_ESTABLISHED && sk->err)                         // 如果连接不成功并且有错误原因则返回{sock->state = SS_UNCONNECTED;err=sk->err;sk->err=0;return(-err);}return(0);
}

首先会先检查套接字的连接状态如果是正在连接则返回,如果套接字正在连接并且是tcp协议并且使用了非阻塞选项则立刻返回正在连接的信息,如果不是正在连接中则进行连接,此时就调用prot对应的connect连接函数进行连接,并将该socket结构状态更新为正在连接中,如果此时地址未绑定则自动绑定,该connect函数就是对应的tcp_connect函数,该函数就是将发送SYN数据包进行三路握手连接建立过程,在tcp_connect函数中,会将sock的状态更新TCP_SYN_SENT,然后就继续判断在连接完成之后的状态,通过一个while循环来判断是否连接成功如果连接成功则更新状态为连接成功并返回。

sock_accept函数调用的inet_accept函数

该函数主要逻辑如下;

/**  Accept a pending connection. The TCP layer now gives BSD semantics.*    接受一个连接请求*/static int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{struct sock *sk1, *sk2;int err;// 获取sock内容sk1 = (struct sock *) sock->data;/** We've been passed an extra socket.* We need to free it up because the tcp module creates* its own when it accepts one.*/if (newsock->data){struct sock *sk=(struct sock *)newsock->data;    // 获取datanewsock->data=NULL;                            // 重置data为空sk->dead = 1;destroy_sock(sk);                               // 销毁该sk}if (sk1->prot->accept == NULL)                     // 如果accept为空则报错return(-EOPNOTSUPP);/* Restore the state if we have been interrupted, and then returned. */if (sk1->pair != NULL )                              // 如果不为空{sk2 = sk1->pair;                               // 获取sk1->pair 因为如果被中断pair就是保存的sock结构sk1->pair = NULL;                               // 置空该字段} else{sk2 = sk1->prot->accept(sk1,flags);           // 调用协议的accept函数处理if (sk2 == NULL)                                // 如果为空{if (sk1->err <= 0)printk("Warning sock.c:sk1->err <= 0.  Returning non-error.\n");err=sk1->err;sk1->err=0;return(-err);                                 // 打印错误信息后并返回错误原因}}newsock->data = (void *)sk2;                         // 设置关联的datask2->sleep = newsock->wait;                      sk2->socket = newsock;                              // 设置socket的关联newsock->conn = NULL; if (flags & O_NONBLOCK)     // 检查是否为非阻塞return(0);           cli(); /* avoid the race. */    while(sk2->state == TCP_SYN_RECV)  // 状态如果等于TCP_SYN_RECV{interruptible_sleep_on(sk2->sleep);        // 中断等待if (current->signal & ~current->blocked) {sti();sk1->pair = sk2;           // 如果中断失败则设置保存到pair中sk2->sleep = NULL;          // 重置为空sk2->socket=NULL;newsock->data = NULL;return(-ERESTARTSYS);      // 返回失败}}sti();                                 // 使能中断if (sk2->state != TCP_ESTABLISHED && sk2->err > 0)     // 如果状态不等于已经连接 并且有错误内容{err = -sk2->err;                                 // 返回错误内容并销毁sk2sk2->err=0;sk2->dead=1;  /* ANK */destroy_sock(sk2);newsock->data = NULL;return(err);}newsock->state = SS_CONNECTED;                         // 如果成功则设置为连接成功return(0);
}

该函数主要用于接收一个套接字,该函数是由服务端调用,服务端在接受到一个远端客户端连接请求后,调用相应函数进行处理,首先会创建一个本地套接字用于通信,原来的套接字仍然处于监听状态,然后调用prot的accept函数进行发送应答数据包,将新创建的套接字对于sock结构接到监听sock结构的接受队列中给accept函数使用,然后就检查是否是阻塞状态,如果为非阻塞则直接返回,如果是阻塞状态则依次遍历当前的套接字状态是否从TCP_SYN_RECV改变,如果改变则说明连接建立成功,此时就表示三次握手的过程就完成。

三次握手的过程梳理

主要的业务逻辑在接受请求的过程中,双方接受连接并更改状态。

总结

本文主要细化到af_inet层来细化tcp的相关的操作函数,本质就是根据tcp协议做了更详细的细化内容,主要涉及到接受tcp协议层的具体的状态检查,并涉及到了tcp协议的几种状态的细节一点的内容,后续会继续根据流程去深入学习。由于本人才疏学浅,如有错误请批评指正。

Linux内核网络栈1.2.13-af_inet.c概述相关推荐

  1. Linux内核网络栈1.2.13-socket.c函数概述

    参考资料 <<linux内核网络栈源代码情景分析>> socket常用函数概述 根据socket提供的常用的库函数,socket,read,write等函数, 执行的过程 in ...

  2. Linux内核网络栈1.2.13-route.c概述

    参考资料 <<linux内核网络栈源代码情景分析>> route路由表概述 在IP协议的实现中,只要发送数据包都要查询路由表,选择合适的路由选项,确定下一站的地址,并构造MAC ...

  3. Linux内核网络栈1.2.13-tcp.c概述

    参考资料 <<linux内核网络栈源代码情景分析>> af_inet.c文件中调用函数在协议层的实现 本文主要根据在af_inet.c文件中根据初始化不同的协议,来调用不同的协 ...

  4. linux内核网络初始化,Linux内核--网络栈实现分析

    本文分析基于内核Linux Kernel 1.2.13 以后的系列博文将深入分析Linux内核的网络栈实现原理,这里看到曹桂平博士的分析后,也决定选择Linux内核1.2.13版本进行分析. 原因如下 ...

  5. Linux内核--网络栈实现分析(三)--驱动程序层+链路层(上)

    本文分析基于Linux Kernel 1.2.13 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7497260 更多请看专栏,地址 ...

  6. Linux内核--网络栈实现分析(一)--网络栈初始化--转

    转载地址 http://blog.csdn.net/yming0221/article/details/7488828 作者:闫明 本文分析基于内核Linux Kernel 1.2.13 以后的系列博 ...

  7. Linux内核--网络栈实现分析(二)--数据包的传递过程--转

    转载地址http://blog.csdn.net/yming0221/article/details/7492423 作者:闫明 本文分析基于Linux Kernel 1.2.13 注:标题中的&qu ...

  8. Linux内核网络栈1.2.13-网卡设备的初始化流程

    参考资料 <<linux内核网络栈源代码情景分析>> 网卡设备的初始化 本文主要描述一下网卡设备的整个初始化的过程,该过程主要就是根据设备的硬件信息来获取与传输网络数据,注册相 ...

  9. Linux内核网络栈1.2.13-有关tcp/ip协议的基础入门

    参考资料 <<linux内核网络栈源代码情景分析>> Linux内核网络栈的基础内容 主要分析tcp/ip相关的基本构成,概述了socket的系统调用进入内核的一个流程,并了解 ...

最新文章

  1. Flex itemRenderer 内联渲染器
  2. cocos2d-x游戏开发(十三)细说回调函数
  3. CRM exception when customizing download is executed in ERP due to empty table gt_crm
  4. 并发–顺序线程和原始线程
  5. Git 的 .gitignore 配置
  6. 使用“时间机器”备份您的 Mac
  7. poj2031(prim)
  8. 【MySQL】语句抓包分析工具MySQL sniffer
  9. 游戏开发之测试篇3(C++)
  10. 我的世界服务器一直没信号,我的世界:6年前突发的MC诡异事件,至今官方也说不出原因!...
  11. CVPR2020(Enhancement):论文解读《Zero-Reference Deep Curve Estimation for Low-Light Image Enhancement》
  12. 【刷题】BZOJ 2959 长跑
  13. 解决关于引入的网络图片,浏览器无法正常打开的问题
  14. 低代码对比分析,从工程化上看产品的优劣
  15. 高精度计算-大整数除法
  16. 傅里叶级数、傅里叶变换、量子傅里叶变换(学习笔记)
  17. 没信号是不是就无服务器,不要没网就说wifi断流,你知道什么是断流吗?
  18. 八.java入门【方法】
  19. Eclipse中java文件图标变成空心J如何解决
  20. 各国男人眼中的经典美人

热门文章

  1. 中科大“九章”历史性突破,但实现真正的量子霸权还有多远?
  2. AI 还原康乾盛世三代皇帝的样貌,简直太太太好玩了!
  3. AI安全最全“排雷图”来了!腾讯发布业内首个AI安全攻击矩阵
  4. 优酷智能档在大型直播场景下的技术实践
  5. 过关斩将打进Kaggle竞赛Top 0.3%,我是这样做的
  6. 一文帮你梳理清楚:奇异值分解和矩阵分解 | 技术头条
  7. Google又放大招:高效实时实现视频目标检测 | 技术头条
  8. 无人驾驶汽车系统入门:基于深度学习的实时激光雷达点云目标检测及ROS实现...
  9. 百万人才工程创新大讲堂开课啦!
  10. MySQL 用 limit 为什么会影响性能?