C/C++ myls_goto
2024-03-30 18:56:03 0 举报
AI智能生成
C/C++ myls_goto
作者其他创作
大纲/内容
第一章 介绍
个人网站/邮箱
http://81.68.177.28/index.html
1933887389@qq.com
C/C++ 擅长领域涉及
游戏
客户端
CF
原神
DNF
我的世界
服务器
Skynet(网易游戏用的服务器框架)
C
lua
服务器
libevent
nignx
TinyHttp
其他
im
QQ
微信
其他聊天软件
嵌入式
冰箱
洗衣机
热水器
汽车
等其他
直播
快手
抖音
哔哩哔哩
YY
等其他直播平台
芯片
ARM
英特尔
等其他
量化交易
股票
等其他
自动驾驶
汽车公司
等其他
人工智能
大厂
神经网络
大厂
机器学习
大厂
内容
C++语法
数据结构与算法
TCP/IP 网络
系统底层
项目经验
第二章 C/C++语法
开始
C/C++语法
C/C++语法糖 封装继承多态 重载
C++对象模型
编译器工作原理
语言是给机器执行
查看汇编代码
数组定义和作为函数参数的区别
数组的定义
一片连续的空间
如果new 数组的话,是申请了数组自身空间+整数(数组大小)
数组访问越界会crash吗? 访问a[5]会如何?
访问不会,但写就会出现未知情况
数组访问越界通常不会导致程序崩溃,但可能会导致未定义的行为。
当访问数组越界时,程序可能会访问到不属于该数组的内存地址,从而导致未定义的行为。这可能会导致程序崩溃,也可能会导致其他错误。
当访问数组越界时,程序可能会访问到不属于该数组的内存地址,从而导致未定义的行为。这可能会导致程序崩溃,也可能会导致其他错误。
参数传递
只传递了地址 int a[]是个语法糖 int *a
DLL导类,调用者未变化,实现者增加了成员会如何
一个DLL或是一个SO导出了一个类
使用者拿到.h头文件和.dll或者.so可执行文件
.dll 或 .so的实现者呢,在类中定义添加了成员变量,更改了成员函数的实现,但是函数参数没有改变于是只给调用者更新了.dll和.so文件
类对象大小看谁?
头文件中类的定义
类导出的是什么?
函数
结果会产生一个未知的错误
类内static函数和一般成员函数有何区别
是否带this指针
普通函数与类中的static函数的区别?
普通的函数就是全局的作用域 限制在本文件使用
类中的static是看作用域是public才能对外可见 还可以访问自己类中的其他成员static成员函数
参数不同
this rcx指针传递到函数内
调用方式不同
类中的static函数需要带上作用域:: 或者 -> 或者 .
详细地谈const
const何时产生临时对象?
函数参数为const引用
传递常量时候会产生临时变量或对象
const 修饰成员函数成员?
用于函数返回值前
修饰返回值
函数参数列表中
加与不加是两个函数,临时对象
可以编译通过
函数参数列表后
const Class * const this
class A
{
int add(int a,const int &b)const //修饰this指针的
{
return a+b;
}
const A*get()
{
return *this;
}
};
int main()
{
A a;
const A*const pA=a.get();
pA->add(1,2);
return 0;
}
{
int add(int a,const int &b)const //修饰this指针的
{
return a+b;
}
const A*get()
{
return *this;
}
};
int main()
{
A a;
const A*const pA=a.get();
pA->add(1,2);
return 0;
}
const 和 constexpr的区别
constexpr是C++11后的新特性
可以用表达式计算常量,必须在编译时候确定具体的值
const
可以在运行时候指向变量
malloc和new的区别
malloc
C语言中stdlib中的函数
仅仅是申请了一块内存 返回值是void*
new
C++中的操作符
先申请一块内容
调用operator new 函数 底层还是调用malloc函数
调用构造函数
再给 对象 初始化
operatorNew 和 newOperator 的区别
operator new
是函数,用于分配内存的
new operator
操作符,先调用opertaor new函数分配内存 ,再调用构造函数来进行对象的初始化
delete 和 detele[]的区别
new[]出来的指针使用delete会如何?
两点补充
new[]出的内存大小 1024+8(64位)
new出来的是在堆上面的内存底层是一个双向链表串起来的 delete的时候要把这个双向链表里的这个节点移除下去和去其他没有被释放的内存连成一串
new出来的是在堆上面的内存底层是一个双向链表串起来的 delete的时候要把这个双向链表里的这个节点移除下去和去其他没有被释放的内存连成一串
operator delete / delete[] 底层调用operator delete 函数 或者 operator delete[]的函数
结果是未定义或者会发生crash
总结
用operator new[]申请的空间用delete释放结果是未定义的有可能产生程序内存泄露或者程序发生crash (取决于不同的编译器)
extern "C" 的含义
extern "C" 的作用
目的
告诉编译器用C语言的命名方式和调用约定
C语言无法实现函数的重载
引用外部用C语言写来编译函数名不可以更改或重载
比如在FFmpeg中就要使用 因为FFmpeg是用C语言写的
什么是函数重载
函数重载
2个特性
函数名相同
参数不同
C++如何实现函数重载的?
编译器
编译器根据参数把函数名称进行了重新命名
本质上还是两个函数名
C语言可以实现函数的重载吗?
不可以
函数名只有一种命名规则
struct 和 class的区别
正确的回答
struct默认的访问权限是public(成员,继承)
使用习惯不同,只定义数据用struct
class还可以用于模板定义,struct不可以用于模板定义
结构体成员的对齐问题
应用场景
C/S二进制协议开发
游戏开发
IM开发 比如QQ 微信 基于TCP协议
对齐方式
C/S对齐方式不同带来的问题
一种解决方案
约定对齐的方式
#pragma pack(push,1)
#pragma pack(pop)
#pragma pack(pop)
默认对齐规则
成员空间分配原则
以成员中出现的最大基础数据类型为分配粒度
小数据能凑则凑
例子
#pragma pack(push,1)
typedef struct S
{
//1+2+8+4 15
char c;
short s;
long long l;
int i;
}SS;
int main()
{
cout<<(sizeof(SS))<<endl;
return 0;
}
#pragma pack(pop)
typedef struct S
{
//1+2+8+4 15
char c;
short s;
long long l;
int i;
}SS;
int main()
{
cout<<(sizeof(SS))<<endl;
return 0;
}
#pragma pack(pop)
typedef struct S
{
//空结构体大小是1个字节
}SS;
{
//空结构体大小是1个字节
}SS;
typedef struct S
{
//4+1+2 8
int i;
char c;
short s;
}SS;
{
//4+1+2 8
int i;
char c;
short s;
}SS;
typedef struct S
{
//1+2+4+2 12
char c;
short s;
int i;
short sh;
}SS;
{
//1+2+4+2 12
char c;
short s;
int i;
short sh;
}SS;
typedef struct S
{
//1+8+2+4 24
char c;
long long l;
short s;
int i;
}SS;
{
//1+8+2+4 24
char c;
long long l;
short s;
int i;
}SS;
volatile变量
volatile 中文 易变的
说给编译器听的 给编译器传达不要优化的信息
不要直接放进CPU寄存器优化,每次要从内存中存取
stdcall函数的参数是否可变
windows api函数的参数是否可变?
stdcall
参数是被调用者退栈
参数必然是确定的不可变
cdecl
参数是调用者退栈,调用者知道传递了多少参数
支持参数可变
调用完一个函数它是怎么找到下一个执行代码的?
在RET指令RETRUN缩写RETURN指令里面就是返回他是把返回值放在栈里面然后在RETURNT退出的时候退出栈的时候从栈里面拿到函数的下一行代码也就是下一句指令这也就是函数调用它怎么返回来的
构造函数的初始化顺序
三种初始化方式
构造函数体
初始化列表
成员定义时候 (C++新特性 本质上和初始化列表是一样的)
初始化顺序
基类
初始化列表(定义时候赋初值) 同时存在是互斥的初始化列表取代掉定义时候赋初值
构造函数 函数体内进行初始化
指针和引用的区别
编译器眼里的
引用
变量的地址,只访问地址中的内容(只能对地址里面的内容进行++ --)
指针
变量的地址,不但可以访问地址中的内容,而且自身也可以运算(地址可以 ++ --)
人可以这么理解
引用
初始化时候必须指向一个对象,永远只操作对象本身(*p)
指针
分配内存,可赋空值,可进行运算
CString类, 实现构造函数,拷贝构造函数,普通赋值函数,右值引用赋值函数,析构函数
#include <iostream>
#include <string.h>
using namespace std;
class CString
{
public:
CString():m_data(nullptr),m_len(0){}
CString(const char *str):m_data(nullptr),m_len(0)
{
if(str!=nullptr)
{
m_len=strlen(str);
m_data=new char[m_len+1];
strcpy(m_data,str);
}
}
CString(const CString&other):m_data(nullptr),m_len(other.m_len)
{
if(other.m_data !=nullptr)
{
m_data=new char[m_len+1];
strcpy(m_data,other.m_data);
}
}
CString&operator=(const CString&other)
{
if(this!=&other)
{
if(m_data!=nullptr)
{
delete []m_data;
}
m_len=other.m_len;
if(other.m_data!=nullptr)
{
m_data=new char[m_len+1];
strcpy(m_data,other.m_data);
}
else
{
m_data=nullptr;
}
}
return *this;
}
CString&operator =(CString&&other)noexcept
{
if(this!=&other)
{
if(m_data!=nullptr)
{
delete []m_data;
}
m_data=other.m_data;
m_len=other.m_len;
//右值语义 偷梁换柱
other.m_data=nullptr;
other.m_len=0;
}
return *this;
}
~CString()
{
if(m_data!=nullptr)
{
delete[]m_data;
}
}
private:
int m_len;
char *m_data;
};
#include <string.h>
using namespace std;
class CString
{
public:
CString():m_data(nullptr),m_len(0){}
CString(const char *str):m_data(nullptr),m_len(0)
{
if(str!=nullptr)
{
m_len=strlen(str);
m_data=new char[m_len+1];
strcpy(m_data,str);
}
}
CString(const CString&other):m_data(nullptr),m_len(other.m_len)
{
if(other.m_data !=nullptr)
{
m_data=new char[m_len+1];
strcpy(m_data,other.m_data);
}
}
CString&operator=(const CString&other)
{
if(this!=&other)
{
if(m_data!=nullptr)
{
delete []m_data;
}
m_len=other.m_len;
if(other.m_data!=nullptr)
{
m_data=new char[m_len+1];
strcpy(m_data,other.m_data);
}
else
{
m_data=nullptr;
}
}
return *this;
}
CString&operator =(CString&&other)noexcept
{
if(this!=&other)
{
if(m_data!=nullptr)
{
delete []m_data;
}
m_data=other.m_data;
m_len=other.m_len;
//右值语义 偷梁换柱
other.m_data=nullptr;
other.m_len=0;
}
return *this;
}
~CString()
{
if(m_data!=nullptr)
{
delete[]m_data;
}
}
private:
int m_len;
char *m_data;
};
C++是如何实现多态
虚函数
虚表指针
父类虚函数表(存放的地址 地址指向具体的函数)
base::fun1
base::fun2
(父类的指针new子类对象的时候此时虚表指针会指向子类的虚函数表)子类虚函数表(子类的虚函数表的地址是指向子类的函数地址)
derived::fun1
derived::fun2
虚表指针是何时赋值的
虚表指针是属于谁?
属于对象的
虚表指针何时初始化的?
构造函数里初始化(编译器安插的代码)
总结
虚函数表指针它是属于对象的并且往往大部分编译器是放在第一个元素也就是说对象的第一个元素然后在构造函数的时候对它进行初始化
虚函数表存放在什么位置
编译器就可以确定的,(属于类的)对象共享的
静态存储区内
数据的只读存储区
代码段
cast 系列 指针转换区别 xxx_cast<new_type>(expression)
const_cast
去除只读属性
把const指针赋值给普通类型
static_cast(往往用在父类和子类之间的转换)(只做值的位移检查)
只做类型检查(子类到父类或者父类到子类会检查有没有继承关系),不做安全检查
向上向下都可以转,只做移位操作
dynamic_cast(正是因为dynamic_cast有类型检查所以它的速度运行速度肯定是比static_cast慢一些的因为它有一个函数调用有一个类型的检查)
做类型和RTTI(运行时的)检查 是否是继承关系上不是返回一个NULL的处理
向下转型,失败返回空(因为有运行时的运行检查)
reinterpret_cast (只是帮忙做类型切换,类型匹不匹配它不管,它只是保证C++语法 能编译过去,但是值它是不变的,靠程序员自行来判断)
不做类型检查
直接转换,靠程序员自己自行判断,比较危险!
rtti
什么是RTTI
Run-Time Type Information
运行时的运行信息(什么是运行时比如说虚函数多态用一个父类的指针区New了一个子类编译的时候它是没有办法确定this指针的到底是指向基类还是指向子类它是没有办法确认的就在运行的时候才能决定所以这就叫做运行时)
type_info
它的构造还是delete 类型 也就是说它是不可以访问的禁止访问的,构造不可以访问还有赋值也不可以访问,所以只能当作右值在使用
dynamic_cast
可以是安全的类型转换
从上往下转换,从父类转子类的时候才用到了RTTI的信息,如果说不是一个继承链上它会返回一个空指针
如何获取RTTI
typeid
可以取typeinfo这个对象,这个对象是不可访问的仿函数,只能访问里面的成员函数,比如说常用的name(到底是什么类),还有一个是判断是否相等
可以用来做类型判断
typeid往往做类型判断的使用,C++里面是没有反射的,而java go 都有反射,如何获取类型信息typeid就是很重要,可以判断类型,可以提取出来它是什么类型的
RTTI存储在哪里
存储在虚函数表的附近
RTTI信息本身也是一个只读的,类似于虚表一样也是只读的,虚表它可以存在只读存储区也可以放在代码段看不同的编译器不同的实现,RTTI的指针往往存在在虚表的附近有的编译器是放在虚表的上面上一个位置当然不同的编译器有不同的实现。
(注意)指针指向的那块空间对象是一样的,但是指针类型是不一样的
两个父类都有虚函数,子类至少有多大
一个子类,同时继承了都带有虚函数的父类,问:子类最小多大?(第一种答案:子类有8个字节把虚函数合并成一个虚函数表。第二种答案:两个8字节分别指向两个父类的虚表。 也就是说子类的虚表可以是一个核心的起点在于合并还是不合并的问题。)
子类是否会合并虚函数表?
static_cast运算
继承两个虚函数类它的子类至少也是16个字节分别两存放不同的虚函数表
析构函数加不加virtual有何区别
是否是虚函数
析构函数加virtual的话意为这它的子类也是虚函数,这形成了多态。如果不加父类里面就没有虚函数那就是没有虚表所以会直接调用析构函数所以就不会实现多态,所以直接到了父类的析构函数它并没有调用到子类的析构函数并且还存在问题因为obj父类的所以这里不但没有实现多态还可能产生一些不可预期的错误
子类析构函数是否具备多态特性
我们在delete父类的时候它会调用继承类的析构函数
如何创建线程私有数据
C++11的新特性
如何定义线程的私有数据
thread_local 变量生命周期如何?
例子
thread_local int x=0; //调试时候断点失效,语句不会被执行,在编译的时候就已经确定了,不会像栈上的数据一样是在运行期间的时候才会给它分配空间并且赋初始值所以那种断点是可以断到的而这种断不到是类似于static它是存储在某个区域里面然后并且是在执行前就已经给它分配好了这块空间所以我们在动态执行的时候这句话是没办法打断点的。static修饰的局部变量它是在作用域是在函数内但是它的生命周期是在外面是静态存储区整个程序的进程空间,那thread_local的生命周期随着线程的存在而存在线程的消亡而消亡线程没有消亡它就是一直存在的
thread_local变量不同线程地址为何不同?
例子
gs:global segment 全局数据段 (都共享的)
tls:qword ptr gs:[58h]
thread local storage 线程局部存储
thread local storage 线程局部存储
不同的线程都有自己的寄存器和寄存器的值,所以不同的线程看到的拿到的gs里面的数据是不一样的
所以两次调用x的地址是不一样的
lambda表达式 (类似于其他语言中的闭包)
[](){}
[] 捕获区 支持 值+引用
() 函数参数
{}函数体
可以理解为绑定对象的函数
class A
{
public:
operator(params){}
private:
[]捕获的作为成员
};
{
public:
operator(params){}
private:
[]捕获的作为成员
};
总结
对于lambda表达式来说引用就是把地址传递下来传递到新的构造函数成员也就是新的类类里面的成员捕获的是类的成员捕获的是引用那类的成员就是它的指针,而捕获value的时候就会把vector整个拷贝过来也就是作为新的类里面的成员它是vector,vector作为拷贝然后在析构的时候在这vertor进行释放,这就是值捕获和引用捕获的区别。
右值引用与移动语义与完美转发
右值引用
什么是右值引用?(对右值的引用)
右值(无法取地址)的引用 (对程序员而言)
&&
指针(对编译器而言)
左值引用默认的引用编译器语义而言也是指针只不过它在使用的时候对指针的内容进行取,而右值引用对编译器来说一样也是指针,无差别的
为什么要有右值引用?
效率(复用将亡值)使用了一次复制
左值引用不可实现吗?
可以,但是使用习惯不同
总结
右值
右值引用是为了提升效率去复用一个将亡值里面内存
左值
左值引用是往往用法不一样它如果说要复用将亡值也是可以的但是使用习惯不一样因此推出了一个右值引用概念
移动语义
如何复用一个将亡左值,提升效率?
把左值赋给右值 std::move
使用完之后极有可能不在使用了 这就是移动语义
在opeartor= 中 偷梁换柱
完美转发
右值引用变量是左值还是右值?
(本身是可以取地址的)右值引用本身是一个左值
如何保证函数使用过程中右值属性不变?
std::forward
完美转发
可以保持右值引用还是右值引用属性
可以保持左值引用还是左值引用属性
为什么左值引用和右值引用可以转化呢?
底层左值引用和右值引用对编译来说它都是一个指针所以左值引用通过语义std::move可以把它转换成右值引用,右值引用在传递的过程中默认的它会变成左值我们要一直保持右值引用的属性不变那就借助std::forward也就是所谓的完美转发
总结
首先右值引用它和左值引用一样对编译器来说都是取的指针
右值引用可以理解为语法糖但是右值引用使用的时候它的目的是为了效率而来也就是说为了解决将亡值的问题,将亡值往往是一个变量一个左值要复用它,所以左值要转换成右值那这个时候要用std::move,看到std::move大概率要把里面的对象废掉复用里面的内存来提升效率
传递的过程中右值引用的属性会丢失索引,我们要用std::forward取保持它的属性 std::forward就是完美转发
override 和 final 的作用(都是用在virtual )
override
编译器来规避虚函数编码错误
覆盖父类的虚函数
override 表面重写了某个虚函数,避免写错时候新建了一个虚函数
final
不可以继承子类不可以在实现它
则不允许派生类进行覆盖/重写
三个ptr智能指针的区别
unique_ptr
独一的独占的
目的
独享变量
自己享有管理这个对象的生命周期
为什么不要auto_ptr
被赋值后置成NULL
shared_ptr
目的
共享变量
底层用了
共享指针
共享引用计数
weak_ptr
为了解决shared_ptr的循环引用而来
例子:
class A
shared_ptr<B>
shared_ptr<B>
class B
shared_ptr<B>
shared_ptr<B>
共享shared_ptr引用计数
共享引用计数对象
使用lock接口来获取对象指针
shared_ptr(不支持多线程)
管理生命周期的方式
引用计数
赋值+1
销毁-1
减0 释放对象
shared_ptr 对象分配在哪里
栈上
引用计数也必须在堆上
传递值的时机
初始化的时候
构造函数
赋值
operator=函数
代码实现
#include <iostream>
#include <string.h>
using namespace std;
template<typename T>
class CS
{
public:
CS(T*obj)
{
if(obj!=nullptr)
{
m_ptr=obj;//m_ptr 是它的对象
m_cout=new int(1);//引用计数设置为1
}
}
CS(const CS&other)
{
if(other.m_cout != nullptr)
{
++(*other.m_cout);
m_ptr=other.m_ptr;
m_cout=other.m_cout;
}
}
//这样写有问题
// CS&operator=(const CS&other)
// {
// if(this!=&other)
// {
// if(other.m_cout != nullptr)
// {
// ++(*other.m_cout);
// m_ptr=other.m_ptr;
// m_cout=other.m_cout;
// }
// }
// return *this;
// }
CS&operator=(const CS&other)
{
if(this!=&other)
{
//用临时的变量做交互解决问题
//生成新的临时对象然后在继续交换,临时对象结束的时候会-1自身的销毁
CS(other).swap(*this);
}
return *this;
}
~CS()
{
if(m_cout != nullptr)
{
--(*m_cout);
if(0==*m_cout)
{
delete m_ptr;
}
}
}
private:
void swap(CS&other)
{
std::swap(m_ptr,other.m_ptr);
std::swap(m_cout,other.m_cout);
}
T*m_ptr;
int*m_cout;
};
int main()
{
CS<string>p1=new string("aa");
CS<string>p2=p1;
p2=p1;
return 0;
}
#include <string.h>
using namespace std;
template<typename T>
class CS
{
public:
CS(T*obj)
{
if(obj!=nullptr)
{
m_ptr=obj;//m_ptr 是它的对象
m_cout=new int(1);//引用计数设置为1
}
}
CS(const CS&other)
{
if(other.m_cout != nullptr)
{
++(*other.m_cout);
m_ptr=other.m_ptr;
m_cout=other.m_cout;
}
}
//这样写有问题
// CS&operator=(const CS&other)
// {
// if(this!=&other)
// {
// if(other.m_cout != nullptr)
// {
// ++(*other.m_cout);
// m_ptr=other.m_ptr;
// m_cout=other.m_cout;
// }
// }
// return *this;
// }
CS&operator=(const CS&other)
{
if(this!=&other)
{
//用临时的变量做交互解决问题
//生成新的临时对象然后在继续交换,临时对象结束的时候会-1自身的销毁
CS(other).swap(*this);
}
return *this;
}
~CS()
{
if(m_cout != nullptr)
{
--(*m_cout);
if(0==*m_cout)
{
delete m_ptr;
}
}
}
private:
void swap(CS&other)
{
std::swap(m_ptr,other.m_ptr);
std::swap(m_cout,other.m_cout);
}
T*m_ptr;
int*m_cout;
};
int main()
{
CS<string>p1=new string("aa");
CS<string>p2=p1;
p2=p1;
return 0;
}
设计模式 Singleton 单例模式(单例基本用于运行程序中第一次使用结束到程序挂掉)
单例模式(保证一个类只有一个实例)
生活中的单例
皇上
导演
作家
等等 ... ...
代码
#include <iostream>
using namespace std;
//传统的方式 private 是不可用了 A a; new A;
//static func 保证了唯一性
class Singleton
{
public:
static Singleton *getInstance()
{
if(_ins==nullptr)
{
_ins=new Singleton;
}
return _ins;
}
private:
static Singleton *_ins;
private:
//构造器 和 析构器 私有化
//意为这常规的构造器 和 析构器 不可以使用
Singleton(){}
~Singleton(){}
Singleton(const Singleton&s){}
Singleton&operator=(const Singleton&s){}
};
Singleton *Singleton::_ins=nullptr;
int main()
{
Singleton*ps=Singleton::getInstance();
return 0;
}
using namespace std;
//传统的方式 private 是不可用了 A a; new A;
//static func 保证了唯一性
class Singleton
{
public:
static Singleton *getInstance()
{
if(_ins==nullptr)
{
_ins=new Singleton;
}
return _ins;
}
private:
static Singleton *_ins;
private:
//构造器 和 析构器 私有化
//意为这常规的构造器 和 析构器 不可以使用
Singleton(){}
~Singleton(){}
Singleton(const Singleton&s){}
Singleton&operator=(const Singleton&s){}
};
Singleton *Singleton::_ins=nullptr;
int main()
{
Singleton*ps=Singleton::getInstance();
return 0;
}
应用代码
Config (A B C 均要用到配置文件 xx.conf 不使用全局变量 -------》 单例)
main.cpp
#include <iostream>
#include "a.h"
#include "b.h"
#include "c.h"
using namespace std;
//A B C 均要用到配置文件 xx.conf 不使用全局变量 -------》 单例
//单例 是 共享数据,取代全局变量,行之有效的方案
int main()
{
A a;
B b;
C c;
return 0;
}
#include "a.h"
#include "b.h"
#include "c.h"
using namespace std;
//A B C 均要用到配置文件 xx.conf 不使用全局变量 -------》 单例
//单例 是 共享数据,取代全局变量,行之有效的方案
int main()
{
A a;
B b;
C c;
return 0;
}
config.h
#ifndef CONFIG_H
#define CONFIG_H
#include <iostream>
#include <string>
using namespace std;
struct info
{
string ip,port;
};
class Config
{
public:
static Config *getIns();
string getIp();
string getPort();
private:
static Config*_ins;
struct info infor;
private:
Config();
Config(const Config&c);
Config&operator=(const Config&c);
~Config();
};
#endif // CONFIG_H
#define CONFIG_H
#include <iostream>
#include <string>
using namespace std;
struct info
{
string ip,port;
};
class Config
{
public:
static Config *getIns();
string getIp();
string getPort();
private:
static Config*_ins;
struct info infor;
private:
Config();
Config(const Config&c);
Config&operator=(const Config&c);
~Config();
};
#endif // CONFIG_H
config.cpp
#include "config.h"
#include <fstream>
#include <iostream>
Config*Config::_ins=nullptr;
Config *Config::getIns()
{
if(_ins==nullptr)
{
_ins=new Config;
}
return _ins;
}
Config::Config()
{
fstream fs;
fs.open("ip_and_port.conf",ios::in|ios::out);
if(!fs)
{
cout<<"open error"<<endl;
}
fs>>infor.ip;
fs>>infor.port;
fs.close();
}
string Config::getIp()
{
return infor.ip;
}
string Config::getPort()
{
return infor.port;
}
#include <fstream>
#include <iostream>
Config*Config::_ins=nullptr;
Config *Config::getIns()
{
if(_ins==nullptr)
{
_ins=new Config;
}
return _ins;
}
Config::Config()
{
fstream fs;
fs.open("ip_and_port.conf",ios::in|ios::out);
if(!fs)
{
cout<<"open error"<<endl;
}
fs>>infor.ip;
fs>>infor.port;
fs.close();
}
string Config::getIp()
{
return infor.ip;
}
string Config::getPort()
{
return infor.port;
}
a.h
#ifndef A_H
#define A_H
#include "config.h"
class A
{
public:
A();
};
#endif // A_H
#define A_H
#include "config.h"
class A
{
public:
A();
};
#endif // A_H
a.cpp
#include "a.h"
A::A()
{
Config*Conf=Config::getIns();
cout<<"ip:"<<Conf->getIp()<<endl;
cout<<"Port:"<<Conf->getPort()<<endl;
}
A::A()
{
Config*Conf=Config::getIns();
cout<<"ip:"<<Conf->getIp()<<endl;
cout<<"Port:"<<Conf->getPort()<<endl;
}
b.h
#ifndef B_H
#define B_H
#include "config.h"
class B
{
public:
B();
};
#endif // B_H
#define B_H
#include "config.h"
class B
{
public:
B();
};
#endif // B_H
b.cpp
#include "b.h"
B::B()
{
Config*Conf=Config::getIns();
cout<<"ip:"<<Conf->getIp()<<endl;
cout<<"Port:"<<Conf->getPort()<<endl;
}
B::B()
{
Config*Conf=Config::getIns();
cout<<"ip:"<<Conf->getIp()<<endl;
cout<<"Port:"<<Conf->getPort()<<endl;
}
c.h
#ifndef C_H
#define C_H
#include "config.h"
class C
{
public:
C();
};
#endif // C_H
#define C_H
#include "config.h"
class C
{
public:
C();
};
#endif // C_H
c.cpp
#include "c.h"
C::C()
{
Config*Conf=Config::getIns();
cout<<"ip:"<<Conf->getIp()<<endl;
cout<<"Port:"<<Conf->getPort()<<endl;
}
C::C()
{
Config*Conf=Config::getIns();
cout<<"ip:"<<Conf->getIp()<<endl;
cout<<"Port:"<<Conf->getPort()<<endl;
}
cocos2dx游戏引擎中导演的单例
auto dir=Director::getInstance();
以及含有二段式初始化
单例 是 共享数据,取代全局变量,行之有效的方案
C/C++语言完结
理解编译器工作原理
<<C++对象模型>>
汇编
第三章 数据结构与算法
算法篇
开始
链表反转
链表合并
区间合并
图像旋转
矩阵相乘
两数之和
LRU缓存
最长无重复字符串
字符串表达式运算
非递归遍历二叉树
二叉树公共最近祖先
数组全排列
连续子数组最大和
最长公共字串
最长回文子串
数组子集
哈希表实现原理
string内部数据结构
verctor考点
数组
map考点
B+树与索引
算法结束
单向链表
子主题
排序
选择
冒泡
快速
插入
希尔
查找
二分查找
线性查找
myString ---------- 函数
mystrlen
mystrcpy
mystrcat
mystrcmp
myString ———— cpp
myString
子主题
子主题
子主题
第四章 TCP/IP 网络
开始
IP
TCP/UDP
HTTP
WebSocket
HTTP2.0/HTTP3.0
校园电脑如何访问百度?
在校园/公司内访问百度,数据是如何从本机到服务器之间往返?
在校园或公司内访问百度时,数据在本机和服务器之间的往返需要经过以下步骤:
当你输入百度网址并按下回车键时,浏览器会向百度的服务器发送一个HTTP请求。这个请求中包含了你的IP地址、请求的网页地址(URL)、浏览器类型和版本等信息。
百度的服务器收到请求后,会根据请求中的URL查找对应的网页内容。找到内容后,服务器会将网页内容返回给浏览器。
浏览器收到服务器返回的网页内容后,会解析HTML、CSS和JavaScript代码,然后将网页显示在你的电脑上。
如果你在网页中输入了信息并提交表单,浏览器会将你的信息发送至百度的服务器。服务器收到信息后,会根据相应的服务逻辑处理你的请求,然后将处理结果返回给浏览器。
浏览器收到服务器返回的结果后,会解析并显示给你。
在整个过程中,数据在本机和服务器之间的往返主要是通过HTTP协议进行的。HTTP协议是一种无状态协议,即服务器不会存储客户端的请求信息。每次请求都会包含完整的请求信息,服务器处理完请求后,会将响应结果返回给客户端,然后断开连接。
当你输入百度网址并按下回车键时,浏览器会向百度的服务器发送一个HTTP请求。这个请求中包含了你的IP地址、请求的网页地址(URL)、浏览器类型和版本等信息。
百度的服务器收到请求后,会根据请求中的URL查找对应的网页内容。找到内容后,服务器会将网页内容返回给浏览器。
浏览器收到服务器返回的网页内容后,会解析HTML、CSS和JavaScript代码,然后将网页显示在你的电脑上。
如果你在网页中输入了信息并提交表单,浏览器会将你的信息发送至百度的服务器。服务器收到信息后,会根据相应的服务逻辑处理你的请求,然后将处理结果返回给浏览器。
浏览器收到服务器返回的结果后,会解析并显示给你。
在整个过程中,数据在本机和服务器之间的往返主要是通过HTTP协议进行的。HTTP协议是一种无状态协议,即服务器不会存储客户端的请求信息。每次请求都会包含完整的请求信息,服务器处理完请求后,会将响应结果返回给客户端,然后断开连接。
在两个校园局域网内的两个学生,是否可以通过P2P进行视频连续?
是的,两个校园局域网内的学生可以通过P2P(点对点)方式进行视频通话。P2P是一种网络拓扑结构,允许数据直接在客户端之间传输,而不需要经过中心服务器。这种方式可以减少服务器负担,提高传输效率。
对于视频通话,可以使用不同的P2P技术,如WebRTC。WebRTC是一种实现实时音视频通信的开源技术,允许在浏览器之间进行点对点的音视频和数据传输。两个学生可以通过WebRTC在各自的局域网内建立连接,然后通过NAT穿透等技术(如STUN、TURN服务器)在不同网络之间建立连接。这样,他们就可以进行视频通话了。
需要注意的是,由于网络环境复杂,P2P通信可能会受到防火墙、路由器设置等因素的影响。在实际应用中,可能需要对网络环境进行适当的配置和调整。
对于视频通话,可以使用不同的P2P技术,如WebRTC。WebRTC是一种实现实时音视频通信的开源技术,允许在浏览器之间进行点对点的音视频和数据传输。两个学生可以通过WebRTC在各自的局域网内建立连接,然后通过NAT穿透等技术(如STUN、TURN服务器)在不同网络之间建立连接。这样,他们就可以进行视频通话了。
需要注意的是,由于网络环境复杂,P2P通信可能会受到防火墙、路由器设置等因素的影响。在实际应用中,可能需要对网络环境进行适当的配置和调整。
跟踪一下路由 traceroute/tracert
tracert www.baidu.com
解答
首先www.baidu.com进行解析然后寻找这个IP地址寻找的时候进行NAT把私网IP换成公网IP,经过物理层寻找MAC2地址然后再查下一级的MACN然后继续查MAC12然后 找到了百度公网IP然后再落到实际的服务器上面,然后再进行转发此时需要通过公网IP 去回来,再公网查找的时候类似于快递中转站。
TCP协议长度
TCP协议的最低长度是多少
TCP分层协议
40字节是错误 TCP是可以脱离IP协议并不是在一起的
20字节是正确的
TCP的上层协议未必就是IP协议,因为可以支持其他网络
TCP协议头
IP分片和TCP分段
TCP分段后还需要IP分片吗?
不需要。TCP协议已经在传输层对数据进行了分段,形成了TCP数据包。当TCP数据包到达网络层时,IP协议会对TCP数据包进行封装,添加IP头信息。在这种情况下,如果TCP数据包的大小超过了IP协议的最大传输单元(MTU),那么IP协议会对TCP数据包进行分片。
但是,通常来说,网络设备的MTU值都已经设置得足够大,因此TCP数据包通常不需要进行IP分片。如果在特殊的网络环境下,确实需要IP分片,那么IP协议会对TCP数据包进行分片。
总的来说,TCP分段后通常不需要再进行IP分片,因为TCP协议已经对数据进行了合理的分段和处理。
但是,通常来说,网络设备的MTU值都已经设置得足够大,因此TCP数据包通常不需要进行IP分片。如果在特殊的网络环境下,确实需要IP分片,那么IP协议会对TCP数据包进行分片。
总的来说,TCP分段后通常不需要再进行IP分片,因为TCP协议已经对数据进行了合理的分段和处理。
谈一下IP分片和TCP分段?
IP分片和TCP分段是计算机网络中两个重要的概念,它们都是用于处理数据传输过程中的大数据包的问题。
IP分片是将一个大的IP数据包分成多个小的IP数据包,以便于在Internet上进行传输。当数据包的大小超过网络设备的MTU(最大传输单元)时,IP协议会将数据包分成多个小数据包,每个小数据包都包含原数据包的头部信息和一个新的数据部分。在接收端,IP协议会将这些小数据包重新组合成原来的大数据包。分片可以提高网络的传输效率,但也可能带来一些问题,如数据包丢失、重组错误等。
TCP分段是将TCP数据包分成多个小的段,以便于在TCP传输过程中进行处理。TCP协议是一种可靠的传输协议,它通过序列号、确认应答、重传机制等方式来保证数据的正确传输。当TCP数据包的大小超过接收端缓冲区的尺寸或者网络带宽的限制时,TCP协议会将数据包分成多个小的段,每个小段都包含原数据包的头部信息和一部分数据。接收端会根据头部信息对这些小段进行顺序重组,以恢复原来的数据包。TCP分段可以保证数据的可靠传输,但也会增加传输延迟和数据处理的复杂性。
总的来说,IP分片和TCP分段都是用于处理大数据包的问题,但它们的工作机制和目的不同。IP分片主要用于在Internet上进行大数据包的传输,而TCP分段主要用于保证数据的可靠传输和在TCP传输过程中进行处理。
IP分片是将一个大的IP数据包分成多个小的IP数据包,以便于在Internet上进行传输。当数据包的大小超过网络设备的MTU(最大传输单元)时,IP协议会将数据包分成多个小数据包,每个小数据包都包含原数据包的头部信息和一个新的数据部分。在接收端,IP协议会将这些小数据包重新组合成原来的大数据包。分片可以提高网络的传输效率,但也可能带来一些问题,如数据包丢失、重组错误等。
TCP分段是将TCP数据包分成多个小的段,以便于在TCP传输过程中进行处理。TCP协议是一种可靠的传输协议,它通过序列号、确认应答、重传机制等方式来保证数据的正确传输。当TCP数据包的大小超过接收端缓冲区的尺寸或者网络带宽的限制时,TCP协议会将数据包分成多个小的段,每个小段都包含原数据包的头部信息和一部分数据。接收端会根据头部信息对这些小段进行顺序重组,以恢复原来的数据包。TCP分段可以保证数据的可靠传输,但也会增加传输延迟和数据处理的复杂性。
总的来说,IP分片和TCP分段都是用于处理大数据包的问题,但它们的工作机制和目的不同。IP分片主要用于在Internet上进行大数据包的传输,而TCP分段主要用于保证数据的可靠传输和在TCP传输过程中进行处理。
TCP最大分段(MSS)最大值是多少,为什么?
TCP最大分段大小(MSS)是指TCP数据包中数据部分的最大尺寸,它的最大值通常被设定为1460字节。这个值是根据IPv4头部的最大长度(64字节)和以太网帧的最大长度(1518字节)来计算的。在TCP协议中,MSS值必须小于或等于IP分组中可用的数据空间,因为TCP头部的尺寸也必须要被考虑在内。
将MSS值设定为1460字节的原因是因为这样可以在大多数网络环境下避免IP分片。分片会增加网络的复杂性,也会降低网络传输的效率,因此通常应该尽量避免。通过将MSS值设定为1460字节,可以确保TCP数据包在大多数网络环境中都可以在不进行分片的情况下进行传输。
需要注意的是,MSS值并不是固定的,它可以根据网络环境进行调整。在一些高速网络环境中,更大的MSS值可能会提高网络的传输效率。然而,在其他一些网络环境中,过大的MSS值可能会导致数据包无法传输,因此需要根据具体的网络环境来选择合适的MSS值。
将MSS值设定为1460字节的原因是因为这样可以在大多数网络环境下避免IP分片。分片会增加网络的复杂性,也会降低网络传输的效率,因此通常应该尽量避免。通过将MSS值设定为1460字节,可以确保TCP数据包在大多数网络环境中都可以在不进行分片的情况下进行传输。
需要注意的是,MSS值并不是固定的,它可以根据网络环境进行调整。在一些高速网络环境中,更大的MSS值可能会提高网络的传输效率。然而,在其他一些网络环境中,过大的MSS值可能会导致数据包无法传输,因此需要根据具体的网络环境来选择合适的MSS值。
TCP/IP四层
结构
应用层 敲代码
传输层 TCP
网络层 ip
链路层 MAC地址
TCP/IP四层结构详细图
为什么MSS不能大于MTU-40
IP层不保证顺序,不保证可靠性(允许丢包)
增加了TCP层的重传负担,影响了性能
TCP报文的序列号为什么不是从0或者从1开始的
反正法
解答
TCP报文的序列号为什么不是从0或者从1开始的
防止被下一个相同四元组接收(对TCP效率造成了困扰)
安全考虑(被模拟攻击)
如何判断TCP连接是否还在存活
两种情况
电脑突然被拔掉网线
TCP 协议层 无法感知到连接被断开
电脑突然被断电了
TCP 协议层 无法感知到连接被断开
解答
如何判断TCP连接是否还在存活?
应用层 --》》 设计C/S心跳包
协议层 --》》 检测ACK包
TCP三次握手
三次握手过程
回答
客户端发出一个SYN包 ,服务器回一个SYN包和ACK包,客户端再发一个ACK包
Syn Flood攻击原理是什么?
在Syn Flood攻击中,攻击者通过伪造大量的IP地址,向目标服务器发送大量的TCP SYN请求。由于这些请求都是伪造的,服务器会响应每个请求,并等待客户端的确认。然而,由于这些请求都是伪造的,客户端并不会发送确认数据包。这导致服务器的网络连接队列被占满,从而无法处理正常的请求。
结合Sock api谈一下细节?
(阻塞模式)首先客户端会建立一个Socket的,Socket之后客户端调用connect 和 服务器进行连接 ,服务器在和客户端连接之前 首先要创建socket bind listen ,监听之后客户端发起一个connect进行连接,也就是三次握手开始了,最后服务器收到一个SYN包,收到SYN包之后listen就会有反应了,之后server会调用accept函数 会在accept内部发送SYN和Ack给客户端,客户端再回ACK给服务器,此时建立了真正的通道。客户端发送SYN的时候有syn_rcvd队列,收到客户端收到ack时候会有accept队列
TCP四次握手
TCP四次握手过程
回答
客户端先发起FIN或者服务器发起FIN,服务器收到了然后AKC一下,然后服务器也FIN,然后客户端AKC一下
解答
客户端 FIN是我不发送的意思,服务器我收到了给个确认 但是服务器这边还有没发完的数据,有可能也发完了,也有可能没发完,然后服务器把数据发完了这个时候客户端可以接受数据了因为它是全双工两个通道它只是发送的通道关闭了但是可以客户端继续接受然后再处理,服务器这边数据发送完毕了服务器也不再发送数据了然后发一个FIN,客户端再回复一个AKC,服务器没有发送FIN的时候服务器处于close_wait的过程
服务器close_wait是怎么形成的,如何定位问题?
原因:sever端socket没有被close但是服务器已经断点了
定位:复现问题 (netstat) (去查看什么条件产生了什么行为或者用模拟的测试工具来发送查看服务器是怎么产生的),借助工具(perf(看堆栈,看资源是否增长,销毁),火焰图(看堆栈什么时候生成是否有没有释放)等)
子主题
TCP滑动窗口的作用是什么
TCP滑动窗口的作用是什么?
窗口大小16位
数据发送过程
滑动窗口的作用就是流量控制的作用它可以决定我们流量发多大或者发多小
如何设计QQ聊天协议
如何设计QQ聊天协议?
考点
TCP协议代码
解答
协议选择 TCP
字段考虑
报文长度 发送方QQ号 接收方QQ号 协议类型 聊天内容
介绍一下TCP套接字API
介绍一下TCP套接字API函数?
Socket API
服务器
Socket 用于监听 =》》 bind端口 =》》 listen监听client连接(接收到客户端的connect) ==>> accept 返回新的socket通信 ==》》 recv(接受客户端数据) ==》》 send (服务器再对客户端进行响应) ==>> close 收到客户端的事件服务器也调用close ,也可以 服务器主动关闭然后通知客户端再进行关闭
客户端1
Socket =>> connect (连接服务器的listen) ==》》send (发送到服务器的)==》》 recv(接受服务器的数据 ) ==》》close(服务器收到事件 服务器也调用close)
客户端2
Socket =>> connect (连接服务器的listen) ==》》send (发送到服务器的)==》》 recv(接受服务器的数据 ) ==》》close(收到服务器的close主动关闭然后客户端再关闭)
如何理解TCP的可靠传输
TCP 会发生丢包吗?
TCP不会丢包 协议自身保证的 重传机制 所以不会丢包
TCP会丢包 一方数据的确发送了,另外一方真的没收到
解答
send() ==>> 发送到Buffer缓冲区 ===》》 网络 ==》》 接收Buffer(收到了会回复一个ACK的包) 再通知用户(应用层 recv接受) (如果没有收到TCP底层有重新传送机制 重新发送给接收方 TCP不会丢包,最多发送失败) (如果一直没有发送成功会发送一个RST的标志位表示连接断开,重新建立连接,如果网络极其不好甚至RST标志位可能也收不到)==>>recv()
TCP丢包了,RST重连接后,内核不会帮忙重复数据
TCP可以保证发送的数据被对方接收到吗?
总结
TCP并非百分之百可靠的,关键信息靠应用层保证
如何保证TCP一方发送的数据被另外一方接收到?
总结
TCP并非百分之百可靠的,关键信息靠应用层保证
如何理解TCP的流式传输
客户端先 发送 4个字节,再发送6个字节,服务器是怎么接收的?
先收4个字节,再收6个字节
一次收10个字节
以上都有可能(收几个不保证)
对流式传输的理解?
send() ==>> 发送 Buffer(类似把水加入水池) ==>>网络 ==》》 接收Buffer (数据混再Buffer里,取多少不一定)==》》recv()
如何拆包/粘包?
根据协议长度来进行拆包粘包
写一段伪代码?
C++
#include<iostream>
#include<vector>
#include<string>
#define MAX_PACKET_SIZE
using namespace std;
// 拆包函数
vector<string> splitPacket(string packet) {
vector<string> packets;
int len = packet.size();
int pos = 0;
while (pos < len) {
int size = min(len - pos, MAX_PACKET_SIZE);
packets.push_back(packet.substr(pos, size));
pos += size;
}
return packets;
}
// 粘包函数
string gluePacket(vector<string> packets) {
string packet;
for (auto p : packets) {
packet += p;
}
return packet;
}
int main() {
string packet = "This is a long packet that needs to be split into smaller packets for transmission.";
vector<string> packets = splitPacket(packet);
for (auto p : packets) {
cout << "Packet: " << p << endl;
}
string gluedPacket = gluePacket(packets);
cout << "Glued packet: " << gluedPacket<< endl;
return 0;
}
#include<vector>
#include<string>
#define MAX_PACKET_SIZE
using namespace std;
// 拆包函数
vector<string> splitPacket(string packet) {
vector<string> packets;
int len = packet.size();
int pos = 0;
while (pos < len) {
int size = min(len - pos, MAX_PACKET_SIZE);
packets.push_back(packet.substr(pos, size));
pos += size;
}
return packets;
}
// 粘包函数
string gluePacket(vector<string> packets) {
string packet;
for (auto p : packets) {
packet += p;
}
return packet;
}
int main() {
string packet = "This is a long packet that needs to be split into smaller packets for transmission.";
vector<string> packets = splitPacket(packet);
for (auto p : packets) {
cout << "Packet: " << p << endl;
}
string gluedPacket = gluePacket(packets);
cout << "Glued packet: " << gluedPacket<< endl;
return 0;
}
epoll和 select的区别
历史
1983 socket Unix 4.2
1994 select Linux 1.0
2002 epoll Linux 2.5.44
epoll
int epoll_create(int size);//上限多少和机器有关系 size>0 即可 内核会增加
int epoll_ctl(int epfd(int epoll_create(int size)),int op,int fd,struct epoll_event*event);
int epoll_ctl(int epfd,int op,int fd,struct epoll_event*event);
int op //事件的监听
EPOLL_CTL_ADD 添加
EPOLL_CTL_MOD 修改
EPOLL_CTL_DEL 删除
struct epoll_event*event
struct epoll_event{uint32_t events; epoll_data_t data;}
epoll_data_t data;
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t;
int epoll_wait(int epfd,struct epoll_event *event,int maxevents,int timeout);
select
int select(int nfds,fd_set * readfds,fd_set *writtefds,fd_set*exceptfds,struct timeeval * timeout);
最大的socket数量 --》》 文件描述符个数 默认是1024
是否有数据,都是全量循环查询,每次都是内核调用 调用更耗时
频繁在用户态和内核态进行切换
epoll和select的区别?
前者支持高并发(文件个数),内核主动通知就绪的socket,效率更高。
epoll 底层使用了哪些数据结构?
所有socket -> 红黑树, 就绪socket-> 链表
epoll 水平模式和边缘模式的区别?
前者未读写完,一直触发,后者只触发一次,效率更高。
UDP协议
介绍UDP协议,以及使用场景?
UDP协议是TCP协议的阉割版
udp协议特点,无连接,不可靠,无序
可丢包,无序到达
效率高
用户DIY可以自己想做啥做啥
UDP协议的应用
局域网 不用考虑丢包的情况,例如腾讯的QQGame以前是前面是TCP和服务器有个连接层处理大量的客户端请求,TCP连接这台机器只做解包把包验证的协议包是合法的,随后它把这个包通过UDP转发给后面的逻辑服务器上去处理,因为它在内网之内环境比较好那udp基本上很少丢包,udp效率高
数据量大,比如RTP直播会减少延时性,视频聊天,还有以前的在公网上p2p的传输
HTTP协议
介绍一下HTTP协议格式
考察是否写过HTTP程序
是否使用过wireshark工具
HTTP建立连接过程
HTTP建立连接的过程包括以下步骤:
客户端发起TCP连接请求。
服务器响应TCP连接请求。
客户端发送HTTP请求。
服务器处理HTTP请求。
服务器发送HTTP响应。
客户端接收HTTP响应。
关闭TCP连接。
客户端发起TCP连接请求。
服务器响应TCP连接请求。
客户端发送HTTP请求。
服务器处理HTTP请求。
服务器发送HTTP响应。
客户端接收HTTP响应。
关闭TCP连接。
HTTP请求
HTTP响应
get和post的区别
get参数在URI 链接内
数据长度get受限制
安全性上,post更高
http1.1 和 http1.0 的主要区别是?
1.0是默认的是短链接
1.1的时候默认连接有短连接改为长连接
HTTPS建立连接的过程
介绍一下HTTPS建立的过程?
了解加密算法
HTTPS是如何做到更安全的
加密算法
对称加密
双方拥有相同的key
运算速度快
非对称加密
两个key 一方拿着公开的key加密,另一方用私密key解密
运算速度慢
安全考虑
HTTP不安全
因为域名劫持的问题
客户端要怎么相信服务器?
引入第三方 证书中心认证机构
服务器发送公开的密钥证书给客户端
客户端去认证机构验证有效性,取公开的key
客户端使用公开的key去加密随机密码串,看服务器是否可以解密成功
HTTPS建立连接过程
HTTPS协议是HTTP协议的安全版本,它在HTTP协议的基础上加入了SSL/TLS加密层,以保证数据的安全性和完整性。HTTPS建立连接的过程包括以下步骤:
客户端发起TCP连接请求。
服务器响应TCP连接请求。
客户端发送SSL/TLS握手请求。
服务器发送SSL/TLS握手响应。
客户端和服务器进行SSL/TLS握手,建立加密通道。
客户端发送HTTP请求。
服务器处理HTTP请求。
服务器发送HTTP响应。
客户端接收HTTP响应。
关闭TCP连接。
客户端发起TCP连接请求。
服务器响应TCP连接请求。
客户端发送SSL/TLS握手请求。
服务器发送SSL/TLS握手响应。
客户端和服务器进行SSL/TLS握手,建立加密通道。
客户端发送HTTP请求。
服务器处理HTTP请求。
服务器发送HTTP响应。
客户端接收HTTP响应。
关闭TCP连接。
服务器先申请证书,然后下发证书给客户端,客户端拿到证书验证证书是否合法,客户端随机生成字符串作为对称加密的密钥给服务器,服务器用私钥解开对称密钥,服务器使用对称密钥加密数据给客户端,客户端以后的加密都会使用对称加密与服务器进行通信。在证书验证的时候用的是非对称加密,正常通信的时候用的是对称加密
HTTP域名劫持的应对方案
如何应对HTTP域名劫持的问题?
选择HTTPS(因为服务器要申请证书,客户端要验证证书的,但是第一申请证书需要费用,第二访问速度会变慢的)
自建HTTP DNS服务
用户 发送域名解析给 DNS 服务器 然后返回IP地址给用户 此时可能IP地址被劫持了返回了一个坏人的IP地址,客户不知道的情况下拿这个坏人的IP地址访问坏人的服务器,此时用户的请求会被坏人拿到了,这时候坏人就会做坏事了.
解决办法是用户拿IP地址访问自己的HTTP DNS 服务器 ,用户自己的HTTP DNS 服务器 返回查询域名对应的IP地址列表
HTTP DNS 防单点
例子
HTTP DNS 北方机房
用户客户端(用户随机IP访问两个机房,然后两个机房回复返回查询域名的业务IP列表)
HTTP DNS 南方机房
双机房,如何解决客户端选择业务IP问题?
交给HTTP DNS 服务器根据客户端的IP地址来做,如果南方机房宕机了就可以去北方机房访问服务
HTTP2.0和HTTP1.1的主要区别
HTTP2.0和HTTP1.1的主要差别
2.0 性能提升,更接近TCP,多路复用可以交叉执行,TCP就支持无限请求应答不需要等待下一次回来(但是HTTP层是有会话的概念),服务器主动推送,头部压缩和内容二进制传输
1.1 服务器不能主动通知客户端,一次请求一次应答,不支持两次同时请求后面陆续应答的方式
gPRC(谷歌的)使用了HTTP2.0
gRPC跨语言(支持流传输)
一次请求一次应答
多次请求一次应答
一次请求多次应答(服务器推送模式)
多次请求多次应答
WebSocket协议
WebSocket是可以主动通知客户端的
协议报文
请求 URL
ws://host:port/path (http->ws)
协议升级
WebSocket协议特点
服务器可以主动Web推送消息
支持二进制和文本传输(json等)
内部处理粘包的问题
WebSocket和TCP的主要区别是什么
WebSocket和TCP的协议不同点
web ==>> Socket
WebSocket 支持文本通信,比如JSON
WebSocket 做了粘包处理,更方便,因为运行在TCP协议之上的
WebSocket 多应用于Web场景,比如服务器通知Web程序
为什么选择WebSocket协议
为什么选择WebSocket通信
http1.0/1.1 不能主动通知,轮询给服务器造成压力
http2.0 目前多用于服务器 和 服务器之间通信 比如说谷歌的GRPC就是用到了http2.0
TCP 并不适合在Web开发中使用
WebSocket诞生的一个原因就是解决服务器通知Web客户端原因
WebSocket案例分析
案例
介绍一下HTTP3.0协议
tcp协议的缺点是什么,让你重新设计TCP,如何设计?
无法满足低延时的需求
比如:直播
弱网环境下效率非常低
重传 多个RTT(Round Trip Time)
拥塞 滑动窗口的限制
RST 反复重传后,重新建立连接
QUIC 协议
继承了TCP优点的同时,解决了它的缺点,握手次数减少
HTTP3.0
HTTP3.0在QUIC协议之上
结束
第五章 系统底层
开始
线程
进程
内存
稳定性
性能
锁和同步
什么是线程它有哪些独占资源
什么是线程?
线程是操作系统中CPU调度的最小单元
线程它是内核对象
独自寄存器上下文
如何创建线程?
标准库创建
void thread_func()
{
std::cout<<arg1<<" "<<arg2<<endl;
}
int main()
{
int a1=2,a2=12;
std::thread t(thread_func,a1,a2);
t.join();
return 0;
}
void thread_func()
{
std::cout<<arg1<<" "<<arg2<<endl;
}
int main()
{
int a1=2,a2=12;
std::thread t(thread_func,a1,a2);
t.join();
return 0;
}
1.线程执行函数
2.线程回调函数
windows下的库函数
linux 下的库函数
创建两个线程,执行顺序是随机的,如果想不随机通过锁或同步的方式
线程有哪些独占资源?
栈和寄存器
TLS
比如:thread_local(线程局部存储)
TEB
(线程环境块)
函数A 调用 函数B 函数B 执行完结束后程序是如何跳转到函数调用处的 下一条语句的 ?
答案:函数在调用函数B 的时候 它会把函数A调用的下一条语句 地址 压到栈里面,然后调用完结束后会把栈里面的地址弹回到EIP里(32位)如果是(64位)返回到RIP寄存器里面,所以栈很重要的作用是它可以存储调用关系
线程之间通信方式哪有哪些?
窗口消息循环的原理?
消息队列
生产消息 ==>> 消息队列 ==>> 循环消费消息
Windows消息循环
ios消息循环
Andriod消息循环
web消息循环
线程AB之间通信
借助消息队列
线程A (std::bind 可把任何参数绑定成没有参数的func)==>> 消息队列 func_obj() ==>> 线程B
什么是进程和进程的加载过程
什么是进程?
进程是程序运行的一次实例
进程的虚拟地址空间
进程的加载过程?(main前发生什么)
虚拟地址空间
2的操作系统位数次幂
文件映射
代码被映射到虚拟内存
初始化
初始化全局对象和线程
初始化其他资源
如何调试进程?
windows
Windbg AppVerifier PerfView
linux
gdb valgrind perf FlameGraph
进程之间的通信有方哪有
进程间通信原理
进程之间的虚拟内存是隔离的
虚拟内存加载到物理内容才可以工作
进程1虚拟空间 《= 内核共享内存 =》 进程2虚拟空间
共享内存映射区 共享内存 共享内存映射区
共享内存映射区 共享内存 共享内存映射区
Windows进程间通信
共享内存 文件映射
Com 通信(接口形式通信,底层也是共享内存)
管道通信 (底层也是共享内存)
消息队列
套接字(TCP进行编程 自己起一个端口和服务 注意杀毒软件会被封杀掉)
Linux进程间通信
共享内存 文件映射
信号
管道通信
消息队列
套接字
进程间的注意点
视频共享类型的
共享内存
多数通信是同步的
两头做异步处理
进程间的数据同步
mutex , semaphore 等内核锁
mutex和semaphore区别
是互斥锁 2 选 1 最大的不同是在于进程 ,进程A B都持有它,进程A持有mutex 假设进程A没释放然后进程A消失或者异常退出了或者正常退出了 mutex 持有资源操作系统会作为清除,但是semaphore信号量 操作系统不会进行处理。
跨机器通信
TCP/UDP/HTTP
RPC
gRPC
MQ(进程之间的消息队列)
变量的存储类型和堆的管理方法
进程的内存布局
windows
linux
变量类型有哪些?
栈
堆
静态存储区
TLS(线程局部存储)
mmap(内存映射)
堆内存管理怎么管理
如何统计内存泄露?
重载 operator new/new[]/delete/delete[] 方法 自己来统计
堆内存管理怎么管理?
其中的一个方法就是我们通过New操作符 new delete 我们在堆上分配管理
如何重载STL内存分配器?
重载std::allocator 的 allocate 和 deallocate 方法 (先继承allocator 然后再重载自己的内存分配器)
是否了解其他内存分配器?
就使用第三方库
TCMalloc
STLPort
自己new一个堆对象 然后进行堆上的分配空间
内存访问属性有哪些?
只读 (代码段 全局变量)(只写要触发就会崩溃程序)
读写 (大部分堆上的 和 堆上的)
不可读写 (只要触发就会崩溃程序)
如何检测内存泄露
内存泄露检测原理
开始跟踪内存
记录起点内存镜像
让内存泄露一会
记录分配/释放 内存堆栈
停止跟踪
记录内存镜像
内存检测工具 windows
AppVerfier
内存泄露
堆破坏
栈破坏
句柄 GDI泄露
内存检测工具 Linux
Valgrind
内存泄露
(借助火焰图看可视化)
越界访问
使用未初始化 已释放内存
使用已释放文件句柄
FLameGraph(通过火焰图 可视化 看报告信息)
如何定位creash
工具
pdb
调试工具
linux:gdb
windows:windbg ide 自带的调试器
技能
会简单的汇编代码
调试基本的调试命令
看线程的堆栈(windbg -> ~*k; gdb -> thread apply all bt)
看寄存器的值 (windbg ->r xx; gdb -> info registers)
如何定位栈溢出引发的creash?
什么是栈溢出?
函数返回地址被改写
如何定位?
查看所有线程堆栈,找出异常的那个
查看异常日志
尝试复现,借助工具 (valgrind AddressSanitizer AppVerifier)
如何保证程序的稳定性
上线前保证程序的稳定性?
Crash的本质
要么读了不可能访问的内存
要么写了不可能写的内存
可能的原因
空指针 堆损 栈损
Coding 中
单元测试
边界检测
代码的静态检测
空指针
野指针 检测
上线前
可执行文件动态运行检测
linux: valgrind
windows : AppVerifier
压力测试 放火演练(针对服务器)
疯狂点击(针对客户端)
CPU百分之百如何定义以及可能的原因
Step1:CPU耗时在哪个线程
windows windbg:!runaway
linux perf:perf record -g -p pid
Step2:看线程调用堆栈
windows windbg:~3kb
linux gdb:thread apply id bt
CPU百分之百可能的原因?
死循环 (空语句的死循环)可以用sleep 把cpu让出去
大量计算 (比如非对称加密解密)
频繁的系统调用(用户态 和 内核态之间 的频繁 切换)
线程资源竞争
资源泄露 (内存 GDI 文件句柄 等)
如何定位程序的性能瓶颈?(检测函数耗时 比如死循环)
如何定位程序的性能瓶颈?(检测函数耗时 比如死循环)
windows
PerfView(看函数调用关系 和 函数的耗时)
linux
PerfView(看函数调用关系 和 函数的耗时)
FlameGraph(可视化输出函数调用关系)
写高性能的代码?
UI/逻辑线程避免阻塞(比如可以预加载)
减少锁的使用
借助MQ异步处理
如何定位死锁?
如何定位死锁?
多次查看所有线程的堆栈
windows
windbg ~*k
linux
gdb thread apply all bt
pstack pid
锁的分类
互斥锁
锁定与非锁定 mutex critical_section(windows 下的互斥锁)
信号量
数量 semaphore 进程退出时表现
自旋锁
线程主动询问锁的状态,占用cpu的时间,避免线程的开销
读写锁
用于读多写少,提升 并发
条件变量
搭配互斥锁使用,条件发生时触发
如何防止死锁的发生
死锁的发生条件
互斥
多个线程无法共享同一个资源
持有并等待
持有资源A,等待资源B
不可剥夺
线程持有资源不释放时候,其他线程无法获取
环路等待
线程等待资源时候,形成环形链
避免死锁
破坏4个条件之一
最常见的是方法是资源有序分配
很被动,业务复杂难免死锁再次发生
如何解决?
从架构上避免
线程A ====》》》 消息队列 ===》》 线程B
基础库使用锁
业务逻辑,实现无锁线程(比如利用消息队列)
结束
线程
进程
内存管理
稳定性
性能
锁和同步
第六章 项目
开始
项目问题
仿QQ (采用C/S模型)
C/S <设计的应用层协议>
客户端
Qt + QTcpSocket
功能
1. 发送登录信息
(发送:0x02(开头) 0x11 0x03(结尾))
(发送:0x02(开头) 0x11 0x03(结尾))
2,获取在线用户链表
((返回:0x02(开头) 0x12 0x03(结尾))
((返回:0x02(开头) 0x12 0x03(结尾))
3,与某给在线用户进行点对点的聊天
(目的id + 源id + 数据内容)
(发送:0x02 0x13 目的id + 源id +数据内容 0x03)
(目的id + 源id + 数据内容)
(发送:0x02 0x13 目的id + 源id +数据内容 0x03)
服务器
Linux +Socket
功能
1.识别登录信息,分配一个唯一的id号,返回给对应的客户端,用链表保存登录信息。
<id数据范围 0000到9999 已ascii 的方式发送 0x30 0x30 0x31 0x32 相当于 “0012”>(返回:0x02(开头) 0x11id 0x03(结尾))
(大数据量用数据库存储,小项目用链表 来保存id号)
<id数据范围 0000到9999 已ascii 的方式发送 0x30 0x30 0x31 0x32 相当于 “0012”>(返回:0x02(开头) 0x11id 0x03(结尾))
(大数据量用数据库存储,小项目用链表 来保存id号)
2,遍历一遍链表,将在线用户id号全部返回给客户端
(返回:0x02 0x12 id0 id1 ... ... idN 0x03) 一个id是4个字节 有多少个id然后乘4 等于数据长度
(返回:0x02 0x12 id0 id1 ... ... idN 0x03) 一个id是4个字节 有多少个id然后乘4 等于数据长度
3, 解析目的id , 转发聊天数据包
(转发:0x02 0x13 目的id + 源id + 数据内容 0x03)
(转发:0x02 0x13 目的id + 源id + 数据内容 0x03)
心跳包机制,相隔一段时间没有收到,就关闭套接字
GameSnake
主窗口可以是QMainWindow
在中央窗口上绘制动画,重写PaintEvent事件处理函数,开多线程处理
游戏的数据,场景宽高,蛇的速度,游戏状态,蛇有效移动位置,食物有效布局位置
控制部分,通过实现按键事件的处理函数来实现游戏控制
流程
子主题
相互依赖的两个类如何解耦?
场景
CA 让 CB 去买可乐,CB是否买到了都要通知CA
接口方式解耦(一对多)
CA 让 CB 去买可乐,CB是否买到了都要通知CA
函数绑定解耦(一对一)
CA 让 CB 去买可乐,CB是否买到了都要通知CA
介绍一下常用的设计模式
设计模式分类
创建型
单例,Buider , 工厂,原型
结构型
组合,外观,装饰,适配器,代理
行为型
策略,命令,迭代器,观察者
外观模式
棋牌游戏C/S通信
观察者
观察者模式客户端(缺点可能会消息爆炸,目录结构要管理好,优点是可以解耦)
观察者:MQ(生产者&消费者)
总结
AB之间解耦的共同点
借助第三者
客户端开发线程模型
绘图在UI线程
业务在逻辑线程
业务线程无锁
耗时在工作线程
浏览器客户端设计
设计
网页内核以外的部分
标签页 地址栏 菜单 工具栏 收藏栏 历史记录栏 等等
依赖分析
解耦推导
UI和UI解耦
UI和逻辑解耦
逻辑层功能分层
浏览器客户端架构设计
优点缺点
优点
类之间解耦
缺点
消息爆炸(通过分层来防止消息混乱)
视频信息查询后台设计
目前有几百G的视频资料,后续会持续增加到几个T,如何设计服务提供查询?
Redis server 随便使用
MySQL 若干台
后台考虑三个点?
高并发?
内存 削峰
可靠性?
单个程序崩溃不影响业务
可扩展?
扩容对业务无感
解答
斗地主赛事后台设计
斗地主赛事后台的设计
进入大厅
报名参赛
游戏
特点分析
游戏中
分布式 or 集中
消息时序
消息同步
TCP or Http
斗地主赛事架构
1,server crash 如何保证服务可用性?
2,人数增加,如何动态扩展?
3, 如何支持高并发?
结束
业务背景
项目是做的什么
技术架构,优缺点
为什么这么选型(一定要结合业务特点)
角色,困难,成长
第七章 结束
面试宝典
建议
Qt
归类
QObject
QApplication
QWidget(它是多重继承)
QPushButton
QLabel
QLineEdit
QTextEdit
QMainWindow
QDialog
QLayout
QFile
QThread
QAction
QPaintDevice
QWidget
QPixmap
QImage
QPicture
数据类
QString
QSize
QPoint
QFont
QColor
QByteArray
QIcon
QDataStream
QMutex
QSemaphore
QBrush
Qt内存管理是怎么实现的?
继承QObject 给它继承一个父节点或者父对象,在析构函数里面打印一句话看看有没有调用就知道内存管理有没有用了。
信号与槽函数需要几个步骤?
第一步继承QObject,第二步加上QOBJECT的宏之后需要指定声明区域权限关键字和slots来指定槽函数的声明区域,槽函数也可以当普通函数使用也可以和信号关联在一起当信号触发的时候可以调用槽函数。第三步想自己使用信号步骤继承QObject然后加QOBJECT的宏之后再加上signal,signal不需要权限,信号只声明不实现,信号可以一个信号连接多个槽函数,也可以多个信号连接一个槽函数。
Qt的编译机制?
Qt里面的所有项目都是通过QMake进行来管理组织的,QMake文件可以解析成MakeFile,MakeFile里面填写了项目所需的文件库,然后再通过Make命令来生成可执行程序,.pro文件属于QMake的工程文件。
类图
QObjecet
QApplication(单实例)
exec()主要函数
QWidget
QPushbutton
QLineEdit
QLabel
QLayout
QHBoxLayout
QVBoxLayout
QGridLayout
单独的类不继承任何基类
QString
QSize
QPoint
怎么显示模态窗口?
继承QDialog并且调用exec
普通窗口和显示Dialog窗口不同?
第一个普通窗口是继承于widget第一种情况是不指定父窗口的情况下也不调用show这样是不显示的,第二种情况是不指定父窗口的情况下调用show是顶级窗口来显示的,第三种指定父窗口不调用show是嵌套方式显示的,第四种指定父窗口并且调用show函数还是嵌套方式显示的,不能以模态窗口显示因为没有exec。第二个QDialog这种窗体如果不指定父窗口也不调用show是不显示的,不指定父窗口但是调用了show也是已顶级窗口显示,当他指定了父窗口不调用show是没嵌套父窗口上面,当他指定了父窗口调用了show还是已顶级窗口显示还多了一个exec模态窗口来显示。
多线程在Qt的开启方式?
第一种继承QThread然后重新实现run函数执行exec,通过start来执行。
第二种继承至QObject 实现槽函数 QThread对象 然后moveToThread 连接信号与槽
什么是原子操作?
低于32位都是原子操作
int a;a=12;//是原子操作
int a=1,b=2; a=b;//不是原子操作
a++;//不是原子操作
原子操作不会被打断,不是原子操作需要锁来保证不会出现错误。
比如读写锁,读时共享,写时独占
互斥锁效率会低,只有一个线程在运行,其他线程都在等待
图形视图框架 MVC
model
QGraphicsScene QGraphicsItem
view(QGraphicsView)
controller (用户输入事件)
图形视图框架 层级
QObject
QWidget
QGraphicsView
QGraphicsScene
QGraphicsItem
QGraphicsLineItem
QGraphicsRectItem
QGraphicsElipseItem
QGraphicsSimpleTextItem
QGraphicsPixmapItem
QGraphicsRectItem
QGraphicsElipseItem
QGraphicsSimpleTextItem
QGraphicsPixmapItem
层级
QObject
QWidget
QGraphicsView
QVideoWidget
QMediaBindableInterface
QVideoWidget
QGraphicsScene
ItemAt
selectedItems
itemsBoundingRect
setSceneRect
render 打印场景
selectedItems
itemsBoundingRect
setSceneRect
render 打印场景
QMediaPlayer
QMediaPlaylist
QGraphicsVideoItem
QGraphicsItem
QGraphicsLineItem
QGraphicsRectItem
QGraphicsPixmapItem
QGraphicsSimpleTextItem
QGraphicsRectItem
QGraphicsPixmapItem
QGraphicsSimpleTextItem
setFlag
contains
collidesWithItem
scene
contains
collidesWithItem
scene
QGraphicsVideoItem
QMediaBindableInterface
QGraphicsVideoItem
QTableView
view QTableView <= model QStanderItemModel <=controller QTableView QItemDelegate(代理能创建QLineEdit QSprinBox QDateEdit)
想显示一个表单可以有两种方式显示:
1.MVC显示方式用QTableView
2.非MVC显示方式用QTableWidget
1.MVC显示方式用QTableView
2.非MVC显示方式用QTableWidget
收藏
0 条评论
下一页