Java網絡編程快速上手(SE基礎)


參考資料:百度百科TCP協議

本文涉及Java IO流、異常的知識,可參考我的另外的博客

一文簡述Java IO

一文簡述JAVA內部類和異常

1.概述

計算機網絡相關知識:

OSI七層模型

一個報文可以類似於一封信,就像下圖(引自狂神說Java)非常生動。

網絡編程的目的:數據交換、通信

網絡通信的要素:

如何實現網絡通信?

通信雙方地址:

  • ip
  • 端口號

網絡協議:

HTTP, FTP, TCP, UDP 等等

1.1 IP

IP地址:InetAddress(無構造器)

  • 唯一定位一台網絡上計算機
  • 127.0.0.1 :本機,localhost
  • ip地址分類:ipv4(4個字節)/ipv6(128位,8個無符號整數組成),公網(ABCD類地址)/私網(局域網)
  • 域名:記憶ip問題

主機名解析

主機名稱到IP地址解析是通過使用本地機器配置信息和網絡命名服務(如域名系統(DNS)和網絡信息服務(NIS))的組合來實現的。所使用的特定命名服務是默認配置的本地機器。對於任何主機名,返回其對應的IP地址。反向名稱解析意味着對於任何IP地址,返回與IP地址關聯的主機。

InetAddress類提供了將主機名解析為其IP地址的方法,反之亦然。

InetAddress常用方法:

舉例:

public static void main(String[] args) throws UnknownHostException {
    //查詢本機地址
    InetAddress inetAddress = InetAddress.getByName("127.0.0.1");
    System.out.println(inetAddress);

    InetAddress i1 = InetAddress.getByName("localhost");
    System.out.println(i1);

    InetAddress i2 = InetAddress.getLocalHost();
    System.out.println(i2);

    //查詢網站ip
    InetAddress i3 = InetAddress.getByName("www.baidu.com");
    System.out.println(i3);
    //常用方法
    System.out.println(i3.getAddress());   //返回的是byte[],所以輸出了亂碼
    System.out.println(i3.getCanonicalHostName());//規范的名字
    System.out.println(i3.getHostAddress());//ip
    System.out.println(i3.getHostName());//主機名
}

InetAddress沒有構造器,所以需要調用靜態方法進行構造。上述代碼結果為:

1.2 端口

端口表示計算機上的一個程序的進程

  • 不同的進程有不同的端口號,用來區分軟件。
  • 一般被規定為0~65535
  • TCP端口和UDP端口,均有65536個,兩個互不沖突。單個協議下端口是不能沖突的,例:TCP占用8080后,不能再次占用此TCP端口了
  • 端口分類:公有端口01023,HTTP:80,HTTPS:443,FTP:21,Telent:23。程序注冊的端口102449151,用來分配給用戶或者程序,Tomcat:8080,MySQL:3306,Oracle:1521。動態、私有:49152~65535,盡量不要用這里的端口。
netstat -ano  #這條命令用於查看所有端口
netstat -ano|findstr "8080"   #查看指定的端口
tasklist|findstr "8696" #查看指定端口的進程

以上均為Linux命令

InetSocketAddress

該類實現IP套接字地址(IP地址+端口號)它也可以是一對(主機名+端口號),在這種情況下將嘗試解析主機名。如果解決方案失敗,那么該地址被認為是未解決的,但在某些情況下仍可以使用,例如通過代理連接。

它提供了用於綁定,連接或返回值的套接字所使用的不可變對象。

通配符是一個特殊的本地IP地址。 通常意味着“任何”,只能用於bind操作。

InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1",8080);
InetSocketAddress inetSocketAddress1 = new InetSocketAddress("localhost",8080);
System.out.println(inetSocketAddress);
System.out.println(inetSocketAddress1);
System.out.println(inetSocketAddress.getAddress());
System.out.println(inetSocketAddress.getHostName());
System.out.println(inetSocketAddress.getPort());

以上為相關代碼。

1.3 通信協議

網絡通信協議可能涉及到:速率,傳輸碼率,代碼結構,傳輸控制等等

主要涉及的是以下兩個:

TCP:用戶傳輸協議(3次握手,確定返回信息,以后網絡相關知識具體說,不在本篇贅述)

UDP:用戶數據報協議(不確定返回信息)

TCP和UDP對比

TCP就像打電話,需要連接,穩定

UDP就像發短信,不需要連接,發完即結束,不穩定

  • 基於連接與無連接;

  • 對系統資源的要求(TCP較多,UDP少);

  • UDP程序結構較簡單;

  • 流模式與數據報模式 ;(從下面demo中即可看出)

  • TCP保證數據正確性,UDP可能丟包;

  • TCP保證數據順序,UDP不保證。

