UDP数据包接收逻辑的优化修改以及对性能的影响

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>static int totalMsg = 0;void sigINT(int dwsigno)
{printf("totalMsg: %d\n", totalMsg);exit(0);
}int openServer()
{struct addrinfo hints;memset(&hints, 0, sizeof(hints));hints.ai_flags = AI_PASSIVE;hints.ai_socktype = SOCK_DGRAM;struct addrinfo *res;static char *port = "4020";int e = getaddrinfo(NULL, port, &hints, &res);if (e == EAI_SYSTEM){printf("openServer: getaddrinfo error=%d(%s)!!!\n", errno, strerror(errno));return -1;}else if (e != 0){printf("openServer: getaddrinfo error=%s!!!\n", gai_strerror(e));return -1;}int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);if (fd < 0){printf("openServer: create socket error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}int rcvBufSize = 131071;socklen_t optlen = sizeof(rcvBufSize);if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, optlen) < 0){printf("openServer: setsockopt error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}int rcvRealSize = -1;if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvRealSize, &optlen) < 0){printf("openServer: getsockopt error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}printf("recrive-buff-size: %d\n", rcvRealSize);if (bind(fd, (struct sockaddr *)res->ai_addr, res->ai_addrlen) < 0){printf("openServer: bind error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}printf("create udp socket(%d) ok!\n", fd);return fd;
}void monUdpSock(int udpSock)
{static fd_set fds;FD_ZERO(&fds);FD_SET(udpSock, &fds);static struct timeval tv = {0, 20000};int readyNum = select(udpSock+1, &fds, NULL, NULL, &tv);if (readyNum < 0){printf("monUdpSock: select error=%d(%s)!!!\n", errno, strerror(errno));// 异常处理return;}else if (readyNum == 0)return; // select超时,do nothingelse; // 存在可读写fdif (!FD_ISSET(udpSock, &fds))return;static char udpMsg[1024*64]; // 64KBint rbytes = read(udpSock, udpMsg, sizeof(udpMsg));if (rbytes <= 0)return;// 处理收到的Udp消息totalMsg++;
}int main()
{if (signal(SIGINT,sigINT) == SIG_ERR){printf("set single handler error!\n");exit(1);}int udpSock = openServer();while (1){monUdpSock(udpSock);usleep(3); // 做其他工作,占用了3ms}
}

如上所示是一个UDP服务端程序,用以接收UDP数据包并统计接收总量。

这个UDP Server设置自己的UDP服务Socket接收缓冲区为128K,实际在内核占用的缓冲区为256K。

在主循环中的睡眠,是为了模拟程序做其他的工作,每次耗时3ms。

性能统计小脚本:

#!/bin/bash

while [ true ]; dosleep 1netstat -an | grep $1
done

脚本功能是每秒中去netstat查看/列出指定UDP Socket的接收缓冲区队列中滞留的,尚未被read到用户态的UDP数据量。

模拟压力测试,每个UDP数据包为1800字节左右:

1)1000caps,总量100000个UDP数据包:

脚本输出部分如下:

udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp     4304      0 0.0.0.0:4020                0.0.0.0:*
udp     4304      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp     2152      0 0.0.0.0:4020                0.0.0.0:*
udp     2152      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp     2152      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp     2152      0 0.0.0.0:4020                0.0.0.0:*
udp     4304      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp     4304      0 0.0.0.0:4020                0.0.0.0:*
udp     4304      0 0.0.0.0:4020                0.0.0.0:*
udp     2152      0 0.0.0.0:4020                0.0.0.0:*

输出表明:

程序执行时,接收缓冲区存在很多数据来不及收,虽然没有太大的堆积量,也没有到接收缓冲区上限256K,但是UDP服务器并不能及时的将数据取出来(即:UDP服务器还是有问题的!!!)。

[jiang@localhost svr]$ ./main
recrive-buff-size: 262142
create udp socket(3) ok!
^CtotalMsg: 100000

实际UDP服务端收到了100000个UDP数据包,并未发生丢包。

但未丢包不代表万事大吉,只能说明,UDP服务器的处理速度(从内核读缓冲区中取数据的速度)和模拟端的写速度达到了一个相对平衡的状态。

此时如果增加一倍的caps呢?

2)2000caps,总量200000个UDP数据包:

脚本输出部分如下:

[jiang@localhost pm]$ ./netstat.sh 4020
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp    17216      0 0.0.0.0:4020                0.0.0.0:*
udp     4304      0 0.0.0.0:4020                0.0.0.0:*
udp     8608      0 0.0.0.0:4020                0.0.0.0:*
udp    15064      0 0.0.0.0:4020                0.0.0.0:*
udp     4304      0 0.0.0.0:4020                0.0.0.0:*
udp    15064      0 0.0.0.0:4020                0.0.0.0:*
udp     8608      0 0.0.0.0:4020                0.0.0.0:*
udp    15064      0 0.0.0.0:4020                0.0.0.0:*
udp     8608      0 0.0.0.0:4020                0.0.0.0:*
udp     4304      0 0.0.0.0:4020                0.0.0.0:*
udp     6456      0 0.0.0.0:4020                0.0.0.0:*
udp     8608      0 0.0.0.0:4020                0.0.0.0:*
udp   262544      0 0.0.0.0:4020                0.0.0.0:*
udp   258240      0 0.0.0.0:4020                0.0.0.0:*
udp   262544      0 0.0.0.0:4020                0.0.0.0:*
udp   260392      0 0.0.0.0:4020                0.0.0.0:*
udp   260392      0 0.0.0.0:4020                0.0.0.0:*
udp   262544      0 0.0.0.0:4020                0.0.0.0:*

输出表明:

程序运行时,由于服务端程序从内核读缓冲区中read慢了(读出速度小于写入速度),导致读缓冲区中数据堆积,UDP接收缓冲区溢出,出现丢包的情况。

[jiang@localhost svr]$ ./main
recrive-buff-size: 262142
create udp socket(3) ok!
^CtotalMsg: 170327

我模拟发送20万个UDP数据包,实际只收到170327个,丢了近3w个数据包。

我们的主循环,除了接收UDP数据包,还会干别的事情(3ms)。

最理想状态下,假如CPU时间片切换、信号唤醒等立刻完成(不耗用时间),睡眠3ms,在2000caps的情况下,意味着这3ms中将会有6个UDP数据包一定堆积。

假设一个UDP数据包100K,也就是说,从3ms开始的时刻计算,到3ms结束的时刻,共计有600K的数据堆积在缓冲区中,得不到处理。

实际情况更为复杂,堆积的数据只会比理想状态下多,绝不会比其少。

仔细观察UDP数据包接收逻辑:

    static struct timeval tv = {0, 20000};int readyNum = select(udpSock+1, &fds, NULL, NULL, &tv);if (readyNum < 0){printf("monUdpSock: select error=%d(%s)!!!\n", errno, strerror(errno));// 异常处理return;}else if (readyNum == 0)return; // select超时,do nothingelse; // 存在可读写fdif (!FD_ISSET(udpSock, &fds))return;static char udpMsg[1024*64]; // 64KBint rbytes = read(udpSock, udpMsg, sizeof(udpMsg));if (rbytes <= 0)return;// 处理收到的Udp消息totalMsg++;

由于每时每刻都有数据到来,所以select并不会真的等待20ms,而是立刻返回。

即,主循环在不停歇地干两件事情:

1)从udp server sock fd中读取【一个】UDP数据包;
2)干其他事情,花费3ms;

换言之,我们可以认为至少每3ms这个周期中,才会去接收**一个**UDP数据包。

