一个简单http_server的实现(1)

最近在学习网络编程的知识,所以准备实现一个http服务器,写博客来记录一下学习的过程

RIO

RIO是csapp中提到的一个健壮的I/O包,为什么要使用这个来进行文件的读写,是因为接下来的socket编程中不适合使用c标准I/O

先贴出Rio包的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef __RIO_H__
#define __RIO_H__
#include <http_server.h>
#define RIO_BUFSIZE 8192//缓冲区的大小
struct rio_t
{
int rio_fd;//缓冲区对应的文件描述符
int rio_cnt;//缓冲区中的元素个数
char * rio_bufptr;//缓冲区头指针
char rio_buf [RIO_BUFSIZE];//缓冲区
rio_t (int fd):rio_fd (fd),rio_cnt (0),rio_bufptr(rio_buf){}
};
class Rio
{
public:
//从文件中读取n个字节,存入usrbuf,读到EOF返回0,出错返回-1,正常返回读入的字节数
ssize_t rio_readn (int fd,void *usrbuf,size_t n);
//将usrbuf中的n个字节写入文件fd中
ssize_t rio_writen (int fd,void *usrbuf,size_t n);
//初始化缓冲区
void rio_readinitb (rio_t * rp,int fd);
//从缓冲区读取一行文本,最多读取maxlen的字节,超出部分截断用'\0'结尾
ssize_t rio_readlineb (rio_t * rp,void * usrbuf,size_t maxlen);
//从缓冲区读n个字节
ssize_t rio_readnb (rio_t * rp,void *usrbuf,size_t n);

private:
//维护缓冲区,为系统read的带缓冲区版本
ssize_t rio_read (rio_t *rp,char *usrbuf,size_t);

};
#endif //__RIO_H__

rio_readn函数和rio_writen函数和linux底层的read和write函数很像,不过处理了EINTR的错误,当read和write在阻塞时被某个信号中断,这时没有字节被读取/写入,系统会将错误代码置为EINTR
显然这个错误是可以修复的,当产生这个错误时我们只需要继续调用read和write即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include "rio.h" 
ssize_t Rio::rio_readn (int fd,void * usrbuf,size_t n)
{
size_t isleft=n;
size_t isread;
char *buf=static_cast<char*>(usrbuf);
while (isleft>0)
{
if ((isread=read (fd,buf,isleft))<0)
{
if (errno==EINTR)
isread=0;
else
return -1;//读取出错
}else if (isread==0)
break;
isleft-=isread;
buf+=isread;
}
return (n-isleft);
}
ssize_t Rio::rio_writen (int fd,void *usrbuf ,size_t n)
{
size_t isleft=n;
size_t iswrite;
char *buf=static_cast<char*>(usrbuf);
while (isleft>0)
{
if ((iswrite=write(fd,buf,isleft))<=0)
{
if (errno==EINTR)
iswrite=0;
else
return -1;//写入出错
}
isleft-=iswrite;
buf+=iswrite;
}
return n;
}

缓冲区是为了优化I/O速度,因为底层I/O要切换到内核态进行,切换过程开销很大
rio_t就是缓冲区的结构,rio_readinitb是将缓冲区与打开的文件关联上
rio_readnb函数就是read的带缓冲区版本
rio_readlineb是一次读取一行,这两个函数的核心是rio_read函数
rio_read函数是来维护缓冲区的,当缓冲区为空时它会将缓冲区填满,如果剩余未读取数据不够将缓冲区填满返回填入的字节数,只要缓冲区不为空就将指定的n与缓冲区剩余字节中小的那个值的字节数拷贝给用户缓冲区

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
...
ssize_t Rio::rio_read (rio_t *rp,char *usrbuf,size_t n)
{
int cnt;
while (rp->rio_cnt<=0)
{
//填满缓冲区
rp->rio_cnt=read (rp->rio_fd,rp->rio_buf,sizeof (rp->rio_buf));
if (rp->rio_cnt<0)
{
if (errno!=EINTR)
return -1;//读取出错
}else if (rp->rio_cnt==0)//EOF
return 0;
else
rp->rio_bufptr=rp->rio_buf;//重置缓冲区指针
}
cnt=std::min (n,(size_t)rp->rio_cnt);
memcpy (usrbuf,rp->rio_bufptr,cnt);
rp->rio_cnt-=cnt;
rp->rio_bufptr+=cnt;
return cnt;
}
ssize_t Rio::rio_readlineb (rio_t* rp,void *usrbuf,size_t maxlen)
{
int isread;
int i;
char c,*buf=static_cast<char*> (usrbuf);
for (i=0;i<maxlen;++i)
{
if ((isread=rio_read (rp,&c,1))==1)
{
*buf++=c;
if (c=='\n')
break;
}else if (isread==0)
{
if (i==0)
return 0;//EOF,且未读入任何数据
else
break;
}else
return -1;
}
*buf='\0';
return i+1;
}
ssize_t Rio::rio_readnb (rio_t* rp,void *usrbuf,size_t n)
{
int isleft=n;
int isread;
char *buf=static_cast<char*> (usrbuf);
while(isleft>0)
{
if ((isread=rio_read(rp,buf,n))<0)
return -1;
else if (isread==0)//EOF
break;
isleft-=isread;
buf+=isread;
}
return (n-isleft);
}

RIO同时是线程安全的,因为它给每一个文件配置了一个缓冲区,当不同线程读取不同文件的时候是线程安全的,当然当不同线程读取同一个文件还是会出问题,这就要用锁机制了
代码会同步更新再github

本文标题:一个简单http_server的实现(1)

文章作者:

发布时间:2020年06月09日 - 21:06

最后更新:2020年06月10日 - 00:06

原始链接:http://startcraft.cn/post/f3ca6604.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------The End-------------