0%

C++(八)-网络

一、计算机网络基础

1、协议

通信双方需要遵循的规则。

2、TCP/IP协议

image-20240201105425483

image-20240201105651383

3、协议格式(==了解==)

在不同的层,都会有相应的包头,每个包头都会有对应的格式,这个作为了解,如果给出头的图,可以将其封装起来就可以了。

4、TCP协议(==重要==)

TCP协议是一个传输层的协议、面向连接的协议、可靠的协议、全双工的协议、字节流的协议、进行流量控制的协议 。

三次握手(建立连接)

image-20240201111121415

四次挥手(断开连接)

image-20240201111639421

2MSL

5、状态迁移图(==重要==)

一共有11中状态。

image-20240201141719014

2MSL时间、半关闭状态

image-20240201175645743

三、网络编程(==重要==)

1、基础

在网络环境中,要确定一台主机,需要知道彼此的ip;在网络环境中,要想确定一个进程,需要知道ip+port

2、字节序

TCP/IP协议规定,网络数据流应采用大端字节序

大端:低地址存高位,高地址存低位。小端:低地址存低位,高地址存高位。
网络字节序,就是在网络中进行传输的字节序列,采用的是大端法主机字节序,就是本地计算机中存储数据采用的字节序列,采用的是小端法

image-20240201143120741

字节序转换的函数

1
2
3
4
5
6
7
8
9
10
11
12
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);//h = host n = network l = long s = short
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

//将字符串类型的ip从本机字节序转换为网络字节序
in_addr_t inet_addr(const char *cp);

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

3、网络通信的原理图(逻辑图)

image-20240201144448624

4、网络通信常规函数(==重要==)

4.1、socket函数
1
2
3
4
5
6
7
8
9
10
11
#include <sys/types.h>     
#include <sys/socket.h>

//创建连接的通信点
//套接字函数
int socket(int domain, int type, int protocol);
//domain:协议族,常规的几种参数:AF_INET/AF_INET6/AF_UNIX
//type:SOCK_STREAM(TCP)/SOCK_DGRAM(UDP)
//protocol:一般都设置为0,使用默认协议

//返回值:返回的结果是一个文件描述符,该值是大于零。如果返回-1,就表明函数执行失败

==文件描述符,可以将其理解为指针、门把手。==

4.2、bind函数

绑定ip与端口号port

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
#include <sys/types.h> 
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//sockfd:就是socket函数调用的返回结果
//addr:是一个结构体,目的就是将ip与端口号传进来
//addrlen:结构体的长度

//返回值:成功返回0,失败返回-1

struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};

struct sockaddr_in
{
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};

struct in_addr
{
uint32_t s_addr; /* address in network byte order */
};

image-20240201152949424

4.3、listen函数

服务器需要监听客户端的连接。

1
2
3
4
5
6
7
8
#include <sys/types.h> 
#include <sys/socket.h>

int listen(int sockfd, int backlog);
//sockfd:是创建套接字socket的返回结果。
//backlog:用来指定监听上限数值,默认使用128

//返回值:成功返回0,失败返回-1.
4.4、accept函数

接收连接请求的函数,==阻塞等待客户端发起连接==

1
2
3
4
5
6
7
8
9
#include <sys/types.h>       
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd:就是socket函数调用的返回结果
//addr:通过该结构体,可以将客户端的ip与端口号反向的解出来。将网络字节序转换为本机字节序
//addrlen:结构体addr的长度

//返回值:会返回非负的整数,标识客户端与服务器已经建立了连接。可以通过该连接进行数据的传输。如果范返回失败,会得到-1
4.5、close函数

关闭文件描述符

1
2
3
4
#include <unistd.h>
int close(int fd);
//fd:需要关闭的文件描述符
//返回值:成功返回0, 失败返回-1.
4.6、connect函数

客户端与服务器进行连接的函数。

1
2
3
4
5
6
7
8
9
#include <sys/types.h>   
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//sockfd:由客户端调用socket函数返回的文件描述符
//addr:ip地址与端口号,客户端将服务器的ip与端口号设置到本addr中,进而为连接到服务器做准备
//addrlen:addr的长度

//返回值:成功返回0, 失败返回-1
4.7、读数据read/recv
1
2
3
4
5
6
7
8
9
10
11
12
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);
//从文件描述符fd中读取count个数据,放在buf开头的缓冲区中。
//函数的返回结果:成功的时候,会读取到所读的字节数;如果返回结果是0,表明读完了;如果小于0,表明出错了

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//从文件描述符sockfd中读取len个数据,放在buf开头的缓冲区中。如果flags=0,那么recv与read是等价的
//如果flags被设置为MSG_PEEK,那么数据会从内核态拷贝到用户态,并且数据不会从内核从删除。

==注意:read读数据的时候,会将数据直接移走(将内核中的数据移走),会将内核缓冲区清空。==

4.8、写数据write/send
1
2
3
4
5
6
7
8
9
10
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
//将buf指向的缓冲区中的count个数据,写入到fd对应的文件描述符中。

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//如果flags=0,那么send与write是等价的

5、代码实现

一、问题回顾

1、什么是协议?TCP/IP协议包括哪几层?什么是TCP协议?什么是三次握手?什么是四次挥手?

2、状态迁移图有哪11中状态?FIN_WAIT_2、TIME_WAIT、CLOSE_WAIT状态?

3、什么是大端存储?什么是小端存储?网络字节序与网络字节序采用什么存储方法?

4、网络编程常规函数有哪些?

二、端口复用

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

int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);

