Tomcat 學習進階曆程之Socket

來源:互聯網
上載者:User

Tomcat 學習進階曆程之Socket

        在前一篇了又重新學習了一下HTTP協議,對它的工作過程和原理又加深了認識。那麼當我們通過瀏覽器訪問一個線上資源的時候,瀏覽器是怎麼要將我們的請求發送到資源所在的伺服器,又如何獲得伺服器對請求的響應呢。其實它用的就是我們常見的Socket。

       Socket中文通常翻譯為‘通訊端’,通訊端是兩台機器之間的通訊端點,Socket是工作在TCP/IP協議之上的。兩台機器通過Socket通訊時有兩種方式,一種是無差錯的TCP方式,一種是無序有且可以有差錯的UDP方式。

       TCP方式一般用在檔案傳輸中,因為兩個機器在通訊前一般要經過三向交握的串連,所以開銷也比較大。

       UDP是一種可以有差錯的協議,它發送的資料是無序的,一般用在視頻,通話等情境下。這些情境一般不要求資料的完整性,可以容許部分的錯誤,只要資料在一定程度上是連續的即可。

1、 UDP方式

       在JAVA中,通過Socket以UDP方式發送資料,常用的javaAPI是DatagramSocket與DatagramPacket類。
       DatagramPacket表示資料報包,資料報包用來實現無串連包投遞服務。每條報文僅根據該包中包含的資訊從一台機器路由到另一台機器。從一台機器發送到另一台機器的多個包可能選擇不同的路由,也可能按不同的順序到達。不對包投遞做出保證。

       DatagramSocket表示用來發送和接收資料報包的通訊端。資料通訊端是包投遞服務的發送或接收點。每個在資料通訊端上發送或接收的包都是單獨編址和路由的。從一台機器發送到另一台機器的多個包可能選擇不同的路由,也可能按不同的順序到達。在 DatagramSocket 上總是啟用 UDP 廣播發送。

2、 TCP方式

       在JAVA中,通過Socket以TCP方式發送與接收資料,常用的JAVAAPI是Socket與ServerSocket。其中ServerSocket類實現伺服器通訊端。伺服器通訊端等待請求通過網路傳入。它基於該請求執行某些操作,然後可能向要求者返回結果。

       下面通過兩段代碼,看一個單線程的用戶端向服務端的Socket串連

1)服務端代碼
       
public static void main(String[] args) {// TODO Auto-generated method stubtry {//建立一個服務端Socket,在1355連接埠等接收資料。預設是本機ServerSocket server = new ServerSocket(1355);//accept方法會在指定連接埠一直偵聽,在有連結到來之前會一直阻塞Socket socket = server.accept();//有連結到來時,獲得SOCKET的輸入資料流,用來擷取用戶端發送過來的資料BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()) );//擷取Socket的輸出資料流,用來向用戶端發送資料PrintWriter out = new PrintWriter(socket.getOutputStream());while(true){String line;//readLine是一個阻塞方法,在輸入資料可用、檢測到流的末尾或者拋出異常前,此方法一直阻塞line = br.readLine();if(line !=null){//向用戶端響應資料out.println("server received "+line);out.flush();//如果用戶端輸出的是bye,關閉連結if(line.equals("bye"))break;}}socket.close();//br.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}

2)用戶端代碼
public static void main(String[] args) {// TODO Auto-generated method stubtry {//向原生1355連接埠請求連結,請求串連時要求服務端已經啟動,否則拋出異常Socket socket = new Socket("127.0.0.1",1355);//有連結到來時,獲得SOCKET的輸入資料流,用來擷取服務端發送過來的資料BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));//擷取Socket的輸出資料流,用來向服務端發送資料PrintWriter out = new PrintWriter(socket.getOutputStream());//用來接收使用者控制台的輸入BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));while(true){//readLine是一個阻塞方法,在輸入資料可用、檢測到流的末尾或者拋出異常前,此方法一直阻塞String msg = reader.readLine();out.flush();if(msg.equals("bye")){break;}//列印服務端返回的資料System.out.println(in.readLine());}socket.close();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}

測試時先啟動服務端,再啟動用戶端,就可以通過用戶端的控制台向伺服器發送資料,並接收用戶端的響應資訊。

通過上面的例子,我們可以總結以下幾點:

1)  伺服器先啟動,並等待用戶端的串連請求

2)  用戶端在需要時(可以是任務時候),向服務端發起串連請求

3)  服務端接收到客戶發起的串連請求,接收用戶端發送過的資料,可以根據需要對資料進行處理,然後向用戶端發送響應資料。

4)  服務端並不主動發起串連通訊。

通過上面幾點我們可以發現,這種工作方式正是J2EE中常用的工作方式。我們通過瀏覽器向一個網站發起請求時,Web伺服器就相當於服務端的ServerSocket,瀏覽器相當於用戶端的Socket,Web伺服器接收客戶瀏覽器的請求,經過對請求處理後向瀏覽器發送響應資料,然後瀏覽器將響應資料顯示出來。

