首页 > 编程知识 正文

c语言搭建服务器,c语言web开发

时间:2023-05-06 07:32:25 阅读:46783 作者:3115

这是为了掌握c 11的一些新特性和网络编程知识而做的项目。

github :3359 github.com/viktor ika/web服务器

模型:

muduo部分的代码进行参考,采用Reactor模型epoll(et )无阻塞IO模式。 添加线程池以提高服务器的并发性能。 主线程accept,其他线程进行读取-分析-写入。

Reactor模型运行过程

返回封装事件循环loop-epoll_wait的轮询事件集handles对每个handle执行事件的handleEvent-handleEvent由回调函数执行

线程池的实现:

穆杜多线程池的代码,讲道理读也不太懂。 有空仔细读一下。 但是那个one loop per thread思想至少我做了。 基本上是简单实现这个思想,每个线程loop。

RAII封装锁:

如果在使用锁定时意外退出,则永远不会释放该锁定。 如果其他线程或当前线程需要获取此锁定,则很容易出现死锁。 为了解决这一点,使用RAII机制封装锁。 也就是说,资源获取将被初始化并从范围中释放。

HTTP解析问题:

因为采用了ET模式,所以每次都是在读取全部数据后进行处理。 处理的时候用状态机处理。 我这里只使用了四种状态: PARSE_ERROR、PARSE_METHOD、PARSE_HEADER和PARSE_SUCCESS。 在每个状态中使用bind

