Http服務(wù)器實(shí)現(xiàn)文件上傳與下載(三)
一、引言
在前2章的內(nèi)容基本上已經(jīng)講解了整個(gè)的大致流程。在設(shè)計(jì)Http服務(wù)器時(shí),我設(shè)計(jì)為四層的結(jié)構(gòu),最底層是網(wǎng)絡(luò)傳輸層,就是socket編程。接著一層是請求和響應(yīng)層,叫做Request和Response。在上一層是URL解析流程走向?qū)印W铐攲游以O(shè)計(jì)為索引層。這一層主要多文件時(shí)對文件進(jìn)行內(nèi)存上的索引,加快文件的查找。或者可能是其他內(nèi)容。在這一次中也包括了一些瀏覽器顯示的頁面內(nèi)容。這些都是可以讀者自行添加。
在寫這一章節(jié)時(shí),我不知道該是從上往下講解,還是從下往上講解能讓讀者更加清楚我的設(shè)計(jì)。在思考中.....,最終選擇從底層的socket層開始講解。如果大家對此還有什么疑問可以查看前兩章的內(nèi)容。
二、socket編程
在開始封裝的時(shí)候,大家可以看看《Unix網(wǎng)路編程》這本書,主要的內(nèi)容還是在上面,主要的代碼和這本書上的大致一致。在這里我在梳理一下。我不會(huì)講解所有的API,客戶端上的API我就不講解了,我只是說一下服務(wù)器用到的API。
在開始講解時(shí),我需要說2種套接字。只要是為后面的內(nèi)容做一下解釋。當(dāng)我們開始socket編程時(shí),需要?jiǎng)?chuàng)建一個(gè)套接字和遠(yuǎn)程端進(jìn)行連接的。在這句話中就包含了2種套接字。一種是監(jiān)聽套接字,一種是連接套接字。比如說監(jiān)聽套接字就是我們宿舍樓下的大爺,而連接套接字就是我們。當(dāng)有一個(gè)連接叫做快遞員到達(dá)樓下時(shí),被宿舍樓下大爺發(fā)現(xiàn)了,其實(shí)就是監(jiān)聽套接字發(fā)現(xiàn)有連接進(jìn)來了。然后大爺告訴我們,你的快遞來了,然后我們下樓向快遞員簽字拿快遞,這就是建立了連接。
從上面得知我們需要一個(gè)監(jiān)聽套接字,也就是宿舍大爺。創(chuàng)建如下:
int socket(int family,int type,int protocal);
發(fā)現(xiàn)沒有,返回值就是套接字,其實(shí)就是一個(gè)整數(shù),或者稱為文件描述符。因?yàn)樵趌inux下所有的設(shè)備都是文件,所以可以用一個(gè)唯一的整數(shù)代表某個(gè)含義。該返回-1時(shí)代表創(chuàng)建套接字失敗。一般我們TCP編程參數(shù)填寫會(huì)是listenfd= socket(AF_INET,SOCK_STREAM,0);創(chuàng)建的是流套接字,具體內(nèi)容可以看上面提到的書籍。
接下來我們需要綁定套接字,為什么?因?yàn)槲覀兿蛳到y(tǒng)申請了一個(gè)套接字(宿舍大爺)后,可是他還沒有工作地點(diǎn)呢,我們需要為他安排工作地點(diǎn)。工作地點(diǎn)也就是端口,要表示一個(gè)唯一的工作地點(diǎn),計(jì)算機(jī)需要IP和端口同時(shí)制定才能確定唯一。
int bind(int sockfd,const struct sockaddr*myaddr,socklen_t addrlen);
若成功返回0,否則返回-1。第一個(gè)參數(shù)代表著剛才創(chuàng)建的套接字listenfd。struct sockaddr是一個(gè)通用套接字結(jié)構(gòu),其實(shí)我們傳輸?shù)拇_實(shí)struct sockaddr_in{}這樣的結(jié)構(gòu),需要進(jìn)行強(qiáng)制轉(zhuǎn)換,原因是這些API都比較老了,歷史原因造成的,主要是之前沒有void*這個(gè)類型。socklen_t就是一個(gè)無符號的整數(shù)類型。調(diào)用方式一般為
bind(listenfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
這個(gè)serveraddr的填充如下:
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
serveraddr.sin_port=htons(PORT);
而serveraddr的類型確實(shí)struct sockaddr_in;
現(xiàn)在也為宿舍大爺安排了工作地點(diǎn)了,接下來無非就是讓大爺工作了,不然申請過來干什么...
int listen(int sockfd,int backlog);
若成功返回0失敗為-1.第一個(gè)套接字還是剛才的監(jiān)聽套接字,backlog這個(gè)參數(shù)主要涉及到未完成連接隊(duì)列和已完成連接隊(duì)列的問題。現(xiàn)在理解為最大的連接數(shù)即可,具體看上面的書本。
最后一個(gè)就是當(dāng)大爺發(fā)現(xiàn)了快遞員,并確定他的身份后,叫我們下來,這是就是下面的api
int accept(int sockfd,struct sockaddr*chliaddr,socklent_t *addrlen);
成功的話返回連接套接字 ,就是我們自己,否則返回-1。在這三個(gè)參數(shù)中第一個(gè)還是這個(gè)監(jiān)聽套接字listenfd,第二個(gè)和第三個(gè)可以獲取連接者的身份。來著的IP和端口。
一般如果不用設(shè)為NULL。例子如下:
clientlen = sizeof(struct sockaddr_in);
int fd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientlen);
當(dāng)不為clientlen填寫一個(gè)默認(rèn)值的話,程序會(huì)報(bào)錯(cuò)的,盡管這個(gè)參數(shù)叫做獲取長度,但是我們要是需要先設(shè)置大小。
接下來的就是我們與快遞員通訊了。我們接受快遞員遞給我的快遞,這個(gè)行為就是read。就是把內(nèi)容寫入打自己的存儲(chǔ)空間。
ssize_t read(int sockfd,void*buf,size_t nbyte);
讀取成功返回實(shí)際讀取的字節(jié)數(shù),如果返回0表示已經(jīng)輸送完畢,小于0表示輸入錯(cuò)誤,buf就是自己存放接受到的內(nèi)容,nbyte表示這個(gè)空間的大小。這里記住sockfd現(xiàn)在填寫的是連接套接字,是我們自己,而不是老大爺了。
ssize_t write(int sockfd,const void* buf,size_t nbytes);
該函數(shù)把buf中nbytes字節(jié)內(nèi)容寫入sockfd套接字,發(fā)送給對方。成功返回實(shí)際寫了多少字節(jié),失敗返回-1。
到這里已經(jīng)講完需要用到的API,但是最后兩個(gè)read和write有點(diǎn)特殊,因?yàn)樵谝恍﹍inux系統(tǒng)中,當(dāng)系統(tǒng)發(fā)生中斷時(shí),可能會(huì)停止read或者write。這是我們需要重新調(diào)用該函數(shù)。為了能正確的獲取數(shù)據(jù),需要填寫一些代碼,僅僅的調(diào)用這2個(gè)函數(shù)是不夠的。
下面就是這個(gè)套接字的代碼段。把套接字封裝在一個(gè)命名空間為TCP的Socket類中。
頭文件(include/socket.h)
1 /* 2 * tcp.h 3 * 4 */ 5 6 #ifndef SOCKET_H_ 7 #define SOCKET_H_ 8 #include9 #include 10 #include 11 #include 12 #include 13 #include 14 //#include 15 namespace TCP{ 16 class Socket { 17 public: 18 Socket(); 19 ~Socket(); 20 int server_socket(); 21 int server_listen(); 22 int server_accept(); 23 int server_bind(); 24 void server_init(); 25 void getClient(sockaddr_in* caddr); 26 int server_read(int fd,char*recvBuf,ssize_t maxlen); 27 int server_write(int fd,char*sendBuf,ssize_t maxlen); 28 void server_close(int confd) ; 29 private: 30 int __readline(int fd,char*recvBuf,ssize_t maxlen) ; 31 int __writen(int fd,char*sendBuf,ssize_t maxlen) ; 32 int listenfd; 33 int confd; 34 struct sockaddr_in serveraddr; 35 socklen_t serverlen; 36 static const int PORT=80; 37 }; 38 } 39 #endif /* SOCKET_H_ */
#p#
源文件(src/socket.h)
1 #include "socket.h" 2 namespace TCP{ 3 Socket::Socket() { 4 } 5 Socket::~Socket() { 6 } 7 int Socket::server_socket() { 8 listenfd= socket(AF_INET,SOCK_STREAM,0); 9 if(listenfd !=-1){ 10 std::cout<<"server_socket() ...succeed"< 11 }else{ 12 std::cout<<"server_socket() ...failed"< 13 } 14 return listenfd; 15 } 16 17 int Socket::server_listen() { 18 int ret = listen(listenfd,100); 19 if(ret ==0){ 20 std::cout<<"server_listen() ...succeed"< 21 }else{ 22 std::cout<<"server_listen() ...failed"< 23 } 24 return ret; 25 } 26 void Socket::server_close(int confd) { 27 close(confd); 28 } 29 int Socket::server_accept() { 30 clientlen = sizeof(struct sockaddr_in); 31 int fd = accept(listenfd,(struct sockaddr *)&clientaddr,&clientlen); 32 if(fd !=-1){ 33 std::cout<<"server_accept() ...succeed"< 34 }else{ 35 std::cout<<"server_accept() ...failed"< 36 } 37 return fd; 38 } 39 int Socket::server_bind() { 40 int ret =bind(listenfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)); 41 if(ret ==0){ 42 std::cout<<"server_bind() ...succeed"< 43 }else{ 44 std::cout<<"server_bind() ...failed"< 45 } 46 return ret; 47 } 48 void Socket::server_init() { 49 bzero(&serveraddr,sizeof(serveraddr)); 50 serveraddr.sin_family=AF_INET; 51 serveraddr.sin_addr.s_addr=htonl(INADDR_ANY); 52 serveraddr.sin_port=htons(PORT); 53 54 } 55 ssize_t Socket::server_read(int fd,char*recvBuf,ssize_t maxlen) { 56 long long havedreadCount=0; 57 int readCount=0; 58 while(1){ 59 readCount = __readline(fd,recvBuf+havedreadCount,maxlen); 60 havedreadCount+=readCount; 61 //std::cout<<"readCount:"< 62 if(readCount==0)//當(dāng)一行是\r\n時(shí),空行,表示這一次讀完。 63 break; 64 } 65 return 0; 66 67 } 68 ssize_t Socket::server_write(int fd,char*sendBuf,ssize_t maxlen){ 69 70 return __writen(fd,sendBuf,maxlen); 71 } 72 int Socket::__writen(int fd,char*sendBuf,ssize_t maxlen){ 73 size_t nleft; 74 ssize_t nwritten; 75 const char *ptr; 76 ptr=sendBuf; 77 nleft=maxlen; 78 //int count=0; 79 80 while(nleft>0){ 81 if((nwritten=write(fd,ptr,nleft))<=0){ 82 if(nwritten<0&& errno==EINTR) 83 nwritten=0; 84 else{ 85 return -1; 86 } 87 } 88 nleft-=nwritten; 89 ptr+=nwritten; 90 } 91 return maxlen; 92 } 93 int Socket::__readline(int fd,char*recvBuf,ssize_t maxlen) { 94 ssize_t n,rc; 95 char c,*ptr; 96 ptr=recvBuf; 97 for(n=1;n 98 again: 99 if((rc=read(fd,&c,1))==1){ 100 *ptr++=c; 101 //std::cout< 102 if(c=='\n') 103 break; 104 }else if(rc ==0){ 105 *ptr=0; 106 return n-1; 107 }else{ 108 if(errno ==EINTR) 109 goto again; 110 return -1; 111 } 112 } 113 *ptr=0; 114 if(n==2&&*(ptr-2)=='\r'&&*(ptr-1)=='\n') 115 n=0; 116 return n; 117 } 118 }