Java網絡編程二:Socket詳解

Socket又稱套接字,是鏈接運行在網絡上兩個程序間的雙向通信的端點。java

1、使用Socket進行網絡通訊的過程

服務端:服務器程序將一個套接字綁定到一個特定的端口,並經過此套接字等待和監聽客戶端的鏈接請求。編程

客戶端:客戶端程序根據你服務器所在的主機名和端口號發出鏈接請求。服務器

二者之間的通訊是經過Socket完成的,咱們能夠認爲Socket是兩個城市之間的交通工具,有了它,就能夠在兩個城市之間穿梭了。網絡

Socket通訊示例socket

主機A的應用程序和主機B的應用程序通訊,必須經過Socket創建鏈接,而創建Socket必須由底層的TCP/IP協議來創建TCP鏈接。創建TCP鏈接須要底層IP協議來尋址網絡中的主機。IP地址只能幫助咱們找到目標主機,可是一個主機上面有多個應用程序,如何才能找到咱們須要的應用程序,這個時候就能夠經過端口號來指定了。 函數

2、簡易服務端、客戶端模擬

服務器端:工具

 1     public static void main(String[] args) throws IOException
 2     {
 3         //建立一個ServerSocket,用於監聽客戶端Socket鏈接請求
 4         ServerSocket ss = new ServerSocket(8888);
 5         System.out.println("server start");
 6         //採用循環方式監聽客戶端的請求
 7         while(true)
 8         {
 9             //偵聽並接受到此套接字的鏈接。此方法在鏈接傳入以前一直阻塞。
10             Socket socket = ss.accept();
11             OutputStream os = socket.getOutputStream();
12             PrintStream ps = new PrintStream(os);
13             ps.print("您好,您收到了來自服務端的中秋祝福");
14             ps.close();
15             os.close();
16             socket.close();
17         }
18     }

執行結果:學習

server startspa

客戶端:.net

 1     public static void main(String[] args) throws IOException, Exception
 2     {
 3         Socket socket = new Socket("localhost",8888);
 4         InputStream is = socket.getInputStream();
 5         BufferedReader br = new BufferedReader(new InputStreamReader(is));
 6         String str = br.readLine();
 7         System.out.println(str);
 8         br.close();
 9         is.close();
10         socket.close();
11     }

執行結果:

您好,您收到了來自服務端的中秋祝福

一、上面展現的是一個簡易的服務端和客戶端通訊的創建過程。

二、咱們經過交互圖來詳細介紹這個過程:

    

三、首先在server端,指定端口號建立serverSocket對象,經過serverSocket的accpet方法獲取套接字,這個方法的特色是:偵聽並接受到此套接字的鏈接,此方法在鏈接傳入以前一直阻塞。這也就意味着,若是沒有客戶端鏈接請求過來,服務端會一致阻塞在這裏。

四、後面的代碼就是經過套接字socket能夠獲得輸入輸出流,到此爲止,就是I/O的內容了。

五、在客戶端這邊,經過指定的服務器主機名和服務器監聽的端口號,獲得套接字Socket,這個時候就表示服務端和客戶端的鏈接已經創建了,而後經過輸入輸出流來進行通訊了。

3、半關閉的socket

  在上面的Demo中,咱們是以行做爲通訊的最小數據單位,服務器端也是逐行進行處理的。可是咱們在大多數場景下,通訊的數據單位是多行的,這時候Socket的輸出流如何表達輸出的數據已經結束?

  在IO學習過程當中提到過,如何要表示輸出已經結束,則經過關閉輸出流來實現,可是在socket中是行不通的,由於關閉socket,會致使沒法再從該socket中讀取數據了。爲了解決這種問題,java提供了兩個半關閉的方法:

一、shutdownInput():關閉該Socket的輸入流,程序還能夠經過該Socket的輸出流輸出數據。

二、shutdownOutput():關閉該Socket的輸出流,程序還能夠經過該Socket的輸入流讀取數據。

若是咱們對同一個Socket實例前後調用shutdownInput和shutdownOutput方法,該Socket實例依然沒有被關閉,只是該Socket既不能輸出數據,也不能讀取數據。

服務器端:

 1         ServerSocket ss = new ServerSocket(5555);
 2         Socket socket = ss.accept();
 3         PrintStream ps = new PrintStream(socket.getOutputStream());
 4         ps.println("服務器端:開源中國杭州論壇");
 5         ps.println("服務器端:杭州G20峯會");
 6         //關閉輸出流,代表輸出已經結束
 7         socket.shutdownOutput();
 8         //判斷該socket是否關閉
 9         System.out.println(socket.isClosed());
10         Scanner scan = new Scanner((socket.getInputStream()));
11         while(scan.hasNextLine())
12         {
13             System.out.println(scan.nextLine());
14         }
15         scan.close();
16         socket.close();
17         ss.close();
18         
19     

 客戶端:

 1         Socket s = new Socket("localhost", 5555);
 2         InputStream is = s.getInputStream();
 3         byte[] buffer = new byte[1024];
 4         int flag = 0;
 5         while(-1 != (flag = is.read(buffer,0,buffer.length)))
 6         {
 7             String str = new String(buffer,0,flag);
 8             System.out.print(str);
 9         }
