1. 程式人生 > >Http伺服器實現檔案上傳與下載(三)

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)

 1 /*
 2  * tcp.h
 3  *
 4  */
 5 
 6 #ifndef SOCKET_H_
 7 #define SOCKET_H_
 8 #include<iostream>
 9 #include<sys/socket.h>
10 #include<sys/types.h>
11 #include<netinet/in.h>
12 #include<errno.h>
13 #include <string.h>
14 //#include <unistd>
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_ */

原始檔(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"<<std::endl;
 11         }else{
 12             std::cout<<"server_socket() ...failed"<<std::endl;
 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"<<std::endl;
 21         }else{
 22             std::cout<<"server_listen() ...failed"<<std::endl;
 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"<<std::endl;
 34         }else{
 35             std::cout<<"server_accept() ...failed"<<std::endl;
 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"<<std::endl;
 43          }else{
 44              std::cout<<"server_bind() ...failed"<<std::endl;
 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:"<<readCount<<std::endl;
 62             if(readCount==0)//當一行是\r\n時,空行,表示這一次讀完。
 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<maxlen;n++){
 98         again:
 99             if((rc=read(fd,&c,1))==1){
100                 *ptr++=c;
101                 //std::cout<<c;
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 }

  到這裡可以看到這一層的內容已經完成,如果感興趣,請關注我,繼續檢視下面的講解《Http伺服器實現檔案上傳與下載(四)

相關推薦

Http伺服器實現檔案下載()

一、引言   在前2章的內容基本上已經講解了整個的大致流程。在設計Http伺服器時,我設計為四層的結構,最底層是網路傳輸層,就是socket程式設計。接著一層是請求和響應層,叫做Request和Response。在上一層是URL解析流程走向層。最頂層我設計為索引層。這一層主要多檔案時對檔案進行記憶體上的索引

Http伺服器實現檔案下載(二)

一、引言 歡迎大家接著看我的部落格,如何大家有什麼想法的話回覆我哦,閒話不多聊了,接著上一講的內容來說吧,在上一節中已經講到了請求頭字串的解析,並且在解析中我我們已經獲取了url。就是上節中提到的/doing。當瀏覽器傳送了/doing請求後,這是的與伺服器的連線並沒有

Http伺服器實現檔案下載(五)

一、引言      歡迎大家和我一起編寫Http伺服器實現檔案的上傳和下載,現在我回顧一下在上一章節中提到的一些內容,之前我已經提到過檔案的下載,在檔案的下載中也提到了檔案的續下載只需要在響應頭中填寫Content-Range這一欄位,並且伺服器的檔案指標指向讀取的指定

Http伺服器實現檔案下載(一)

一、引言   大家都知道web程式設計的協議就是http協議,稱為超文字傳輸協議。在J2EE中我們可以很快的實現一個Web工程,但在C++中就不是非常的迅速,原因無非就是底層的socket網路編寫需要自己完成,上層的http協議需要我們自己完成,使用者介面需要我們自己完

Http伺服器實現檔案下載(四)

一、引言   歡迎大家來到和我一起編寫Http伺服器實現檔案的上傳和下載,現在我稍微回顧一下之前我說的,第一、二章說明說明了整體的HTTP走向,第三章實現底層的網路程式設計。接著這一章我想給大家講的是請求獲取,和響應傳送的內容。這裡主要講解的響應內容,為什麼?因為我們編寫的是一個與瀏覽器互動的HTTP伺服器

struts2實現檔案下載功能

一、Demo介紹 基於struts2框架,實現多檔案的上傳和下載功能。 實現原理圖: 部分介面圖: 上傳成功及下載頁面: 二、主要程式碼 uploadFile.jsp:在form表單中包含一個文字框(上傳使用者的姓名)和兩個檔案上傳選項. <%@

JAVA通訊(1)-- 使用Socket實現檔案下載

客戶端 /** * 檔案上傳客戶端 * * @author chen.lin * */ public class UploadClient extends JFrame { /** * */ priva

使用jsp/servlet簡單實現檔案下載

public class UploadServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException,

Spring 實現檔案下載

檔案上傳 upload.jsp: <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="

struts2實現檔案下載

一、單檔案上傳 1、檔案上傳條件: (1)請求方法必須是post (2)enctype的屬性值必須為multipart/form-data (3)提供一個檔案選擇域 2、檔案上傳jsp程式碼 <%@ page language="java" c

C# 之 FTP伺服器檔案下載(二)

        通過上一篇部落格《C# 之 FTP伺服器中檔案上傳與下載(一)》,我們已經建立好了一個FTP伺服器,並且該伺服器需要使用者名稱和密碼的驗證。今天我們來實現檔案的上傳。 首先,我們前臺需要一個FileUpload控制元件和一個Button控制元件 <

java實現檔案下載

           東風化宇,檔案上傳 一、對於檔案上傳,瀏覽器在上傳的過程中是將檔案以流的形式提交到伺服器端的,Servlet獲取上傳檔案的輸入流然後再解析裡面的請求引數是比較麻煩。 JSP程式碼,POST請求,表單必須設定為enctype="multipar

基於TCP伺服器檔案下載

** ## service.c ** #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include &l

SpringBoot下檔案下載實現

本文歡迎轉載,轉載請註明出處,謝謝~(作者:喝酒不騎馬 Colton_Null) from CSDN SpringBoot後臺如何實現檔案上傳下載? 最近做的一個專案涉及到檔案上傳與下載。前端上傳採用百度webUploader外掛。有關該外掛的

檔案下載的功能實現

檔案上傳 檔案上傳原理分析 1、檔案上傳的必要前提: 一、進行檔案上傳時只能使用post方式提交表單 二、表單必須新增一個屬性:enctype=”multipart/form-data” 三、用於上傳檔案的元素必須時 2、enctype屬性 作

java使用Jsch實現遠端操作linux伺服器進行檔案下載,刪除和顯示目錄資訊

1 package com.fline.aic.utils; 2 3 import java.io.BufferedReader; 4 import java.io.File; 5 import java.io.FileInputStream; 6 import jav

基於tobato的fastdfsspring boot整合實現檔案下載

專案結構: pom.xml檔案新增配置: <!-- fastdfs --> <dependency> <groupId>com.github.tobato</groupId> <artifactId>fastd

基於js-ipfs-api實現ipfs的檔案下載

配置本地的ipfs節點 # 初始化ipfs節點 ipfs init # 執行ipfs節點 ipfs daemon 使用js-ipfs-api呼叫ipfs服務 連線本地ipfs節點 const ipfsAPI = require('ip

Struts2實現檔案,多檔案下載(十)

    “兩個蝴蝶飛”特別喜歡"java1234知識分享網"小峰的實用主義,所以本文及其系列文章均是採用實用主義,從專案和程式碼的角度去分析。由於本人經驗有限,嘴皮子不溜,所以學術性,概念性,底層性的知識點暫時不做介紹。文章中有錯誤之處,歡迎拍磚和指點。特別感謝"java12

中間有跳板機,mac電腦如何伺服器之間進行下載檔案

安裝zssh brew install zssh 上傳檔案 zssh登陸上跳板機 在跳板機上ssh到相應伺服器 在伺服器上cd至相應要放上傳檔案的目錄 rz -bye //在遠端伺服器的相應目錄上執行此命令,表示做好接