连续复制
一键复制
一键打包

01-理解网络编程和套接字

理解网络编程和套接字

网络编程(套接字编程)就是编写程序使两台联网的计算机互相交换数据,而套接字就是用来连接网络的工具。

重要函数

生成套接字

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

返回:
    成功:套接字文件描述符
    失败:-1

绑定地址

#include <sys/socket.h>
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

返回:
    成功:0
    失败:-1

监听连接

#include <sys/socket.h>
int listen(int socket, int backlog);

返回:
    成功:0
    失败:-1

接受请求

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

返回:
    成功:返回请求连接端的套接字文件描述符
    失败:-1

请求连接

#include <sys/socket.h>
int connect(int socket, const struct sockaddr *address, socklen_t address_len);

返回:
    成功:0
    失败:-1

Tips:在Linux上,可使用man命令去查询每一个函数或者头文件

Linux文件操作

在Linux里,socket也被认为是文件的一种,故在网络传输过程中亦可使用文件I/O的相关函数。

文件描述符(文件句柄)

文件描述符是系统分配给文件或套接字的整数。

系统文件描述符

文件描述符对象
0标准输入:Standard Input
1标准输出:Standard Output
2标准错误:Standard Error

I/O函数

打开文件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *path, int flag);

返回:
    成功:返回文件描述符
    失败:-1

文件打开模式(flag):

打开模式含义
O_CREAT必要时创建文件
O_TRUNC删除全部现有数据
O_APPEND追加到已有数据后面
O_RDONLY只读打开
O_WRONLY只写打开
O_RDWR读写打开

关闭文件

使用文件后必须关闭

#include <unistd.h>
int close(int fd);

返回:
    成功:0
    失败:-1

写文件

#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t nbytes);

返回:
    成功:返回写入的字节数
    失败:-1

读文件

#include <unistd.h>
int read(int fd, void *buf, size_t nbytes);

返回:
    成功:返回接收的字节数,遇到文件尾(EOF)则返回0
    失败:-1

hello_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handling(char *message);

int main(int argc, char const *argv[])
{
    int serv_sock;
    int clnt_sock;

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;

    char message[] = "Hello World!";

    if(argc != 2) {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1) {
        error_handling("socket() error!");
    }
    //将serv_addr.sin_zero初始化为0
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        error_handling("bind() error!");
    }

    if(listen(serv_sock, 5) == -1) {
        error_handling("listen() error!");
    }

    clnt_addr_size = sizeof(clnt_addr);
    clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
    if(clnt_sock == -1) {
        error_handling("accept() error!");
    }

    write(clnt_sock, message, sizeof(message));
    close(clnt_sock);
    close(serv_sock);

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

low_open.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>

void error_handling(char *message);