int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));

三、IO多路复用(==重难点==)

0、原理图

image-20240202093905100

1、select的使用

1.1、函数接口
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
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
//nfds:是监听的文件描述符的个数。最大文件描述符 +1
//文件描述符对应的有读事件、写事件、以及异常事件
//fd_set:本质是位图。
//readfds、writefds、exceptfds这三个参数是三个位图的集合,可以将文件描述符对应的读事件、写事件、异常事件分别放在三个参数中。
//timeout:表示的是时间,表明等待的时间长短。
//定时阻塞监控时间,3中情况:
//1、NULL,永远等下去
//2、设置timeval,等待固定时间
//3、设置timeval里时间均为0,检查描述字后立即返回,轮询

//函数的返回值:文件描述符的个数。也就是三个位图中满足条件的文件描述符的总和,也就是三个位图中1的个数。
//如果返回是-1,那就是异常

struct timeval
{
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};

struct timespec
{
long tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};

//将文件描述符fd从set中删除
void FD_CLR(int fd, fd_set *set);

//查看文件描述符fd是不是还在set中
int FD_ISSET(int fd, fd_set *set);

//将文件描述符fd放在set中进行监听
void FD_SET(int fd, fd_set *set);

//将位图中的每一位全部清空为0(初始化为0)
void FD_ZERO(fd_set *set);

fd=4 0 1 2 3

fd = 4; 0 1 2 3

1.2、位图

image-20240202102423847

3 5 8

readfs 3、5、8号文件描述符的读事件0 1 1

writefds 3、5号文件描述符的写事件0 1

exceptfds 3、5号文件描述符的异常事件1 0

1.3、优缺点
  • 监听的文件描述符是有上限的1024。
  • 当监听的文件描述符个数比较稀疏的时候(比如6, 600, 1023),循环判断比较麻烦,所以需要自定义数据结构:数组(在我们的代码中就是client数组)
  • 监听集合(也就是位图)与满足监听条件的集合(也就是位图)是同一个,需要将原有集合保存(allset)
  • 如果监听的文件描述符比较密集,那么select效率还是比较不错的。

2、poll的使用

2.1、函数接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

//fds:是一个结构体的数组
//nfds:数组的长度
//timeout 毫秒级等待
//-1:阻塞等,#define INFTIM -1 Linux中没有定义此宏
//0:立即返回,不阻塞进程
//>0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值。

//函数返回值:满足监听条件的文件描述符的数目


struct pollfd
{
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
events/revents:可以被设置为POLLIN(读)/POLLOUT(写)/POLLERR(异常)
2.2、优缺点
  • 突破了文件描述符1024的上限
  • 监听集合与返回的集合分离
  • 监听1000个文件描述符,但是只有3个满足条件,这样也需要全部遍历,效率依旧低

3、epoll的使用

3.1、函数接口
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
#include <sys/epoll.h>

//创建新的文件描述符
int epoll_create(int size);
int epoll_create1(int flags);
//size:从内核版本2.6.8以后,可以将size设置为大于0的值即可
//flags:直接将其设置为0,效果与epoll_create一样
//返回值:成功返回文件描述符,该值是大于0的,失败返回-1
//以上两个函数底层会创建一个红黑树


#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//epfd:epoll_create返回的文件描述符。
//op:可以被设置为EPOLL_CTL_ADD(添加)/EPOLL_CTL_MOD(修改)/EPOLL_CTL_DEL(删除)
//fd:传递文件描述符。
//event:该函数是一个结构体指针。

//返回值:成功返回0,失败返回-1

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 */
};
//events:可以是EPOLLIN(读)/EPOLLOUT(写)/EPOLLERR(异常)


#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//epfd:epoll_create返回的文件描述符。
//events:将满足条件的文件描述符存在该结构体指针中(也就是结构体数组)
//maxevents:前面的结构体的大小
//timeout:时间。timeout:是超时时间
//-1:阻塞
//=0:立即返回,非阻塞
//>0:指定毫秒

//返回值:成功返回有多少文件描述符就绪,出错返回-1
3.2、优缺点
  • 文件描述符数目没有上限:通过epoll_ctl()来注册一个文件描述符,内核中使用红黑树的数据结构来管理所有需要监控的文件描述符。
  • 基于事件就绪通知方式:一旦被监听的某个文件描述符就绪,内核会采用类似于callback的回调机制,迅速激活这个文件描述符,这样随着文件描述符数量的增加,也不会影响判定就绪的性能。
  • 维护就绪队列:当文件描述符就绪,就会被放到内核中的一个就绪队列中,这样调用epoll_weit获取就绪文件描述符的时候,只要取队列中的元素即可,操作的时间复杂度恒为O(1)
  • 对于大量连续文件描述符活跃的时候,epoll的效果不一定就比select强
3.3、两种模式(==了解==)

水平触发与边沿触发。

image-20240202170954547

四、5种网络IO模型(==重要==)

1、阻塞式IO

image-20240202172235740

2、非阻塞式IO

image-20240202172527923

3、IO多路复用

image-20240202172958230

4、信号驱动IO

image-20240202173619132

==总结:以上四种同步IO而言,第一阶段“等待数据”阶段有区别,但是在第二阶段,都是阻塞的。==

5、异步IO

image-20240202174052137

image-20240202174205776

在五种网络IO模型中,同步机制的时候,第二阶段都是阻塞的,但是对于异步IO而言,第二步是非阻塞的。只有异步IO才能达到真正的非阻塞的。

-------------本文结束感谢您的阅读-------------