Youmai の Blog


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

孤儿进程与僵尸进程

发表于 2015-03-12 | 分类于 linux

##前言
孤儿继承和僵尸进程是APUE里面的一个重要概念,之前看书不仔细,也没有总结,所以这两个概念一直很模糊,只知道是父进程和子进程有一个退了,至于到底是父进程退还是子进程退会产生孤儿进程和僵尸进程,一直是我的一块心病啊。今天有空,来认真总结一下。

##基本概念

在unix/linux中,子进程是通过父进程创建的(fork)。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。 当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

我们再来分析一下:

孤儿的意思是什么?被父母抛弃了,或者没有父母。所以孤儿进程就是父进程不在了,留下子进程在继续运行。但孤儿不能自己存在啊,所以总会有好心人收养他,在unix系统里,这个好心人就是init进程,init进程会收养所有的孤儿进程,代替父进程手机子进程的终止状态。

同理,什么是僵尸?如果你死了,你就有可能成为僵尸。子进程挂了之后,有一个重要的步骤就是父进程应该调用wait或者waitpid来获取它的终止状态,让它入土为安的。但有些父母非常不负责,他没有做。所以子进程不能入土,就只能继续在系统里飘荡,成了僵尸。这不怪他们啊,都是父进程害的!

##危害

有的人就说了,那干嘛一定要父进程调用wait和waitpid来回收子进程啊,子进程挂了就让他挂好了,父进程不要回收,让系统自己把回收的事干了。我也是这样想的,可是你知道父母的通病在哪里吗?就是他们对自己的小孩,都有旺盛的控制欲!

unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是:

在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到父进程通过wait / waitpid来取时才释放。

所以就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果产生大量的僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害。

孤儿进程是没有父进程的进程,处理孤儿进程的这个重任落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个 子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。

##如何避免产生僵尸进程

我们知道了僵尸进程产生的原因和危害,那么如何避免产生僵尸进程呢?

一般,为了防止产生僵尸进程,在fork子进程之后我们都要wait它们;同时,当子进程退出的时候,内核都会给父进程一个SIGCHLD信号,所以我们可以建立一个捕获SIGCHLD信号的信号处理函数,在函数体中调用wait(或waitpid),就可以清理退出的子进程以达到防止僵尸进程的目的。如下代码所示:

void sig_chld( int signo ) {
    pid_t pid;
    int stat;
    pid = wait(&stat);    
    printf( "child %d exit\n", pid );
    return;
}

int main() {
    signal(SIGCHLD,  &sig_chld);
}

先在main函数中给SIGCHLD信号注册一个信号处理函数(sig_chld),然后在子进程退出的时候,内核递交一个SIGCHLD的时候就会被主进程捕获而进入信号处理函数sig_chld,然后再在sig_chld中调用wait,就可以清理退出的子进程。这样退出的子进程就不会成为僵尸进程。

但是,这种方法并不是完美的,有时候还是会有漏网之鱼,下面是就是一个例子:

我们假设有一个client/server的程序,对于每一个连接过来的client,server都启动一个新的进程去处理来自这个client的请求。然后我们有一个client进程,在这个进程内,发起了多个到server的请求(假设5个),则server会fork 5个子进程来读取client输入并处理(同时,当客户端关闭套接字的时候,每个子进程都退出);当我们终止这个client进程的时候 ,内核将自动关闭所有由这个client进程打开的套接字,那么由这个client进程发起的5个连接基本在同一时刻终止。这就引发了5个FIN,每个连接一个。server端接受到这5个FIN的时候,5个子进程基本在同一时刻终止。这就又导致差不多在同一时刻递交5个SIGCHLD信号给父进程,而最终结果大家将会发现,我们没有能够回收所有的5个进程,有僵尸进程产生了。

wait函数不能处理这种情况的原因是:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次,因为Unix信号一般是不排队的。 更为严重的是,本问题是不确定的,依赖于客户FIN到达服务器主机的时机,信号处理函数执行的次数并不确定。

这种情况的正确的解决办法是调用waitpid而不是wait,方法为:信号处理函数中,在一个循环内调用waitpid,以获取所有已终止子进程的状态。我们必须指定WNOHANG选项,他告知waitpid在有尚未终止的子进程在运行时不要阻塞。(我们不能在循环内调用wait,因为没有办法防止wait在尚有未终止的子进程在运行时阻塞,wait将会阻塞到现有的子进程中第一个终止为止)。

##产生了僵尸进程怎么办

如果系统中出现了僵尸进程,如何打僵尸呢?

僵尸进程用kill命令是无法杀掉的,但是我们可以结果掉僵尸进程的爸爸,僵尸daddy挂了之后,僵尸进程就成了孤儿进程,会被init程序收养,然后init程序将其回收