int main(void)
{
    int fd;
    char buf[] = "Let's go!\n";
    //以只写模式打开data.txt并清空其内容,且必要时创建该文件
    fd = open("data.txt", O_CREAT|O_WRONLY|O_TRUNC);
    if(fd == -1) {
        error_handling("open() error!");
    }
    printf("The file descriptor is: %d \n", fd);

    if(write(fd, buf, sizeof(buf)) == -1) {
        error_handling("write() error!");
    }

    close(fd);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

hello_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handling(char *message);

int main(int argc, char const *argv[])
{
    int sock;
    struct sockaddr_in serv_addr;
    char message[30];
    int str_len;

    if(argc != 3) {
        printf("Usage: %s <IP> <port> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1) {
        error_handling("socket() error!");
    }
    //将serv_addr.sin_zero初始化为0
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        error_handling("connect() error!");
    }

    str_len = read(sock, message, sizeof(message) - 1);    
    if(str_len == -1) {
        error_handling("read() error!");
    }
    printf("Message from server: %s \n", message);
    close(sock);

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

low_read.c

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BUF_SIZE 100

void error_handling(char *message); 

int main(void)
{
    int fd;
    char buf[BUF_SIZE];

    fd = open("data.txt", O_RDONLY);
    if(fd == -1) {
        error_handling("open() error!");
    }

    printf("The file descriptor is %d \n", fd);

    if(read(fd, buf, sizeof(buf)) == -1) {
        error_handling("read() error!");
    }

    printf("File data: %s", buf);
    close(fd);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

fd_seri.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/socket.h>

int main(void)
{
    int fd1, fd2, fd3;
    fd1 = socket(PF_INET, SOCK_STREAM, 0);
    //以只写模式打开test.dat并清空其内容,且必要时创建该文件
    fd2 = open("test.dat", O_CREAT|O_WRONLY|O_TRUNC);
    fd3 = socket(PF_INET, SOCK_DGRAM, 0);

    printf("fd1 file descriptor is: %d \n",fd1);
    printf("fd2 file descriptor is: %d \n",fd2);
    printf("fd3 file descriptor is: %d \n",fd3);

    close(fd1);
    close(fd2);
    close(fd3);
}

02-套接字类型与协议设置

套接字类型与协议设置

协议就是为了完成数据交换而定好的约定

协议族(Protocol Family)

名称协议族
PF_INETIPv4互联网协议族
PF_INET6IPv6互联网协议族
PF_LOCAL本地通信的UNIX协议族
PF_PACKET底层套接字的协议族
PF_IPXIPX Novell协议族

函数int socket(int domain, int type, int protocol);,参数domain设置套接字使用的协议族信息,type设置套接字数据传输类型信息,protocol设置计算机通信使用的协议族。另外,套接字中实际采用的最终协议信息通过protocol参数传递,且其由参数domain决定

套接字类型(Type)

面向连接的套接字(SOCK_STREAM)

  • 传输数据过程中数据不会消失

  • 按序传输数据

  • 传输的数据不存在数据边界(Boundary)

面向消息的套接字(SOCK_DGRAM)

  • 传输的数据可能会消失或销毁

  • 强调传输速度而非传输顺序

  • 传输的数据存在边界

  • 限制每次传输数据的大小

tcp_client.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handling(char* message);

int main(int argc, char const *argv[])
{
    int sock;
    struct sockaddr_in serv_addr;
    char message[30];
    int str_len = 0;
    int idx = 0, read_len = 0;

    if(argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1) {
        error_handling("socket() error");
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
        error_handling("connect() error");
    }
    
    while(read_len = read(sock, &message[idx++], 1)) {
        if(read_len == -1) {
            error_handling("read() error");
        }

        str_len += read_len;
    }

    printf("Message from server: %s \n", message);
    printf("Function read() called count: %d \n", str_len);
    close(sock);
    return 0;
}

void error_handling(char* message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

tcp_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handling(char *message);

int main(int argc, char const *argv[])
{
    int serv_sock;
    int clnt_sock;

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;

    char message[] = "Hello World!";

    if(argc != 2) {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1) {
        error_handling("socket() error!");
    }
    //将serv_addr.sin_zero初始化为0
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        error_handling("bind() error!");
    }

    if(listen(serv_sock, 5) == -1) {
        error_handling("listen() error!");
    }

    clnt_addr_size = sizeof(clnt_addr);
    clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
    if(clnt_sock == -1) {
        error_handling("accept() error!");
    }

    write(clnt_sock, message, sizeof(message));
    close(clnt_sock);
    close(serv_sock);

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

03-地址族与数据序列

地址族与数据序列

IP地址分两类:IPv4(4字节)IPv6(16字节)

IPv4地址族:

   +--------+--------+--------+--------+
A: |0aaaaaaa|bbbbbbbb|bbbbbbbb|bbbbbbbb|  首字节:00000000~01111111=0~127
   +--------+--------+--------+--------+
   +--------+--------+--------+--------+
B: |10aaaaaa|aaaaaaaa|bbbbbbbb|bbbbbbbb|  首字节:10000000~10111111=128~191
   +--------+--------+--------+--------+
   +--------+--------+--------+--------+
C: |110aaaaa|aaaaaaaa|aaaaaaaa|bbbbbbbb|  首字节:1100000~,11011111=192~223
   +--------+--------+--------+--------+
   
以上为常用的3种类别的IP地址,其中,包含有a的部分和称为网络号,b的部分则为主机号。网络号字段中的0,10,110为类别位(即A,B,C类地址)
在A类地址中,0段与127段是不使用的(0段为保留地址,表示本网络;而127段为环回地址),故A类地址范围为:1.0.0.0 ~ 126.255.255.255
B类地址的范围为:128.0.0.0 ~ 191.255.255.255
C类地址的范围为:192.0.0.0 ~ 223.255.255.255

地址信息表示

表示IPv4的结构体
struct sockaddr_in
{
    sa_family        sin_family;        //地址族(AF_INET|AF_INET6|...)
    uint16_t        sin_port;        //16位端口号
    struct in_addr    sin_addr;        //32位IP地址
    char        sin_zero[8];        //占位用(必须填充为0)
}
struct in_addr
{
    In_addr_t    s_addr;                //32位IPv4地址
}

网络字节序与地址转换

字节序

大端序:高位字节存放到地位地址

小端序:高位字节存放到高位地址

对于0x12345678, 0x12为高位字节,0x78为低位字节。
其大端序:
      +----+
      |0x78|
0x03: +----+
      |0x56|
0x02: +----+
      |0x34|
0x01: +----+
      |0x12|
0x00: +----+
其小端序:
      +----+
      |0x12|
0x03: +----+
      |0x34|
0x02: +----+
      |0x56|
0x01: +----+
      |0x78|
0x00: +----+

网络字节序使用大端序

字节序转换
unsigned short htons(unsigned short);    //主机字节序转网络字节序(转换端口)
unsigned short ntohs(unsigned short);    //网络字节序转主机字节序(转换端口)
unsigned long htonl(unsigned long);    //主机字节序转网络字节序(转换IP)
unsigned long ntohl(unsigned long);    //网络字节序转主机字节序(转换IP)

网络地址初始化及分配

字符串转网络字节序
#include <arpa/inet.h>
int_addr_t inet_addr(const char *string);
返回:
    成功:返回32位大端序整型数值
    失败:INADDR_NONE
#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr *addr);
返回:
    成功:1,并将结果保存在addr里
    失败:0
网络字节序转字符串
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr __in);
返回:
    成功:返回转换的字符串地址
    失败:-1
INADDR_ANY

可利用此常数来自动分配服务器端的IP地址(适用于单网卡情况)

inet_ntoa.c

#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    struct sockaddr_in addr1, addr2;
    char *str_ptr;
    char str_arr[20];

    addr1.sin_addr.s_addr = htonl(0x1020304);
    addr2.sin_addr.s_addr = htonl(0x1010101);

    str_ptr = inet_ntoa(addr1.sin_addr);
    strcpy(str_arr, str_ptr);    //复制字符串
    printf("Dotted-Decimal ntoation1: %s \n", str_ptr);

    str_ptr = inet_ntoa(addr2.sin_addr);
    printf("Dotted-Decimal ntoation2: %s \n", str_ptr);
    printf("Dotted-Decimal ntoation3: %s \n", str_arr);

    return 0;
}

inet_aton.c

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>

void error_handling(char *message);

int main(int argc, char const *argv[])
{
    char *addr = "127.232.124.79";
    struct sockaddr_in addr_inet;

    if(!inet_aton(addr, &addr_inet.sin_addr)) {
        error_handling("Conversion error!");
    }else {
        printf("Network ordered integer addr: %#x \n", addr_inet.sin_addr.s_addr);
    }

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

inet_addr.c

#include <stdio.h>
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    char *addr1 = "1.2.3.4";
    char *addr2 = "1.2.3.256";    //错误的IP地址

    unsigned long conv_addr = inet_addr(addr1);
    if(conv_addr == INADDR_NONE) {
        printf("Error occured! \n");
    }else {
        printf("Network ordered integer addr: %#lx \n", conv_addr);
    }

    conv_addr = inet_addr(addr2);
    if(conv_addr == INADDR_NONE) {
        printf("Error occured! \n");
    }else {
        printf("Network ordered integer addr: %#lx \n\n", conv_addr);
    }

    return 0;
}

endian_conv.c

#include <stdio.h>
#include <arpa/inet.h> 

int main(int argc, char const *argv[])
{
    unsigned short host_port = 0x1234;
    unsigned short net_port;
    unsigned long host_addr = 0x12345678;
    unsigned long net_addr;

    net_port = htonl(host_port);
    net_addr = htonl(host_addr);
    printf("Host ordered port: %#x \n", host_port);
    printf("Network ordered port: %#x \n", net_port);
    printf("Host ordered address: %#lx \n", host_addr);
    printf("Network ordered address: %#lx \n",net_addr);
    
    return 0;
}

04-基于TCP的服务端⁄客户端(1)

基于TCP的服务器端/客户端(1)

TCP/IP协议栈共分4层

+--------------------+
|     Application    |
+--------------------+
|       TCP/UDP      |
+--------------------+
|         IP         |
+--------------------+
|      Ethernet      |
+--------------------+

TCP服务器端默认函数调用顺序

+------------------+
|      socket()    |创建套接字
+------------------+
|       bind()     |分配套接字地址
+------------------+
|      listen()    |监听连接
+------------------+
|      aceept()    |允许连接
+------------------+
|  read()/write()  |数据交换
+------------------+
|      close()     |断开连接
+------------------+

TCP客户端默认函数调用顺序

+------------------+
|      socket()    |创建套接字
+------------------+
|      connect()   |请求连接
+------------------+
|  read()/write()  |数据交换
+------------------+
|      close()     |断开连接
+------------------+

echo_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char const *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_addr;

    if(argc != 3) {
        printf("Usage: %s <IP> <port> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1) {
        error_handling("socket() error!");
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        error_handling("connect() error!");
    }else {
        puts("Connected...");
    }

    while(1) {
        fputs("Input message(Q to quit): ",stdout);
        fgets(message, BUF_SIZE, stdin);

        if(!strcmp(message, "q\n") | !strcmp(message, "Q\n")) {
            break;
        }

        write(sock, message, strlen(message));
        str_len = read(sock, message, BUF_SIZE - 1);
        message[str_len] = 0;
        printf("Message from server: %s \n", message);
    }

    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

echo_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char const *argv[])
{
    int serv_sock, clnt_sock;
    char message[BUF_SIZE];
    int str_len, i;

    struct sockaddr_in serv_addr, clnt_addr;
    socklen_t clnt_addr_size;

    if(argc != 2) {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1) {
        error_handling("socket() error!");
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        printf("bind() error!");
    }

    if(listen(serv_sock, 5) == -1) {
        printf("listen() error!");
    }

    clnt_addr_size = sizeof(clnt_addr);

    for(i = 0; i < 5; i++) {
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_addr, &clnt_addr_size);
        if(clnt_sock == -1) {
            error_handling("accept() error!");
        }else {
            printf("Connected client %d \n", i + 1);
        }

        while((str_len = read(clnt_sock, message, BUF_SIZE)) != 0) {
            write(clnt_sock, message, str_len);
        }
    
        close(clnt_sock);
    }
    
    close(serv_sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

05-基于TCP的服务端⁄客户端(2)

基于TCP的服务器端/客户端(2)

TCP原理

TCP报文头部格式(至少20字节)
 0                   1                   2                   3   
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      Source Port(16bits)      |    Destination Port(16bits)   |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Sequence Number(32bits)                     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                Acknowledgement Number(32bits)                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Data |           |U|A|P|R|S|F|                               |
| Offset| Reserved  |R|C|S|S|Y|I|         Window(16bits)        |
|(4bits)| (6bits)   |G|K|H|T|N|N|                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       Checksum(16bits)        |    Urgent Pointer(16bits)     |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                              Data                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

 Sequence Number: 此TCP报文段的第一个八位字节数据的序列号(存在SYN时除外),若存在SYN,则序列号为初始序列号(ISN),且第一个八位字节数据为ISN+1
 Acknowledgement Number: 如果设置了ACK控制位,则此字段包含TCP报文的发送方希望接收的下一个序列号的值(发送方发送的序列号+1)。一旦建立了连接,就会发送该连接。
 控制位:
     URG: 紧急
     ACK: 确认
     PSH: 传送
     RST: 重置
     SYN: 连接
     FIN: 结束
 
 Data Offset: TCP报文首部的长度
 Reserved: 留至后用
 Window: 控制数据量
 Checksum: 传输数据完整性校验
 Urgent Pointer: 将紧急数据插入到报文数据的最前面
TCP连接的建立(三报文握手)
1.客户端发送一个连接请求报文段,报文首部中的SYN控制位置1,并设置一个序列号(Sequence Number)seq = x
2.服务端返回一个确认报文段,报文首部中的SYN与ACK控制位都置1,并设置一个自己的序列号seq = y,一个确认号(Acknowledgement Number)ack = x + 1
3.客户端再发送一个确认报文段,报文首部ACK控制位置1,设置确认号ack = y + 1,序列号seq = z(若未携带数据,seq = x + 1)
TCP连接的结束(四报文挥手)
1.客户端发送一个连接释放报文段,报文首部中的FIN控制位置1,并设置一个序列号seq = u
2.服务端返回一个确认报文段,报文首部中的SYN与ACK控制位置1,并设置一个自己的序列号seq = v,确认号ack = u + 1,TCP连接进入半关闭状态(服务端仍可发送数据)
3.若服务端已再不用发送数据,则服务端发送一个连接释放报文段,报文首部FIN控制位置1,设置序号seq = w,ack = u + 1
4.客户端再发送一个确认报文段,报文首部ACK控制位置1,设置确认号ack = w + 1 
TCP套接字中的I/O缓冲

write()函数调用后并非立即传输数据,在write()调用瞬间,数据移至输出缓冲;read()函数调用后也并非马上接收数据,在read()调用瞬间,其从输入缓冲读取数据

I/O缓冲特性:

  • I/O缓冲在每个TCP套接字中单独存在

  • I/O缓冲在创建套接字时自动生成

  • 即使关闭套接字也会继续传递输出缓冲中遗留的数据

  • 关闭套接字将丢失输入缓冲中的数据

TCP协议通过滑动窗口协议,不会因缓冲溢出而丢失数据

op_server.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
#define OPSZ 4

void error_handling(char *message);
int calculate(int opnum, int opnds[], char operator);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    char opinfo[BUF_SIZE];
    int result, opnd_cnt, i;
    int recv_cnt, recv_len;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    if(argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1) {
        error_handling("socket() error!");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == - 1) {
        error_handling("bind() error!");
    }
    if(listen(serv_sock, 5) == -1) {
        error_handling("listen() error!");
    }

    clnt_adr_sz = sizeof(clnt_adr);

    for(i = 0; i < 5; i++) {
        opnd_cnt = 0;
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
        read(clnt_sock, &opnd_cnt, 1);

        recv_len = 0;
        while((opnd_cnt * OPSZ + 1) > recv_len) {
            recv_cnt = read(clnt_sock, &opinfo[recv_len], BUF_SIZE - 1);
            recv_len += recv_cnt;
        }
        result = calculate(opnd_cnt, (int*)opinfo, opinfo[recv_len - 1]);
        write(clnt_sock, (char*)&result, sizeof(result));
        close(clnt_sock);
    }

    close(serv_sock);
    return 0;
}

int calculate(int opnum, int opnds[], char op)
{
    int result = opnds[0], i;
    switch(op) {
        case '+':
            for(i = 1; i< opnum; i++) {
                result += opnds[i];
            }
            break;
        case '-':
            for(i = 1; i< opnum; i++) {
                result -= opnds[i];
            }
            break;
        case '*':
            for(i = 1; i< opnum; i++) {
                    result *= opnds[i];
            }
            break;
    }
    return result;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

echo_client2.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char const *argv[])
{
    int sock;
    char message[BUF_SIZE];
    //相比前面的echo_client.c,增加变量recv_len,recv_cnt
    int str_len, recv_len, recv_cnt;
    struct sockaddr_in serv_addr;

    if(argc != 3) {
        printf("Usage: %s <IP> <port> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1) {
        error_handling("socket() error!");
    }

    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        error_handling("connect() error!");
    }else {
        puts("Connected...");
    }

    while(1) {
        fputs("Input message(Q to quit): ",stdout);
        fgets(message, BUF_SIZE, stdin);

        if(!strcmp(message, "q\n") | !strcmp(message, "Q\n")) {
            break;
        }

        // write(sock, message, strlen(message));
        // str_len = read(sock, message, BUF_SIZE - 1);
        // 较前面的echo_client.c,注释部分修改如下:

        str_len = write(sock, message, strlen(message));
        
        recv_len = 0;
        while(recv_len < str_len) {
            recv_cnt = read(sock, message, BUF_SIZE - 1);
            if (recv_cnt == -1) {
                error_handling("read() error!");
            }
            recv_len += recv_cnt;
        }
        message[recv_len] = 0;
        // 修改结束
        printf("Message from server: %s \n", message);
    }

    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

op_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
#define RLT_SIZE 4     //运算结果字节数
#define OPSZ 4        //待操作数字节数

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char opmsg[BUF_SIZE]; //char类型数组,以保存多种数据类型
    int result, opnd_cnt, i;
    struct sockaddr_in serv_adr;
    if(argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1) {
        error_handling("socket() error!");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("connect() error!");
    }else {
        puts("Connected...........");
    }

    fputs("Operand count: ", stdout);
    scanf("%d", &opnd_cnt);
    opmsg[0] = (char)opnd_cnt;

    for(i = 0; i < opnd_cnt; i++) {
        printf("Operand %d : ", i + 1);
        scanf("%d", (int*)&opmsg[i * OPSZ + 1]);
    }
    fgetc(stdin);    //删除缓存中的字符'\n'
    fputs("Operator: ", stdout);
    scanf("%c", &opmsg[opnd_cnt * OPSZ + 1]);
    write(sock, opmsg, opnd_cnt * OPSZ + 2);
    read(sock, &result, RLT_SIZE);

    printf("Operation result: %d \n",  result);
    close(sock);

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

06-基于UDP的服务器端⁄客户端

基于UDP的服务端/客户端

#### 理解UDP

UDP在结构上比TCP简洁,在更注重性能而非可靠性的情况下,UDP是一种很好的选择。

流控制是区分UDP与TCP最重要的标志。UDP也并非总是快于TCP,TCP比UDP慢的原因通常为以下两点:

  • 收发数据前后进行的连接设置及清除过程

  • 收发数据过程中为保证可靠性而添加的流控制

如果收发的数据量小但需要频繁连接,UDP比TCP高效

1.UDP中服务端与客户端间没有连接:

UDP中只有创建套接字的过程和数据交换的过程

UDP不同于TCP,不存在请求连接和受理过程,因此在某种意义上无法明确区分服务器端与客户端

2.UDP服务端与客户端均只需一个套接字

只需一个UDP套接字即可向任意主机传送数据

基于UDP的数据I/O函数

传输数据
#include <sys/socket.h>
ssize_t sendto(int sock, void *buff, size_t nbytes,
           int flags, struct sockaddr *to,socklen_t addrlen);
返回:
    成功:返回传输的字节数
    失败:-1
  • sock:用于传输数据的UDP套接字文件描述符

  • buff:待传输数据的缓冲地址值

  • nbytes:待传输数据长度

  • flags:可选参数,若无则为0

  • to:存有目标地址信息的sockaddr结构体变量的地址值

  • addrlen:to变量的长度

接收数据
#include <sys/socket.h>
ssize_t recvfrom(int sock, void *buff, size_t nbytes,
                int flags, struct sockaddr *from, socklen_t *addrlen);
返回:
    成功:返回接收的字节数
    失败:-1
  • sock:用于接收数据的UDP套接字文件描述符

  • buff:保存接收数据的缓冲地址值

  • nbytes:可接收最大字节数,故无法超过buff所设缓冲大小

  • flags:可选参数,若无则为0

  • from:存有发送端地址信息的sockaddr结构体变量的地址值

  • addrlen:from变量的长度

UDP的数据传输特性和调用connect函数

UDP套接字存在数据边界

对于UDP,传输中的I/O函数的调用次数非常重要。输入函数与输出函数的调用次数应完全一致,才能保证全部接收已发数据。

已连接UDP套接字与未连接UDP套接字

UDP中无需注册待传输数据的目标IP和端口号,通过sendto()传输数据的过程大致可分为以下3个阶段:

  • 第一阶段:向UDP套接字注册目标IP和端口号

  • 第二阶段:传输数据

  • 第三阶段:删除UDP套接字中注册的目标地址信息

每次调用sendto()会重复以上过程,每次都改变目标地址,因此可重复利用同一UDP套接字向不同目标传输数据。

未连接套接字:未注册目标地址信息的套接字(UDP默认)

已连接套接字:注册了目标地址信息的套接字(使用connect()connect()作用只是为了注册目标地址信息),在需要长时间通信时,使用已连接UDP套接字更高效

创建已连接套接字
sock = socket(PF_INET, SOCK_DGRAM, 0);
memset(&adr, 0, sizeof(adr));
adr.sin_family = AF_INET;
adr.sin_addr.s_addr = ....;
adr.sin_port = ....;
connect(sock, (struct sockaddr*)&adr, sizeof(adr));

bound_host1.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);


int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    struct sockaddr_in my_adr, your_adr;
    socklen_t adr_sz;
    int str_len, i;

    if(argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(sock == -1) {
        error_handling("socket() error!");
    }

    memset(&my_adr, 0, sizeof(my_adr));
    my_adr.sin_family = AF_INET;
    my_adr.sin_addr.s_addr =  htonl(INADDR_ANY);
    my_adr.sin_port = htons(atoi(argv[1]));

    if(bind(sock, (struct sockaddr*)&my_adr, sizeof(my_adr)) == -1) {
        error_handling("bind() error!");
    }

    for(i = 0; i < 3; i++) {
        /*
        *每5秒调用一次recvfrom(),此前,bound_host2已经用sendto()传输完数据,
        *若为TCP,无数据边界,可一次全部接收,UDP则需调用3次recvfrom()分别读取 
        */
        sleep(5);
        adr_sz = sizeof(your_adr);
        str_len = recvfrom(sock, message, BUF_SIZE, 0, 
        (struct sockaddr*)&your_adr, &adr_sz);
        
        printf("Message %d: %s \n", i+1, message);
    }

    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

uecho_server.c

/*
* UDP不同于TCP,不存在请求连接和受理过程,因此在某种意义上无法明确区分服务器端与客户端
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock;
    char message[BUF_SIZE];
    int str_len;
    socklen_t clnt_adr_sz;
    struct sockaddr_in serv_adr, clnt_adr;

    if(argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }
    //套接字类型设为SOCK_DGRAM
    serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(serv_sock == -1) {
        error_handling("udp socket() error!");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("bind() error!");
    }

    while(1) {
        clnt_adr_sz = sizeof(clnt_adr);
        str_len = recvfrom(serv_sock, message, BUF_SIZE, 0,
        (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

        sendto(serv_sock, message, str_len, 0, 
        (struct sockaddr*)&clnt_adr, clnt_adr_sz);
    }

    close(serv_sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

bound_host2.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char msg1[] = "Hi!";
    char msg2[] = "I'm another UDP host!";
    char msg3[] = "Nice to meet you";

    struct sockaddr_in your_adr;

    if(argc != 3) {
        printf("Usage: %s <IP> <port> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(sock == -1) {
        error_handling("socket() error!");
    }

    memset(&your_adr, 0, sizeof(your_adr));
    your_adr.sin_family = AF_INET;
    your_adr.sin_addr.s_addr = inet_addr(argv[1]);
    your_adr.sin_port = htons(atoi(argv[2]));

    sendto(sock, msg1, sizeof(msg1), 0, 
    (struct sockaddr*)&your_adr, sizeof(your_adr));
    sendto(sock, msg2, sizeof(msg2), 0, 
    (struct sockaddr*)&your_adr, sizeof(your_adr));
    sendto(sock, msg3, sizeof(msg3), 0, 
    (struct sockaddr*)&your_adr, sizeof(your_adr));

    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

uecho_con_client.c

/*
* 此程序为uecho_client的已连接UDP套接字版
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;
    
    if(argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(sock == -1) {
        error_handling("socket() error!");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));

    while(1) {
        fputs("Insert message(q to quit): ", stdout);
        fgets(message, sizeof(message), stdin);
        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n")) {
            break;
        }

        //已连接UDP套接字,可直接使用write()和read()代替recvfrom()与sendto()
        write(sock, message, strlen(message));
        str_len = read(sock, message, sizeof(message) - 1);
        message[str_len] = 0;
        printf("Message from server: %s", message);
    }

    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

uecho_client.c

/*
* UDP不同于TCP,不存在请求连接和受理过程,因此在某种意义上无法明确区分服务器端与客户端
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int sock;
    char message[BUF_SIZE];
    int str_len;
    socklen_t adr_sz;
    struct sockaddr_in serv_adr, from_adr;

    if(argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_DGRAM, 0);
    if(sock == -1) {
        error_handling("socket() error!");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    while(1) {
        fputs("Insert message(q to quit): ",stdout);
        fgets(message, sizeof(message), stdin);
        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n")) {
            break;
        }

        sendto(sock, message, strlen(message), 0, 
        (struct sockaddr*)&serv_adr, sizeof(serv_adr));

        adr_sz = sizeof(from_adr);
        str_len = recvfrom(sock, message, BUF_SIZE, 0, 
        (struct sockaddr*)&from_adr, &adr_sz);
        message[str_len] = 0;
        printf("Message from server: %s", message);
    }

    close(sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

07-优雅地断开套接字连接

优雅地断开套接字连接

基于TCP的半关闭

单方面断开连接带来的问题

Linux的close()函数意味着完全断开连接,完全断开后,套接字既无法传输数据,也无法接收数据。

套接字和流

一旦两台主机建立了套接字连接,每个主机就会拥有单独的输入流与输出流。一个主机的输入流与另一台主机的输出流相连,输出流与另一台主机的输入流相连。

半关闭函数shutdown()
#include <sys/socket.h>
int shutdown(int sock, int howto);

返回:
    成功:0
    失败:-1
  • sock:需要断开的套接字文件描述符

  • howto:断开方式[SHUT_RD(断开输入流)| SHUT_WR(断开输出流)| SHUT_RDWR(同时断开I/O流)]

receive.dat

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char const *argv[])
{
    int serv_sd, clnt_sd;
    FILE *fp;
    char buf[BUF_SIZE];
    int read_cnt;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

    if(argc != 2){
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    fp = fopen("file_server.c","rb");
    serv_sd = socket(PF_INET, SOCK_STREAM,0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    bind(serv_sd, (struct sockaddr *)&serv_adr,sizeof(serv_adr));
    listen(serv_sd, 5);

    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sd = accept(serv_sd, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);

    while(1) {
        //从fp中每次读取1个字符到buf中,共计BUF_SIZE个字符,部分
        read_cnt = fread((void *)buf, 1, BUF_SIZE, fp);
        if(read_cnt < BUF_SIZE) {
            write(clnt_sd, buf, read_cnt);
            break;
        }
        write(clnt_sd, buf, BUF_SIZE);
    }
    //半关闭:客户端的输出流(传输EOF)
    shutdown(clnt_sd, SHUT_WR);
    read(clnt_sd, buf, BUF_SIZE);
    printf("Message from client: %s\n", buf);

    fclose(fp);
    close(clnt_sd);
    close(serv_sd);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

file_server.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char const *argv[])
{
    int serv_sd, clnt_sd;
    FILE *fp;
    char buf[BUF_SIZE];
    int read_cnt;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;

    if(argc != 2){
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    fp = fopen("file_server.c","rb");
    serv_sd = socket(PF_INET, SOCK_STREAM,0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    bind(serv_sd, (struct sockaddr *)&serv_adr,sizeof(serv_adr));
    listen(serv_sd, 5);

    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sd = accept(serv_sd, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);

    while(1) {
        //从fp中每次读取1个字符到buf中,共计BUF_SIZE个字符,部分
        read_cnt = fread((void *)buf, 1, BUF_SIZE, fp);
        if(read_cnt < BUF_SIZE) {
            write(clnt_sd, buf, read_cnt);
            break;
        }
        write(clnt_sd, buf, BUF_SIZE);
    }
    //半关闭客户端的输出流(也就传输了EOF)
    shutdown(clnt_sd, SHUT_WR);
    read(clnt_sd, buf, BUF_SIZE);
    printf("Message from client: %s\n", buf);

    fclose(fp);
    close(clnt_sd);
    close(serv_sd);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

file_client.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char const *argv[])
{
    int sd;
    FILE *fp;

    char buf[BUF_SIZE];
    int read_cnt;
    struct sockaddr_in serv_adr;

    if(argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    fp = fopen("receive.dat","wb");
    sd = socket(PF_INET, SOCK_STREAM, 0);
    
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    connect(sd, (struct sockaddr *)&serv_adr, sizeof(serv_adr));

    while((read_cnt = read(sd, buf, BUF_SIZE)) != 0 ){
        fwrite((void *)buf, 1, read_cnt, fp);
    }

    puts("Received file data");
    write(sd, "Thank you", 10);
    fclose(fp);
    close(sd);
    return 0;
}


void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

08-域名及网络地址

域名及网络地址

IP地址与域名之间的转换

IP地址发生更改的几率比域名发生更改的几率高,因此在程序中使用域名不失为一个好的方法

利用域名获取IP地址
#include <netdb.h>
struct hostent * gethostbyname(const char *hostname);

返回:
    成功:返回hostent结构体地址
    失败:返回 NULL指针

hostent结构体:

struct hostent
{
    char * h_name;    //官方域名
    char ** h_aliases;    //别名列表
    int h_addrtype;    //IP协议类型
    int h_length;    //IP地址长度
    char ** h_addr_list;    //域名对应IP地址列表
}
利用IP地址获取域名
#include <netdb.h>
struct hostent * gethostbyaddr(const char *addr, socklen_t len, int family);

返回:
    成功:返回hostent结构体地址
    失败:返回 NULL指针
  • addr:in_addr 结构体指针

  • len:addr的字节数,IPv4为4,IPv6为16

  • family:地址族信息,IPv4为AF_INET,IPv6为AF_INET6

gethostbyname.c

#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netdb.h>

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int i;
    struct hostent *host;

    if(argc != 2) {
        printf("Usage: %s <addr>\n", argv[0]);
        exit(1);
    }

    host = gethostbyname(argv[1]);
    
    if(!host) {
        error_handling("gethost...error");
    }
    printf("Offcial name: %s \n", host->h_name);
    for(i = 0; host->h_aliases[i]; i++) {
        printf("Aliases %d: %s \n", i + 1, host->h_aliases[i]);
    }
    printf("Address type: %s \n", (host -> h_addrtype == AF_INET) ? "AF_INNET" : "AF_INNET6");
    for(i = 0; host->h_addr_list[i]; i++) {
        printf("IP addr %d: %s \n", i + 1, inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
    }

    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

gethostbyaddr.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>

void error_handling(char *message);

int main(int argc, char *argv[])
{
    int i;
    struct hostent *host;
    struct sockaddr_in addr;

    if(argc != 2) {
        printf("Usage: %s <IP>\n", argv[0]);
        exit(1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    host = gethostbyaddr((char*)&addr.sin_addr, 4, AF_INET);
    if(!host) {
        error_handling("gethostbyaddr() error!");
    }
    printf("Official name: %s \n", host->h_name);
    for(i = 0; host->h_aliases[i]; i++) {
        printf("Aliases %d: %s \n", i+1, host->h_aliases[i]);
    }
    printf("Address type: %s \n", (host -> h_addrtype == AF_INET) ? "AF_INNET" : "AF_INNET6");
    for(i = 0; host->h_addr_list[i]; i++) {
        printf("IP addr %d: %s \n", i+1, inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
    }
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

09-套接字的多种可选项

套接字的多种可选项

套接字选项汇总.png

getsockopt & setsockopt

getsockopt():

#include <sys/socket.h>
int getsockopt(int socket, int level, int option_name,
               void *restrict option_value, socklen_t *restrict option_len);

返回:
    成功:0
    失败:-1
  • socket:用于查看选项套接字文件描述符

  • level:要查看的可选项的协议层

  • option_name:要查看的可选项名

  • option_value:保存查看结果的缓冲地址值

  • option_len:向第四个参数option_value传递的缓冲大小。调用函数后,该变量中保存通过第四个参数返回的可选项信息的字节数

setsockopt()

#include <sys/socket.h>
int setsockopt(int socket, int level, int option_name,
               const void *option_value, socklen_t option_len);

返回:
    成功:0
    失败:-1
  • 参数描述同上

SO_SNDBUF & SO_RCVBUF

创建套接字时将同时生成I/O缓冲,而该缓存大小可由SO_SNDBUFSO_RCVBUF可选项进行操作。

SO_REUSEADDR

time-wait状态:在结束TCP连接时,会经过四次握手过程。但套接字在该过程后并非立即销毁,先断开连接的一端还要更早的经过一段时间的time-wait状态。这么设计的原因是为了确保四次握手中的最后一条ACK消息能够到达对端,避免对方反复重传自己的FIN消息。

由于time-wait状态的存在,若我们在服务端紧急停止后想要立即重启服务,可能会出现地址无法绑定(被占用)的情况,使用SO_REUSEADDR可选项则可将time-wait状态下的套接字端口重新分配给新的套接字。

TCP_NODELAY

Nagle算法:TCP套接字默认使用该算法交换数据,因此会最大限度的对数据进行缓冲,直到收到ACK,这么做可以提高网络传输速率。但如大文件传输这样的场景下,关闭该算法会比较好。

set_buf.c

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

void error_handling(char *message);

int main(int argc, char **argv) {
    int sock;
    int snd_buf = 1024*3, rcv_buf=1024*3;
    int state;
    socklen_t len;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    
    state = setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, sizeof(rcv_buf));
    if(state) {
        error_handling("setsockopt() error!");
    }

    state = setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, sizeof(snd_buf));
    if(state) {
        error_handling("setsockopt() error!");
    }

    len = sizeof(snd_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
    if(state) {
        error_handling("getsockopt() error!");
    }

    len = sizeof(rcv_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
    if(state) {
        error_handling("getsockopt() error!");
    }
    
    printf("Input buffer size: %d \n", rcv_buf);
    printf("Output buffer size: %d \n", snd_buf);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

get_buf.c

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

void error_handling(char *message);

int main(int argc, char **argv) {
    int sock;
    int snd_buf, rcv_buf, state;
    socklen_t len;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    len = sizeof(snd_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void*)&snd_buf, &len);
    if(state) {
        error_handling("getsockopt() error!");
    }

    len = sizeof(rcv_buf);
    state = getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void*)&rcv_buf, &len);
    if(state) {
        error_handling("getsockopt() error!");
    }
    
    printf("Input buffer size: %d \n", rcv_buf);
    printf("Output buffer size: %d \n", snd_buf);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

reuseadr_eserver.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define TRUE 1
#define FALSE 0
void error_handling(char *message);

int main(int argc, char **argv) {
    int serv_sock, clnt_sock;
    char message[30];
    int option, str_len;
    socklen_t optlen, clnt_adr_sz;
    struct sockaddr_in serv_adr, clnt_adr;
    if(argc != 2) {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if(serv_sock == -1) {
        error_handling("socket() error!");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))) {
        error_handling("bind() error!");
    }
    if(listen(serv_sock, 5) == -1) {
        error_handling("listen() error!");
    }

    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

    while((str_len=read(clnt_sock, message, sizeof(message))) != 0) {
        write(clnt_sock, message, str_len);
        write(1, message, str_len);
    }
    close(clnt_sock);
    close(serv_sock);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

sock_type.c

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

void error_handling(char *message);

int main(int argc, char **argv) {
    int tcp_sock, udp_sock;
    int sock_type;
    socklen_t optlen;
    int state;

    optlen = sizeof(sock_type);
    tcp_sock = socket(PF_INET, SOCK_STREAM, 0);
    udp_sock = socket(PF_INET, SOCK_DGRAM, 0);
    printf("SOCK_STREAM: %d \n", SOCK_STREAM);
    printf("SOCK_DGRAM: %d \n", SOCK_DGRAM);

    state = getsockopt(tcp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    if(state) {
        error_handling("getsockopt() error!");
    }
    printf("Socket type one: %d \n", sock_type);

    state = getsockopt(udp_sock, SOL_SOCKET, SO_TYPE, (void*)&sock_type, &optlen);
    if(state) {
        error_handling("getsockopt() error!");
    }
    printf("Socket type two: %d \n", sock_type);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

10-多进程服务器端

多进程服务器端

并发服务器端实现模型和方法:

  • 多进程服务器:通过创建多个进程提供服务(Windows不支持)

  • 多路复用服务器:通过捆绑并统一管理I/O对象提供服务

  • 多线程服务器:通过生成与客户端等量的线程提供服务

进程

进程的定义为:“占用内存的正在运行的程序“,进程同时也是操作系统进行资源分配的一个基本单位

处理器核心数与可同时运行的进程数相等,若进程数 > 核心数,处理器将进行分时处理

通过fork函数创建进程
#include <unistd.h>
pid_t fork(void);

返回:
    成功:0
    失败:-1

fork函数创建的子进程将会获得其父进程(fork函数调用者)的数据段、堆和栈的副本,并与其父进程共享代码段

区分父进程与子进程:通过fork函数的返回值

  • 父进程:fork函数返回子进程pid

  • 子进程:fork函数返回0

进程的状态
  • 可运行状态(TASK_RUNNING,简称R)

  • 可中断的睡眠状态(TASK_INTERRUPTIBLE,简称S)

  • 不可中断的睡眠状态(TASK_UNINTERRUPTIBLE,简称D)

  • 暂停或追踪状态(TASK_STOPPED/TASK_TRACED,简称T)

  • 僵尸状态(TASK_DEAD-EXIT_ZOMBIE,简称Z)

  • 退出状态(TASK_DEAD-EXIT_DEAD,简称X)

僵尸进程

进程在完成工作后应被销毁,但有时这些进程会进入僵尸状态,占用系统的资源,成为僵尸进程。

僵尸进程产生原因

给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取。这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)。如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(init进程将wait它们,从而去除僵尸状态)。

避免僵尸进程

避免产生僵尸进程方法:

  • fork()两次并杀死其父进程,使其变成孤儿进程,从而移交init处理

  • 在fork()后调用wait()/waitpid()函数主动取得子进程的退出状态

wait()
#include <sys/wait.h>
pid_t wait(int *statloc);

返回:
    成功:返回终止的子进程pid
    失败:-1

调用此函数时如果已有子进程终止,那么子进程终止时传递的返回值将保存在该函数参数所指内存空间,但其中还包含其他信息,需要使用以下宏进行分离:

  • WIFEXITED 子进程正常终止时返回true

  • WEXITSTSTUS 返回子进程的返回值

宏使用:在调用wait()函数后,编写如下代码

if(WIFEXITED(status)) { //是否是正常终止
    puts("Normal termination!");
    printf("Child pass num: %d", WEXITSTATUS(status));  //子进程返回值
}

调用wait()函数时,如果没有已终止的子进程,那么程序将阻塞直至有子进程终止

waitpid()
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *statloc, int options);

返回:
    成功:返回终止的子进程pid(或0)
    失败:-1
  • pid:等待终止的目标子进程pid,若传递-1,则与wait()函数相同,可等待任意子进程终止

  • statloc:保存子进程的返回值

  • options:传递头文件sys/wait.h中声明的常量WNOHANG,即使无终止的子进程也不会进入阻塞状态,而是返回0并退出函数

信号处理

父进程无法知道子进程的具体终止时间,因此wait()/waitpid()并不是一个很好的解决方式。而如果当在子进程退出时,向操作系统发送信号,再由操作系统提示父进程处理子进程相关事宜则不失为一个合理实用的方式

信号与signal函数

信号注册函数:

#include <signal.h>
void (*signal(int signo, void (*func)(int)))(int);

返回:返回函数指针

可在signal函数中注册的部分特殊情况和对应的常数:

  • SIGALRM:已到通过调用alarm函数注册的时间

  • SIGINT:输入CTRL+C

  • SIGCHLD:子进程终止信号

信号处理函数应有一个int参数,且返回值为void

alarm函数:

#include <unistd.h>
unsigned int alarm(unsigned int seconds);

返回:返回0或者以秒为单位的距SIGALRM信号发生所剩时间
sigaction函数
#include <signal.h>
int aigaction(int signo, const struct sigaction *act, struct sigaction *oldact);

返回:
    成功:0
    失败:-1
  • signo:信号信息

  • act:信号处理函数信息

  • oldact:获取之前注册的信号处理函数指针,不需要则传0

sigaction结构体如下:

struct sigaction
{
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
}

sigaction.c

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void timeout(int sig)
{
    if(sig == SIGALRM) {
        puts("Time out!");
    }

    alarm(2);
}

int main(void)
{
    int i;
    struct sigaction act;
    act.sa_handler = timeout;
    //初始化为零
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    
    sigaction(SIGALRM, &act, 0);

    alarm(2);

    for(i = 0; i< 3; i++) {
        puts("wait...");
        sleep(100);
    }

    return 0;
}

echo_mpserv.c

/*
    echo服务器多进程版
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);
void read_childproc(int sig);

/*
    多进程版echo服务器主要有三个阶段:
    1. 服务器端(父进程)通过调用accept函数受理连接请求
    2. 获取的套接字文件描述符创建并传递给子进程
    3. 子进程利用传递来的文件描述符提供服务
*/

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;

    pid_t pid;
    struct sigaction act;
    socklen_t adr_sz;
    int str_len, state;
    char buf[BUF_SIZE];

    if(argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    
    //回收完成任务的子进程,避免其成为僵尸进程
    state = sigaction(SIGCHLD, &act, 0);
    if(state == -1) {
        error_handling("sigaction() error");
    }
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("bind() error");
    }
    if(listen(serv_sock, 5) == -1) {
        error_handling("listen() error");
    }
    
    while(1) {
        adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
        if(clnt_sock == -1) {
            continue;
        }else {
            puts("New client connected...");
        }

        pid = fork();
        if(pid == -1) {
            close(clnt_sock);
            continue;
        }

        if(pid == 0) {
            //子进程执行区域
            
            /*
                调用fork()之后,由于子进程复制了父进程的文件描述符,
                使得同一套接字中存在2个文件描述符,必须关闭无关的套接字文件描述符
                
                子进程仅需clnt_sock来与客户端进行通信,同理父进程在将通信分交给子
                进程后,无需拥有clnt_sock
            */
            //子进程关闭监听套接字
            close(serv_sock);
            while(str_len = read(clnt_sock, buf, BUF_SIZE) != 0) {
                write(clnt_sock, buf, str_len);
            }
            
            close(clnt_sock);
            puts("Client disconnected...");
            return EXIT_SUCCESS;
        }else {
            //父进程关闭无需用的clnt_sock
            close(clnt_sock);
        }
    }
    
    close(serv_sock);
    return EXIT_SUCCESS;
}

void read_childproc(int sig)
{
    pid_t pid;
    int status;
    pid = waitpid(-1, &status, WNOHANG);
    printf("Remove proc id: %d \n", pid);
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(EXIT_FAILURE);
}

fork.c

#include<stdio.h>
#include<unistd.h>

int gval = 10;

int main(void)
{
    pid_t pid;
    int lval = 20;
    gval++; //gval = 11
    lval += 5; //lval = 25

    pid = fork();
    //子进程开始
    if(pid == 0) {
        gval += 2;
        lval += 2;
    } else {
        gval -= 2;
        lval -= 2;
    }

    if(pid == 0) {
        printf("Child Proc: [%d, %d] \n", gval, lval);
    } else {
        printf("Parent Proc: [%d, %d] \n", gval, lval);
    }

    return 0;
}

remove_zombie.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void read_childproc(int sig)
{
    int status;
    pid_t id = waitpid(-1, &status, WNOHANG);

    if(WIFEXITED(status)) {
        printf("Remove proc id: %d \n", id);
        printf("Child send: %d \n", WEXITSTATUS(status));
    }
}

int main(void)
{
    pid_t pid;
    struct sigaction act;
    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGCHLD, &act, 0);

    pid = fork();
    if(pid == 0) {
        //子进程执行区域
        puts("Hi! I'm child process");
        sleep(10);
        return 12;
    } else {
        //父进程执行区域
        printf("Child proc id: %d \n", pid);
        //再新开一个子进程
        pid = fork();
        if(pid == 0) {
            //另一个子进程执行区域
            puts("Hi! I'm child process");
            sleep(10);
            return 24;
        } else {
            //父进程执行区域
            int i;
            printf("Child proc id: %d \n", pid);
            for(i = 0; i < 5; i++) {
                puts("wait...");
                sleep(5);
            }
        }
    }

    return 0;
}

zombie.c

#include <stdio.h>
#include <unistd.h>

int main(void)
{
    //pid_t pid = fork(); 此行gcc8.2编译不通过
    __pid_t pid = fork();
    //子程序开始
    if(pid == 0) {
        puts("Hi,I am a child process.");
    } else {
        printf("Child Process ID: %d \n", pid);
        sleep(30);
    }

    if(pid == 0) {
        puts("End child process");
    } else {
        puts("End parent process");
    }

    return 0;
}

waitpid.c

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    int status;
    pid_t pid = fork();

    if(pid == 0) {
        sleep(15);
        return 24;
    }else {
        /*第一个参数-1表示可等待任意子进程终止*/
        while(!waitpid(-1, &status, WNOHANG)) {
            sleep(3);
            puts("sleep 3 sec.");
        }
        if(WIFEXITED(status)) {
            printf("Child send %d\n", WEXITSTATUS(status));
        }
    }
    return 0;
}