实际上,在select指明有数据到来时,我们可以不断地读取接收缓冲区中的数据包,直到缓冲区再无可读取的数据包。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <netdb.h>
#include <signal.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>static int totalMsg = 0;void sigINT(int dwsigno)
{printf("totalMsg: %d\n", totalMsg);exit(0);
}int openServer()
{struct addrinfo hints;memset(&hints, 0, sizeof(hints));hints.ai_flags = AI_PASSIVE;hints.ai_socktype = SOCK_DGRAM;struct addrinfo *res;static char *port = "4020";int e = getaddrinfo(NULL, port, &hints, &res);if (e == EAI_SYSTEM){printf("openServer: getaddrinfo error=%d(%s)!!!\n", errno, strerror(errno));return -1;}else if (e != 0){printf("openServer: getaddrinfo error=%s!!!\n", gai_strerror(e));return -1;}int fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);if (fd < 0){printf("openServer: create socket error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}int rcvBufSize = 131071;socklen_t optlen = sizeof(rcvBufSize);if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvBufSize, optlen) < 0){printf("openServer: setsockopt error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}int rcvRealSize = -1;if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvRealSize, &optlen) < 0){printf("openServer: getsockopt error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}printf("recrive-buff-size: %d\n", rcvRealSize);if (bind(fd, (struct sockaddr *)res->ai_addr, res->ai_addrlen) < 0){printf("openServer: bind error=%d(%s)!!!\n", errno, strerror(errno));freeaddrinfo(res);return -1;}freeaddrinfo(res);// 设置UDP服务Socket为非阻塞模式int sockflag;if ((sockflag = fcntl(fd, F_GETFL, 0)) < 0){printf("openServer: get socket(%d) flag error=%d(%s)", fd, errno, strerror(errno));return -1;}sockflag = sockflag | O_NONBLOCK;if (fcntl(fd, F_SETFL, sockflag) < 0){printf("openServer: set socket(%d) flag error=%d(%s)", fd, errno, strerror(errno));return -1;}printf("create udp socket(%d) ok!\n", fd);return fd;
}void monUdpSock(int udpSock)
{static fd_set fds;FD_ZERO(&fds);FD_SET(udpSock, &fds);static struct timeval tv = {0, 20000};int readyNum = select(udpSock+1, &fds, NULL, NULL, &tv);if (readyNum < 0){printf("monUdpSock: select error=%d(%s)!!!\n", errno, strerror(errno));// 异常处理return;}else if (readyNum == 0)return; // select超时,do nothingelse; // 存在可读写fdif (!FD_ISSET(udpSock, &fds))return;static char udpMsg[1024*64]; // 64KBint rbytes = read(udpSock, udpMsg, sizeof(udpMsg));while (rbytes > 0){// 处理收到的Udp消息totalMsg++;rbytes = read(udpSock, udpMsg, sizeof(udpMsg));}if (rbytes < 0 &&errno != EAGAIN &&errno != EWOULDBLOCK &&errno != EINTR &&errno != EINVAL){printf("monUdpSock: read error=%d(%s)!!!\n", errno, strerror(errno));return;}
}int main()
{if (signal(SIGINT,sigINT) == SIG_ERR){printf("set single handler error!\n");exit(1);}int udpSock = openServer();while (1){monUdpSock(udpSock);usleep(3); // 做其他工作,占用了3ms}
}

注意:

除了在monUdpSock中不断read直到read返回非正值退出的逻辑改动之外,在openServer时,还将服务端的UDP Socket设置为非阻塞模式。

如果不是非阻塞模式会有什么后果呢?

如果read不发生错误,monUdpSock永远不会结束,即使没有UDP数据到来。

由于sock是阻塞型的,读不到就一直挂着自己(read挂起),这个线程再也没办法做别的事情(例如usleep(3))。

如果设置为非阻塞模式,read不到数据并不会真的把自己(执行线程)挂起来,而是立刻返回-1,设置errno为EAGAIN。

我们的核心思想就是:

用read从内核接收缓冲区中尝试收一个UDP数据包,收的到,就继续尝试再收下一个……直到收不到返回。

新版的UDP服务端程序压测如下:

UDP服务端Socket接收缓冲区256KB(同上),每个消息1800B,3000caps:

udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp    10760      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp    12912      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*

略微有点堆积,但是也是正常的,那个时刻可能由于CPU调度线程没得到时间片,没来得及收。

[jiang@localhost svr]$ ./main
recrive-buff-size: 262142
create udp socket(3) ok!
^CtotalMsg: 30000

3000cap毫无压力!

最高压测:

UDP服务端Socket接收缓冲区256KB(同上),每个消息1800B,30000caps(不是3000,是3wcaps):

[jiang@localhost pm]$ ./netstat.sh 4020
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp   122664      0 0.0.0.0:4020                0.0.0.0:*
udp   126968      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*

此次测试是30000个包/秒,数据量也就是:30000*1800B=51MB/秒。

接收缓冲区略微有点堆积也应该是系统调度的原因。

测试共计5次,30000caps无丢包现象。

UDP Socket接收缓冲区在256KB,30000caps,1800B/消息,不会出接收缓冲区满发生丢包。

但是此时再增加量到40000caps,则总出现少许的丢包现象。


其实可以通过增大内核读缓冲区,支持更大的数据量。

通过setsockopt修改读缓冲区大小为1MB,测试100000caps(10wcaps),1800B/消息:

udp   294824      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp   294824      0 0.0.0.0:4020                0.0.0.0:*
udp   299128      0 0.0.0.0:4020                0.0.0.0:*
udp   294824      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp   290520      0 0.0.0.0:4020                0.0.0.0:*
udp   355080      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp   288368      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp   458376      0 0.0.0.0:4020                0.0.0.0:*
udp   393816      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp   292672      0 0.0.0.0:4020                0.0.0.0:*
udp   294824      0 0.0.0.0:4020                0.0.0.0:*
udp   314192      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*
udp   294824      0 0.0.0.0:4020                0.0.0.0:*
udp        0      0 0.0.0.0:4020                0.0.0.0:*

堆积量挺多,不过还远未接近1MB上限,并不会发生丢包。

[jiang@localhost pm]$ ./main 10000000 100000
totalUdp: 10000000
maxRate: 100000
udp data len: 1806
loaded 1806 Bytes Data
finish 10000000 in 100.001, rate: 99999
[jiang@localhost svr]$ ./main
recrive-buff-size: 2097152
create udp socket(3) ok!
^CtotalMsg: 10000000

1000万个UDP包,一个也没有丢失,全部收到。

所谓的缓冲区,除了提供UDP数据包的数据之外,还有一个功能就是容错,容时差。

UDP数据包可以在接收缓冲区中堆积,但一定是由于某种原因暂时的堆积,例如时间片切换,可以堆积部分包,到下次线程轮到时间片时全部处理。

如果服务端程序从接收缓冲区读出的速度小于写入速度,那即使缓冲区设置再大也没有用,终将会堆满接收缓冲区并溢出丢包。

增大接收缓冲区,可以提高由于某些原因导致服务端“抖动”(间隔不规律的读出)而导致接收缓冲区满(UDP溢出)的抗性;

增强服务器消耗(从缓冲区中读出数据)的性能,可以从根本上减少读缓冲区溢出的问题。

UDP数据包是否丢失,与一个UDP数据包的大小、服务端接收缓冲区的大小、读出(接收缓冲区数据)的性能密切相关。

UDP数据包接收逻辑的优化修改以及对性能的影响相关推荐

  1. JAVA实现udp接收文件数据,java – 播放以UDP数据包接收的原始PCM音频

    以下是获取输出线并在其上播放PCM的简单示例.在运行时,它会播放大约一秒长的恼人的哔哔声. import javax.sound.sampled.AudioFormat; import javax.s ...

  2. java 远程udp_远程客户端不接收UDP数据包

    我有简单的UDP服务器/客户端程序,我转发我的端口和服务器通过互联网接收和发送数据包,但远程机器上的客户端无法接收它们,所以我想知道如何在客户端没有转发端口的情况下接收数据包(如果它甚至可能)?如果它 ...

  3. android+udp传输大小,Android UDP数据包如何接收可变大小的数据包

    我有一个Android应用程序,它监视UDP数据包并调用一个方法来处理收到的消息.我有一个问题,如果传入的消息更长,它将调用方法来处理消息.但是如果传入的消息较短,则不会调用该方法,但如果我发送短消息 ...

  4. java发送接收UDP数据包:字符串,byte[]字节数组,文件等

    全栈工程师开发手册 (作者:栾鹏) java教程全解 java发送接收UDP数据包,数据内容为byte[],包括一切可以转换为byte[]的内容. 测试代码 public static void ma ...

  5. 如何在Linux命令行下发送和接收UDP数据包

    众所周知,在传输层有两个常用的协议 TCP 和 UDP,本文介绍在 Linux 命令行下,如何使用 nc 命令发送或接收 UDP 数据包,这些命令的用法对调试 UDP 通信程序将有所帮助. 1. 问题 ...

  6. Linux内核网络协议栈:udp数据包发送(源码解读)

    <监视和调整Linux网络协议栈:接收数据> <监控和调整Linux网络协议栈的图解指南:接收数据> <Linux网络 - 数据包的接收过程> <Linux网 ...

  7. 【Socket网络编程】7.以太网数据包、IP数据包、UDP数据包

    以太网数据包.ip数据包.udp数据包 搭配这篇博文服用,效果更好:数据封装 和 数据拆封:https://blog.csdn.net/u011754972/article/details/11794 ...

  8. IP、TCP、UDP数据包长度问题

      IP数据包长度问题总结 首先要看TCP/IP协议,涉及到四层:链路层,网络层,传输层,应用层. 其中以太网(Ethernet)的数据帧在链路层 IP包在网络层 TCP或UDP包在传输层 TCP或U ...

  9. linux内核丢弃udp报文,c++ Linux UDP数据包丢失的原因

    我有一个 Linux C应用程序接收有序的UDP数据包.由于排序,我可以很容易地确定数据包何时丢失或重新排序,即当遇到"间隙"时.该系统具有处理差距的恢复机制,但最好避免出现差距. ...

最新文章

  1. 系统复制-快速重装系统
  2. Python命令行补全设置
  3. 题目1178:复数集合
  4. yum 安装PHP之后如何启动,如何用yum安装php_后端开发
  5. CodeForces - 1325D Ehab the Xorcist(构造+异或)
  6. LiveVideoStack线上分享第三季(十二):复杂网络下多码率视频流切换关键技术...
  7. Wget用法、参数解释的比较好的一个文章
  8. Volatile原子性一致性JVM指令重排
  9. 架构的“一小步”,业务的一大步 1
  10. is not a function_libcxx 的 std::function 源码分析
  11. 【原创】小米路由器R1D 丢失SN号,刷回官方系统
  12. 使用gensim训练维基百科
  13. react富文本编辑器
  14. 华为路由器显示连接到服务器失败怎么办,华为路由器设置好后不能上网怎么办...
  15. 2020ICPC昆明热身赛 C.Statues(前缀优化dp+滚动数组优化空间)
  16. hive Cannot truncate non-managed table
  17. 注解处理器(Annoation Processor)
  18. 我的世界服务器信号下面是红,适用于服务器的红石抽奖机我的世界抽奖机电路图...
  19. WrapPanel控件增加滚动条
  20. Django--基于Python的Web应用框架

热门文章

  1. vue:配置其他ip地址进行跨域
  2. 微信小程序项目实战:如何提交表单数据到云数据库
  3. 济南SEO推荐高质量外链时代的到来
  4. 回答离职原因的6条法则
  5. 渗透测试-C段主机信息收集
  6. mysql groupby字符串拼接
  7. Android手机4G网络设置ipv6
  8. Friends Mp3
  9. mc有无限火力的服务器地址,我的世界花雨庭无限火力怎么玩 花雨庭无限火力玩法教程...
  10. Cannot open channel to 2 at election address zj03/192.168.8.132:3888