2. TCP協議

TCP三次握手的過程如下:

  1. 客戶端發送SYN(SEQ=x)報文給服務器端,進入SYN_SEND狀態。
  2. 服務器端收到SYN報文,回應一個SYN (SEQ=y)ACK(ACK=x+1)報文,進入SYN_RECV狀態。
  3. 客戶端收到服務器端的SYN報文,回應一個ACK(ACK=y+1)報文,進入Established狀態。

三次握手完成,TCP客戶端和服務器端成功地建立連接,可以開始傳輸數據了。

TCP連接終止過程:

建立一個連接需要三次握手,而終止一個連接要經過四次握手,這是由TCP的半關閉(half-close)造成的。具體過程如上圖所示。

(1) 某個應用進程首先調用close,稱該端執行“主動關閉”(active close)。該端的TCP於是發送一個FIN分節,表示數據發送完畢。

(2) 接收到這個FIN的對端執行 “被動關閉”(passive close),這個FIN由TCP確認。

注意:FIN的接收也作為一個文件結束符(end-of-file)傳遞給接收端應用進程,放在已排隊等候該應用進程接收的任何其他數據之后,因為,FIN的接收意味着接收端應用進程在相應連接上再無額外數據可接收。

(3) 一段時間后,接收到這個文件結束符的應用進程將調用close關閉它的套接字。這導致它的TCP也發送一個FIN。

(4) 接收這個最終FIN的原發送端TCP(即執行主動關閉的那一端)確認這個FIN。 [3]

既然每個方向都需要一個FIN和一個ACK,因此通常需要4個分節。

TCP協議相關資料參考自百度百科,計算機網絡相關知識不再詳細描述。

2.1 TCP連接的實現

服務端:

public class TestTCPserver {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9999);
        Socket accept = serverSocket.accept();//等待client連接
        InputStream is = accept.getInputStream();
        //管道流,將一個輸入流通過管道轉化為一個合適的輸出流,不用管道流直接String可能會輸出亂碼
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len;
        while((len=is.read(buffer))!=-1){
            baos.write(buffer,0,len);
        }
        System.out.println(baos.toString());
        baos.close();
        is.close();
        accept.close();
        serverSocket.close();
        //正式寫代碼的過程中,一定要用try,catch,finally,為了代碼的安全,出了事故容易判斷
    }
}

用到了socket類,其中涉及了IO流部分的知識。

客戶端:

public class TestTCPclient {
    public static void main(String[] args) throws IOException {
        InetAddress serverIp = InetAddress.getByName("127.0.0.1");
        int port = 9999;
        //創建一個socket連接,連接的是本機
        Socket socket = new Socket(serverIp,port);
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());
        os.close();
        socket.close();
    }
}

注意:socket是需要關閉的

  • socket該類實現客戶端套接字(也稱為“套接字”)。套接字是兩台機器之間通訊的端點。套接字的實際工作由SocketImpl類的實例執行。 應用程序通過更改創建套接字實現的套接字工廠,可以配置自己創建適合本地防火牆的套接字。

  • ServerSocket該類實現了服務器套接字。 服務器套接字等待通過網絡進入的請求。 它根據該請求執行一些操作,然后可能將結果返回給請求者。
    服務器套接字的實際工作由SocketImpl類的實例執行。 應用程序可以更改創建套接字實現的套接字工廠,以配置自己創建適合本地防火牆的套接字。

客戶端:連接服務器socket,發送消息

服務器:建立服務的端口 ServerSocket,等待用戶連接,接受用戶消息

2.1 TCP實現文件上傳

與消息傳遞類似,只是IO操作稍微變了一下

服務端:

