socket編程

Socket和HTTP的區別

1. Socket是基於TCP/IP協議,是傳輸層的連接;而HTTP是基於應用層的連接。

2. HTTP連接最顯著的特點是客戶端發送的每次請求都需要服務器回送響應,在請求結束後,會主動釋放連接,下次建立連接需要tcp重新進行三次握手。因此HTTP連接是一種“短連接”。要保持客戶端程序的在線狀態,需要不斷地向服務器發起連接請求。通常的做法是即使不需要獲得任何數據,客戶端也保持每隔一段固定的時間向服務器發送一次“保持連接”的請求,服務器在收到該請求後對客戶端進行回覆,表明知道客戶端“在線”。若服務器長時間無法收到客戶端的請求,則認爲客戶端“下線”;

由於通常情況下Socket連接就是TCP連接,因此Socket連接一旦建立,通信雙方即可開始相互發送數據內容,直到雙方連接斷開,"長鏈接"。

3. HTTP連接使用的是“請求—響應”的方式,不僅在請求時需要先建立連接,而且需要客戶端向服務器發出請求後,服務器端才能回覆數據;而Socket連接,服務器就可以直接將數據傳送給客戶端,實現雙向通信


Socket優點

          傳輸數據爲字節級,傳輸數據可自定義,數據量小;

傳輸數據時間短,性能高;

適合C/S之間信息實時交互;

可以加密,數據安全性高

缺點: 需要對傳輸的數據進行解析,轉化爲應用層的數據

          相對於Http協議傳輸,增加了開發量

HTTP優點

         基於應用層的接口使用方便

缺點: 傳輸速度慢,數據包大。

          如實現實時交互,服務器性能壓力大

          數據傳輸安全性差

            應用場景

Socket適用於對實時性安全性要求較高,或連接較長的場景,比如網絡遊戲,銀行支付,收發消息。

HTTP適用於對信息傳輸實時性安全性要求不高,可以一次連接解決的場景,如互聯網服務。


Socket

        消息目的地:socket地址 = ip地址+端口號

        進程間通信需要一對socket,數據在兩個Socket間通過IO傳輸。

   對於Java Socket編程而言,有兩個概念,一個是ServerSocket,一個是Socket

   套接字之間的連接過程分爲三個步驟:

    1. 服務器監聽(ServerSocket將在服務端監聽某個端口);

    2. 客戶端請求(客戶端的Socket提出連接請求,指出服務器端套接字的地址和端口號)

    3. 連接確認(ServerSocket監聽到客戶端套接字的連接請求,accept該Socket的連接請求,同時在服務端建立一個對應的Socket與之進行通信)


Socket編程有兩種通信協議可以選擇:

1. UDP 數據報通信

        UDP是一種無連接的協議,每次發送數據報時,需要同時發送本機的socket描述符和接收端的socket描述符。

   每次通信都需要發送額外的數據。

2. TCP 流通信

        TCP是一種基於連接的協議,在使用流通信之前,我們需要在通信的一對socket之間建立連接。其中一個socket作爲服務器進行監聽連接請求,另一個作爲客戶端進行連接請求。

區別

UDP中,數據報大小有64k的限制,而TCP無限制;

UDP不可靠,數據報不一定按照發送順序被接受,而TCP所有數據按照接受時的順序讀取

總之TCP適合於如遠程登錄和文件傳輸這類的網絡服務,因爲這些服務需要傳輸數據大小不確定;

而UDP更加簡單輕量,用來實現實時性較高或丟包不重要的一些服務。


TCP通信的Java實現

所有socket相關的類都位於java.net包下。

客戶端Client

1.創建Socket對象,指明需要連接的服務器的ip地址和端口號

2.連接建立後,通過輸出流OutputStreamWriter向服務器端發送請求信息

3.通過輸入流獲取服務器響應的信息

4.關閉響應資源

Socket client = new Socket(host, port);

Writer writer = new OutputStreamWriter(client.getOutputStream());

writer.write("Hello From Client");

writer.flush();

writer.close();

client.close();

服務器Server

1.創建ServerSocket對象,綁定監聽端口

2.通過accept()方法監聽客戶端請求(Accept是阻塞方法,服務器端與客戶端建立聯繫前會一直等待阻塞。)

3.連接建立後,通過輸入流讀取客戶端發送的請求信息

4.通過輸出流向客戶端發送響應信息

5.關閉相關資源


常見服務器Server模型

1. 阻塞服務器

        當一個消息執行發出後,這個消息在發送端的socket中處於排隊狀態,直到下層網絡協議將這些消息發送出去。

        當消息到達接收端的socket後,也會處於排隊狀態,直到接收端的進程對這條消息進行了接收處理。

        前一個請求沒有處理完成,服務器不會處理後面的請求。

    ServerSocket server = new ServerSocket(port);

    Socket socket = server.accept();

    Reader reader = new InputStreamReader(socket.getInputStream());

    char chars[] = new char[1024];

    int len;

    StringBuilder builder = new StringBuilder();

    while ((len=reader.read(chars)) != -1) {

         builder.append(new String(chars, 0, len));

    }

    System.out.println("Receive from client message=: " + builder);

    reader.close();

    socket.close();

    server.close();  // 關閉socket