signal.c

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

//签名函数
void timeout(int sig)
{
    if(sig == SIGALRM) {
        puts("Time out!");
    }
    //generate a SIGALRM signal
    alarm(2);
}

//签名函数
void keycontrol(int sig)
{
    if(sig == SIGINT) {
        puts("CTRL+C pressed");
    }
}

int main(void)
{
    int i;
    signal(SIGALRM, timeout);
    signal(SIGINT, keycontrol);
    alarm(2);

    for(i = 0; i < 3; i++) {
        puts("wait...");
        sleep(100);
    }

    return 0;
}

wait.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void)
{
    int status;
    pid_t pid = fork();
    
    if(pid == 0) {
        //子进程返回3
        return 3;
    }else {
        printf("Child PID: %d\n", pid);
        //回到本进程,再次fork一个子进程
        pid = fork();
        if(pid == 0) {
            //新子进程返回7
            exit(7);
        }else {
            printf("Child PID: %d\n", pid);
            wait(&status);
            //分离信息
            if(WIFEXITED(status)) {
                printf("Child send one: %d\n", WEXITSTATUS(status));
            }
            wait(&status);
            if(WIFEXITED(status)) {
                printf("Child send two: %d\n", WEXITSTATUS(status));
            }
            sleep(30);    //暂停主进程,以便查看子进程状态
        }
    }
    return 0;
}

