用C编写一个简单服务器

前言

本文使用C语言编写一个简单服务器,旨在更好的理解服务端/客户端程序,迭代服务器,并发服务器等概念,仅供学习参考。这篇文章的例子很简单,就是当客户端连接上服务端之后,服务端给出一个“Hello World”回应。

C/S结构流程图

整个客户端,服务端交互流程可以用下图表示,服务端是优先启动进程并监听某一个端口,并且进程一直阻塞,直到有客户端连接进来,才开始处理客户端连接。

image

服务端

通过流程图可以看出,服务端涉及的Socket函数有socket, bind, listen, accept, read, write, close。使用这7个函数就可以编写出一个简易服务器。

socket函数

为了执行网络I/O,一个进程必须做的第一件事情就是创建一个socket函数,函数原型

# family 表示协议族
# type 表示套接字类型
# protocol 表示传输协议
# 若成功返回非负描述符,若出错返回-1
int socket(int family, int type, int protocol);

这个函数需要传入协议族,套接字类型,传输层协议三个参数。

协议族可以有以下取值

family 说明
AF_INET IPv4协议
AF_INET6 IPv6协议
AF_LOCAL Unix域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字

套接字类型可以有以下取值

type 说明
SOCK_STREAM 字节流套接字
SOCK_DGRAM 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCK_ROW 原始套接字

传输层协议可以有以下取值

protocol 说明
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议

这里我们选择IPv4协议,使用字节流套接字,传输层选择TCP协议,所以第一段代码:

#include <stdio.h>
#include <sys/socket.h>
int main()
{
    int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    
    if(server_sockfd == -1){
        printf("socket error");
        return -1;
    }
}

bind函数

bind函数把一个本地协议地址赋予一个套接字,对于网际协议,协议地址就是IP加端口的组合,函数原型

# sockfd 初始化的套接字
# myaddr 协议地址
# addrlen 协议地址长度
# 若成功返回0 出错返回-1
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen)

注意,这个函数不是必须的,如果不使用这个函数绑定一个特定的端口,那么内核会帮我们的套接字选择一个临时端口。作为服务器,一般不会这么做,需要指定特定的端口。

这个函数的第二个参数是协议地址,注意,这个协议地址已经有定义好的结构体,使用IPv4套接字结构地址时候,地址结构体定义如下

struct sockaddr_in {
    uint8_t sin_len; /*结构体长度*/
    sa_family_t sin_family; /*AF_INET*/
    in_port_t sin_port; /*端口(16-bie)*/
    struct in_addr sin_addr; /*IPv4地址(32-bit)*/
    char sin_zero[8]; /*没啥用,设置0即可*/
}

我们让我们的服务器绑定8887端口(80端口被web占用了,用8887端口代替),所以我们的第二段代码

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    
    if(server_sockfd == -1){
        printf("socket error");
        return -1;
    }
    
    struct sockaddr_in server_sockaddr;/*声明一个变量,类型为协议地址类型*/
    server_sockaddr.sin_family = AF_INET;/*使用IPv4协议*/
    server_sockaddr.sin_port = htons(8887);/*监听8887端口*/
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*绑定本机IP,使用宏定义绑定*/

    if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
        printf("bind error");
        return -1;
    }
}

listen函数

listen函数仅有服务器调用,它完成两件事情:

  1. 当使用socket函数创建一个套接字时,它被假设为一个主动套接字,也就是说,它是一个将发送connect发起连接的客户端套接字。当调用listen函数之后,它被转成一个被动套接字,只是内核应该接受连接请求。所以,调用listen之后套接字由CLOSED状态转到LISTEN状态
  2. 这个函数规定内核应该为相应套接字排队的最大连接数

函数原型

/*失败时返回-1*/
int listen(int sockfd, int backlog)

backlog参数的设定其实是表示两个队列的总和,这两个队列分别是

  1. 未完成连接队列,在客户端发送一个SYN直到三次握手完成,都是这个状态,SYN_RCVD状态。
  2. 已完成连接队列,这个表示三次握手完成的状态,ESTABLISHED状态

因为我们是测试,这个值设置成20就可以了。所以我们的第三段代码

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    
    if(server_sockfd == -1){
        printf("socket error");
        return -1;
    }
    
    struct sockaddr_in server_sockaddr;/*声明一个变量,类型为协议地址类型*/
    server_sockaddr.sin_family = AF_INET;/*使用IPv4协议*/
    server_sockaddr.sin_port = htons(8887);/*监听8887端口*/
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*绑定本机IP,使用宏定义绑定*/

    if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
        printf("bind error");
        return -1;
    }
    
    if(listen(server_sockfd, 20) == -1){
        printf("listen error");
        return -1;
    }

}

accept函数

