金秋十月當然是分享下重磅技術文——網路程式設計大揭祕

Java鬥帝之路發表於2020-10-06

目錄

  • 1、網路程式設計的基本概念
  • 2、IP地址及埠號
  • 3、通訊協議
  • 4、TCP通訊程式碼實踐
    • 4.1訊息傳遞
    • 4.2檔案上傳
  • 5、UDP通訊程式碼實踐
    • 5.1 UDP實現訊息傳送
    • 5.2 使用UDP迴圈傳送和接收訊息
    • 5.3使用UDP實現聊天

1、網路程式設計的基本概念

引例

在學習網路程式設計之前,我們先看這樣一個例子。一般處於我們這個年齡段的同學,大都經歷過寫信/寄信的經歷,在寫信的時候,我們一般都需要明確傳送的地址、聯絡人資訊以及所在地區的郵編,郵遞員可以根據這上面的資訊找到接收信件的人,接收人在閱讀過信件的內容後可以以同樣的方式回信,這樣就使得身處異地的朋友間可以進行通訊。

在這種情況下,身處異地的朋友如果要進行通訊,必須同時滿足以下條件

1.接收人的所在地區及詳細的住址

2.需要有快遞員接收併傳送

慢慢的,隨著科技的發展,我們有手機通訊、QQ聊天和微信聊天等多種通訊方式,採用這些方式我們可以更便捷的進行通訊,但是無論是那種方式,通訊過程中總是需要這樣兩個元素:地址及傳輸過程。

在網路通訊中,這兩個元素可以使用更專業的名詞來代替:

1、IP地址和埠號

IP地址可以幫助我們找到通訊的接收方,埠號則可以幫助我們找到計算機中的具體應用,如QQ、微信等

2、傳輸協議

協議是為了幫助我們更好的進行傳輸,如TCP、UDP、FTP、SMTP和HTTP協議等

2、IP地址及埠號

IP地址是網際網路協議地址,它是一種統一的地址格式,網際網路中的每一臺主機都會有一個邏輯地址。在計算機網路中,localhost(意為“本地主機”,指“這臺計算機”)是給回的一個標準主機名,相對應的IP地址為127.0.0.1

通過IP地址,我們就可以連線到指定的計算機了,但是如果要訪問目標計算機中的某個應用程式,還需要知道埠號。在計算中,不同的應用程式就是通過埠號來進行區分的。

金秋十月當然是分享下重磅技術文——網路程式設計大揭祕

 

在網路程式設計中,可以使用InetAddress進行主機名解析和反向解析,即給定確定的主機名,返回確定的IP地址;給定IP地址,返回主機名

localhost解析為127.0.0.1

127.0.0.1反向解析為localhost

NOTE:不同協議的埠是可以重複的,如UDP和TCP

埠的查詢操作

netstat -ano #檢視所有的埠
netstat -ano | findstr "XXX" # 檢視指定的埠

3、通訊協議

IP地址及埠號解決了通訊過程中的地址問題,但是在計算機中,我們還要解決如何通訊問題,所謂通訊就是計算機間如何交流,而通訊協議就是將計算機雙方遵循的一種規則和約定(如同普通話、英語),它可以通過通訊通道將處於不同地理位置的裝置連線起來,能夠實現資訊的交換和資源共享。

在計算機網路中,常用的協議就是TCP/IP,它是協議簇,由多個子協議組成了,如我們常見的TCP、IP、UDP、ARP等,我們主要講解網路程式設計中常用的TCP、UDP和IP

  • TCP

TCP協議是一種傳輸協議,面向連線、可靠的、基於位元組流的傳輸層通訊協議

  • UDP

UDP是一種無連線的傳輸協議,無需建立連線就可以傳送資料包

  • IP

IP協議整個TCP/IP協議族的核心,對上可載送傳輸層各種協議的資訊,例如TCP、UDP等;對下可將IP資訊包放到鏈路層,通過乙太網等各種技術來傳送。

TCP和UDP對比

TCP可以類比於打電話,它具有以下特點

  • 在資料傳輸前,需要建立連線(三次握手),所以連線穩定可靠
  • 有客戶端、服務端的概念,客戶端傳送,服務端接收
  • 傳輸完成後,會釋放連線(四次揮手)

UDP可以類比於發簡訊,它具有以下特點

  • 資料傳輸前,不需要建立連線,所以不可靠,不穩定
  • 客戶端和服務端沒有明確界限,客戶端和服務端都可以進行收/發
  • 不需要建立連線,所以速度較快

4、TCP通訊程式碼實踐

TCP網路程式設計需要以下Java類

InetAddress:表示IP協議的地址,可以解析IP地址和主機名

Socket:實現客戶端的套接字,建立連線,套接字就是兩臺機器間通訊的端點

ServerSocket:實現服務端的套接字

4.1訊息傳遞

客戶端

1.拿到服務端的地址及埠,InetAddress

2.建立Socket連線

3.傳送訊息