void http _ conn :3360 parse ({ bool zero=false; intreadsum=readn (通道- getfd )、inbuffer、zero ); if(readsum0|||zero({initmsg ) ); 通道集删除(true; channel-getLoop ().lock ()-addtimer () channel,0 ); 返回; 我读了//RST和FIN默认FIN的处理方法,不知道在这里和读rst该怎么处理,所以一起这样处理了} while (in buffer.length (~in buffer

handle parse [0]=bind (http _ conn :3360 parse error,this ); handle parse [1]=bind (http _ conn :3360 parse method,this ); handle parse [2]=bind (http _ conn :3360 parse header,this ); handle parse [3]=bind (http _ conn :3360 parse success,this );

剩下的细节请看我的github代码。

计时器:

这实际上是Reactor的一部分吧。 用于处理超时连接的. timer处理逻辑如下:

Timer有两种数据结构: unordered_map和priority_queue,首选队列包含TimerNode节点。 如果节点时间到了或与该节点对应的channel事件关闭,则可以删除该节点。 此外,unorder_map是fd映射timernoder。如果TimerNode节点是与fd对应的真正节点,而priority_queue删除的节点不是与unorder_map对应的节点,则当前

Log日志实现逻辑(参考muduo):

首先,是Logger类。 Logger类包含Impl类,但实际上具体实现是Impl类。 我不明白穆杜奥为什么要再封装一层。 那么,让我来说明Impl做了什么。 初始化时,Impl将时间信息存储在LogStream的缓冲区中。 实际使用Log时,实际写入的缓冲区也是LogStream。在析构函数中,Impl会将当前文件和行数等信息写入LogStream,并将LogStream的内容写入同步日志的缓冲区当然,此时,打开后端线程以将缓冲区信息写入文件

里.

然后来说说LogStream类,里面其实就一个Buffer缓冲区,是用来暂时存放我们写入的信息的.还有就是重载运算符,因为我们采用的是C++ Stream的风格

再来说说AsyncLogging类,这个是最核心的部分,我们知道在多线程程序中写Log无非就是前端往后端写,后端往硬盘写,首先前面提到了将LogStream的内容写到了AsyncLogging缓冲区里,也就是前端往后端写,这个过程通过append函数实现,后端实现通过threadfunc函数,两个线程的同步和等待通过互斥锁和条件变量来实现,具体实现使用了双缓冲技术,双缓冲技术的基本思路:准备两块buffer,A和B,前端往A写数据,后端从B里面往硬盘写数据,当A写满后,交换A和B,如此反复.不过实际的实现的话和这个还是有点区别,具体看代码吧

剩下的LogFile类和FileUtil类其实没什么好说的,就是把文件用RAII机制封装了,LogFile在FileUtil的基础上再封装增加了点功能罢了.

内存池

结合STL的实现和github上第一个memorypool的实现,我创建了64个块,里面维护了一个free_list,每一块都是8的倍数,比如下标为0的块free_list维护的是8B的链表,下标为1的是16B的链表,根据需要去找不同的块,当块内的空闲内存不足时通过malloc申请一大块内存,大致实现思路是这样,详细的可以去看github上的第一个memorypool的实现,我只不过在其基础上多了64个块罢了。

LFU缓存

具体实现请看我另一篇博客,https://blog.csdn.net/qq_34262582/article/details/80358388

涉及的知识点:

 

getopt():这个函数是Linux的c程序接口,用于转换命令行参数作为Linux命令选项和参数。

 

函数原型:int getopt(int argc,char * const argv[],const char * optstring);

前两个参数就是命令行参数,第三个就是选项字符串。

举个例子:选项字符串为"ab:c:d"

那么该程序有四个命令选项-a,-b,-c,-d。其中带冒号的b和c后面带参数,参数默认保存在optarg里。返回值为命令选项,返回-1证明没有参数了。

 

std::bind和std::function:这两个是C++11的新特性,用于函数调用。

std::bind,这个可以绑定函数参数,返回一个可用的临时变量,用于延迟调用。

std::function,作用类似函数指针,但是它比函数指针要安全,无需释放,可以认为是函数指针的智能指针。

通常两者结合使用,通过bind绑定参数返回给function,在需要的时候调用function就好了。

 

std::move:也是C++11的东西,用于赋值

忧心的果汁一个变量a要赋值给另一个变量b的时候,如果赋值完后变量a就要销毁,那么这一步赋值的代价就不划算,故提供了一种新的方式,权限转移机制,你也可以理解为让这个变量a变成临时变量,也就是左值变成了右值。通过这种操作赋值完后a的值会变得未知。这里又谈到了左值和右值的问题,那么我们下面谈谈右值引用。

 

右值引用&&:这个顾名思义就是引用右值的。还是C++11的特性。

先来说说左值和右值

左值:可以出现在赋值号的左边或右边的变量。

右值:只能出现在赋值号右边的常量或临时变量。

举个例子a=3。a是变量可以出现在等号的左边和右边,所以是左值,3是常量只能出现在等号的右边是右值。

再有:a=a+b,a+b返回的是临时变量,只能出现在右边,故是右值。

然后右值引用就是用来处理无法引用右值的问题,在C++11以前,右值是没法引用的。

 

智能指针:虽然以前也有智能指针auto_ptr,但是到了C++11后已经摒弃这个东西了。

首先讲讲智能指针的原理,因为一般指针使用都是要new/malloc,然后离开作用域前要delete/free,那么你很有可能会忘了delete/free,而导致内存泄露。这时候智能指针就诞生了,通过把指针封装在模板类里面,调用构造函数分配内存,析构函数释放内存,那么只要离开作用域就会自动调用析构函数释放内存,就可以解决你忘了delete/free而导致的内存泄露问题了。其实本质上就是RAII机制.

shared_ptr:采用引用计数原理,每次复制都会把引用计数加1,销毁一个就减一,当引用计数为0时释放内存。

unique_ptr:控制权唯一,同一时间一个对象只能由一个unique_ptr指向它,离开作用域后释放内存,unique_ptr若要赋值给另一个unique_ptr,只能通过std::move变成右值,释放掉控制权才可以赋值给另一个unique_ptr。

weak_ptr:shared_ptr的辅助指针,只获得观测权,并不会使引用计数增加或减少。他的作用主要是解决循环引用问题,例如有两个类a和b,a的成员变量是shared_ptr <类b>,b的成员变量是shared_ptr<类a>,然后创建两个智能指针类,互相对成员变量赋值,最后导致a包含b,b包含a,那么他们的引用计数是2,当离开作用域后,假设b先销毁,那么b的引用计数减1,所以b的内存空间不释放,b的内存空间不释放意味着b里面的成员变量不释放,所以a的引用计数还是2,然后到a离开作用域,a也和b的结果一样,引用计数变成1,故两个都不能释放资源。为了解决这种循环引用问题,引入了weak_ptr来解决。

 

for循环语句:C++11新特性帮助你简化for语句

知道这个之前首先得知道auto变量,还是C++11的新特性,编译器自动根据上下文推断应该是什么类型,有些时候例如你要描述一个迭代器,那么你得写很长,这样看起来很难受,写起来也麻烦,直接用auto它会自己推断为相应的类型,例如auto i=1,那么i为int类型.不要以为这是弱类型,其实是不一样的.

然后for的新用法,直接举例比较快:

int main(){ int numbers[] = { 1,2,3,4,5 }; std::cout << "numbers:" << std::endl; for (auto &number : numbers) { std::cout << number << std::endl; }}

代码的意思是遍历numbers,对于每一个numbers的元素赋值给&number,当然你也可以写成auto number,这样的话就无法修改numbers里面的元素.

 

pthread_once_t:控制变量

其实这个我觉得也没什么好讲的,就是用来使某个函数在整个进程里只执行一次,所以叫控制变量。

这里如果有看过设计模式的朋友们会想起单例模式,没错单例模式使用控制变量的话就可以很优雅的实现了。不需要互斥锁+条件变量的使用。

用法:pthread_once(函数地址,控制变量地址);

 

mmap:内存映射

把文件或一个Posix共享内存区对象映射到进程的地址空间。

一般来说使用这个有三个目的:

(1)使用普通文件以提供内存映射I/O.

(2)使用特殊文件以提供匿名内存映射.

(3)使用shm_open以提供无亲缘关系进程间的Posix共享内存区.

项目中使用mmap的目的是就是第一个,通过映射可以不需要调用read和write系统调用,也可以释放fd资源。

 

gettimeofday:精确的获取时间

使用方法就不贴了,看github里的代码一看就懂的,要注意的问题是值都是32位的,所以*1000之后有可能溢出,这里我改成long long 以防溢出。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。