一.tcp三次握手四次挥手
1.三次握手
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
2.四次挥手
1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
问题一 为什么需要三次握手?
为了防止已失效的连接请求报文段突然又传送到了服务端,不采用三次握手,会导致服务器处于一直等待发送数据的状态,浪费资源。“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”
问题二 为什么需要四次挥手?
TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。
二、#define 和 inline
(1)内联函数的优势在于做参数类型检查,而宏定义不会,宏只是简单的文本替换。
(2)内联函数尽量避免函数体过长、含有循环、递归。
三、static
1.C语言中的static
(1)静态局部变量:用于函数体内部修饰变量,这种变量的生存期长于该函数。
void fun()
{
static int i = 10;
++i;
cout<<"i = "<<i<<endl;
cout<<"j = "<<j<<endl;
}
那么我们总结一下,静态局部变量的特点(括号内为note:2,也就是局部变量的对比):
(a)该变量在全局数据区分配内存(局部变量在栈区分配内存);
(b)静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化(局部变量每次函数调用都会被初始化);
(c)静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0(局部变量不会被初始化);
(d)它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,也就是不能在函数体外面使用它(局部变量在栈区,在函数结束后立即释放内存);
(2)静态全局变量:定义在函数体外,用于修饰全局变量,表示该变量只在本文件可见。
// 静态全局变量,作用于在文件内
static int j = 0;
(a)静态全局变量不能被其它文件所用(全局变量可以);
(b)其它文件中可以定义相同名字的变量,不会发生冲突(自然了,因为static隔离了文件,其它文件使用相同的名字的变量,也跟它没关系了);
(3)静态函数:准确的说,静态函数跟静态全局变量的作用类似
(a)静态函数不能被其它文件所用;
(b)其它文件中可以定义相同名字的函数,不会发生冲突;
2.C++ 语言的 static 关键字
(1)静态数据成员
用于修饰 class 的数据成员,即所谓“静态成员”。这种数据成员的生存期大于 class 的对象(实体 instance)。静态数据成员是每个 class 有一份,普通数据成员是每个 instance 有一份,因此静态数据成员也叫做类变量,而普通数据成员也叫做实例变量。
// 类内静态成员
// 类内静态成员:数据成员和函数成员
class Rectangle
{
private:
int m_w,m_h;
// 静态数据成员
static int s_sum ;
public:
Rectangle(int w,int h)
{
this->m_w = w;
this->m_h = h;
s_sum += (this->m_w * this->m_h);
}
void GetSum()
{
cout<<"sum = "<<s_sum<<endl;
}
// 静态函数成员
static void GetSum2()
{
cout<<"sum = "<<s_sum<<endl;
}
};
int main()
{
cout<<"sizeof(Rectangle)="<<sizeof(Rectangle)<<endl;
Rectangle *rect1 = new Rectangle(3,4);
rect1->GetSum();
cout<<"sizeof(rect1)="<<sizeof(*rect1)<<endl;
Rectangle rect2(2,3);
rect2.GetSum();
cout<<"sizeof(rect2)="<<sizeof(rect2)<<endl;
cout << "Hello World!" << endl;
return 0;
}
对于非静态数据成员,每个类对象(实例)都有自己的拷贝。
而静态数据成员被当作是类的成员,由该类型的所有对象共享访问,对该类的多个对象来说,静态数据成员只分配一次内存。
静态数据成员存储在全局数据区。
静态数据成员定义时要分配空间,所以不能在类声明中定义。static变量并不占用类的内存空间。
(2)静态成员函数:用于修饰 class 的成员函数
int main()
{
cout<<"sizeof(Rectangle)="<<sizeof(Rectangle)<<endl;
Rectangle *rect1 = new Rectangle(3,4);
rect1->GetSum2();
cout<<"sizeof(rect1)="<<sizeof(*rect1)<<endl;
Rectangle rect2(2,3);
rect2.GetSum2(); //可以用对象名.函数名访问
cout<<"sizeof(rect2)="<<sizeof(rect2)<<endl;
Rectangle::GetSum2(); //也可以可以用类名::函数名访问
cout << "Hello World!" << endl;
return 0;
}
上面注释可见:对GetSum()加上static,使它变成一个静态成员函数,可以用类名::函数名进行访问。
那么静态成员函数有特点呢?
(a).静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
(b).非静态成员函数可以任意地访问静态成员函数和静态数据成员;
(c).静态成员函数不能访问非静态成员函数和非静态数据成员;
(d).调用静态成员函数,可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数,也可以用类名::函数名调用(因为他本来就是属于类的,用类名调用很正常)
四、线程和进程:
知乎好帖1
知乎好帖2
1.线程和进程的区别
进程:一个具有一定独立功能的程序,在一个数据集合上的,一次动态执行过程,这整个的过程,就是进程。
线程:线程是进程中的一条执行流程
①进程是资源分配的最小单位,线程是CPU调度的最小单位。
②一个线程只能属于一个进程,而一个进程可以有多个线程。
③进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
④进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉。
⑤进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
⑥部分任务可能比较耗时,长时间占用CPU(你肯定不希望应用执行某个功能时整个程序都卡死),如果创建进程解决可能额外CPU开销更大,因此部分时候需要使用多线程技术。
2.同步和互斥
①线程之间通信的两个基本问题是互斥和同步。
②线程同步是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。
③线程互斥是指对于共享的操作系统资源(指的是广义的”资源”,而不是Windows的.res文件,譬如全局变量就是一种共享资源),在各线程访问时的排它性。当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。
④线程互斥是一种特殊的线程同步。实际上,互斥和同步对应着线程间通信发生的两种情况:(a)当有多个线程访问共享资源而不使资源被破坏时;(互斥)(b)当一个线程需要将某个任务已经完成的情况通知另外一个或多个线程时。(同步)
- linux下C++线程同步
(1)互斥锁:std::mutex
(2)信号量:semaphore,互斥锁只允许一个线程进入临界区,而信号量允许多个线程同时进入临界区。
(3)条件变量:std::condition_variable
①条件变量本身不是锁!但它也可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个会合的场所。
②与互斥锁不同,条件变量是用来等待而不是用来上锁的。条件变量用来自动阻塞一个线程,直到某特殊情况发生为止。通常条件变量和互斥锁同时使用。
③条件变量分为两部分: 条件和变量。条件本身是由互斥量保护的。线程在改变条件状态前先要锁住互斥量。条件变量使我们可以睡眠等待某种条件出现。
④条件变量是利用线程间共享的全局变量进行同步的一种机制,主要包括两个动作:一个线程等待"条件变量的条件成立"而挂起;另一个线程使"条件成立"(给出条件成立信号)。条件的检测是在互斥锁的保护下进行的。如果一个条件为假,一个线程自动阻塞,并释放等待状态改变的互斥锁。如果另一个线程改变了条件,它发信号给关联的条件变量,唤醒一个或多个等待它的线程,重新获得互斥锁,重新评价条件。如果两进程共享可读写的内存,条件变量可以被用来实现这两进程间的线程同步。
(4)读写锁:读写锁最适用于对数据结构的读操作次数多于写操作的场合,因为,读模式锁定时可以共享,而写模式锁定时只能某个线程独占资源,因而,读写锁也可以叫做个共享-独占锁。(read-write lock)
①读写锁比起mutex具有更高的适用性,具有更高的并行性,可以有多个线程同时占用读模式的读写锁,但是只能有一个线程占②用写模式的读写锁,读写锁的三种状态:
(a)当读写锁是写加锁状态时,在这个锁被解锁之前,所有试图对这个锁加锁的线程都会被阻塞
(b)当读写锁在读加锁状态时,所有试图以读模式对它进行加锁的线程都可以得到访问权,但是以写模式对它进行加锁的线程将会被阻塞
(c)当读写锁在读模式的锁状态时,如果有另外的线程试图以写模式加锁,读写锁通常会阻塞随后的读模式锁的请求,这样可以避免读模式锁长期占用,而等待的写模式锁请求则长期阻塞。
4.进程通信
Linux下进程通信的八种方法:管道(pipe),命名管道(FIFO),内存映射(mapped memeory),消息队列(message queue),共享内存(shared memory),信号量(semaphore),信号(signal),套接字(Socket)
(1) 管道(pipe):管道允许一个进程和另一个与它有共同祖先的进程之间进行通信;
(2) 命名管道(FIFO):类似于管道,但是它可以用于任何两个进程之间的通信,命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建;
(3) 信号(signal):信号是比较复杂的通信方式,用于通知接收进程有某种事情发生,除了用于进程间通信外,进程还可以发送信号给进程本身;Linux除了支持UNIX早期信号语义函数signal外,还支持语义符合POSIX.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD即能实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数的功能);
(4) 内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它;
(5) 消息队列(message queue):消息队列是消息的连接表,包括POSIX消息对和System V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能成该无格式字节流以及缓冲区大小受限等缺点;
(6) 信号量(semaphore):信号量主要作为进程间以及同进程不同线程之间的同步手段;
(7) 共享内存 (shared memory):它使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。这是针对其他通信机制运行效率较低而设计的。它往往与其他通信机制,如信号量结合使用,以达到进程间的同步及互斥;
(8) 套接字(Socket):它是更为通用的进程间通信机制,可用于不同机器之间的进程间通信。起初是由UNIX系统的BSD分支开发出来的,但现在一般可以移植到其他类UNIX系统上:Linux和System V的变种都支持套接字。
五、编译原理
1.程序的编译
(1)预处理:处理头文件,宏定义和条件编译,生成hello.ii文件
(2)编译:将hello.ii文件连同其他源文件一起生成汇编代码,hello.s
(3)汇编:将第二步产生的hello.s转化成目标文件hello.o
(4)链接:将第三步产生的目标文件hello.o链接成可执行文件,hello.exe或hello.out
注意:宏是在预处理的时候展开,inline是在编译的时候展开
2.静态链接和动态链接
静态链接:静态链接是由链接器在链接时将库的内容加入到可执行程序中的做法。链接器是一个独立程序,将一个或多个库或目标文件(先前由编译器或汇编器生成)链接到一块生成可执行程序。
优点:不同的程序模块可以独立开发和测试,最后链接在一起供用户使用,促进程序开发效率;
缺点:
(1) 浪费空间: 这是由于多进程情况下,每个进程都要保存静态链接函数的副本
(2)更新困难 :当链接的众多目标文件中有一个改变后,整个程序都要重新链接才能使用心得版本
动态链接(Dynamic Linking):相对于静态链接而言,要等到程序运行时再将组成程序的目标文件进行链接的过程。
优点:
(1)当系统多次使用同一个目标文件时,只需要加载一次即可,节省内存空间
(2)不同数据间的数据和指令访问都集中在了同一个共享模块,可以减少物理页面的换入换出,增加CPU的缓存命中率
(3)程序升级变得容易 当升级某个共享模块时,只需要简单的将旧目标文件替换掉,程序下次运行时,新版目标文件会被自动装载到内存并链接起来,即完成升级
(4)插件的引入 程序运行时可以动态选择加载的各种模块,即选择插件
(5)加强程序的兼容性 动态链接库相当于在程序和操作系统间增加一个中间层,消除程序对不同平台之间的依赖性
缺点:
(1)当程序依赖的某个模块更新后,如果新旧模块接口不兼容,将导致整个程序无法运行
(2)导致性能损失 动态链接把链接的过程推迟到装载的时候,那么程序每次被加载时都要进行重新链接,但是导致的性能损失与节省的空间和灵活性相比,还是值得的!