参考 《TCP IP网络编程》
参考 《Linux高性能服务器编程》
参考 《深入浅出TCP/IP协议》

TCP/IP框架

TCP/IP结构简图

  1. 链路层: 对电信号进行分组形成数据帧, 通过以太网广播, 通过数据帧的头部主要包含MAC(源和目标)进行网卡对比.
  2. 网络层: 定义网络地址, 区分网段, 子网内的MAC寻址, 不同子网的路由. IP协议: 通过子网掩码对源/目标IP地址继续宁AND运算, 用于判断二者是否属于一个网络. ARP协议: 地址解析协议, 通过IP地址获取MAC地址的网络层协议. arp -a可以查看本机缓存的ARP数据. 但是ARP只能解决同一个子网中. 路由协议: 解决不在一个子网中的数据转发. 转到目标IP网段, 然后还是通过ARP获取目标机器.
  3. 传输层: 数据传输后的数据处理协议. 用于识别应用程序, 实现端口-端口的通信, 以及可靠数据传输.
  4. 应用层: 用于定义数据格式并按照格式进行数据解析. 例如http, ftp的解析协议.

应用层协议对该请求包做了格式定义;
紧接着传输层协议加上了双方的端口号,确认了双方通信的应用程序;
然后网络协议加上了双方的IP地址,确认了双方的网络位置;
最后链路层协议加上了双方的MAC地址,确认了双方的物理位置,同时将数据进行分组,形成数据帧,采用广播方式,通过传输介质发送给对方主机。而对于不同网段,该数据包首先会转发给网关路由器,经过多次转发后,最终被发送到目标主机。目标机接收到数据包后,采用对应的协议,对帧数据进行组装,然后再通过一层一层的协议进行解析,
最终被应用层的协议解析并交给服务器处理。

从socket到accept

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <sys/socket.h>

// 通过socket函数创建网络套接字
int socket(int domain, int type, int protocol);

// 通过bind函数给套接字分配地址
int bind(int sockfd, struct sockaddr *addr, socketlen_t addr_len);

// 通过listen函数监听socket
int listen(int sockfd, int backlog);

// 通过accept函数获取信息
int accept(int sockfd, struct sockaddr *addr, scoklen_t *addrlen);
  • listen_sock.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

// error_handle 处理错误
void error_handle(char *msg) {
    fputs(msg, stderr);
    fputc('\n', stderr);
    exit(1);
}

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

    struct sockaddr_in serv_addr;
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size;

    char message[] = "Say Hello";

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

    serv_sock = socket(PF_INET, SOCK_STREAM, argv[0]);
    if(serv_sock == -1) {
        error_handle("Create Error: 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]));

    bind_stat = bind(serv_socket, (struct sockaddr*) &serv_addr, sizeof(serv_addr));
    if(vind_stat == -1) {
        error_handle("Bind Error: bind() error");
    }

    listen_stat = listen(serv_socket, 5);
    if(listen_stat == -1) {
        error_handle("Lsiten Error: listen() error");
    }

    clnt_addr_size = sizeof(clnt_addr);
    clnt_sock = accept(serv_socket, (struct sockaddr*) &clnt_addr, &clnt_addr_size);
    if(clnt_sock == -1) {
        error_handle("Accept Error: accept() error");
    }

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

    return 0;
}
  • connect_sock.c
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main(int argc, char *argv[]) {
    
    int sock;

    struct sockaddr_in serv_addr;
    char message[30];
    int str_len;

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

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(sock == -1) {
        error_handle("Create Error: 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]));

    connect_sock = connect(sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr));
    if(connect_sock == -1) {
        error_handle("Connect Error: connect() error");
    }

    str_len = read(sock, message, sizeof(message)-1);
    if(str_len == -1) {
        error_handle("Read Error: read() error");
    }

    printf("Message from server: %s\n", message);
    close(sock);

    return 0;
}

epoll

epoll

eventpoll核心数据结构

fs/eventpoll.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/* 存储在文件结构的“ private_data”成员内部的数据结构,用于表示eventpoll接口的主要数据结构。*/
struct eventpoll {
    /* 调用 epoll_create 时会在内核中创建一个特殊的 file 节点 */
    struct file *file;
    /* 红黑树,用于存储监听的 fd,即 epoll_ctl 传过来的 fd*/
    struct rb_root_cached rbr;
    /* 双向链表,用于存储将要通过 epoll_wait 返回给用户的满足条件的事件 */
    struct list_head rdllist;
    /* 就绪链表,用于存储将从内核空间转移到用户空间的已就绪的 epitem */
    struct epitem *ovflist;
};
  1. epoll_create(int size)

该函数会在系统内核生成一个eventpoll实例, 并返回一个epoll的文件描述epfd.

如果epfd<0 为失败; >=0为成功.

  1. epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)

管理注册事件, 向epoll添加,删除和更新需要监听的fd. 操作如: EPOLL_CTL_ADD/EPOLL_CTL_DEL/EPOLL_CTL_MOD

fd: 需要监听的文件描述; event: epoll_event结构体.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
typedef union epoll_data {
   void        *ptr; // 可以用于区分储合处理不同的fd
   int          fd;  // 需要监听的文件描述
   uint32_t     u32;
   uint64_t     u64;
} epoll_data_t;

struct epoll_event {
   uint32_t     events;  // 表示监听的事件类型(EPOLLIN/EPOLLHUP/EPOLLOUT...)
   epoll_data_t data; // 用户自定义数据,当事件发生时将会原样返回给用户
};
  1. epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

等到监听事件到来, 返回值表示到来事件个数, 返回的事件存储在events中.

线程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#include <iostream>
// 引入线程thread头文件
#include <thread>

void hello() {
    std::cout << "Hello concurrent world\n";
}

int main() { // initial thread 应用级起始线程
    std::thread t(hello); // thread启动新的线程, 从hello开始 
    t.join();  // 主线程等待子线程结束后再结束
}

线程管控

管控: 线程启动–运行–消亡的控制.

  1. 启动线程
1
2
3
#include <thread>
void foo();
std::thread thread_func(foo);