echo_mpclient.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);

int main(int argc, char *argv[])
{
    int sock;
    pid_t pid;
    char buf[BUF_SIZE];
    struct sockaddr_in serv_adr;
    if(argc != 3) {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("connect() error");
    }

    pid = fork();
    if(pid == 0) {
        write_routine(sock, buf);
    } else {
        read_routine(sock, buf);
    }
    
    close(sock);
    return 0;
}
/*
    分割I/O程序
    父进程负责读数据,子进程负责写数据
    分割I/O的一个好处是提高频繁交换数据的程序的性能
*/

void read_routine(int sock, char *buf)
{
    while(1) {
        int str_len = read(sock, buf, BUF_SIZE);
        if(str_len == 0) {
            return;
        }

        buf[str_len] = 0;
        printf("Message from server: %s\n", buf);
    }
}

void write_routine(int sock, char *buf)
{
    while(1) {
        fgets(buf, BUF_SIZE, stdin);
        if(!strcmp(buf, "q\n") || !strcmp(buf, "Q\n")) {
            shutdown(sock, SHUT_WR);
            return;
        }
        write(sock, buf, strlen(buf));
    }
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(EXIT_FAILURE);
}

11-进程间通信

进程间通信

只要有两个进程可以同时访问的内存空间,就可以通过此空间交换数据。

通过管道实现进程间通信

管道与套接字一样,都是属于操作系统提供的资源。所以,使用管道的两个进程通过操作系统提供的内存空间来进行通信。

#include <unistd.h>
int pipe(int filedes[2]);

返回:
    成功:0
    失败:-1
  • filedes[0] 管道出口,用于接收数据

  • filedes[1] 管道入口,用于传输数据

通过管道进行进程间双向通信

通过管道来进行进程间的双向通信,不建议只使用一个管道,需要程序预测并控制运行流程,好的方案是创建两个管道,各自负责不同的数据流动。

进入到管道里的数据,哪个进程先读就先拥有。

pipe1.c

#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30