网络编程调试

发表于 2015-03-10 | 分类于 unix网络编程

##记录网络服务名和它们对应使用的端口号及协议

cat /etc/services

##查看某个端口号的使用状态

lsof -i :6000 (查看哪个进程正在监听6000这个端口)
lsof -i TCP:6000 (只显示监听此端口的TCP连接,上面不加选项的就是指TCP&UDP)

lsof命令还可以用来显示谁在使用某个文件或文件夹,例如:

lsof  /root/michael/unp(会显示谁当前在用/root/michael/unp文件夹)

##netstat
netstat 默认情况下,绑定在INADDR_ANY的服务器的套接字是不列出来的,不过可以用 -a 选项改变默认设置(解释:不加-a选项,netstat命令不显示LISTEN状态的条目,所以如果需要显示LISTEN,就需要加上-a)。

netstat -apt(-p选项表示输出PID,-t选项表示监听tcp端口),这个命令可以很方便地显示套接字上程序的PID和名字

netstat -apu:显示监听在udp端口的信息

netstat -napt(加上-n选项表示以数字形式表示地址和端口号,而不是别名)

netstat -nr:可以显示路由表

netstat -i:可以获得计算机基本的网络接口信息

这些命令输出中的*是表示通配地址(INADDR_ANY)

Netstat 命令用于显示各种网络相关信息,如网络连接,路由表,接口状态 (Interface Statistics),masquerade 连接,多播成员 (Multicast Memberships) 等等。

百度的一些面试题

发表于 2015-03-09 | 分类于 面试

#ping是工作在哪一层的,用的什么协议
ping工作在IP层,用的是ICMP协议。traceroute也是基于ICMP协议

#常用的即时通讯软件用的是什么协议

  1. QQ主要采用UDP,某些情况下采用TCP。
  2. 其他IM大多数时候采用TCP,涉及到音频、视频传输会用到UDP

#http状态码

  • 首位为1:这一类型的状态码,代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束。由于 HTTP/1.0 协议中没有定义任何 1xx 状态码,所以除非在某些试验条件下,服务器禁止向此类客户端发送 1xx 响应。
  • 首位为2:成功
  • 首位为3:重定向
  • 首位为4:客户方错误
  • 首位为5:服务器错误

#设有1000个元素,用二分法查找时,最大比较次数是

10次 = [log2(1000)] + 1,方括号是取整等于9

epoll为什么高效

发表于 2015-03-07 | 分类于 unix网络编程

##无需遍历整个fd集合
首先回忆一下select 模型,当有I/O 事件到来时,select 通知应用程序有事件到了快去处理,而应用程序必须轮询所有的fd集合,测试每个fd是否有事件发生,并处理事件;代码像下面这样:

int res = select(maxfd+1, &readfds,NULL, NULL, 120);
if (res > 0)
{
    for (int i = 0; i <MAX_CONNECTION; i++)
    {
       if (FD_ISSET(allConnection[i], &readfds))
       {
           handleEvent(allConnection[i]);
       }
    }
}
// if(res == 0) handle timeout, res < 0handle error

Epoll 不仅会告诉应用程序有I/0事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个fd集合。

int res = epoll_wait(epfd, events, 20,120);
for (int i = 0; i < res;i++)
{
    handleEvent(events[n]);
}

##Epoll 关键数据结构
Epoll 速度快和其数据结构密不可分,其关键数据结构就是:

struct epoll_event {
    __uint32_tevents;      // Epoll events
    epoll_data_tdata;      // User data variable
};
typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

可见epoll_data 是一个 union 结构体 , 借助于它应用程序可以保存很多类型的信息 :fd 、指针等等。有了它,应用程序就可以直接定位目标了。

别小看了这些效率的提高,在一个大规模并发的服务器中,轮询IO是最耗时间的操作之一。

对比最早给出的阻塞IO的处理模型, 可以看到采用了多路复用IO之后, 程序可以自由的进行自己除了IO操作之外的工作, 只有到IO状态发生变化的时候由多路复用IO进行通知, 然后再采取相应的操作, 而不用一直阻塞等待IO状态发生变化了.

epoll的事件触发方式

发表于 2015-03-07 | 分类于 unix网络编程

参考自这里

假如有这样一个例子:

  1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
  2. 这个时候从管道的另一端被写入了2KB的数据
  3. 调用epoll_wait(),并且它会返回RFD,说明它已经准备好读取操作
  4. 然后我们读取了1KB的数据
  5. 调用epoll_wait()……

##Edge Triggered 工作模式:
如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait()之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。

因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait()完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。

  1. 基于非阻塞文件句柄
  2. 只有当read()或者write()返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。