public class TCPserverdemo1 {
    public static void main(String[] args) throws IOException {
        //創建服務
        ServerSocket serverSocket = new ServerSocket(9000);
        //監聽客戶端的連接
        Socket accept = serverSocket.accept();  //阻塞式監聽,會一定等待客戶端連接
        InputStream is = accept.getInputStream();
        FileOutputStream fos = new FileOutputStream(new File("copide1.jpg"));
        byte[] buffer = new byte[1024];
        int len;
        while((len = is.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }
        fos.close();
        is.close();
        accept.close();
        serverSocket.close();
    }
}

客戶端:

public class TCPclientdemo1 {
    public static void main(String[] args) throws IOException {
        //創建一個socket連接
        Socket socket = new Socket(InetAddress.getByName("127.0.0.1"),9000);
        //創建一個輸出流
        OutputStream os = socket.getOutputStream();  //.getOutputStream獲得了一個SocketOutputStream實例
        //讀取文件
        FileInputStream fis = new FileInputStream(new File("image.jpg"));
        byte[] buffer = new byte[1024];
        int len;
        while((len=fis.read(buffer))!=-1){
            os.write(buffer,0,len);    //涉及到BIO
        }
        fis.close();
        os.close();
        socket.close();
    }
}

上面用的是字節流,字節緩沖流也可以使用。字符流不可以,因為可能會讀其他文件的類型,字節流比較穩妥。

服務器接收完信息后其實是可以返回消息到客戶端的,socket通信:

服務端可增加:

accept.shutdownInput();
//通知客戶端已經接收完畢
OutputStream os = accept.getOutputStream();
os.write("ending".getBytes());    //發送給客戶端

客戶端可增加:

socket.shutdownOutput();//通知服務器傳輸完畢
InputStream is = socket.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[1024];
int len2;
while((len2 = is.read(buffer))!=-1){
    baos.write(buffer,0,len2);
}
System.out.println(baos);

客戶端讀入了服務端返回的ending字符串。

實現兩端通信之后再決定是否進行其他操作,例如close()等等。

3. UDP

無需連接,但是需要知道對方地址

3.1 UDP消息發送

主要依賴的是DatagramSocketDatagramPacket

  • DatagramSocket此類表示用於發送和接收數據報數據包的套接字。 數據報套接字是分組傳送服務的發送或接收點。 在數據報套接字上發送或接收的每個數據包都被單獨尋址和路由。 從一個機器發送到另一個機器的多個分組可以不同地路由,並且可以以任何順序到達。 在可能的情況下,新構建的DatagramSocket啟用了SO_BROADCAST套接字選項,以允許廣播數據報的傳輸。 為了接收廣播數據包,DatagramSocket應該綁定到通配符地址。 在一些實現中,當DatagramSocket綁定到更具體的地址時,也可以接收廣播分組。
  • DatagramPacket該類表示數據報包。 數據報包用於實現無連接分組傳送服務。 僅基於該數據包中包含的信息,每個消息從一台機器路由到另一台機器。 從一台機器發送到另一台機器的多個分組可能會有不同的路由,並且可能以任何順序到達。 包傳送不能保證。

以下實現UDP消息傳送的一個簡單例子:

public class TestUDP1 {
    //不需要連接服務器
    public static void main(String[] args) throws IOException {
        //建立Socket
        DatagramSocket datagramSocket = new DatagramSocket(); //為空將默認綁定一個可用端口

        //建個數據報
        String message = "hello,server";
        InetAddress inetAddress = InetAddress.getByName("localhost");
        int port = 9000;
        int len = message.getBytes().length;
        //數據,數據的長度起始位置,要發送給誰
        DatagramPacket datagramPacket = new DatagramPacket(message.getBytes(), 0,len, inetAddress, port);

        datagramSocket.send(datagramPacket);   //進行發送
        datagramSocket.close();
    }
}

UDP發送完就不需要其他操作了,為了驗證我們發送的消息,建立了一個接收端:

public class UDPaccept {
    public static void main(String[] args) throws IOException {
        //開放端口
        DatagramSocket socket = new DatagramSocket(9000);
        //接收數據報
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer,0,buffer.length); //接收並不需要對方地址和端口
        socket.receive(packet);   //阻塞式接收
        System.out.println(packet.getAddress());
        System.out.println(new String(packet.getData(),0,packet.getData().length));
        socket.close();
    }
}

其中收發消息用到了兩個方法DatagramSocket.send()DatagramSocket.receive()

3.2 UDP聊天的實現(單向)

通過UDP協議進行一個發送端和接收端的demo。當出現"bye"時,結束對話。

發送端:

public class UDPsender {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(8080);//發送端口
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        //BufferedInputStream bis = new BufferedInputStream(System.in);
        int len;
        while(true){
            String data = reader.readLine();      //接收鍵盤輸入的信息
            DatagramPacket packet = new DatagramPacket(data.getBytes(),0,data.getBytes().length,new InetSocketAddress("localhost",6000));
            socket.send(packet);
            if(data.equals("bye"))
                break;
        }
        reader.close();
        socket.close();
    }
}

接收端:

public class UDPreceiver {
    public static void main(String[] args) throws IOException {
        DatagramSocket socket = new DatagramSocket(6000);//打開接收端口

        while(true){
            byte[] container = new byte[1024]; //用來裝數據報內容
            DatagramPacket packet = new DatagramPacket(container,0,container.length);   //接受時候只需要一個空byte[]
            socket.receive(packet);
            byte[]data = packet.getData();
            String datas = new String(data,0,data.length);  //仍帶有byte[]的其他信息,若轉為真正字符串需trim()
            System.out.println(datas);
            if(datas.trim().equals("bye"))
                break;
        }
        socket.close();
    }
}

這個demo只實現了單向發消息。后續將實現雙向發送。

3.3 雙向聊天(多線程)

雙向聊天和以上內容相似,只需要每個端開啟兩個線程(接收線程和發送線程),以下為代碼演示:

public class TalkSend implements Runnable{    //發送線程
    DatagramSocket socket = null;
    BufferedReader reader = null;

    private String ToIP;
    private int ToPort;

    public TalkSend(String toString, int toPort) {
        ToIP = toString;
        ToPort = toPort;
    }

    @Override
    public void run() {
        try {
            socket = new DatagramSocket();//發送端口
            reader = new BufferedReader(new InputStreamReader(System.in));
        } catch (SocketException e) {
            e.printStackTrace();
        }
        //BufferedInputStream bis = new BufferedInputStream(System.in);
        while(true){
            String data = null;
            try {
                data = reader.readLine();
                DatagramPacket packet = new DatagramPacket(data.getBytes(),0,data.getBytes().length,new InetSocketAddress(this.ToIP,this.ToPort));
                socket.send(packet);
                if(data.equals("bye"))
                    break;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        socket.close();
    }
}
public class TalkReceive implements Runnable{   //接收線程
    DatagramSocket socket = null;

    private int FromPort;
    private String Person;

    public TalkReceive(int fromPort,String person) {
        FromPort = fromPort;
        Person = person;
    }

    @Override
    public void run() {
        try {
            socket = new DatagramSocket(this.FromPort);//打開接收端口
        } catch (SocketException e) {
            e.printStackTrace();
        }

        while(true){
            byte[] container = new byte[1024]; //用來裝數據報內容
            DatagramPacket packet = new DatagramPacket(container,0,container.length);
            try {
                socket.receive(packet);
            } catch (IOException e) {
                e.printStackTrace();
            }
            byte[]data = packet.getData();
            String datas = new String(data,0,data.length);
            System.out.println(Person+":"+datas);
            if(datas.trim().equals("bye"))
                break;
        }
        socket.close();
    }
}

然后需要設置兩個端進行聊天:

new Thread(new TalkSend("localhost",8080)).start();
new Thread(new TalkReceive(6000,"老師")).start();

這里設置為學生端,然后開啟兩個線程,設置發送端口和接收端口

new Thread(new TalkSend("localhost",6000)).start();
new Thread(new TalkReceive(8080,"學生")).start();

這里設置為老師端,然后開啟線程,設置端口,其實打開了4個端口,因為學生端和老師端發送線程中,DatagramSocket默認綁定的還有兩個端口。

4. URL

https://www.baidu.com/

統一資源定位符:定位互聯網上的某個資源

DNS域名解析: www.baidu.com ——》xxx.xxx.xxx.xxx

協議://ip地址:端口/項目名/資源

URL類的基本使用

URL url = new URL("http://localhost:8080/helloworld/index.jsp?username=yuan&password=123");
System.out.println(url.getProtocol());//得到協議名
System.out.println(url.getHost());//主機
System.out.println(url.getPort());//端口
System.out.println(url.getPath());//文件
System.out.println(url.getFile());//文件全路徑
System.out.println(url.getQuery());   //得到url查詢的部分(參數)

Java萬物皆對象

下載一個URL資源

public class UrlDown {
    public static void main(String[] args) throws IOException {
        URL url = new URL("http://localhost:8080/gaoyuan/SecurityFile.txt");
        //連接到這個資源
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection(); //需要轉換類型,因為返回的是URPConnection
        InputStream is = urlConnection.getInputStream();
        FileOutputStream fos = new FileOutputStream("SecurityFile.txt");
        byte[] buffer = new byte[1024];
        int len;
        while((len = is.read(buffer))!=-1){
            fos.write(buffer,0,len);
        }
        fos.close();
        is.close();
        urlConnection.disconnect();//斷開連接
    }
}

這是打開Tomcat服務器后,把一個文件添加到相應目錄之后下載的。

網絡編程基礎部分結束,例如:計算機網絡,BIO等知識將會在后續博客中發布。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM