参考 《TCP IP网络编程》
参考 《Linux高性能服务器编程》
参考 《深入浅出TCP/IP协议》
TCP/IP框架
- 链路层: 对电信号进行分组形成数据帧, 通过以太网广播, 通过数据帧的头部主要包含MAC(源和目标)进行网卡对比.
- 网络层: 定义网络地址, 区分网段, 子网内的MAC寻址, 不同子网的路由.
IP协议: 通过子网掩码对源/目标IP地址继续宁AND运算, 用于判断二者是否属于一个网络.
ARP协议: 地址解析协议, 通过IP地址获取MAC地址的网络层协议.
arp -a
可以查看本机缓存的ARP数据. 但是ARP只能解决同一个子网中.
路由协议: 解决不在一个子网中的数据转发. 转到目标IP网段, 然后还是通过ARP获取目标机器.
- 传输层: 数据传输后的数据处理协议. 用于识别应用程序, 实现端口-端口的通信, 以及可靠数据传输.
- 应用层: 用于定义数据格式并按照格式进行数据解析. 例如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);
|
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;
}
|
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;
};
|
epoll_create(int size)
该函数会在系统内核生成一个eventpoll
实例, 并返回一个epoll的文件描述epfd
.
如果epfd<0 为失败; >=0为成功.
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; // 用户自定义数据,当事件发生时将会原样返回给用户
};
|
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
2
3
|
#include <thread>
void foo();
std::thread thread_func(foo);
|
- 线程传参
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
#include <iostream>
#include <thread>
void func(int i, std::string const &s) {
std::cout << i << " " << s << std::endl;
}
int main() {
char buff[1024];
sprintf(buff, "%i", 3);
//
std::thread t(func, 3, std::string(buff));
t.join();
return 0;
}
|
mutex
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
|
#include <iostream>
#include <thread>
#include <mutex>
int num = 0;
std::mutex m;
void plus() {
// 创建锁
std::lock_guard<std::mutex> guard(m);
std::cout << num++ << std::endl;
}
int main() {
// 开启10个线程
std::thread threads[10];
// 注意运行
for (auto &i: threads) {
i = std::thread(plus);
}
// 等待所有thread结束
for (auto &thread: threads) {
thread.join();
}
return 0;
}
// g++ -std=c++11 -pthread -Wl,--no-sa-needed main.cpp -o main
// 解决编译出现的error: Enable multithreading to use std:🧵 Operation not permitted
|
或者cmake
1
2
3
4
5
|
cmake_minimum_required(VERSION 3.28)
project(cthread)
# 增加 -std
SET(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} "-std=c++11 -pthread")
add_executable(cthread main.cpp)
|
基础概念
函数类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
// 重载了括号操作符()的类
// 调用该类对象就像调用普通函数一样, 故称之函数类。
// 其实就是重载 () 操作符
class FuncObjType {
public:
void operator() () {
std::cout << "Hello c++" << std::endl;
}
};
// 以上类似以下函数
void func() {
std::cout << "Hello c++" << std::endl;
}
|
- 函数对象拥有自己状态, 可以在多次调用中共享使用。一般函数是不存在这样局部变量保存共享能力,需要全局维护
- 函数对象拥有特有类型,并于将特有类型作为参数进行处理, 普通函数是不存在的
Lambda 匿名函数
通过最前面[]明确指明其内部可以访问的外部变量,此为Lambda表达式“捕获”外部变量
形如传参, 包括值捕获,引用捕获和隐式捕获 三种方式