int main(int argc ,char *argv[])
{
    int fds[2];
    char str[] = "Who are you?";
    char buf[BUF_SIZE];

    pid_t pid;

    pipe(fds);
    //注意!fork之后,子进程复制的并非是管道,而是用于管道I/O的文件描述符
    pid = fork();
    if(pid == 0) {
        //子进程仅传输数据
        write(fds[1], str, sizeof(str));
    } else {
        //父进程仅读取数据
        read(fds[0], buf, BUF_SIZE);
        puts(buf);
    }
    return 0;
}

pipe2.c

/*
    双向通信管道
*/

#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30

int main(int argc, char *argv[])
{
    int fds[2];
    char str1[] = "Who are you?";
    char str2[] = "Thank you for your message";
    char buf[BUF_SIZE];
    pid_t pid;

    pipe(fds);
    pid = fork();
    if(pid == 0) {
        //子进程执行区域
        write(fds[1], str1, sizeof(str1));
        sleep(2);
        read(fds[0], buf, BUF_SIZE);
        printf("Child proc output: %s \n", buf);
    } else {
        read(fds[0], buf, BUF_SIZE);
        printf("Parent proc output: %s \n", buf);
        write(fds[1], str2, sizeof(str2));
        sleep(3);
    }

    return 0;
}

pipe_close_unused.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#define BUF_SIZE 100

int main(void)
{
    int fds[2];
    char str[] = "HELLO!";
    char buf[BUF_SIZE];
        
    if(pipe(fds) == -1) {
        perror("pipe() error");
        exit(EXIT_FAILURE);
    }

    switch(fork()) {
    case -1:
            perror("fork() error");
            break;
    case 0:
            //子进程执行区域
            //子进程仅写入数据
            close(fds[0]);                //关闭复制来的无用的管道出口文件描述符
            write(fds[1], str, sizeof(str));    //向管道入口写入数据
            close(fds[1]);                //不再使用管道
            exit(EXIT_SUCCESS);
    default:
            //父进程执行区域
            //父进程仅读取数据
            close(fds[1]);                    //关闭无用的管道入口文件描述符
            read(fds[0], buf, BUF_SIZE);            //从管道出口读取数据
            printf("Message from child: %s\n", buf);    
            close(fds[0]);                    //不再使用
    }

    return 0;
}

echo_storeserv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define BUF_SIZE 100

void read_childproc(int);
void error_handling(char *);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    int fds[2];

    pid_t pid;
    struct sigaction act;
    socklen_t adr_sz;
    int str_len, state;
    char buf[BUF_SIZE];

    if(argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    act.sa_handler = read_childproc;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    state = sigaction(SIGCHLD, &act, 0);

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("bind() error");
    }

    if(listen(serv_sock, 5) == -1) {
        error_handling("listen() error");
    }
    
    pipe(fds);
    pid = fork();
    
    if(pid == 0) {
        //子进程执行区域
        FILE *fp = fopen("echo.txt", "wt");
        char msgbuf[BUF_SIZE];

        int i, len;

        for(i = 0; i < 10; i++) {
            len = read(fds[0], msgbuf, BUF_SIZE);
            fwrite((void*)msgbuf, 1, len, fp);
        }
        fclose(fp);
        return 0;
    }
    
    while(1) {
        adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
        if(clnt_sock == -1) {
            continue;
        } else {
            puts("New client connected...");
        }

        pid = fork();
        if(pid == 0) {
            //另一个子进程
            //关闭无用的监听socket
            close(serv_sock);
            while(str_len = read(clnt_sock, buf, BUF_SIZE) != 0) {
                write(clnt_sock, buf, str_len);
                write(fds[1], buf, str_len);
            }
            
            close(clnt_sock);
            puts("Client disconnected...");
            return 0;
        } else {
            close(clnt_sock);
        }
    }
    
    close(serv_sock);
    return 0;
}

//处理子进程
void read_childproc(int sig)
{
    pid_t pid;
    int status;
    pid = waitpid(-1, &status, WNOHANG);
    printf("Remove proc id: %d \n", pid);
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(EXIT_FAILURE);
}

pipe3.c

#include <stdio.h>
#include <unistd.h>
#define BUF_SIZE 30

int main(int argc, char *argv[])
{
    //使用两个管道
    int fds1[2], fds2[2];
    char str1[] = "Who are you?";
    char str2[] = "Thank you for your message";
    char buf[BUF_SIZE];
    pid_t pid;

    pipe(fds1);
    pipe(fds2);
    pid = fork();
    if(pid == 0) {
        write(fds1[1], str1, sizeof(str1));
        read(fds2[0], buf, BUF_SIZE);
        printf("Child proc output: %s \n", buf);
    } else {
        read(fds1[0], buf, BUF_SIZE);
        printf("Parent proc output: %s \n", buf);
        write(fds2[1], str2, sizeof(str2));
        //延迟父进程终止
        sleep(3);
    }


    return 0;
}

12-I⁄O复用

I/O复用

I/O多路复用通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),就能够通知程序进行相应的读写操作。

select函数

select函数具有良好的可移植性。使用select函数可以将多个文件描述符集中到一起统一监视。

select函数的调用方法及顺序:

步骤一:
+------------------+
|   设置文件描述符   |
+------------------+
|    指定监视范围    |
+------------------+
|      设置超时     |
+------------------+
步骤二:
+------------------+
|   调用select函数  |
+------------------+
步骤三:
+------------------+
|    查看调用结果    |
+------------------+
设置文件描述符

使用fd_set数组变量来将需要监视的文件描述符集中到一起。集中时按照监视项(接收、传输、异常)进行区分,分成3类。

fd_set类型以位掩码的形式来实现:

  fd0   fd1   fd2   fd3     <--充当索引
+-----+-----+-----+-----+---------------+
|  0  |  1  |  0  |  1  |   ..........  |
+-----+-----+-----+-----+---------------+

位值设置为1即表示该文件描述符为监视对象,如上图fd1与fd3就是我们的监视对象。

在fd_set变量中注册或更改值的操作由以下宏来完成:

  • FD_ZERO(fd_set *fdset):将fd_set变量的所有位初始化为0。

  • FD_SET(int fd, fd_set *fdset):在参数fdset指向的变量中注册文件描述符fd的信息。

  • FD_CLR(int fd, fd_set *fdset):从参数fdset指向的变量中清除文件描述符fd的信息。

  • FD_ISSET(int fd, fd_set *fdset):若参数fdset指向的变量中存在文件描述符fd的信息,返回true。此函数用于验证select()的调用结果。

文件描述符集合有一个最大容量限制,由常量FD_SETSIZE来决定,值通常为1024。

设置监视范围以及超时

select函数:

#include <sys/select.h>
#include <sys/time.h>

int select(int nfds, fd_set *restrict readfds,
    fd_set *restrict writefds, fd_set *restrict errorfds,
    struct timeval *restrict timeout);

返回:
    成功:0
    失败:-1
  • nfds:监视对象文件描述符数量

  • readfds:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set型变量,并传递其地址值。

  • writefds:将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set型变量,并传递其地址值。

  • errorfds:将所有关注“是否发生异常”的文件描述符注册到fd_set型变量,并传递其地址值。

  • timeout:调用select函数之后,为防止进入无限阻塞状态,设置一个超时。不设置则传入NULL

nfds必须设为比3个文件描述符中所包含的最大文件描述符号还要大1,该参数使select()更加高效,因为内核就不用去检查大于这个值的文件描述符是否属于这些文件描述符集合。

timeval结构体:

struct timeval
{
    long tv_sec;    //seconds
    long tv_usec;   //microseconds
}
调用select函数后产看结果

通过select函数的返回值,若其返回大于0的整数,则表明有相应数量的文件描述符发生了变化。

13-多种I⁄O函数

多种I/O函数

Linux 的 send & recv

send:

#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags);

返回:
    成功:返回发送的字节数
    失败:-1
  • socket:与数据传输对象的链接的套接字文件描述符

  • buffer:保存待传输数据的缓冲池地址

  • length:待传输的字节数

  • flags:传输数据时指定的可选项

recv

#include <sys/socket.h>
ssize_t recv(int socket, void *buffer, size_t length, int flags);

返回:
    成功:接收的字节数(收到EOF则返回0)
    失败:-1
  • 参数大致同上

可选项:

Option Name含义sendrecv
MSG_OOB用于传输带外数据(Out-of-band data)**
MSG_PEEK验证输入缓存中是否存在接收的数据*
MSG_DONTROUTE数据传输过程中不参照路由表,在本地网络中寻找目的地*
MSG_DONTWAIT调用I/O函数时不阻塞,用于使用Non-blocking I/O**
MSG_WAITALL阻止函数返回,知道接收全部请求的字节数*

这里的OOB含义为:通过完全不同的通信路径进行传输的数据,真正的OOB需要单独的通信路径来高速传输数据,但TCP并不提供,只利用TCP的紧急模式来进行传输。

同时设置MSG_PEEKMSG_DONTWAIT选项,可以用于验证输入缓存中是否存在需要接收的数据(不用真正读取)。

readv & writev

通过一个iovec,其内部存储了多个缓冲池(数组)的地址,配合v型函数可对数据进行整合传输及发送,提高数据通信效率。

readv:

#include <sys/uio.h>
ssize_t readv(int fildes, const struct iovec *iov, int iovcnt);

返回:
    成功:读取到的字节数
    失败:-1
  • fildes:套接字文件描述符,当然也可以是文件或标准输出等描述符

  • iov:iovec结构体数组的地址值,该结构体中包含了待发送数据的位置和大小信息

  • iovcnt:向第二个参数传递的数组长度

writev:

#include <sys/uio.h>
ssize_t writev(int fildes, const struct iovec *iov, int iovcnt);

返回:
    成功:发送的字节数
    失败:-1

iovec结构体:

struct iovec
{
    void * iov_base; // 缓冲地址
    size_t iov_len;  // 缓冲大小
}
// example
#include <sys/types.h>
#include <sys/uio.h>
#include <unistd.h>
...
ssize_t bytes_written;
int fd;
char *buf0 = "short string\n";
char *buf1 = "This is a longer string\n";
char *buf2 = "This is the longest string in this example\n";
int iovcnt;
struct iovec iov[3];
iov[0].iov_base = buf0;
iov[0].iov_len = strlen(buf0);
iov[1].iov_base = buf1;
iov[1].iov_len = strlen(buf1);
iov[2].iov_base = buf2;
iov[2].iov_len = strlen(buf2);
...
iovcnt = sizeof(iov) / sizeof(struct iovec);
bytes_written = writev(fd, iov, iovcnt);
...

echo_selectserv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/select.h>

#define BUF_SIZE 100
void error_handling(char *message);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    struct timeval timeout;
    fd_set reads, copy_reads;

    socklen_t adr_sz;
    int fd_max, str_len, fd_num, i;
    char buf[BUF_SIZE];
    
    if(argc != 2) {
        printf("Usage: %s <port>\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("bind() error");
    }

    if(listen(serv_sock, 5) == -1) {
        error_handling("listen() error");
    }
    
    FD_ZERO(&reads);
    FD_SET(serv_sock, &reads);
    fd_max = serv_sock;

    while(1) {
        copy_reads = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 5000;

        if(fd_num = select(fd_max + 1, &copy_reads, NULL, NULL, &timeout) == -1) {
            //出现错误
            break;
        }

        if(fd_num == 0) {
            //超时期限内无事发生,重新开始监控
            continue;
        }

        //遍历文件描述符
        for(i=0; i<fd_max+1; i++) {
            //有I/O事件的文件描述符
            if(FD_ISSET(i, &copy_reads)) {
                if(i == serv_sock) {    //connection request
                    adr_sz = sizeof(clnt_adr);
                    clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
                    FD_SET(clnt_sock, &reads);
                    if(fd_max < clnt_sock) {
                        fd_max = clnt_sock;
                    }
                    printf("connected client: %d \n", clnt_sock);
                } else {    //read message
                    str_len = read(i, buf, BUF_SIZE);
                    //读到EOF
                    if(str_len == 0) {    //close request
                        FD_CLR(i, &reads);
                        close(i);
                        printf("close client: %d \n", i);
                    } else {
                        write(i, buf, str_len);
                    }
                }
            }
        }
    }

    close(serv_sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(EXIT_FAILURE);
}

select.c

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/select.h>
#define BUF_SIZE 30

int main(void)
{
    fd_set reads, temps;
    int result, str_len;
    char buf[BUF_SIZE];
    struct timeval timeout;

    FD_ZERO(&reads);
    FD_SET(0, &reads);    //0 is standard input(console)
    
    //不应在此处设置超时,在调用select()之后,
    //结构体timeval的两个成员会被替换为超时前剩余时间,
    //应在调用select前,都初始化一次超时。
    /*
    timeout.tv_sec = 5;
    timeout.tv_usec = 5000;    
    */

    while(1) {
        //记住初始值
        temps = reads;
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;
        
        result = select(1, &temps, 0, 0, &timeout);
        if(result == -1) {
            puts("select() error");
            break;
        } else if(result == 0) {
            puts("Time-out!");
        } else {
            if(FD_ISSET(0, &temps)) {
                str_len = read(0, buf, BUF_SIZE);
                buf[str_len] = 0;
                printf("message from console: %s\n", buf);
            }
        }
    }

    return 0;
}

oob_recv.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);
void urg_handler(int signo);

int acpt_sock;
int recv_sock;

int main(int argc, char **argv) {
    struct sockaddr_in recv_adr, serv_adr;
    int str_len, state;
    socklen_t serv_adr_sz;
    struct sigaction act;
    char buf[BUF_SIZE];

    if(argc != 2) {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    act.sa_handler = urg_handler;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;

    acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    recv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(acpt_sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr)) == -1) {
       error_handling("bind() error!");
    }
    listen(acpt_sock, 5);

    serv_adr_sz = sizeof(serv_adr);
    recv_sock = accept(acpt_sock, (struct sockaddr*)&serv_adr, &serv_adr_sz);

    // 将socket引发的SIGURG信号交由本程序处理
    fcntl(recv_sock, F_SETOWN, getpid());
    state = sigaction(SIGURG, &act, 0);

    while((str_len = recv(recv_sock, buf, sizeof(buf), 0))!=0) {
        if(str_len == -1) {
            continue;
        }
        buf[str_len] = 0;
        puts(buf);
    }

    close(recv_sock);
    close(acpt_sock);
    return 0;
}

void urg_handler(int signo) {
    int str_len;
    char buf[BUF_SIZE];
    str_len = recv(recv_sock, buf, sizeof(buf)-1, MSG_OOB);
    buf[str_len] = 0;
    printf("Urgent message: %s \n", buf);
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

writev.c

#include <stdio.h>
#include <sys/uio.h>

int main(int argc, char **argv) {
    struct iovec vec[2];
    char buf1[] = "ABCDEFG";
    char buf2[] = "1234567";
    int str_len;

    vec[0].iov_base = buf1;
    vec[0].iov_len = 3;
    vec[1].iov_base = buf2;
    vec[1].iov_len = 4;

    str_len = writev(1, vec, 2);
    puts("");
    printf("Write bytes: %d \n", str_len);

    return 0;
}

peek_send.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>

void error_handling(char *message);

int main(int argc, char **argv) {
    int sock;
    struct sockaddr_in send_adr;
    if(argc != 3) {
        printf("Usage: %s <IP> <PORT> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&send_adr, 0, sizeof(send_adr));
    send_adr.sin_family = AF_INET;
    send_adr.sin_addr.s_addr = inet_addr(argv[1]);
    send_adr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&send_adr, sizeof(send_adr))==-1) {
        error_handling("connect() error!");
    }

    write(sock, "123", strlen("123"));

    close(sock);
    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

oob_send.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30;
void error_handling(char *message);

int main(int argc, char **argv) {
    int sock;
    struct sockaddr_in recv_adr;
    if(argc != 3) {
        printf("Usage: %s <IP> <PORT> \n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    recv_adr.sin_port = htons(atoi(argv[2]));

    if(connect(sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr))==-1) {
        error_handling("connect() error!");
    }

    write(sock, "123", strlen("123"));
    send(sock, "4", strlen("4"), MSG_OOB);
    write(sock, "567", strlen("567"));
    send(sock, "890", strlen("890"), MSG_OOB);

    close(sock);
    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

readv.c

#include <stdio.h>
#include <sys/uio.h>

#define BUF_SIZE 100

int main(int argc, char **argv) {
    struct iovec vec[2];
    char buf1[BUF_SIZE] = {0, };
    char buf2[BUF_SIZE] = {0, };
    int str_len;

    vec[0].iov_base = buf1;
    vec[0].iov_len = 5;
    vec[1].iov_base = buf2;
    vec[1].iov_len = BUF_SIZE;

    str_len = readv(0, vec, 2);
    printf("Read bytes: %d \n", str_len);
    printf("First message: %s \n", buf1);
    printf("Second message: %s \n", buf2);

    return 0;
}

peek_recv.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc, char **argv) {
    int acpt_sock, recv_sock;
    struct sockaddr_in recv_adr, acpt_adr;
    int str_len, state;
    socklen_t recv_adr_sz;
    char buf[BUF_SIZE];

    if(argc != 2) {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    acpt_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&recv_adr, 0, sizeof(recv_adr));
    recv_adr.sin_family = AF_INET;
    recv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    recv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(acpt_sock, (struct sockaddr*)&recv_adr, sizeof(recv_adr)) == -1) {
       error_handling("bind() error!");
    }
    listen(acpt_sock, 5);

    recv_adr_sz = sizeof(recv_adr);
    recv_sock = accept(acpt_sock, (struct sockaddr*)&recv_adr, &recv_adr_sz);

    while(1) {
        str_len = recv(recv_sock, buf, sizeof(buf)-1, MSG_PEEK | MSG_DONTWAIT);
        if(str_len > 0)  {
            break;
        }
    }

    buf[str_len] = 0;
    printf("Buffering %d bytes: %s \n", str_len, buf);

    str_len = recv(recv_sock, buf, sizeof(buf)-1, MSG_PEEK | MSG_DONTWAIT);
    buf[str_len] = 0;
    printf("Read again: %s \n", buf);

    close(recv_sock);
    close(acpt_sock);
    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

14-多播与广播

多播与广播

多播

多播(Multicast)方式的数据传输基于UDP完成,一次可向多播组中的多个主机同时传送数据。多播组是D类IP地址(224.0.0.0 - 239.255.255.255)。

多播数据包格式与UDP数据包并无二致,但路由器在识别到多播数据包时,会复制该数据包并传递到多个主机。

路由、TTL,以及如何加入多播组

为了传递多播数据包,必须设置TTL。用于决定该数据包可传送的距离:每经过一个路由,TTL减1,当TTL减至0时,该数据包就无法再被传递,即销毁。

TTL设置通过IPPROTO_IP层的可选项IP_MULTICAST_TTL完成。

加入多播组则通过IPPROTO_IP层的IP_ADD_MEMBERSHIP来完成。

广播

虽然作用都是”一次性向多个主机发送数据“,但与多播(multicast)不同的是,广播(broadcast)只能向同一网络中的主机传输数据。

根据传输数据时使用的IP地址的形式,广播可分为:

  • 直接广播(Directed Broadcast):地址中除网络地址外,其余主机地址位全为1(向给定网络中所有主机广播)

  • 本地广播(Local Broadcast):地址为255.255.255.255

默认生成的套接字会阻止广播,使用可选项SO_BROADCAST来设置广播。

news_sender_brd.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30

void error_handling(char *message);

int main(int argc, char **argv) {
    int send_sock;
    struct sockaddr_in brd_adr;
    FILE *fp;
    char buf[BUF_SIZE];
    int so_brd=1;

    if(argc != 3) {
        printf("Usage: %s <Broadcast IP> <PORT> \n", argv[0]);
        exit(1);
    }

    send_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&brd_adr, 0, sizeof(brd_adr));
    brd_adr.sin_family = AF_INET;
    brd_adr.sin_addr.s_addr = inet_addr(argv[1]);
    brd_adr.sin_port = htons(atoi(argv[2]));

    setsockopt(send_sock, SOL_SOCKET, SO_BROADCAST, 
            (void*)&so_brd, sizeof(so_brd));

    if((fp = fopen("news.txt", "r")) == NULL) {
        error_handling("fopen() error!");
    }

    while(!feof(fp)) {
        fgets(buf, BUF_SIZE, fp);
        sendto(send_sock, buf, strlen(buf), 0, 
                (struct sockaddr*)&brd_adr, sizeof(brd_adr));
        sleep(1);
    }
    fclose(fp);
    close(send_sock);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

news.txt

TITLE: McLaughlin sets world record in 400 hurdles.

Sydney McLaughlin set a world record in the 400-meter hurdles at the U.S. track and field trials, finishing in 51.90 seconds.

McLaughlin bested the record of 52.16 set by second-place finisher Dalilah Muhammad, who crossed in 52.42. Anna Cockrell was third in 53.70. The trio will head to the Olympics in Tokyo.

news_receiver_brd.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30

void error_handling(char *message);

int main(int argc, char **argv) {
    int recv_sock;
    int str_len;
    char buf[BUF_SIZE];
    struct sockaddr_in adr;

    if(argc != 2) {
        printf("Usage: %s <PORT> \n", argv[0]);
        exit(1);
    }

    recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&adr, 0, sizeof(adr));
    adr.sin_family = AF_INET;
    adr.sin_addr.s_addr = htonl(INADDR_ANY);
    adr.sin_port = htons(atoi(argv[1]));

    if(bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1) {
        error_handling("bind() error!");
    }

    while(1) {
        str_len = recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0);
        if(str_len < 0) {
            break;
        }
        buf[str_len] = 0;
        fputs(buf, stdout);
    }

    close(recv_sock);
    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

news_receiver.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30

void error_handling(char *message);

int main(int argc, char **argv) {
    int recv_sock;
    int str_len;
    char buf[BUF_SIZE];
    struct sockaddr_in adr;
    struct ip_mreq join_adr;

    if(argc != 3) {
        printf("Usage: %s <GroupIP> <PORT> \n", argv[0]);
        exit(1);
    }

    recv_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&adr, 0, sizeof(adr));
    adr.sin_family = AF_INET;
    adr.sin_addr.s_addr = htonl(INADDR_ANY);
    adr.sin_port = htons(atoi(argv[2]));

    if(bind(recv_sock, (struct sockaddr*)&adr, sizeof(adr)) == -1) {
        error_handling("bind() error!");
    }
    join_adr.imr_multiaddr.s_addr=inet_addr(argv[1]);
    join_adr.imr_interface.s_addr=htonl(INADDR_ANY);

    setsockopt(recv_sock, IPPROTO_IP, IP_ADD_MEMBERSHIP,
            (void*)&join_adr, sizeof(join_adr));

    while(1) {
        str_len = recvfrom(recv_sock, buf, BUF_SIZE-1, 0, NULL, 0);
        if(str_len < 0) {
            break;
        }
        buf[str_len] = 0;
        fputs(buf, stdout);
    }

    close(recv_sock);
    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

news_sender.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define TTL 64
#define BUF_SIZE 30

void error_handling(char *message);

int main(int argc, char **argv) {
    int send_sock;
    struct sockaddr_in mul_adr;
    int time_live = TTL;
    FILE *fp;
    char buf[BUF_SIZE];

    if(argc != 3) {
        printf("Usage: %s <GroupIP> <PORT> \n", argv[0]);
        exit(1);
    }

    send_sock = socket(PF_INET, SOCK_DGRAM, 0);
    memset(&mul_adr, 0, sizeof(mul_adr));
    mul_adr.sin_family = AF_INET;
    mul_adr.sin_addr.s_addr = inet_addr(argv[1]);
    mul_adr.sin_port = htons(atoi(argv[2]));

    setsockopt(send_sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&time_live, sizeof(time_live));
    if((fp = fopen("news.txt", "r")) == NULL) {
        error_handling("fopen() error!");
    }

    while(!feof(fp)) {
        fgets(buf, BUF_SIZE, fp);
        sendto(send_sock, buf, strlen(buf), 0, (struct sockaddr*)&mul_adr, sizeof(mul_adr));
        sleep(1);
    }
    fclose(fp);
    close(send_sock);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

15-套接字和标准I⁄O

套接字和标准I/O

使用标准I/O函数的优点:

  • 标准I/O函数具有良好的移植性

  • 标准I/O函数可以利用缓冲提高性能

缺点:

  • 不容易进行双向通信

  • 由于缓冲的缘故,有时可能频繁调用fflush函数

  • 需要以FILE结构体指针的形式返回文件描述符

使用标准I/O函数

#include <stdio.h>
FILE *fdopen(int fd, const char *mode);

返回:
    成功:返回转换后的FILE结构体指针
    失败: NULL
#include <stdio.h>
int fileno(FILE *stream);

返回:
    成功:返回转换后的文件描述符
    失败:-1

echo_client.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char **argv) {
    int sock;
    char message[BUF_SIZE];
    int str_len;
    struct sockaddr_in serv_adr;
    FILE *readfp;
    FILE *writefp;
    if (argc != 3) {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        error_handling("socket() error");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("connect() error!");
    } else {
        puts("Connected...........");
    }

    readfp = fdopen(sock, "r");
    writefp = fdopen(sock, "w");
    while (1)
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);

        if (!strcmp(message, "q\n") || !strcmp(message, "Q\n")) {
            break;
        }

        fputs(message, writefp);
        fflush(writefp);
        fgets(message, BUF_SIZE, readfp);
        printf("Message from server: %s", message);
    }

    fclose(writefp);
    fclose(readfp);
    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

desto.c

#include <stdio.h>
#include <fcntl.h>

int main(void) {
    FILE * fp;
    int fd = open("data.dat", O_WRONLY|O_CREAT|O_TRUNC);
    if(fd == -1) {
        fputs("file open error", stdout);
        return -1;
    }

    fp = fdopen(fd, "w");
    fputs("Network C programming \n", fp);

    fclose(fp);
    return 0;
}

stdcpy.c

#include <stdio.h>

#define BUF_SIZE 3

int main(int argc, char **argv) {
    FILE * fp1;
    FILE * fp2;
    char buf[BUF_SIZE];

    fp1 = fopen("300MB.test", "r");
    fp2 = fopen("300MB.cpy", "w");

    while(fgets(buf, BUF_SIZE, fp1) != NULL) {
        fputs(buf, fp2);
    }
    
    fclose(fp1);
    fclose(fp2);
    return 0;
}

todes.c

#include <stdio.h>
#include <fcntl.h>

int main(void) {
    FILE *fp;
    int fd = open("data.dat", O_WRONLY|O_CREAT|O_TRUNC);
    if(fd == -1) {
        fputs("file open error", stdout);
        return -1;
    }

    printf("First file descriptor: %d \n", fd);
    fp = fdopen(fd, "w");
    fputs("TCP/IP SOCKET PROGRAMMING \n", fp);
    printf("Second file descriptor: %d \n", fileno(fp));

    fclose(fp);
    return 0;
}

syscpy.c

#include <unistd.h>
#include <fcntl.h>

#define BUF_SIZE 3

int main(int argc, char **argv) {
    int fd1, fd2;
    int len;
    char buf[BUF_SIZE];

    // dd if=/dev/urandom of=300MB.test bs=1M count=300 iflag=fullblock
    // 简单用time命令测试一下运行时间
    fd1 = open("300MB.test", O_RDONLY);
    fd2 = open("300MB.cpy", O_WRONLY|O_CREAT|O_TRUNC);
    
    while((len = read(fd1, buf, sizeof(buf))) > 0) {
        write(fd2, buf, len);
    }

    close(fd1);
    close(fd2);
    return 0;
}

echo_stdserv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024
void error_handling(char *message);

int main(int argc, char **argv)
{
    int serv_sock, clnt_sock;
    char message[BUF_SIZE];
    int str_len, i;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    FILE *readfp;
    FILE *writefp;

    if (argc != 2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1) {
        error_handling("socket() error");
    }

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("bind() error");
    }

    if (listen(serv_sock, 5) == -1) {
        error_handling("listen() error");
    }

    clnt_adr_sz = sizeof(clnt_adr);
    for (i = 0; i < 5; i++)
    {
        clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_sz);
        if (clnt_sock == -1) {
            error_handling("accept() error");
        } else {
            printf("Connect client %d \n", i + 1);
        }

        readfp = fdopen(clnt_sock, "r");
        writefp = fdopen(clnt_sock, "w");
        while (!feof(readfp)) {
            fgets(message, BUF_SIZE, readfp);
            fputs(message, writefp);
            fflush(writefp);
        }

        fclose(readfp);
        fclose(writefp);
    }

    close(serv_sock);
    return 0;
}

void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

16-关于I⁄O流分离的其他内容

关于I/O流分离的其他内容

分离I/O流

有I/O工具可以区分二者,无论使用何种方法,都可以认为分离了I/O流。

2次I/O流分离
  • 第一次:在这里,通过fork复制出一个文件描述符,以区分输入和输出中使用的文件描述符。

  • 第二次:在这里,通过2次fdopen,创建了读模式FILE指针和写模式FILE指针。

分离“流”的好处

上述第一次分流:

  • 通过分开输入过程(代码)和输出过程降低实现难度

  • 与输入无关的输出操作可以提升速度

第二次分流:

  • 为了将FILE指针按读模式与写模式加以区分

  • 可以通过区分读写模式降低实现难度

  • 通过区分I/O缓冲提高缓冲性能

“流“分离带来的EOF问题

对于文件描述符,我们可以通过shutdown半关闭客户端的输出流来传递EOF,而对基于fdopen的“流”就不一样了。

文件描述符的复制和半关闭

通常,我们的读模式FILE指针与写模式FILE指针都是基于同一文件描述符创建的,那么针对任一FILE结构体指针调用fclose()都会关闭该文件描述符,从而终止套接字。要想达到半关闭的状态,可以复制一份文件描述符。

复制文件描述符

这里的复制是指:为了访问同一文件或套接字,再创建出另一个文件描述符。

文件描述符的复制通过dupdup2函数调用完成:

#include <unistd.h>
int dup(int fildes);
int dup2(int fildes, int fildes2);  // dup2可指定复制的文件描述符整数值

返回:
    成功:返回复制的文件描述符
    失败:-1

sep_serv.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024

int main(int argc, char **argv) {
    int serv_sock, clnt_sock;
    FILE * readfp;
    FILE * writefp;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    char buf[BUF_SIZE] = {0, };

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));
    
    bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    listen(serv_sock, 5);
    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

    readfp = fdopen(clnt_sock, "r");
    writefp = fdopen(clnt_sock, "w");

    fputs("FROM SERVER: HI~ client? \n", writefp);
    fputs("I love all of the world. \n", writefp);
    fputs("You're awesome! \n", writefp);
    // 结束发送
    fflush(writefp);
    // 对方收到EOF
    fclose(writefp);
    // 能接收到么?
    fgets(buf, sizeof(buf), readfp);
    fputs(buf, stdout);

    fclose(readfp);
    
    return 0;
}

sep_serv2.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024

int main(int argc, char **argv) {
    int serv_sock, clnt_sock;
    FILE * readfp;
    FILE * writefp;

    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t clnt_adr_sz;
    char buf[BUF_SIZE] = {0, };

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));
    
    bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    listen(serv_sock, 5);
    clnt_adr_sz = sizeof(clnt_adr);
    clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

    readfp = fdopen(clnt_sock, "r");
    writefp = fdopen(dup(clnt_sock), "w");

    fputs("FROM SERVER: HI~ client? \n", writefp);
    fputs("I love all of the world. \n", writefp);
    fputs("You're awesome! \n", writefp);
    // 结束发送
    fflush(writefp);
    // 对方收到EOF
    shutdown(fileno(writefp), SHUT_WR);
    fclose(writefp);

    fgets(buf, sizeof(buf), readfp);
    fputs(buf, stdout);

    fclose(readfp);
    
    return 0;
}

dup.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int cfd1, cfd2;
    char str1[] = "Hi~ \n";
    char str2[] = "It's a nice day~ \n";

    cfd1 = dup(1);
    cfd2 = dup2(cfd1, 7);

    printf("fd1=%d, fd2=%d \n", cfd1, cfd2);
    write(cfd1, str1, sizeof(str1));
    write(cfd2, str2, sizeof(str2));

    close(cfd1);
    close(cfd2);
    write(1, str1, sizeof(str1));
    close(1);
    // 无法输出了
    write(1, str2, sizeof(str2));

    return 0;
}

sep_clnt.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 1024

int main(int argc, char **argv)
{
    int sock;
    char buf[BUF_SIZE];
    struct sockaddr_in serv_adr;

    FILE * readfp;
    FILE * writefp;

    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_adr.sin_port = htons(atoi(argv[2]));

    connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr));
    readfp = fdopen(sock, "r");
    writefp = fdopen(sock, "w");

    while(1)
    {
        if(fgets(buf, sizeof(buf), readfp) == NULL) {
            // 收到EOF
            break;
        }
        fputs(buf, stdout);
        fflush(stdout);
    }

    fputs("FROM CLIENT: Thank you! \n", writefp);
    fflush(writefp);
    fclose(writefp);
    fclose(readfp);
    return 0;
}

17-优于select的epoll

优于SELECT的EPOLL

在连接数较小的情况下,select具有不错的性能,跨平台兼容性也不错。但随着对单机性能提升的不断追求,select已经无法满足我们的需求。其暴露出的缺点也越来越不被人们所忍受:需循环查询所有监视对象、无法动态添加新的监视对象,需重新调用select并添加新目标等等。而Linux平台上的对select进行改进的epoll实现了:无需编写以监视状态变化为目的的针对所有文件描述符的循环语句、以及无需每次传递监视对象信息,可监视的描述符数量也几乎没有了限制。

epoll api:

#include <sys/epoll.h>
// 创建epoll instance
int epoll_create(int size);
// 将感兴趣的fd注册进epoll实例的interest list中
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// 阻塞等待当前线程上的IO事件
int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

typedef union epoll_data {
    void    *ptr;
    int      fd;
    uint32_t u32;
    uint64_t u64;
} epoll_data_t;

struct epoll_event {
    uint32_t     events;    /* Epoll events */
    epoll_data_t data;      /* User data variable */
};

LT 与 ET

LT(水平触发)是默认的模式,只要输入缓冲中有数据可读,就会注册事件并进行通知,而ET(边缘触发)则只在文件描述符上有I/O事件时,才进行通知,用户必须自己对数据进行有效的处理,该模式能减少不必要的重复通知从而提高效率,一般与non-blocking配合食用(避免未读取完全而造成长时间的阻塞 -- 除非fd上有新事件)。

设置非阻塞模式
#include <fcntl.h>
int flag = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flag|O_NONBLOCK);

echo_EPETserv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 4
#define EPOLL_SIZE 50

void error_handling(char *message);
void setnonblockingmode(int fd);

int main(int argc, char **argv)
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int str_len, i;
    char buf[BUF_SIZE];

    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;

    if(argc != 2) {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1) {
        error_handling("bind() error!");
    }
    if(listen(serv_sock, 5)==-1) {
        error_handling("listen() error!");
    }

    epfd = epoll_create(EPOLL_SIZE);
    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    event.events = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    while(1)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if(event_cnt == -1) {
            puts("epoll_wait() error");
            break;
        }

        puts("return epoll_wait");
        for(i=0; i<event_cnt; i++)
        {
           if(ep_events[i].data.fd == serv_sock) {
                adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                setnonblockingmode(clnt_sock); // 设置非阻塞模式
                event.events = EPOLLIN | EPOLLET; // 添加ET模式
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf("connected client: %d \n", clnt_sock);
           } else {
               while(1) // 确保读出所有数据
               {
                    str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                    if(str_len == 0) {
                        epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                        close(ep_events[i].data.fd);
                        printf("close client: %d \n", ep_events[i].data.fd);
                        break;
                    } else if(str_len < 0){
                        if(errno == EAGAIN) {
                            break;
                        }
                    } else {
                        write(ep_events[i].data.fd, buf, str_len);
                    }
               }
           }
        }
    }
    close(serv_sock);
    close(epfd);

    return 0;
}

void setnonblockingmode(int fd) {
    int flag = fcntl(fd, F_GETFL, 0);
    fcntl(fd, F_SETFL, flag|O_NONBLOCK);
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

echo_epollserv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 100
#define EPOLL_SIZE 50

void error_handling(char *message);

int main(int argc, char **argv)
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int str_len, i;
    char buf[BUF_SIZE];

    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;

    if(argc != 2) {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1) {
        error_handling("bind() error!");
    }
    if(listen(serv_sock, 5)==-1) {
        error_handling("listen() error!");
    }

    epfd = epoll_create(EPOLL_SIZE);
    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    event.events = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    while(1)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if(event_cnt == -1) {
            puts("epoll_wait() error");
            break;
        }

        for(i=0; i<event_cnt; i++)
        {
           if(ep_events[i].data.fd == serv_sock) {
                adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                event.events = EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf("connected client: %d \n", clnt_sock);
           } else {
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if(str_len == 0) {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                    close(ep_events[i].data.fd);
                    printf("close client: %d \n", ep_events[i].data.fd);
                } else {
                    write(ep_events[i].data.fd, buf, str_len);
                }
           }
        }
    }

    close(serv_sock);
    close(epfd);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

echo_EPLTserv.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 4
#define EPOLL_SIZE 50

void error_handling(char *message);

int main(int argc, char **argv)
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int str_len, i;
    char buf[BUF_SIZE];

    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;

    if(argc != 2) {
        printf("Usage: %s <port> \n", argv[0]);
        exit(1);
    }

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1) {
        error_handling("bind() error!");
    }
    if(listen(serv_sock, 5)==-1) {
        error_handling("listen() error!");
    }

    epfd = epoll_create(EPOLL_SIZE);
    ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    event.events = EPOLLIN;
    event.data.fd = serv_sock;
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    while(1)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if(event_cnt == -1) {
            puts("epoll_wait() error");
            break;
        }

        puts("return epoll_wait");
        for(i=0; i<event_cnt; i++)
        {
           if(ep_events[i].data.fd == serv_sock) {
                adr_sz = sizeof(clnt_adr);
                clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
                event.events = EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf("connected client: %d \n", clnt_sock);
           } else {
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if(str_len == 0) {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                    close(ep_events[i].data.fd);
                    printf("close client: %d \n", ep_events[i].data.fd);
                } else {
                    write(ep_events[i].data.fd, buf, str_len);
                }
           }
        }
    }

    close(serv_sock);
    close(epfd);

    return 0;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

18-多线程服务器端的实现

多线程服务器端的实现

多进程模型的缺点:

  • 创建开销大

  • 为完成进程间数据交换,引入IPC技术

  • 进程上下文切换相对较慢

与之对比,线程:

  • 线程的创建及上下文切换较进程更快

  • 线程间交换数据无需特殊技术

多个线程之间可以共享父进程的数据区与堆。

线程创建及运行

fork-join模型

// Compile and link with -pthread.
#include <pthread.h>
int pthread_create(pthread_t *restrict thread,
                   const pthread_attr_t *restrict attr,
                   void *(*start_routine)(void *),
                   void *restrict arg);

返回:
    成功:0
    失败:非0值
  • thread:保存新创建线程ID的变量地址值

  • attr:传递线程属性的参数,传递NULL则表示默认

  • start_routine:函数指针,用于在线程中执行

  • arg:传递上面参数函数指针运行所需的参数等

#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);

返回:
    成功:0
    失败:非0值
  • 该参数值ID的线程终止后才会从该函数返回

  • 保存线程start_routine函数返回值的指针变量地址值

编译时加-D_REENTRANT可自动将一些非线程安全的函数调用替换为其线程安全的版本:

gethostbyname() -> gethostbyname_r()

线程同步

从两个方面来考虑同步的情况:

  • 同时访问同一内存空间时发生的情况 (使用互斥锁)

  • 需要指定访问同一内存空间的线程执行顺序的情况 (使用信号量)

互斥量
#include <pthread.h>
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
                       const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