public class TcpClient {
    public static void main(String[] args) throws IOException {
        Socket socket=null;
        OutputStream os=null;
        try {
            //1、得到服務端的地址,埠號
            InetAddress inetAddress=InetAddress.getByName("127.0.0.1");
            int port=9898;
            //2、建立Socket連線
          socket=new Socket(inetAddress,port);
            //3、傳送訊息
            os=socket.getOutputStream();
            os.write("hello,Simon".getBytes());
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //關閉資源
           os.close();
           socket.close();
        } 
    }
}

服務端

1.用ServerSocket設定自己的埠號

2.等待連線,Socket

2.1:可以等待一次連線,接收到訊息後關閉

2.2:等待多次連線,用while(true)把等待連線的方法包裹起來,重複接收訊息

3、獲取傳送端的訊息

public class TcpServer {
    public static void  main(String[] args) throws IOException {
        ServerSocket serverSocket=null;
        Socket socket=null;
        InputStream is=null;
        ByteArrayOutputStream baos=null;
        try {
            //1、設定自己的埠號
            serverSocket=new ServerSocket(9898);
            //2、等待客戶端的連線
            socket=serverSocket.accept();
            //3、讀取客戶端的訊息
            is=socket.getInputStream();
            baos=new ByteArrayOutputStream();
            byte[] buffer=new byte[1024];
            int len;
            while ((len=is.read(buffer))!=-1){
                baos.write(buffer,0,len);
            }
            System.out.print(baos.toString());

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            baos.close();
            is.close();
            socket.close();
            serverSocket.close();
        }
    }
}

4.2檔案上傳

客戶端

1.建立一個連線

2.建立一個位元組輸出流用於通訊

建立一個檔案輸入流用於接收檔案,然後將接收後的檔案給位元組輸出流用於通訊

3.輸出結束後,呼叫shutdownInput方法通知服務端已經傳送完畢

4.關閉連線資源

public class TcpFileClient {
    public static void main(String[] args) throws Exception {

        //1、建立連線
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9987);
        //2、定義一個輸出流用於通訊
        OutputStream os = socket.getOutputStream();
        FileInputStream fis = new FileInputStream(new File("simon.png"));
        byte[] buffer = new byte[1024];
        int len;
        while ((len = fis.read(buffer)) != -1) {
            os.write(buffer, 0, len);
        }
        //3、通知伺服器,我已經結束了
        socket.shutdownOutput();

        //確定伺服器接收完畢,才可以斷開連線,這一段程式碼主要是接收服務端發出的結束資訊
       InputStream inputStream= socket.getInputStream();
        byte[] buffer2=new byte[1024];
        ByteArrayOutputStream baos=new ByteArrayOutputStream();
        int len2;
        while ((len2=inputStream.read(buffer2))!=-1){
            baos.write(buffer2,0,len2);
        }
        System.out.print(baos.toString());
        //關閉連線
        baos.close();
        inputStream.close();
        os.close();
        fis.close();
        socket.close();

    }
}

服務端

1.建立一個連線用於等待客戶端的接入

2.建立一個輸入流

建立一個檔案輸出流,將輸入流輸出

3.關閉資源

public class TcpFileServerDemo02 {
    public static void main(String[] args) throws Exception {
        //設定埠號
        ServerSocket serverSocket=new ServerSocket(9987);
        //等待連線
        Socket socket=serverSocket.accept();
        //獲取輸入流
        InputStream is=socket.getInputStream();
        //檔案輸出
        FileOutputStream fos=new FileOutputStream(new File("snow.png"));
        byte[] buffer=new byte[1024];
        int len;
        while ((len=is.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }
        //通知客戶端接收完畢
        OutputStream os=socket.getOutputStream();
        os.write("我已經接收結束,你可以斷開了".getBytes());

        //關閉資源
        fos.close();
        is.close();
        socket.close();
        serverSocket.close();
    }
}

5、UDP通訊程式碼實踐

UDP不需要連線,但是需要知道對方的地址和埠號,主要用到了以下Java類

DatagramPacket:表示資料包,用於實現無連線分組傳送服務

DatagramSocket:表示用於傳送和接收資料包的套接字

UDP是不存在客戶端和服務端的概念,但是為了程式設計模擬方便,我們假定存在客戶端和服務端

5.1 UDP實現訊息傳送

客戶端

  • 建立連線
  • 建立資料包
  • 傳送資料包
public class UdpClientDemo1 {
    public static void main(String[] args) throws Exception {
        //1、建立連線(這個埠號是客戶端的)
        DatagramSocket datagramSocket=new DatagramSocket(9888);
        //2、建立資料包
        String msg="hello,Simon";
        InetAddress inetAddress=InetAddress.getByName("127.0.0.1");
        //資料,資料的起始位置,要傳送的地址與埠號
        DatagramPacket datagramPacket=new DatagramPacket(msg.getBytes(),0,msg.getBytes().length,inetAddress,9887);
        //3、傳送資料包
        datagramSocket.send(datagramPacket);
        //4、關閉資料流
        datagramSocket.close();

    }
}

服務端

  • 建立連線
  • 接收資料
public class UdpServerDemo2 {
    public static void main(String[] args) throws Exception{
        //建立連線,開放的埠
        DatagramSocket datagramSocket=new DatagramSocket(9887);
        //接收資料包
        byte[] buffer=new byte[1024];
        DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length);
        datagramSocket.receive(datagramPacket);
        //列印資料包資訊
        System.out.println(datagramPacket.getAddress());
        System.out.println(datagramPacket.getPort());
        System.out.println(new String(datagramPacket.getData()));
        //關閉連線
        datagramSocket.close();
    }

5.2 使用UDP迴圈傳送和接收訊息

使用while(true)方法將客戶端中的傳送資料和接收資料包裹起來,只要當他們滿足一定的條件時(如輸入的字串為"bye"),退出即可。這樣就可以達到迴圈傳送和接收訊息。

客戶端

public class UdpSend {
    public static void main(String[] args) throws Exception {
        //1、建立連線
        DatagramSocket datagramSocket=new DatagramSocket(9888);
        //2、建立資料包,從鍵盤輸入
        InetAddress inetAddress=InetAddress.getByName("127.0.0.1");
        int port=9887;
        BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
        while (true){
            String s=bufferedReader.readLine();
            DatagramPacket datagramPacket=new DatagramPacket(s.getBytes(),0,s.getBytes().length,inetAddress,9887);
            //3、傳送資料
            datagramSocket.send(datagramPacket);
            if(s.equals("bye")){
                break;
            }
        }
        //4、關閉資料
        datagramSocket.close();
        }
}

服務端

public class UdpReceive{
    public static void main(String[] args) throws Exception {
        //1、建立連線
        DatagramSocket datagramSocket=new DatagramSocket(9887);
        while (true){
           //2、接收資料包
           byte[] buffer=new byte[1024];
            DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length);
            datagramSocket.receive(datagramPacket);
            //3、斷開連線
            byte[] data=datagramPacket.getData();
            String receiveData=new String(data,0,data.length);
            System.out.println(receiveData);
            if(receiveData.equals("bye")){
                break;
            }
        }
        datagramSocket.close();
    }
}

5.3使用UDP實現聊天

使用UDP實現聊天,則客戶端和服務端(其實不存在客戶端和服務端的概念)既需要接收資訊也需要傳送訊息,這就需要多執行緒的支援了。

我們首先構造兩個接收類和傳送的執行緒類,然後構造倆個使用者類進行通訊。

傳送類

public class TalkSend implements Runnable {
    DatagramSocket datagramSocket = null;
    BufferedReader bufferedReader = null;

    private int fromPort;
    private int toPort;
    private String toIp;

    public TalkSend(int fromPort,int toPort,String toIp){
        this.fromPort=fromPort;
        this.toPort=toPort;
        this.toIp=toIp;

        //1、建立連線
        try {
            datagramSocket = new DatagramSocket(fromPort);
            //2、建立資料包,從鍵盤輸入
            bufferedReader = new BufferedReader(new InputStreamReader(System.in));
        } catch (SocketException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            try {
                String s = bufferedReader.readLine();
                DatagramPacket datagramPacket = new DatagramPacket(s.getBytes(), 0, s.getBytes().length, new InetSocketAddress(toIp,toPort));
                //3、傳送資料
                datagramSocket.send(datagramPacket);
                if (s.equals("bye")) {
                    break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        //4、關閉資料
        datagramSocket.close();
    }
}

接收類

public class TalkReceive implements Runnable{
    DatagramSocket datagramSocket=null;
    private int port;
    private String msgFrom;

    public TalkReceive(int port,String msgFrom)  {
        this.port=port;
        this.msgFrom=msgFrom;

        //1、建立連線
        try {
            datagramSocket=new DatagramSocket(port);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }



    @Override
    public void run() {
        while (true){
            try {
                //2、接收資料包
                byte[] buffer=new byte[1024];
                DatagramPacket datagramPacket=new DatagramPacket(buffer,0,buffer.length);
                datagramSocket.receive(datagramPacket);
                //3、斷開連線
                byte[] data=datagramPacket.getData();
                String receiveData=new String(data,0,data.length);
                System.out.println(msgFrom+": "+receiveData);
                if(receiveData.equals("bye")){
                    break;
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        datagramSocket.close();
    }
}

使用者類1

public class TalkStudent {
    public static void main(String[] args) {
        new Thread(new TalkSend(7777,9999,"localhost")).start();
        new Thread(new TalkReceive(8888,"小包")).start();
    }
}

使用者類2

public class TalkTeacher {
    public static void main(String[] args){
        new Thread(new TalkSend(5555,8888,"localhost")).start();
        new Thread(new TalkReceive(9999,"小郎")).start();
    }
}

覺得此文寫的還不錯的朋友可以轉發+關注下小編哦,持續分享技術系列文章中!

看完三件事❤️

如果你覺得這篇內容對你還蠻有幫助,我想邀請你幫我三個小忙:

  1. 點贊,轉發,有你們的 『點贊和評論』,才是我創造的動力。

  2. 關注公眾號 『 Java鬥帝 』,不定期分享原創知識。

  3. 同時可以期待後續文章ing?

相關文章