accept函数是由TCP服务器调用,用于从已完成连接队列的队头返回下一个已完成连接,如果已完成连接队列为空,那么进程进入睡眠模式,函数原型

# sockdf 服务器套接字莫描述符
# cliaddr 已连接的客户端协议地址
# addrlen 已连接的客户端协议地址长度
# 成功返回非负描述符,出错返回-1
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

当accept成功时,返回值是由内核自动生成的全新描述符,代表与所返回的客户端TCP连接。所以,在我们讨论accept函数时,我们称第一个参数为监听套接字,它的返回值是已连接套接字,一个服务器通常指创建一个监听套接字(通常是80端口),内核为每个由服务器进程接受的客户端连接创建一个已连接套接字,当服务器完成对某个给定的客户端服务时,连接就会被关闭。

函数的第二个参数也是一个协议地址结构体,这个结构体和服务端协议地址是同一个结构体。我们可以不关心客户端的协议,直接传空,我们关系的是这个函数的返回值,因为它返回的是客户端连接描述符,我们可以对这个描述符进行写操作,从而实现给客户端传输数据。所以我们第四段代码

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    
    if(server_sockfd == -1){
        printf("socket error");
        return -1;
    }
    
    struct sockaddr_in server_sockaddr;/*声明一个变量,类型为协议地址类型*/
    server_sockaddr.sin_family = AF_INET;/*使用IPv4协议*/
    server_sockaddr.sin_port = htons(8887);/*监听8887端口*/
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*绑定本机IP,使用宏定义绑定*/
    
    if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
        printf("bind error");
        exit(1);
    }
    
    if(listen(server_sockfd, 20) == -1){
        printf("listen error");
        exit(1);
    }
    
    struct sockaddr_in clnt_addr;/*只是声明,并没有赋值*/
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    
    if(clnt_sock == -1){
        printf("appect error");
        return -1;
    }
    
}

write函数

前面的操作都完成之后,说明服务端和客户端已经建立连接,由于TCP的传输是全双工的,这时候客户端和服务端都可以向对方发送数据。这里为了简化,我们实现服务端发送“Hello World”给请求连接的客户端。给客户端发送数据很简单,就是对返回的客户端描述符进行写操作就可以了

# sockfd socket文件描述符
# buf 文件内容
# count 内容长度
ssize_t write(int sockfd, const void * buf, size_t count);

完整的服务器代码

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    
    if(server_sockfd == -1){
        printf("socket error");
        return -1;
    }
    
    struct sockaddr_in server_sockaddr;/*声明一个变量,类型为协议地址类型*/
    server_sockaddr.sin_family = AF_INET;/*使用IPv4协议*/
    server_sockaddr.sin_port = htons(8887);/*监听8887端口*/
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*绑定本机IP,使用宏定义绑定*/
    
    if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
        printf("bind error");
        return -1;
    }
    
    if(listen(server_sockfd, 20) == -1){
        printf("listen error");
        return -1;
    }
    
    struct sockaddr_in clnt_addr;/*只是声明,并没有赋值*/
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    
    if(clnt_sock == -1){
        printf("appect error");
        return -1;
    }
    
    char str[] = "Hello World";
    write(clnt_sock, str, sizeof(str));
    
    close(clnt_sock);
    close(server_sockfd);
}

客户端

客户端要和服务器进行通信,从流程图上可以看出,需要使用socket, connect, write, read, close这5个函数

socket函数

客户端要和服务端进行网络通讯,首先也必须调用socket函数,这里客户端也使用IPv4协议,使用字节流套接字,传输层选择TCP协议,所以第一段代码

#include <stdio.h>
#include <sys/socket.h>
int main()
{
    int sock_cli = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    
    if(sock_cli == -1){
        printf("socket error");
        return -1;
    }
}

connect函数

TCP客户端就是使用connect函数和服务端建立连接,函数原型

# sockfd 客户端TCP描述符
# sockaddr 服务端协议地址
# addrlen 服务端协议地址长度
int connect(int sockfd, const struct sockaddr * servaddr, socklen_t addrlen);

这个函数将触发客户端和服务端三次握手,函数的第一个参数sockfd表示客户端返回的描述符,这里不需要调用bind函数绑定端口,系统会自动分配。函数的第二个参数需要配置服务端IP和端口信息,同样有结构体规范这些信息,结构体也是和服务端一样使用sockaddr_in类型。所以第二段代码

#include <stdio.h>
#include <sys/socket.h>
int main()
{
    int sock_cli = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    
    if(sock_cli == -1){
        printf("socket error");
        return -1;
    }
    
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;/*使用IPv4协议*/
    servaddr.sin_port = htons(8887);/*需要连接的远程服务器端口*/
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*需要连接的远程服务器IP*/
    
    if(connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr))  == -1){
        printf("connect error");
        return -1;
    }

}