信号量
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t *sem);
int sem_post(sem_t *sem);
int sem_wait(sem_t *sem);

sem传递保存信号量读取值的变量地址值,传递给sem_post时信号量+1,传递给sem_wait时信号量-1

chat_server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>

#define BUF_SIZE 100
#define MAX_CLNT 256

void *handle_clnt(void *arg);
void send_msg(char *msg, int len);
void error_handling(char *message);

int clnt_cnt = 0;
int clnt_socks[MAX_CLNT];
pthread_mutex_t mutx;

int main(int argc, char **argv)
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    int clnt_adr_sz;
    pthread_t t_id;
    if (argc != 2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    pthread_mutex_init(&mutx, NULL); //创建互斥锁
    serv_sock = socket(PF_INET, SOCK_STREAM, 0);

    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1) {
        error_handling("bind() error");
    }
    if (listen(serv_sock, 5) == -1) {
        error_handling("listen() error");
    }

    while (1)
    {
        clnt_adr_sz = sizeof(clnt_adr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);

        pthread_mutex_lock(&mutx);
        clnt_socks[clnt_cnt++] = clnt_sock;
        pthread_mutex_unlock(&mutx);

        pthread_create(&t_id, NULL, handle_clnt, (void *)&clnt_sock);
        pthread_detach(t_id); //引导线程销毁,不阻塞
        printf("Connected client IP: %s \n", inet_ntoa(clnt_adr.sin_addr));
    }
    close(serv_sock);
    return 0;
}

void *handle_clnt(void *arg)
{
    int clnt_sock = *((int *)arg);
    int str_len = 0, i;
    char msg[BUF_SIZE];

    while ((str_len = read(clnt_sock, msg, sizeof(msg))) != 0)
    {
        send_msg(msg, str_len);
    }
    pthread_mutex_lock(&mutx);
    for (i = 0; i < clnt_cnt; i++)
    {
        if (clnt_sock == clnt_socks[i])
        {
            while (i++ < clnt_cnt - 1)
                clnt_socks[i] = clnt_socks[i + 1];
            break;
        }
    }
    clnt_cnt--;
    pthread_mutex_unlock(&mutx);
    close(clnt_sock);
    return NULL;
}

void send_msg(char *msg, int len) {
    int i;
    pthread_mutex_lock(&mutx);
    for (i = 0; i < clnt_cnt; i++)
    {
        write(clnt_socks[i], msg, len);
    }
    pthread_mutex_unlock(&mutx);
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

thread4.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREAD 100

void *thread_inc(void *arg);
void *thread_des(void *arg);
long long num = 0;

int main(int argc, char **argv)
{
    pthread_t thread_id[NUM_THREAD];
    int i;

    printf("sizeof long long: %d \n", sizeof(long long));
    for (i = 0; i < NUM_THREAD; i++)
    {
        if (i % 2) {
            pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
        } else {
            pthread_create(&(thread_id[i]), NULL, thread_des, NULL);
        }
    }

    for (i = 0; i < NUM_THREAD; i++)
    {
        pthread_join(thread_id[i], NULL);
    }

    printf("result: %lld \n", num);
    return 0;
}

void *thread_inc(void *arg)
{
    int i;
    for (i = 0; i < 50000000; i++)
    {
        num += 1;
    }
    return NULL;
}
void *thread_des(void *arg)
{
    int i;
    for (i = 0; i < 50000000; i++)
    {
        num -= 1;
    }
    return NULL;
}

mutex.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define NUM_THREAD 100
void *thread_inc(void *arg);
void *thread_des(void *arg);

long long num = 0;
pthread_mutex_t mutex;

int main(int argc, char *argv[])
{
    pthread_t thread_id[NUM_THREAD];
    int i;

    pthread_mutex_init(&mutex, NULL);

    for (i = 0; i < NUM_THREAD; i++)
    {
        if (i % 2) {
            pthread_create(&(thread_id[i]), NULL, thread_inc, NULL);
        } else {
            pthread_create(&(thread_id[i]), NULL, thread_des, NULL);
        }
    }

    for (i = 0; i < NUM_THREAD; i++)
    {
        pthread_join(thread_id[i], NULL);
    }

    printf("result: %lld \n", num);
    pthread_mutex_destroy(&mutex);
    return 0;
}

void *thread_inc(void *arg)
{
    int i;
    pthread_mutex_lock(&mutex); 
    for (i = 0; i < 50000000; i++)
    {
        num += 1;
    }
    pthread_mutex_unlock(&mutex);
    return NULL;
}
void *thread_des(void *arg)
{
    int i;
    pthread_mutex_lock(&mutex);
    for (i = 0; i < 50000000; i++)
    {
        num -= 1;
    }
    pthread_mutex_unlock(&mutex);
    return NULL;
}

thread3.c

#include <stdio.h>
#include <pthread.h>

void* thread_summation(void *arg);
int sum = 0;

int main(int argc, char **argv)
{
    pthread_t id_t1, id_t2;
    int range1[] = {1, 5};
    int range2[] = {6, 10};

    pthread_create(&id_t1, NULL, thread_summation, (void*)range1);
    pthread_create(&id_t2, NULL, thread_summation, (void*)range2);

    pthread_join(id_t1, NULL);
    pthread_join(id_t2, NULL);

    printf("result: %d \n", sum);
    return 0;
}

void* thread_summation(void* arg) {
    int start = ((int*)arg)[0];
    int end = ((int*)arg)[1];

    while(start <= end)
    {
        sum += start;
        start++;
    }
    return NULL;
}

semaphore.c

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>

void *read(void *arg);
void *accu(void *arg);
static sem_t sem_one;
static sem_t sem_two;
static int num;

int main(int argc, char **argv)
{
    pthread_t id_t1, id_t2;
    sem_init(&sem_one, 0, 0);
    sem_init(&sem_two, 0, 1);

    pthread_create(&id_t1, NULL, read, NULL);
    pthread_create(&id_t2, NULL, accu, NULL);

    pthread_join(id_t1, NULL);
    pthread_join(id_t2, NULL);

    sem_destroy(&sem_one);
    sem_destroy(&sem_two);
    return 0;
}

void *read(void *arg) {
    int i;
    for (i = 0; i < 5; i++)
    {
        fputs("Input num: ", stdout);

        sem_wait(&sem_two);
        scanf("%d", &num);
        sem_post(&sem_one);
    }
    return NULL;
}

void *accu(void *arg) {
    int sum = 0, i;
    for (i = 0; i < 5; i++)
    {
        sem_wait(&sem_one);
        sum += num;
        sem_post(&sem_two);
    }
    printf("Result: %d \n", sum);
    return NULL;
}

thread2.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

void* thread_main(void *arg);

// compile: gcc xxx.c -l pthread

int main(int argc, char **argv)
{
    pthread_t t_id;
    int thread_param = 5;
    void * thr_ret;

    if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param) != 0) {
        puts("pthread_create() error!");
        return -1;
    }

    if(pthread_join(t_id, &thr_ret) != 0) {
        puts("pthread_join() error!");
        return -1;
    }

    printf("Thread return message: %s \n", (char *)thr_ret);
    free(thr_ret);
    return 0;
}

void* thread_main(void *arg) {
    int i;
    int cnt = *((int*)arg);
    char *msg=(char *)malloc(sizeof(char)*50);
    strcpy(msg, "Hello, I'm a thread~ \n");

    for(i=0; i<cnt; i++)
    {
        sleep(1);
        puts("running thread");
    }
    return (void *)msg;
}

chat_clnt.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>

#define BUF_SIZE 100
#define NAME_SIZE 20

void *send_msg(void *arg);
void *recv_msg(void *arg);
void error_handling(char *message);

char name[NAME_SIZE] = "[DEFAULT]";
char msg[BUF_SIZE];

int main(int argc, char **argv)
{
    int sock;
    struct sockaddr_in serv_addr;
    pthread_t snd_thread, rcv_thread;
    void *thread_return;
    if (argc != 4) {
        printf("Usage : %s <IP> <port> <name>\n", argv[0]);
        exit(1);
    }

    sprintf(name, "[%s]", argv[3]);
    sock = socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
    serv_addr.sin_port = htons(atoi(argv[2]));

    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) == -1) {
        error_handling("connect() error");
    }

    pthread_create(&snd_thread, NULL, send_msg, (void *)&sock);
    pthread_create(&rcv_thread, NULL, recv_msg, (void *)&sock);
    pthread_join(snd_thread, &thread_return);
    pthread_join(rcv_thread, &thread_return);

    close(sock);
    return 0;
}

void *send_msg(void *arg) {
    int sock = *((int *)arg);
    char name_msg[NAME_SIZE + BUF_SIZE];
    while (1)
    {
        fgets(msg, BUF_SIZE, stdin);
        if (!strcmp(msg, "q\n") || !strcmp(msg, "Q\n")) {
            close(sock);
            exit(0);
        }
        sprintf(name_msg, "%s %s", name, msg);
        write(sock, name_msg, strlen(name_msg));
    }
    return NULL;
}

void *recv_msg(void *arg) {
    int sock = *((int *)arg);
    char name_msg[NAME_SIZE + BUF_SIZE];
    int str_len;
    while (1)
    {
        str_len = read(sock, name_msg, NAME_SIZE + BUF_SIZE - 1);
        if (str_len == -1) {
            return (void *)-1;
        }
        name_msg[str_len] = 0;
        fputs(name_msg, stdout);
    }
    return NULL;
}

void error_handling(char *message) {
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

thread1.c

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void* thread_main(void *arg);

// compile: gcc xxx.c -l pthread

int main(int argc, char **argv)
{
    pthread_t t_id;
    int thread_param = 5;

    if(pthread_create(&t_id, NULL, thread_main, (void*)&thread_param) != 0) {
        puts("pthread_create() error!");
        return -1;
    }

    sleep(10);
    puts("end of main");

    return 0;
}

void* thread_main(void *arg) {
    int i;
    int cnt = *((int*)arg);
    for(i=0; i<cnt; i++)
    {
        sleep(1);
        puts("running thread");
    }
    return NULL;
}

目录

   ├── README.md
   └── 01-理解网络编程和套接字
       ├── hello_server.c
       ├── low_open.c
       ├── README.md
       ├── hello_client.c
       ├── low_read.c
       ├── fd_seri.c
   └── 02-套接字类型与协议设置
       ├── tcp_client.c
       ├── README.md
       ├── tcp_server.c
   └── 03-地址族与数据序列
       ├── README.md
       ├── inet_ntoa.c
       ├── inet_aton.c
       ├── inet_addr.c
       ├── endian_conv.c
   └── 04-基于TCP的服务端⁄客户端(1)
       ├── echo_client.c
       ├── README.md
       ├── echo_server.c
   └── 05-基于TCP的服务端⁄客户端(2)
       ├── op_server.c
       ├── echo_client2.c
       ├── README.md
       ├── op_client.c
   └── 06-基于UDP的服务器端⁄客户端
       ├── bound_host1.c
       ├── uecho_server.c
       ├── bound_host2.c
       ├── README.md
       ├── uecho_con_client.c
       ├── uecho_client.c
   └── 07-优雅地断开套接字连接
       ├── receive.dat
       ├── file_server.c
       ├── README.md
       ├── file_client.c
   └── 08-域名及网络地址
       ├── gethostbyname.c
       ├── README.md
       ├── gethostbyaddr.c
   └── 09-套接字的多种可选项
       ├── set_buf.c
       ├── get_buf.c
       ├── reuseadr_eserver.c
       ├── README.md
       ├── sock_type.c
   └── 10-多进程服务器端
       ├── sigaction.c
       ├── echo_mpserv.c
       ├── fork.c
       ├── remove_zombie.c
       ├── zombie.c
       ├── README.md
       ├── waitpid.c
       ├── signal.c
       ├── wait.c
       ├── echo_mpclient.c
   └── 11-进程间通信
       ├── pipe1.c
       ├── pipe2.c
       ├── README.md
       ├── pipe_close_unused.c
       ├── echo_storeserv.c
       ├── pipe3.c
   └── 12-I⁄O复用
       ├── echo_selectserv.c
       ├── select.c
       ├── README.md
   └── 13-多种I⁄O函数
       ├── oob_recv.c
       ├── writev.c
       ├── peek_send.c
       ├── README.md
       ├── oob_send.c
       ├── readv.c
       ├── peek_recv.c
   └── 14-多播与广播
       ├── news_sender_brd.c
       ├── news.txt
       ├── news_receiver_brd.c
       ├── news_receiver.c
       ├── news_sender.c
       ├── README.md
   └── 15-套接字和标准I⁄O
       ├── echo_client.c
       ├── desto.c
       ├── stdcpy.c
       ├── todes.c
       ├── syscpy.c
       ├── echo_stdserv.c
       ├── README.md
   └── 16-关于I⁄O流分离的其他内容
       ├── sep_serv.c
       ├── sep_serv2.c
       ├── README.md
       ├── dup.c
       ├── sep_clnt.c
   └── 17-优于select的epoll
       ├── echo_EPETserv.c
       ├── echo_epollserv.c
       ├── README.md
       ├── echo_EPLTserv.c
   └── 18-多线程服务器端的实现
       ├── chat_server.c
       ├── thread4.c
       ├── mutex.c
       ├── thread3.c
       ├── README.md
       ├── semaphore.c
       ├── thread2.c
       ├── chat_clnt.c
       ├── thread1.c