下面,通過幾小段代碼,使用Socket手動建立一個Web伺服器,響應用戶端瀏覽器對伺服器靜態資源的請求。下面的代碼需要一點點HTTP協議的知識。

為了測試以下的代碼,可以建立一個項目,JAVA項目或Web項目都可以



httpServer代碼如下

public class HttpServer {public static final String WEB_ROOT = System.getProperty("user.dir")+File.separator+"webroot";private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";private boolean shutdown = false;/** * @param args */public static void main(String[] args) {// TODO Auto-generated method stubHttpServer server = new HttpServer();server.await();}public void await(){ServerSocket serverSocket = null;int port = 8080;try{//在本機8080連接埠開啟一個服務端SOCKETserverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));}catch (Exception e) {// TODO: handle exceptione.printStackTrace();System.exit(1);}//如果傳遞的請求不是shutdown,調用accept,等待用戶端瀏覽器的請求while(!shutdown){Socket socket = null;InputStream input = null;OutputStream output = null;try{socket = serverSocket.accept();//接收到請求,擷取輸入輸出資料流input = socket.getInputStream();output = socket.getOutputStream();Request request = new Request(input);//格式化請求,解析HTTP要求標頭資訊,解析出請求URIrequest.parse();//向用戶端發送響應資訊,然後關閉SOCKETResponse response = new Response(output);response.setRequest(request);response.sendStaticResource();socket.close();shutdown = request.getUri().equals(SHUTDOWN_COMMAND);}catch (Exception e) {// TODO: handle exceptione.printStackTrace();continue;}}}}

Request代碼如下:

public class Request {public Request(InputStream input) {// TODO Auto-generated constructor stubthis.input = input;}private InputStream input;private String uri;/** * 返回HTTP請求字串中頭兩個空格之間的文本。 * HTTP要求標頭一行是‘請求行’格式如:GET /index.html HTTP/1.1 * 所以此方法返回的是請求的URI */private String parseUri(String requestString){int index1,index2;index1 = requestString.indexOf(" ");if(index1 != -1){index2 = requestString.indexOf(" ",index1+1);if(index2 > index1)return requestString.substring(index1+1,index2);}return null;}/** * 從Socket的輸入資料流中擷取請求資訊 */public void parse(){StringBuffer request = new StringBuffer();int i;byte[] buffer = new byte[2048];try{i = input.read(buffer);}catch (Exception e) {// TODO: handle exceptione.printStackTrace();i = -1;}for(int j=0;j<i;j++){request.append((char)buffer[j]);}uri = parseUri(request.toString());}public String getUri() {return uri;}public void setUri(String uri) {this.uri = uri;}}

Response代碼如下:

public class Response {private static final int BUFFER_SIZE = 1024;Request request;OutputStream output;public Response(OutputStream output){this.output = output;}public void setRequest(Request request){this.request = request;}/** * 根據用戶端瀏覽器的請求,讀取一個靜態資源檔案響應到用戶端 */public void sendStaticResource()throws IOException{byte[] bytes = new byte[BUFFER_SIZE];FileInputStream fis = null;try{//根據請求URI,讀取指定目錄下的靜態資源檔案File file = new File(HttpServer.WEB_ROOT,request.getUri());if(file.exists()){fis = new FileInputStream(file);int ch = fis.read(bytes,0,BUFFER_SIZE);while(ch!=-1){output.write(bytes,0,ch);ch = fis.read(bytes,0,BUFFER_SIZE);}}else{//資源檔不存在就返回一個錯誤頁面StringBuffer sb = new StringBuffer();sb.append("HTTP/1.1 404 FILE NOT FOUND\r\n");sb.append("Content-Type: text/html\r\n");sb.append("Content-Length: 23\r\n");sb.append("\r\n");sb.append("<h1>File Not Found</h1>");output.write(sb.toString().getBytes());}}catch (Exception e) {// TODO: handle exceptionSystem.out.println(e.toString());}finally{if(fis!=null)fis.close();}}}

為了測試,還建立了一個名為hello.html的簡單HTML頁面。內容如下:

<html><body>  <h4>大家好,歡迎訪問我!!!</h4> </body></html>

測試時,先啟動HttpServer類。然後在瀏覽器地址欄中輸出:http://localhost:8080/aa.html,因為aa.html頁面並不存在,所以可在瀏覽器中看到如下的響應:


然後再輸入http://localhost:8080/hello.html測試,如下:


可以看到成功訪問了hello.html這個靜態資源檔案。

通過以上的內容大家可以看到Socket的基本工作原理。也可以看到Web伺服器一般是如何工作的。
參考:《深入剖析Tomcat》、百度。。。


上一篇:Tomcat學習進階曆程之HTTP協議

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.