10         PrintStream ps = new PrintStream(s.getOutputStream());
11         ps.println("客戶端:歡迎參加開源中國論壇");
12         ps.println("客戶端:歡迎參加G20峯會");
13         is.close();
14         ps.close();
15         s.close();
16     

執行結果:

   

  

  在服務器端程序中能夠看到,在輸出兩段字符串以後,調用了shutdownOutput方法,表示輸出已經結束。隨即又去判斷了socket是否關閉,執行的結果爲false,表示socket並未關閉。

  可是在調用了這兩個半關閉的方法關閉了輸出輸入流以後,該socket沒法再次打開該輸出流或者輸入流。所以這種場景不適合保持持久通訊狀態的交互使用,只適合一站式的通訊協議.例如http協議:客戶端鏈接到服務器以後,開始發送數據,發送完成以後無須再次發送數據,只須要讀取服務器響應數據便可,讀取數據完畢以後,該socket鏈接也被關閉了。

4、基於UDP協議的網絡編程

  前面介紹的socket編程都是基於TCP協議的,如今來看下基於UDP協議的編程,TCP和UDP的區別在上一章已經有過介紹。

  UDP協議的主要做用就是完成網絡數據流和數據報之間的轉換-----在信息的發送端,UDP協議將網絡數據流封裝到數據報,而後將數據報發送出去;在信息的接收端,UDP協議將數據報轉換成實際數據報內容。

一、首先在UDP網絡編程中沒有服務器端和客戶端這種說法,兩個socket之間沒有虛擬鏈路,只是接收和發送數據報文而已。

二、這裏面有兩個重要的類:DatagramSocket 和DatagramPacket。前者是用來發送和接收數據包的套接字,後者表示數據包,每條報文僅根據該包中的包含的信息從一臺機器        路由到另外一臺機器。

三、DatagramSocket 的兩個構造函數:

     DatagramSocket():構造數據報套接字並將其綁定到本地主機上任何可用的端口。

     DatagramSocket(int port):建立數據報套接字並將其綁定到本地主機上的指定端口。

     在咱們下面的DEMO中,UDPServerTest類中先發送數據報,使用的是套接字的無參構造器,而UDPClientTest類中先接收數據報,必須監聽某一個端口,因此使用的是套接字的有參構造器。

四、DatagramPacket:建立的時候分爲接收和發送兩種

      DatagramPacket(byte[] buf, int length):用來接收長度爲 length 的數據包。

      DatagramPacket(byte[] buf, int length, InetAddress address, int port):用來將長度爲 length 的包發送到指定主機上的指定端口號。

 1 public class UDPServerTest
 2 {
 3     public static void main(String[] args) throws IOException
 4     {
 5         DatagramSocket ds = new DatagramSocket();
 6         String str = "hello world";
 7         //構造用於發送的數據包,指定主機和端口號
 8         DatagramPacket packet = new DatagramPacket(str.getBytes(),
 9                 str.length(), InetAddress.getByName("localhost"), 5555);
10         ds.send(packet);
11         
12         //讀取從客戶端發送過來的響應
13         byte[] buffer = new byte[1024];
14         DatagramPacket packet2 = new DatagramPacket(buffer,buffer.length);
15         ds.receive(packet2);
16         String str2 = new String(buffer,0,packet2.getLength());
17         System.out.println(str2);
18         ds.close();
19     }
20 }
public class UDPClientTest
{
    public static void main(String[] args) throws Exception
    {
        DatagramSocket ds = new DatagramSocket(5555);
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        ds.receive(packet);
        String str = new String(buffer, 0, packet.getLength());
        System.out.println(str);

        // 接收到數據包以後,客戶端返回響應回去
        String str2 = "welcome";
        DatagramPacket packet2 = new DatagramPacket(str2.getBytes(), str2
                .length(), packet.getAddress(), packet.getPort());
        ds.send(packet2);
        ds.close();
    }
}

執行過程:

 

一、上面的程序中,第一步是服務器端(暫且以這種叫法來區分這兩個類)建立一個UDP套接字,沒有指定端口,使用的是系統分配的端口。而後構建了一個數據包,包中指定      了目標機器的ip和端口號。

二、做爲客戶端,建立了一個UDP套接字,而且綁定了端口,若是想要接收到服務端發送過來的報文,綁定的端口必須和服務器端發送的包中指定的端口一致。

三、客戶端打印了包中的內容以後,想要返回一些內容回去。這個時候,服務器端的ip和端口號能夠從以前發送過來的數據包中獲取。

     DatagramPacket packet2 = new DatagramPacket(str2.getBytes(), str2.length(), packet.getAddress(), packet.getPort());

四、在服務器接收數據包的時候,已經不須要再像客戶端建立套接字同樣去綁定端口了,由於目前監聽的端口和客戶端發送的包中指定的端口是同樣的。

五、打印看下服務器端的ip和監聽的端口號:

serverIp =/127.0.0.1;serverPort=62965

六、其中DatagramSocket的receive(DatagramPacket p)方法在接收到數據包前一直阻塞。

相關文章
相關標籤/搜索