read函数

客户端连接上服务器之后返回的是一个Socket文件描述符,既然是文件描述符,就可以通过简单的read函数获取网络数据,read函数原型

# sockdf 文件描述符
# buf 文件内容存放地址
# count 内容长度
ssize_t read(int sockfd,void *buf,size_t count)

这里我们读取64个字节就够了,不需要太多

char str[64];
read(sock_cli, str, 64);

完整的客户端代码

#include <stdio.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    int sock_cli = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    
    if(sock_cli == -1){
        printf("socket error");
        return -1;
    }
    
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;/*使用IPv4协议*/
    servaddr.sin_port = htons(8887);/*需要连接的远程服务器端口*/
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");/*需要连接的远程服务器IP*/
    
    if(connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr))  == -1){
        printf("connect error");
        return -1;
    }

    char str[64];
    read(sock_cli, str, 64);
    printf(str);

    close(sock_cli);
}

运行客户端服务端

将我们的服务端代码保存为server.c,将我们的客户端代码保存为client.c。分别编译客户端和服务端代码

[root@iZ940ofmvruZ socket]# gcc server.c -o server
[root@iZ940ofmvruZ socket]# gcc client.c -o client

然后会分别生成两个可执行文件server和client。在一个窗口中先执行server

[root@iZ940ofmvruZ socket]# ./server


执行server之后,我们知道accept函数会阻塞,所以程序一直运行,等待客户端连接进来。这时候在另一个窗口执行客户端

[root@iZ940ofmvruZ socket]# ./client 
Hello World

可以看到服务端给我们发送的Hello World,我们再回到服务端执行窗口时,服务端也终止了进程,这个交互完成。然后我们会发现一个问题,服务端在提供完服务之后,它自己也关闭了,很显然,我们希望服务器是一致运行的提供服务,所以我们需要实现一直运行的服务器

不间断提供服务

让服务器一直运行的方式很简单,就是死循环。循环的过程是accept一个客户端连接,然后处理数据请求,最后关闭客户端连接。注意,我们不能关闭服务端连接。所以我们改进这个程序,让server不间断的调用accept,因为accept总是从已连接的队列中返回一个连接,然后处理。改进内容片断

/**
 * 进入死循环调用accept,给每一个连接上来的客户端发送Hello World
 */
for( ; ; ){
    struct sockaddr_in clnt_addr;/*只是声明,并没有赋值*/
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

    if(clnt_sock == -1){
        printf("appect error");
        return -1;
    }

    char str[] = "Hello World";
    write(clnt_sock, str, sizeof(str));

    close(clnt_sock);
    /*close(server_sockfd);*/
}

这样修改之后,这个服务端程序就是一直不间断提供服务了

并发服务器

迭代服务器

我们首先来看下什么是迭代服务器,因为我们刚才所写的就是一个迭代服务器,思考一个问题,假如我们的服务器不是输出Hello World这么简单,而是需要经过一系列复杂逻辑计算甚至网络调用,那我们的程序执行起来就不会怎么快了,为了模拟这种场景,我们在服务端程序中假如sleep函数,我们让程序睡眠3秒钟,模拟服务器处理复杂逻辑时间

#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>
int main()
{
    int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    
    if(server_sockfd == -1){
        printf("socket error");
        return -1;
    }
    
    struct sockaddr_in server_sockaddr;/*声明一个变量,类型为协议地址类型*/
    server_sockaddr.sin_family = AF_INET;/*使用IPv4协议*/
    server_sockaddr.sin_port = htons(8887);/*监听8887端口*/
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*绑定本机IP,使用宏定义绑定*/
    
    if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
        printf("bind error");
        return -1;
    }
    
    if(listen(server_sockfd, 20) == -1){
        printf("listen error");
        return -1;
    }

    for( ; ; ){    
        struct sockaddr_in clnt_addr;/*只是声明,并没有赋值*/
        socklen_t clnt_addr_size = sizeof(clnt_addr);
        int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        
        if(clnt_sock == -1){
            printf("appect error");
            return -1;
        }
        
        char str[] = "Hello World";
        sleep(3);//3秒之后再向客户端发送数据
        write(clnt_sock, str, sizeof(str));
    
        close(clnt_sock);
        /*close(server_sockfd);*/
    }
}

运行这个服务端程序之后,我们同时执行10个客户端

for(( i=0; i< 10; i++ ))
    do
    {
        ./client
    }&
done