##Level Triggered 工作模式
相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait()收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl()处理文件句柄就成为调用者必须作的事情。

##总结
LT(level triggered水平触发或条件触发)是epoll缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.

ET (edge-triggered边缘触发或事件触发)是高速工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。
因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。

执行可执行程序时内存分配的方式&&BSS段

发表于 2015-03-07 | 分类于 c

##一个由C/C++编译的程序占用的内存分为以下几个部分

  1. 栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
  2. 堆区(heap)— 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
  3. 全局区(静态区)(static)—全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
  4. 文字常量区 —常量字符串就是放在这里的。程序结束后由系统释放
  5. 程序代码区(text)—存放函数体的二进制代码。

##举例

int a = 0;    //全局初始化区 
char *p1;     //全局未初始化区 
main() 
{ 
    int b;                   //栈 
    char s[] = "abc";        //s[]在栈区,abc在常量区 
    char *p2;                //栈 
    char *p3 = "123456";     //123456\0在常量区,p3在栈上。 
    static int c = 0;       //全局(静态)初始化区 
    p1 = (char *)malloc(10); 
    p2 = (char *)malloc(20); //分配得来得10和20字节的区域就在堆区。 
    strcpy(p1, "123456");    //123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一块。 
} 

##堆和栈的区别

  1. stack的空间由操作系统自动分配/释放,heap上的空间需要手动分配/释放
  2. 栈的空间有限,堆是很大的自由存储区。
  3. 程序在编译期对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用参数的传递也在栈上进行

##BSS段

可执行程序包括BSS段、数据段、代码段(也称文本段)。

BSS(Block Started by Symbol)通常是指用来存放程序中未初始化的全局变量和静态变量的一块内存区域。特点是:可读写的,在程序执行之前BSS段会自动清0。所以,未初始的全局变量在程序执行之前已经成0了。

注意和数据段的区别,BSS存放的是未初始化的全局变量和静态变量,数据段存放的是初始化后的全局变量和静态变量。

连续子数组的最大和 && 负数的表示形式

发表于 2015-03-07 | 分类于 算法

输入一个整形数组,数组里有正数也有负数。
数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。
求所有子数组的和的最大值。要求时间复杂度为O(n)。

例如输入的数组为1, -2, 3, 10, -4, 7, 2, -5,
和最大的子数组为3, 10, -4, 7, 2,
因此输出为该子数组的和18。

int find_greatest_sum_of_subarray(int *array, int length) {
    if(array == NULL || length <=0)
        return -1; //返回错误

    int sum = 0;
    int greatest_sum = 0x80000000;

    int i;
    for(i = 0; i < length; i++) {
        if(sum <= 0)
            sum = array[i];
        else
            sum += array[i];

        if(sum > greatest_sum)
            greatest_sum = sum;
    }

    return greatest_sum;
}

代码是很简单的,分析可以参考何海涛的《剑指offer》面试题31

这里想说的是这一句

int greatest_sum = 0x80000000;

greatest_sum初始化为什么不是0呢?你可以初始化为0试一下,会发现,当输入数组的字数组的最大和是负数的时候,程序的输出是0,也就是greatest_sum的初始值。说到这里大家应该明白了,我们应该将greatest_sum初始化为32位有符号数的最小值(32位的最小负数),那为什么是0x80000000,最小负数不是0xffffffff吗?

这就要考虑到负数在计算机中的存储形式了

正数在计算机中是用源码存储的,正数1就是0x00000001,正数2就是0x00000002(0000 0000 0000 0000 0000 0000 0000 0010),但是负数是不一样的,负数在计算机中是用补码存储的

补码 = 原码去反(符号位不变) + 1

举例:-1

#####原码:1000 0000 0000 0000 0000 0000 0000 0001

#####反码:1111 1111 1111 1111 1111 1111 1111 1110

#####补码:1111 1111 1111 1111 1111 1111 1111 1111

举例:-2147483647(0xffffffff)

#####原码:1111 1111 1111 1111 1111 1111 1111 1111

#####反码:1000 0000 0000 0000 0000 0000 0000 0000

#####补码:1000 0000 0000 0000 0000 0000 0000 0001(ox80000001)

同理-2147483646在计算机内是0x80000002

那么问题来了,0x80000000是什么呢?在计算机内它表示的是-2147483648,是有符号32位整数的最小值。

32位有符号整数的表示范围为:
-2147483648 – 2147483647

TCP/IP的一些基础知识

发表于 2015-03-06 | 分类于 TCP/IP

TCP为什么要被称为数据流?

建立连接,数据就在该连接上流动,可以是双向的,没有边界,所以叫数据流

