C/C++ WebServer服务器一


基于TCP的套接字通信

在这里插入图片描述       这是一个单线程流程,服务器创建用于监听的套接字,绑定本地的ip和端口,listen函数去监听绑定的端口。
      如果有客户端进行连接,服务器端就可以和发起连接的客户端建立连接,连接建立成功会生成一个用于通信的套接字。用于监听的套接字和用于通信的套接字是不一样的。监听的套接字用于建立连接,通信的套接字用于数据交互。用于数据交互的read和write都是阻塞函数,在单线程下面,一个服务器想和多客户端进行通信,肯定是做不到的,因为accept,read,write都是阻塞的。
      为了使服务器可以正常的与多个客户端建立连接,并进行数据交互,需要用到多线程,多线程中的主线程负责建立连接(调用accept),子线程负责数据通信。
      多线程切换有一定的开销,因此引入非阻塞 I/O。非阻塞 I/O 不会将进程挂起,调用时会立即返回成功或错误,因此可以在一个线程里轮询多个文件描述符是否就绪。但是这种做法缺点是,每次发起系统调用,只能检查一个文件描述符是否就绪。当文件描述符很多时,系统调用的成本很高
      IO多路复用,也就是select,poll,epoll,可以通过一次系统调用,检查多个文件描述符的状态,相比于非阻塞 I/O,在文件描述符较多的场景下,避免了频繁的用户态和内核态的切换,减少了系统调用的开销。在IO多路复用中,阻塞是由内核实现的,自己编写的代码可以少许多不必要的阻塞。
      在单线程下,只用IO多路复用,没办法同时处理两件事情,为了提高效率,一般采用多线程+IO多路复用的方法。

单线程服务器流程

      自己编写的代码充当服务器,浏览器作为客户端的角色进行固定地址的访问。

1.	在终端输入 启动程序 端口 和 程序主目录。
2.	启动监听套接字initListenFd(unsigned short port):
	a)	创建监听fd,采用IPv4,TCP协议
	b)	设置端口复用:如果程序服务器是主动断开连接的一方,会有一个2msl的等待时长,为了确认客户端已经收到我断开确认ack
	c)	绑定IP和端口
	d)	设置监听
	e)	返回监听套接字lfd
3.	启动服务器程序epollRun(int lfd):
	a)	创建epoll 树的根节点
	b)	lfd上树:上树用的是epoll_ctl函数
	c)	while true不停地检测是否有事件到来,根据epoll_wait返回的数组中的fd文件描述符,判断事件是连接请求,还是数据通信请求:
		i.	如果是连接请求,调用acceptClient(lfd, epfd),建立新的连接。
		ii.	如果是数据通信请求,调用recvHttpRequest(cfd, epfd),以http协议的方式传递消息。

acceptClient(lfd, epfd)1.	建立连接,调用accept函数。
2.	设置非阻塞模式,非阻塞说的是文件描述符,默认得到的cfd是阻塞的,用fcntl修改文件描述符的属性。
3.	cfd添加到epoll中,设置边沿触发方式。


recvHttpRequest(cfd, epfd)1.	读取客户端发送过来的http请求头:
2.	判断数据是否被接收完毕:
	a)	if (len == -1 && errno == EAGAIN):证明有数据
	解析请求行:parseRequestLine(const char* line, int cfd):
		i.	sscanf拆分字符串,得到请求方法和请求路径,仅处理get请求:
		ii.	对请求路径中的中文进行处理,否则会乱码
		iii. 判断文件路径是指向目录,还是文件,或者文件不存在:
			1.	若文件路径不存在,发送404.html
			2.	若文件路径是目录,则发送html的头部,然后发送格式化的目录列表,也是符合html格式。
			3.	若文件路径指向具体文件,则分析文件类型,然后发送具体文件。
			由于通信的文件描述符是非阻塞的,用sendfile发送文件的时候要处理返回值,
			不断根据偏移量去发送文件,直到文件发送完毕,否则大文件的传输会出现问题。
			cfd去读取发送数据内存的时候,是非阻塞的,读数据块速度很快,读到文件末尾偏移量之后,再进行while循环读取的时候
			ret返回值为-1,errno == EAGAIN代表没有数据,可以再次进行尝试。
			
			off_t offset = 0;
			int size = lseek(fd, 0, SEEK_END);
			lseek(fd, 0, SEEK_SET);
			while (offset < size) // 如果偏移量小于size,则表示文件没有发送完,继续发送
			{
   
   
				// 通信的文件描述符是非阻塞的
				int ret = sendfile(cfd, fd, &offset, size - offset);
				printf("ret value: %d\n", ret);
				if (ret == -1 && errno == EAGAIN) // EAGAIN的意思是没有数据,可以再次进行尝试
				{
   
   
					printf("没数据...\n"); 
				}
			}
			close(fd);
	b)	否则说明客户端断开了连接,要及时的将cfd下树,并且关闭对应文件描述符

多线程服务流程

单线程的服务器模型中,主程序会不断阻塞的进行连接和数据通信两项工作,两项工作和主线程不独立,当连接请求比较多的时候,效率相对较低。
多线程的处理方法:在建立连接 acceptClient(lfd, epfd) 和 数据通信模块 recvHttpRequest(cfd, epfd)两部分,都开辟新的线程去做,让子线程去处理动作。
注意要在项目的输入,库依赖项中输入pthread,否则linux链接的时候找不到。

main.c代码:

#include <stdio.h>
#include"Server.h"
#include<unistd.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
   
   
    if (argc < 3) {
   
   
        printf("./a.out port path\n");
        return -1;
    }
    
    unsigned short port = atoi(argv[1]);
    // 切换服务器的工作目录
    chdir(argv[2]);

    // 初始化监听的套接字
    int lfd = initListenFd(port);

    // 启动服务器程序
    epollRun(lfd);

    return 0;
}

Server.h代码:

#pragma once

// 初始化监听的文件描述符
int initListenFd(unsigned short port);

// 启动epoll
int epollRun(int lfd);

// 和客户端建立连接
// int acceptClient(int lfd, int epfd);
void* acceptClient(void* arg);

// 接收http请求
// int recvHttpRequest(int cfd, int epfd);
void recvHttpRequest(void* arg);

// 解析请求行
int parseRequestLine(const char* line, int cfd);

// 发送文件
int sendFile(const char* fileName, int cfd);

// 发送响应头(状态行和响应头)
/**
* cfd 通信文件描述符
* status 状态码
* descr 状态描述
* type 描述数据格式
* length 数据库长度 若为-1,则告诉浏览器去计算长度
*/
int sendHeadMsg(int cfd, int status, const char* descr, const char* type, int length);

// 获取文件类型,已经有,不用再写
const char* getFileType(const char* name);

// 发送目录
int sendDir(const char* dirName, int cfd);

// 将数字从十六进制转换成十进制
int hexToDec(char c);

// 解码,解决中文乱码问题,from传入参数,to传出参数
void decodeMsg(char* to, char* from);

Server.c代码:

#include "Server.h"
#include <arpa/inet.h>
#include 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值