2. 併發服務器

        每接收到一個請求,就啓動一個線程處理該請求。

   這種模式的服務器,好處是不會出現阻塞式服務器請求被擁堵的情況。

   存在問題:服務器啓動線程有一定開銷,請求過多時,將會導致服務器的資源耗盡。

   解決:建立線程池來處理請求,每當請求到來時,向線程池申請線程進行處理。

            ServerSocket serverSocket = new ServerSocket(SERVER_PORT, 5, serverAddr);

            Executor executor = Executors.newFixedThreadPool(100);  

            //有客戶端向服務器發起tcp連接時,accept會返回一個Socket  

            //該Socket的対端就是客戶端的Socket  

            //具體過程可以查看TCP三次握手過程  

            Socket connection = serverSocket.accept();    

            //利用線程池,啓動線程  

            executor.execute(new Runnable() {  

            @Override  

                public void run() {  

                    //使用局部引用,防止connection被回收  

                 Socket conn = connection;  

                    try {  

                      InputStream in = conn.getInputStream();  

   與阻塞服務器比較,差別僅在於當accept返回socket後啓動線程處理,這裏使用了Excutor,基於線程池進行處理。

3. 異步服務器

一般藉助於系統的異步IO機制(NIO),當一個請求到達時,我們可以先將請求註冊,當有數據可以讀取時,會得到通知,這時候我們處理請求,這樣服務器進程沒有必要阻塞處理,也不會存在很大的系統開銷,因此,目前對於併發量要求比較高的服務器,一般都是採用這種方式。

 


UDP通信的java實現

Java通過DatagramPacket類和DatagramSocket類來使用UDP套接字,客戶端和服務器端都通過DatagramSocket的send()方法和receive()方法來發送和接收數據,用DatagramPacket來包裝需要發送或者接收到的數據。

發送信息時,Java創建一個包含待發送信息的DatagramPacket實例,並將其作爲參數傳遞給DatagramSocket實例的send()方法;接收信息時,Java程序首先創建一個DatagramPacket實例,該實例預先分配了一些空間,並將接收到的信息存放在該空間中,然後把該實例作爲參數傳遞給DatagramSocket實例的receive()方法。

在創建DatagramPacket實例時,要注意:如果該實例用來包裝待接收的數據,則不指定數據來源的遠程主機和端口,只需指定一個緩存數據的byte數組即可(在調用receive()方法接收到數據後,源地址和端口等信息會自動包含在DatagramPacket實例中),而如果該實例用來包裝待發送的數據,則要指定要發送到的目的主機和端口。


客戶端

         1. 定義發送信息

         2. 創建DatagramPacket,包含將要發送的信息

         3. 創建DatagramSocket(可以有選擇地對本地地址和端口號進行設置)

        4. 發送數據

            //1、定義服務器的地址、端口號、數據

            InetAddress address =InetAddress.getByName("localhost");

            int port =10010;

            byte[] data ="用戶名:admin;密碼:123".getBytes();

            //2、創建數據報,包含發送的數據信息

            DatagramPacket packet = newDatagramPacket(data,data,length,address,port);

            //3、創建DatagramSocket對象

            DatagramSocket socket =newDatagramSocket();

            //4、向服務器發送數據

            socket.send(packet);

 

            //接受服務器端響應數據

          //======================================

          //1、創建數據報,用於接受服務器端響應數據

          byte[] data2 = new byte[1024];

          DatagramPacket packet2 = new DatagramPacket(data2,data2.length);

          //2、接受服務器響應的數據

          socket.receive(packet2);

          String raply = new String(data2,0,packet2.getLenth());

          System.out.println("我是客戶端,服務器說:"+reply);

          //4、關閉資源

          socket.close();

服務器

        1. 創建DatagramSocket,指定端口號

   2. 創建DatagramPacket

   3. 接受客戶端發送的數據信息

   4. 讀取數據

            //服務器端,實現基於UDP的用戶登錄

            //1、創建服務器端DatagramSocket,指定端口,UDP服務器爲所有通信使用同一套接字

            DatagramSocket socket =new datagramSocket(10010);

            //2、創建數據報,用於接受客戶端發送的數據

            byte[] data =newbyte[1024];//

            DatagramPacket packet =newDatagramPacket(data,data.length);

            //3、接受客戶端發送的數據

            socket.receive(packet); //此方法在接受數據報之前會一直阻塞

            //4、讀取數據

            String info =newString(data,o,data.length);

            System.out.println("我是服務器,客戶端告訴我"+info);

 

            //=========================================================

            //向客戶端響應數據

            //1、定義客戶端的地址、端口號、數據

            InetAddress address = packet.getAddress();

            int port = packet.getPort();

            byte[] data2 = "歡迎您!".geyBytes();

            //2、創建數據報,包含響應的數據信息

            DatagramPacket packet2 = new DatagramPacket(data2,data2.length,address,port);

            //3、響應客戶端

            socket.send(packet2);

            //4、關閉資源

            socket.close();

由於UDP協議是不可靠協議,如果數據報在傳輸過程中發生丟失,那麼程序將會一直阻塞在receive()方法處,這樣客戶端將永遠都接收不到服務器端發送回來的數據,但是又沒有任何提示。爲了避免這個問題,我們在客戶端使用DatagramSocket類的setSoTimeout()方法來制定receive()方法的最長阻塞時間,並指定重發數據報的次數,如果每次阻塞都超時,並且重發次數達到了設置的上限,則關閉客戶端。

int TIMEOUT = 5000;  //設置接收數據的超時時間

//客戶端在9000端口監聽接收到的數據

DatagramSocket ds = new DatagramSocket(9000);

ds.setSoTimeout(TIMEOUT);    


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章