TCP是面向连接的、可靠的流协议。流就是指不间断的数据结构,你可以把它想象成排水道中的水流。当应用程序采用TCP发送消息时,虽然可以保证发送的顺序,但还是犹如没有任何间隔的数据流发送给接收端。TCP充分地实现了数据传输时各种控制功能,可以进行丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。根据TCP的这些机制,在IP这种无连接的网络上也能够实现高可靠性的通信。

流量控制与拥塞控制的区别

拥塞控制:

防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素。

流量控制:

指点对点通信量的控制,是端到端的问题。流量控制所要做的就是抑制发送端发送数据的速率,以便使接收端来得及接收

TCP和UDP有什么不同?

  1. 在链接状态上,TCP 是面向链接的,UDP 则是面向无链接的;
  2. 在数据传输的质量上,TCP 以数据流的方式传输,保证数据的有序性,准确性,完整性,数据传输质量高;UDP 则是以数据报的形式,独立发送数据报,不能保证数据是否到达,是否完整,是否有序,数据传输质量低于TCP;
  3. 在数据包的大小上,TCP 包头至少20个字节,但是 UDP 只有 8个字节;
  4. 对系统资源的要求(TCP 较多,UDP 少)

IO复用--select,poll,epoll比较

发表于 2015-03-06 | 分类于 unix网络编程

##select模型

  1. 最大并发数限制,因为一个进程所打开的FD(文件描述符)是有限制的,由FD_SETSIZE设置,默认值是1024/2048,因此Select模型的最大并发数就被相应限制了。自己改改这个FD_SETSIZE?想法虽好,可是先看看下面吧…
  2. 效率问题,select每次调用都会线性扫描全部的FD集合,这样效率就会呈现线性下降,把FD_SETSIZE改大的后果就是,大家都慢慢来,什么?都超时了??!!
  3. 内核/用户空间 内存拷贝问题,如何让内核把FD消息通知给用户空间呢?在这个问题上select采取了内存拷贝方法。

##poll模型

基本上效率和select是相同的,select缺点的2和3它都没有改掉。

##Epoll的提升

把其他模型逐个批判了一下,再来看看Epoll的改进之处吧,其实把select的缺点反过来那就是Epoll的优点了。

  1. Epoll没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于2048, 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。
  2. 效率提升,Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
  3. 内存拷贝,Epoll在这点上使用了“共享内存”,这个内存拷贝也省略了。

##总结

select 是采用内核轮询方式,每次调用都需要轮询 FD_SET,默认最多可以接受 1024 个fd,可更改为更大,但是随着数量的增多,轮询周期的变长,性能会急剧下降;

poll 是 select 的改进版,将 FD_SET 改造成由( fd,监听事件类型,实际事件类型 )为节点组成的链,解除了1024 的限制,其他并无大的区别,当 fd 多时,同样会造成效率下降;

epoll 将 轮询机制 改造为 事件触发机制,给每一个 fd 附上一个 callback,当监听事件发生时,就将 fd 链接到 就绪链表,调用 epoll_wait 时,只用检查就绪链表就可以了,而不需要像 select 和 poll 一样进行轮询。

另外,select 和 poll 是将存有 fd 的结构或者数组再每次调用的时候都复制到内核态,然后调用完再复制回用户态,而无所谓是否有意义。epoll 使用内存映射,减去了这部分的data-copy操作。

再者,从触发方式上来看,select 和 poll 都只有 条件触发(也可以叫水平触发),epoll 则有条件触发 和 事件触发(也可以叫边缘触发)两种。

在选择使用哪种方式的时候,需要根据 fd 的多少和活跃程度来判断。当fd 数量较少,且都比较活跃的时候,使用 select 或者 poll 反而有可能效率更高,因为毕竟 epoll 要有多次的回调函数。

好东西都是要付出代价的!

keepalive机制

发表于 2015-03-06 | 分类于 TCP/IP

keepalive 是 TCP 协议内的心跳机制,用来维护链接的状态,默认不开启。服务器端每隔一段事件会向空闲两小时以上的链接的对端发送一个 keepalive 包,有以下情形:

  1. 对端回复 ACK,链接保活,再次空闲两小时后重新探测;
  2. 对端的应用程序已经退出,TCP 回复一个 RST包,链接关闭;
  3. 对端的应用程序卡死/无反应,TCP 回复一个 FIN包,来终止链接;
  4. 对端机器无任何反应,服务器端将持续发送 keepalive 包,超时则关闭链接。时间范围半小时到两小时。
1…91011…18
You Wangqiu

You Wangqiu

世之奇伟、瑰怪,非常之观,常在于险远,而人之所罕至焉,故非有志者不能至也

171 日志
21 分类
24 标签
GitHub 知乎 E-Mail
© 2018 You Wangqiu
由 Hexo 强力驱动
主题 - NexT.Muse