在shell中执行这段代码,你会发现每隔3秒钟输出一个Hello World。这是因为我们的服务端程序是阻塞的,在处理一个请求的同时,其他请求只能等。所以最后一个客户端连接需要等到30秒才能收到服务端的输出。我们称这种服务器为迭代服务器,迭代服务器会依次处理客户端的连接,只要当前连接的任务没有完成,服务器的进程就会一直被占用,直到任务完成后,服务器关闭这个socket,释放连接。这显然不是我们想要的,我们希望每一个Hello Wrold都在3秒钟后马上输出。

并发服务器

当一个服务处理客户端请求需要花费较长时间,但是我们又不希望整个服务器被单个客户端长期占用,而是希望同时服务多个客户。Unix中编写并发服务器最简单的办法就是fork一个子进程来服务每个客户。利用fork函数可以把处理客户端请求的任务交接到子进程,这样就实现多进程并发,我们可以写出这样服务器的轮廓

pid_t pid;
for( ; ; ){    
    struct sockaddr_in clnt_addr;/*只是声明,并没有赋值*/
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    
    if(clnt_sock == -1){
        printf("appect error");
        return -1;
    }
    
    /**
     * 这一段直接fork一个子进程
     * 子进程处理单独处理完请求之后退出
     */
    if( (pid = fork()) == 0 ){
        close(server_sockfd);/*子进程不需要监听,关闭*/
        doit(clnt_sock);/*针对已连接的客户端套接字进行读写*/
        close(clnt_sock);/*处理完毕,关闭客户端连接*/
        exit(0);/*自觉退出*/
    }
    
    close(clnt_sock); /*连接已经交由子进程处理,父进程可以关闭客户端连接了*/
    /*close(server_sockfd);*/
}

其中,doit函数我们先不实现,我们来看一下一个并发服务器处理一个客户端连接的流程

  1. 服务器阻塞于accept调用且来自客户的连接请求到达时的客户端与服务器的状态
image
  1. 从accept返回后,连接已经在内核中注册,并且新的套接口connfd被创建。这是一个已建起连接的套接口,可以进行数据的读写。
image

3.并发服务器在调用fork之后,listenfd和connfd这两个描述字在父进程以及子进程之间共享(实际为其中一份为copy)

image
  1. 接下来是由父进程关闭已连接套接口(connfd),由子进程关闭监听套接口(listenfd)。然后由子进程负责为客户端提供服务
image

最终我们的并发服务器代码为

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/socket.h>

void doit(int sockfd);

int main()
{
    int server_sockfd = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);
    pid_t pid;
    
    if(server_sockfd == -1){
        printf("socket error");
        return -1;
    }
    
    struct sockaddr_in server_sockaddr;/*声明一个变量,类型为协议地址类型*/
    server_sockaddr.sin_family = AF_INET;/*使用IPv4协议*/
    server_sockaddr.sin_port = htons(8887);/*监听8887端口*/
    server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);/*绑定本机IP,使用宏定义绑定*/
    
    if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1){
        printf("bind error");
        return -1;
    }
    
    if(listen(server_sockfd, 20) == -1){
        printf("listen error");
        return -1;
    }

    for( ; ; ){    
        struct sockaddr_in clnt_addr;/*只是声明,并没有赋值*/
        socklen_t clnt_addr_size = sizeof(clnt_addr);
        int clnt_sock = accept(server_sockfd, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
    
        if(clnt_sock == -1){
            printf("appect error");
            return -1;
        }

        if( (pid = fork()) == 0 ){
            close(server_sockfd);/*子进程不需要监听,关闭*/
            doit(clnt_sock);/*针对已连接的客户端套接字进行读写*/
            close(clnt_sock);/*处理完毕,关闭客户端连接*/
            exit(0);/*自觉退出*/
        }    

        close(clnt_sock);
        /*close(server_sockfd);*/
    }
}

void doit(int sockfd){
    char str[] = "Hello World";
    sleep(3);//3秒之后再向客户端发送数据
    write(sockfd, str, sizeof(str));
}

这个时候再次利用shell并行执行我们的客户端,就会发现,所有的Hello World是同时输出来的。这种服务器就可以做到快速同时处理多个客户端连接。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,596评论 18 139
  • 参考:http://www.2cto.com/net/201611/569006.html TCP HTTP UD...
    F麦子阅读 2,938评论 0 14
  • 1、TCP状态linux查看tcp的状态命令:1)、netstat -nat 查看TCP各个状态的数量2)、lso...
    北辰青阅读 9,397评论 0 11
  • 最近在看《UNIX网络编程 卷1》和《FREEBSD操作系统设计与实现》这两本书,我重点关注了TCP协议相关的内容...
    腩啵兔子阅读 1,151评论 0 7
  • 总结一些常用的衔接词,虽然不能涵盖所,但如果把这十类词的关系搞清楚,把列举的具体衔接词背诵并会用的话,就能把一篇文...
    张简亦阅读 8,453评论 0 1