1、语法类
-
强制类型转换
- static_cast:和C语言类似,(type)expression 注意:不执行类型检查,需要自己确保合法性
- dynamic_cast:应用于父子类层次结构的类型转换,要使用有效基类至少需要一个虚函数,运行时执行类型检查,转换失败返回空指针(对于指针类型)或者抛出异常(对于引用类型)
- const_cast:使用:const_cast <new_type> (expression),new_type 必须是一个指针、引用或者指向对象类型成员的指针
2、Socket通信
客户端主动发起请求,客户端---服务端:一般为多对一
通信:指定IP和端口号
关键点:IP、端口、通信数据
通信数据:两端(发送端和接收端)数据存储顺序要一致,否则接收端不能解析得到发送端一致的是数据,即需要字节序
2.1 字节序
单字节没有字节序问题,数据格式大于1个字节需要考虑字节序问题
1:大端(也称为网络字节序)
低高高低,低位字节存储在高地址
网络通信按照大端通信,且符合阅读顺序
2:小端(也称为主机字节序)
低低高高,低位字节存储低地址

3:大端小端转换
#include <arpa/inet.h>
// u:unsigned
// 16: 16位, 32:32位
// h: host, 主机字节序
// n: net, 网络字节序
// s: short
// l: int
// 这套api主要用于 网络通信过程中 IP 和 端口 的 转换
// 将一个短整形从主机字节序 -> 网络字节序
uint16_t htons(uint16_t hostshort);
// 将一个整形从主机字节序 -> 网络字节序
uint32_t htonl(uint32_t hostlong);
// 将一个短整形从网络字节序 -> 主机字节序
uint16_t ntohs(uint16_t netshort)
// 将一个整形从网络字节序 -> 主机字节序
uint32_t ntohl(uint32_t netlong);
以大端转小端为例:IPV4和IPV6都适用
step1:由于IP地址一般是使用字符串来表示,需要先将字符串转换成整型数
// 主机字节序的IP地址转换为网络字节序
// 主机字节序的IP地址是字符串, 网络字节序IP地址是整形
int inet_pton(int af, const char *src, void *dst);
step2:再将大端的整型数转换成小端的IP地址
#include <arpa/inet.h>
// 将大端的整形数, 转换为小端的点分十进制的IP地址
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
2.2 TCP通信
概念:传输层协议,可靠安全的连接,连接需要三次握手,断开需要四次挥手
先启动服务器进程,再启动客户端进程
套接字创建有两种:监听套接字、通信套接字
- 监听套接字:只需要有一个
- 通信套接字:有几个客户端和服务器连接就有几个
1:服务器端通信
①创建套接字,用于监听
int lfd = socket();
②绑定套接字IP端口
bind();
③监听客户端连接,检测有没有客户端(一次最多检测128个连接请求)
只需要调用一次
listen();
④等待并接受客户端请求,返回值为新的fd用于通信
int cfd = accept();
默认阻塞性函数,有客户端发起连接则解除阻塞,一次调用accept只能连接一个客户端
⑤通信,进行读写操作
⑥断开连接
close();
一次close(),进行两次挥手
2:客户端通信
①创建套接字,用于通信
int cfd = socket();
②连接服务器,指定服务器的IP和端口(注:输入参数IP为大端)
connect();
③通信,进行读写操作
④断开连接
close();
3:阻塞问题
上述函数中如 accept()、read()/write(),属于阻塞函数,无法处理多连接,无法实现并发
并发实现:
- 多线程
- 多进程
- 使用IO多路转接(复用)实现
- 使用IO多路转接 + 多线程实现
(1)采取多线程实现
- 主线程用于监听
- 子线程用于通信
父子线程共用同一个地址空间中的文件描述符,因此每当在主线程中建立一个新的连接,都需要将得到文件描述符值保存起来,不能在同一变量上进行覆盖
只要保证存储每个有效文件描述符值的变量对应不同的内存地址,在使用的时候就不会发生数据覆盖的现象,造成通信数据的混乱了

(2)采取多进程实现


僵尸进程:子进程已经结束,但是资源未被回收,仍然占据进程表的位置
2.3 线程池
频繁创建线程和销毁线程需要时间,因此引入线程池
组成:任务队列、工作线程、管理线程
- 任务队列
将要处理的任务加入队列,已经完成的任务从队列中删除
生产者线程:向任务队列中添加任务的线程
- 工作线程
消费者线程:读任务队列,取出任务并处理
任务队列为空,工作的线程被阻塞
- 管理线程
检测任务队列中的任务数量以及处于忙状态的工作线程数
线程池结构体:
// 线程池结构体
struct ThreadPool
{
// 任务队列
Task* taskQ;
int queueCapacity; // 容量
int queueSize; // 当前任务个数
int queueFront; // 队头 -> 取数据
int queueRear; // 队尾 -> 放数据
pthread_t managerID; // 管理者线程ID
pthread_t *threadIDs; // 工作的线程ID
int minNum; // 最小线程数量
int maxNum; // 最大线程数量
int busyNum; // 忙的线程的个数
int liveNum; // 存活的线程的个数
int exitNum; // 要销毁的线程个数
pthread_mutex_t mutexPool; // 锁整个的线程池
pthread_mutex_t mutexBusy; // 锁busyNum变量
pthread_cond_t notFull; // 任务队列是不是满了
pthread_cond_t notEmpty; // 任务队列是不是空了
int shutdown; // 是不是要销毁线程池, 销毁为1, 不销毁为0
};
809

被折叠的 条评论
为什么被折叠?



