Http服务器实现文件上传与下载(三)
2015-09-28 15:54
483 查看
一、引言
在前2章的内容基本上已经讲解了整个的大致流程。在设计Http服务器时,我设计为四层的结构,最底层是网络传输层,就是socket编程。接着一层是请求和响应层,叫做Request和Response。在上一层是URL解析流程走向层。最顶层我设计为索引层。这一层主要多文件时对文件进行内存上的索引,加快文件的查找。或者可能是其他内容。在这一次中也包括了一些浏览器显示的页面内容。这些都是可以读者自行添加。
在写这一章节时,我不知道该是从上往下讲解,还是从下往上讲解能让读者更加清楚我的设计。在思考中.....,最终选择从底层的socket层开始讲解。如果大家对此还有什么疑问可以查看前两章的内容。
二、socket编程
在开始封装的时候,大家可以看看《Unix网路编程》这本书,主要的内容还是在上面,主要的代码和这本书上的大致一致。在这里我在梳理一下。我不会讲解所有的API,客户端上的API我就不讲解了,我只是说一下服务器用到的API。
在开始讲解时,我需要说2种套接字。只要是为后面的内容做一下解释。当我们开始socket编程时,需要创建一个套接字和远程端进行连接的。在这句话中就包含了2种套接字。一种是监听套接字,一种是连接套接字。比如说监听套接字就是我们宿舍楼下的大爷,而连接套接字就是我们。当有一个连接叫做快递员到达楼下时,被宿舍楼下大爷发现了,其实就是监听套接字发现有连接进来了。然后大爷告诉我们,你的快递来了,然后我们下楼向快递员签字拿快递,这就是建立了连接。
从上面得知我们需要一个监听套接字,也就是宿舍大爷。创建如下:
int socket(int family,int type,int protocal);
发现没有,返回值就是套接字,其实就是一个整数,或者称为文件描述符。因为在linux下所有的设备都是文件,所以可以用一个唯一的整数代表某个含义。该返回-1时代表创建套接字失败。一般我们TCP编程参数填写会是listenfd= socket(AF_INET,SOCK_STREAM,0);创建的是流套接字,具体内容可以看上面提到的书籍。
接下来我们需要绑定套接字,为什么?因为我们向系统申请了一个套接字(宿舍大爷)后,可是他还没有工作地点呢,我们需要为他安排工作地点。工作地点也就是端口,要表示一个唯一的工作地点,计算机需要IP和端口同时制定才能确定唯一。
int bind(int sockfd,const struct sockaddr*myaddr,socklen_t addrlen);
若成功返回0,否则返回-1。第一个参数代表着刚才创建的套接字listenfd。struct sockaddr是一个通用套接字结构,其实我们传输的确实struct sockaddr_in{}这样的结构,需要进行强制转换,原因是这些API都比较老了,历史原因造成的,主要是之前没有void*这个类型。socklen_t就是一个无符号的整数类型。调用方式一般为
bind(listenfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
这个serveraddr的填充如下:
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
serveraddr.sin_port=htons(PORT);
而serveraddr的类型确实struct sockaddr_in;
现在也为宿舍大爷安排了工作地点了,接下来无非就是让大爷工作了,不然申请过来干什么...
int listen(int sockfd,int backlog);
若成功返回0失败为-1.第一个套接字还是刚才的监听套接字,backlog这个参数主要涉及到未完成连接队列和已完成连接队列的问题。现在理解为最大的连接数即可,具体看上面的书本。
最后一个就是当大爷发现了快递员,并确定他的身份后,叫我们下来,这是就是下面的api
int accept(int sockfd,struct sockaddr*chliaddr,socklent_t *addrlen);
成功的话返回连接套接字 ,就是我们自己,否则返回-1。在这三个参数中第一个还是这个监听套接字listenfd,第二个和第三个可以获取连接者的身份。来着的IP和端口。
一般如果不用设为NULL。例子如下:
clientlen = sizeof(struct sockaddr_in);
int fd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientlen);
当不为clientlen填写一个默认值的话,程序会报错的,尽管这个参数叫做获取长度,但是我们要是需要先设置大小。
接下来的就是我们与快递员通讯了。我们接受快递员递给我的快递,这个行为就是read。就是把内容写入打自己的存储空间。
ssize_t read(int sockfd,void*buf,size_t nbyte);
读取成功返回实际读取的字节数,如果返回0表示已经输送完毕,小于0表示输入错误,buf就是自己存放接受到的内容,nbyte表示这个空间的大小。这里记住sockfd现在填写的是连接套接字,是我们自己,而不是老大爷了。
ssize_t write(int sockfd,const void* buf,size_t nbytes);
该函数把buf中nbytes字节内容写入sockfd套接字,发送给对方。成功返回实际写了多少字节,失败返回-1。
到这里已经讲完需要用到的API,但是最后两个read和write有点特殊,因为在一些linux系统中,当系统发生中断时,可能会停止read或者write。这是我们需要重新调用该函数。为了能正确的获取数据,需要填写一些代码,仅仅的调用这2个函数是不够的。
下面就是这个套接字的代码段。把套接字封装在一个命名空间为TCP的Socket类中。
头文件(include/socket.h)
源文件(src/socket.h)
到这里可以看到这一层的内容已经完成,如果感兴趣,请关注我,继续查看下面的讲解《Http服务器实现文件上传与下载(四)》
在前2章的内容基本上已经讲解了整个的大致流程。在设计Http服务器时,我设计为四层的结构,最底层是网络传输层,就是socket编程。接着一层是请求和响应层,叫做Request和Response。在上一层是URL解析流程走向层。最顶层我设计为索引层。这一层主要多文件时对文件进行内存上的索引,加快文件的查找。或者可能是其他内容。在这一次中也包括了一些浏览器显示的页面内容。这些都是可以读者自行添加。
在写这一章节时,我不知道该是从上往下讲解,还是从下往上讲解能让读者更加清楚我的设计。在思考中.....,最终选择从底层的socket层开始讲解。如果大家对此还有什么疑问可以查看前两章的内容。
二、socket编程
在开始封装的时候,大家可以看看《Unix网路编程》这本书,主要的内容还是在上面,主要的代码和这本书上的大致一致。在这里我在梳理一下。我不会讲解所有的API,客户端上的API我就不讲解了,我只是说一下服务器用到的API。
在开始讲解时,我需要说2种套接字。只要是为后面的内容做一下解释。当我们开始socket编程时,需要创建一个套接字和远程端进行连接的。在这句话中就包含了2种套接字。一种是监听套接字,一种是连接套接字。比如说监听套接字就是我们宿舍楼下的大爷,而连接套接字就是我们。当有一个连接叫做快递员到达楼下时,被宿舍楼下大爷发现了,其实就是监听套接字发现有连接进来了。然后大爷告诉我们,你的快递来了,然后我们下楼向快递员签字拿快递,这就是建立了连接。
从上面得知我们需要一个监听套接字,也就是宿舍大爷。创建如下:
int socket(int family,int type,int protocal);
发现没有,返回值就是套接字,其实就是一个整数,或者称为文件描述符。因为在linux下所有的设备都是文件,所以可以用一个唯一的整数代表某个含义。该返回-1时代表创建套接字失败。一般我们TCP编程参数填写会是listenfd= socket(AF_INET,SOCK_STREAM,0);创建的是流套接字,具体内容可以看上面提到的书籍。
接下来我们需要绑定套接字,为什么?因为我们向系统申请了一个套接字(宿舍大爷)后,可是他还没有工作地点呢,我们需要为他安排工作地点。工作地点也就是端口,要表示一个唯一的工作地点,计算机需要IP和端口同时制定才能确定唯一。
int bind(int sockfd,const struct sockaddr*myaddr,socklen_t addrlen);
若成功返回0,否则返回-1。第一个参数代表着刚才创建的套接字listenfd。struct sockaddr是一个通用套接字结构,其实我们传输的确实struct sockaddr_in{}这样的结构,需要进行强制转换,原因是这些API都比较老了,历史原因造成的,主要是之前没有void*这个类型。socklen_t就是一个无符号的整数类型。调用方式一般为
bind(listenfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
这个serveraddr的填充如下:
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
serveraddr.sin_port=htons(PORT);
而serveraddr的类型确实struct sockaddr_in;
现在也为宿舍大爷安排了工作地点了,接下来无非就是让大爷工作了,不然申请过来干什么...
int listen(int sockfd,int backlog);
若成功返回0失败为-1.第一个套接字还是刚才的监听套接字,backlog这个参数主要涉及到未完成连接队列和已完成连接队列的问题。现在理解为最大的连接数即可,具体看上面的书本。
最后一个就是当大爷发现了快递员,并确定他的身份后,叫我们下来,这是就是下面的api
int accept(int sockfd,struct sockaddr*chliaddr,socklent_t *addrlen);
成功的话返回连接套接字 ,就是我们自己,否则返回-1。在这三个参数中第一个还是这个监听套接字listenfd,第二个和第三个可以获取连接者的身份。来着的IP和端口。
一般如果不用设为NULL。例子如下:
clientlen = sizeof(struct sockaddr_in);
int fd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientlen);
当不为clientlen填写一个默认值的话,程序会报错的,尽管这个参数叫做获取长度,但是我们要是需要先设置大小。
接下来的就是我们与快递员通讯了。我们接受快递员递给我的快递,这个行为就是read。就是把内容写入打自己的存储空间。
ssize_t read(int sockfd,void*buf,size_t nbyte);
读取成功返回实际读取的字节数,如果返回0表示已经输送完毕,小于0表示输入错误,buf就是自己存放接受到的内容,nbyte表示这个空间的大小。这里记住sockfd现在填写的是连接套接字,是我们自己,而不是老大爷了。
ssize_t write(int sockfd,const void* buf,size_t nbytes);
该函数把buf中nbytes字节内容写入sockfd套接字,发送给对方。成功返回实际写了多少字节,失败返回-1。
到这里已经讲完需要用到的API,但是最后两个read和write有点特殊,因为在一些linux系统中,当系统发生中断时,可能会停止read或者write。这是我们需要重新调用该函数。为了能正确的获取数据,需要填写一些代码,仅仅的调用这2个函数是不够的。
下面就是这个套接字的代码段。把套接字封装在一个命名空间为TCP的Socket类中。
头文件(include/socket.h)
/* * tcp.h * */ #ifndef SOCKET_H_ #define SOCKET_H_ #include<iostream> #include<sys/socket.h> #include<sys/types.h> #include<netinet/in.h> #include<errno.h> #include <string.h> //#include <unistd> namespace TCP{ class Socket { public: Socket(); ~Socket(); int server_socket(); int server_listen(); int server_accept(); int server_bind(); void server_init(); void getClient(sockaddr_in* caddr); int server_read(int fd,char*recvBuf,ssize_t maxlen); int server_write(int fd,char*sendBuf,ssize_t maxlen); void server_close(int confd) ; private: int __readline(int fd,char*recvBuf,ssize_t maxlen) ; int __writen(int fd,char*sendBuf,ssize_t maxlen) ; int listenfd; int confd; struct sockaddr_in serveraddr; socklen_t serverlen; static const int PORT=80; }; } #endif /* SOCKET_H_ */
源文件(src/socket.h)
#include "socket.h" namespace TCP{ Socket::Socket() { } Socket::~Socket() { } int Socket::server_socket() { listenfd= socket(AF_INET,SOCK_STREAM,0); if(listenfd !=-1){ std::cout<<"server_socket() ...succeed"<<std::endl; }else{ std::cout<<"server_socket() ...failed"<<std::endl; } return listenfd; } int Socket::server_listen() { int ret = listen(listenfd,100); if(ret ==0){ std::cout<<"server_listen() ...succeed"<<std::endl; }else{ std::cout<<"server_listen() ...failed"<<std::endl; } return ret; } void Socket::server_close(int confd) { close(confd); } int Socket::server_accept() { clientlen = sizeof(struct sockaddr_in); int fd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientlen); if(fd !=-1){ std::cout<<"server_accept() ...succeed"<<std::endl; }else{ std::cout<<"server_accept() ...failed"<<std::endl; } return fd; } int Socket::server_bind() { int ret =bind(listenfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)); if(ret ==0){ std::cout<<"server_bind() ...succeed"<<std::endl; }else{ std::cout<<"server_bind() ...failed"<<std::endl; } return ret; } void Socket::server_init() { bzero(&serveraddr,sizeof(serveraddr)); serveraddr.sin_family=AF_INET; serveraddr.sin_addr.s_addr=htonl(INADDR_ANY); serveraddr.sin_port=htons(PORT); } ssize_t Socket::server_read(int fd,char*recvBuf,ssize_t maxlen) { long long havedreadCount=0; int readCount=0; while(1){ readCount = __readline(fd,recvBuf+havedreadCount,maxlen); havedreadCount+=readCount; //std::cout<<"readCount:"<<readCount<<std::endl; if(readCount==0)//当一行是\r\n时,空行,表示这一次读完。 break; } return 0; } ssize_t Socket::server_write(int fd,char*sendBuf,ssize_t maxlen){ return __writen(fd,sendBuf,maxlen); } int Socket::__writen(int fd,char*sendBuf,ssize_t maxlen){ size_t nleft; ssize_t nwritten; const char *ptr; ptr=sendBuf; nleft=maxlen; //int count=0; while(nleft>0){ if((nwritten=write(fd,ptr,nleft))<=0){ if(nwritten<0&& errno==EINTR) nwritten=0; else{ return -1; } } nleft-=nwritten; ptr+=nwritten; } return maxlen; } int Socket::__readline(int fd,char*recvBuf,ssize_t maxlen) { ssize_t n,rc; char c,*ptr; ptr=recvBuf; for(n=1;n<maxlen;n++){ again: if((rc=read(fd,&c,1))==1){ *ptr++=c; //std::cout<<c; if(c=='\n') break; }else if(rc ==0){ *ptr=0; return n-1; }else{ if(errno ==EINTR) goto again; return -1; } } *ptr=0; if(n==2&&*(ptr-2)=='\r'&&*(ptr-1)=='\n') n=0; return n; } }
到这里可以看到这一层的内容已经完成,如果感兴趣,请关注我,继续查看下面的讲解《Http服务器实现文件上传与下载(四)》
相关文章推荐
- 跨域访问http接口的使用
- 如何计算网络地址和广播地址
- AWS EC2服务器的HTTPS负载均衡器配置过程
- AWS EC2服务器的HTTPS负载均衡器配置过程
- C#获取网页内容 (WebClient、WebBrowser和HttpWebRequest/HttpWebResponse)
- 安卓:启动service,下载网络图片,并将图片存放到内存卡,保存成功后发出广播提醒,然后从SD卡读出显示
- TCP/IP详解卷1 读书笔记:第一章 概述
- httpd属性配置
- subversion各个旧版本下载地址:http://archive.apache.org/dist/subversion/
- HttpServletRequestWrapper重新并修改http请求信息
- 《SDN软件定义网络从入门到精通》理论课
- python多线程http压力测试脚本
- HTTPS原理
- HTTP制作代理服务器
- wget下载网络图片
- Altium Designer(Protel)网络连接方式Port和Net Label详解
- HTTP 头部详细解释
- HTTP向本地服务器请求数据
- https://ruby.taobao.org/
- http & https