Java:網(wǎng)絡(luò)編程

1、網(wǎng)絡(luò)編程三要素

  • IP地址
    要想讓網(wǎng)絡(luò)中的計算機能夠互相通信,必須為每臺計算機指定一個標識號,通過這個標識號來指定要接收數(shù)據(jù)的計算機和識別發(fā)送的計算機,而IP地址就是這個標識號。也就是設(shè)備的標識

  • 端口
    網(wǎng)絡(luò)的通信,本質(zhì)上是兩個應用程序的通信。每臺計算機都有很多的應用程序,那么在網(wǎng)絡(luò)通信時,如何區(qū)分這些應用程序呢?如果說IP地址可以唯一標識網(wǎng)絡(luò)中的設(shè)備,那么端口號就可以唯一標識設(shè)備中的應用程序了。也就是應用程序的標識

  • 協(xié)議
    通過計算機網(wǎng)絡(luò)可以使多臺計算機實現(xiàn)連接,位于同一個網(wǎng)絡(luò)中的計算機在進行連接和通信時需要遵守一定的規(guī)則,這就好比在道路中行駛的汽車一定要遵守交通規(guī)則一樣。在計算機網(wǎng)絡(luò)中,這些連接和通信的規(guī)則被稱為網(wǎng)絡(luò)通信協(xié)議,它對數(shù)據(jù)的傳輸格式、傳輸速率、傳輸步驟等做了統(tǒng)一規(guī)定,通信雙方必須同時遵守才能完成數(shù)據(jù)交換。常見的協(xié)議有UDP協(xié)議和TCP協(xié)議

  • 端口號

    • 用兩個字節(jié)表示的整數(shù),它的取值范圍是065535。其中,01023之間的端口號用于一些知名的網(wǎng)絡(luò)服務和應用,普通的應用程序需要使用1024以上的端口號。如果端口號被另外一個服務或應用所占用,會導致當前程序啟動失敗
  • 獲取當前主機IP

InetAddress address = InetAddress.getByName("localhost");
String hostName = address.getHostName();
System.out.println("主機名為" + hostName);
String ip = address.getHostAddress();
System.out.println("IP為" + ip);

2、UDP通信

  • UDP發(fā)送數(shù)據(jù)

    方法名 說明
    DatagramSocket() 創(chuàng)建數(shù)據(jù)報套接字并將其綁定到本機地址上的任何可用端口
    DatagramPacket(byte[] buf,int len,InetAddress add,int port) 創(chuàng)建數(shù)據(jù)包,發(fā)送長度為len的數(shù)據(jù)包到指定主機的指定端口
    void send(DatagramPacket p) 發(fā)送數(shù)據(jù)報包
    void close() 關(guān)閉數(shù)據(jù)報套接字
    void receive(DatagramPacket p) 從此套接字接受數(shù)據(jù)報包
  • UDP接收數(shù)據(jù)

    方法名 說明
    DatagramPacket(byte[] buf, int len) 創(chuàng)建一個DatagramPacket用于接收長度為len的數(shù)據(jù)包
    byte[] getData() 返回數(shù)據(jù)緩沖區(qū)
    int getLength() 返回要發(fā)送的數(shù)據(jù)的長度或接收的數(shù)據(jù)的長度
  • 客戶端

import java.io.IOException;
import java.net.*;
import java.util.Scanner;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        //創(chuàng)建接收端的Socket對象
        DatagramSocket ds = new DatagramSocket();

        while (true) {
            String s = sc.nextLine();
            byte[] bytes = s.getBytes();
            // 構(gòu)造數(shù)據(jù)報套接字并將其綁定到本地主機上的任何可用端口
            InetAddress address = InetAddress.getByName("127.0.0.1");
            int port = 10000;

            //調(diào)用DatagramSocket對象的方法接收數(shù)據(jù)
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
            ds.send(dp);

            // 結(jié)束
            if("404".equals(s)){
                break;
            }
        }
        //關(guān)閉此數(shù)據(jù)報套接字
        ds.close();
    }
}
  • 服務端
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {
        //創(chuàng)建接收端的Socket對象
        DatagramSocket ds = new DatagramSocket(10000);

        while (true) {
            //創(chuàng)建一個數(shù)據(jù)包,用于接收數(shù)據(jù)
            byte [] bytes = new byte[1024];
            //調(diào)用DatagramSocket對象的方法接收數(shù)據(jù)
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length);
            ds.receive(dp);

            byte[] data = dp.getData();
            int length = dp.getLength();
            String s = new String(data, 0, length);
            System.out.println(s);

             // 結(jié)束
            if("404".equals(s)){
                break;
            }
        }
        ds.close();
    }
}

3、UDP三種通訊方式

  • 單播
    單播用于兩個主機之間的端對端通信
  • 組播
    組播用于對一組特定的主機進行通信
  • 廣播
    廣播用于一個主機對整個局域網(wǎng)上所有主機上的數(shù)據(jù)通信
  • 組播
  // 發(fā)送端
  public class ClinetDemo {
      public static void main(String[] args) throws IOException {
          // 1. 創(chuàng)建發(fā)送端的Socket對象(DatagramSocket)
          DatagramSocket ds = new DatagramSocket();
          String s = "hello 組播";
          byte[] bytes = s.getBytes();
          InetAddress address = InetAddress.getByName("224.0.1.0");
          int port = 10000;
          // 2. 創(chuàng)建數(shù)據(jù),并把數(shù)據(jù)打包(DatagramPacket)
          DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
          // 3. 調(diào)用DatagramSocket對象的方法發(fā)送數(shù)據(jù)(在單播中,這里是發(fā)給指定IP的電腦但是在組播當中,這里是發(fā)給組播地址)
          ds.send(dp);
          // 4. 釋放資源
          ds.close();
      }
  }
  // 接收端
  public class ServerDemo {
      public static void main(String[] args) throws IOException {
          // 1. 創(chuàng)建接收端Socket對象(MulticastSocket)
          MulticastSocket ms = new MulticastSocket(10000);
          // 2. 創(chuàng)建一個箱子,用于接收數(shù)據(jù)
          DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
          // 3. 把當前計算機綁定一個組播地址,表示添加到這一組中.
          ms.joinGroup(InetAddress.getByName("224.0.1.0"));
          // 4. 將數(shù)據(jù)接收到箱子中
          ms.receive(dp);
          // 5. 解析數(shù)據(jù)包,并打印數(shù)據(jù)
          byte[] data = dp.getData();
          int length = dp.getLength();
          System.out.println(new String(data,0,length));
          // 6. 釋放資源
          ms.close();
      }
  }
  • 廣播
    // 發(fā)送端
    public class ClientDemo {
        public static void main(String[] args) throws IOException {
              // 1. 創(chuàng)建發(fā)送端Socket對象(DatagramSocket)
            DatagramSocket ds = new DatagramSocket();
          // 2. 創(chuàng)建存儲數(shù)據(jù)的箱子,將廣播地址封裝進去
            String s = "廣播 hello";
            byte[] bytes = s.getBytes();
            InetAddress address = InetAddress.getByName("255.255.255.255");
            int port = 10000;
            DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
          // 3. 發(fā)送數(shù)據(jù)
            ds.send(dp);
          // 4. 釋放資源
            ds.close();
        }
    }
    // 接收端
    public class ServerDemo {
        public static void main(String[] args) throws IOException {
            // 1. 創(chuàng)建接收端的Socket對象(DatagramSocket)
            DatagramSocket ds = new DatagramSocket(10000);
            // 2. 創(chuàng)建一個數(shù)據(jù)包,用于接收數(shù)據(jù)
            DatagramPacket dp = new DatagramPacket(new byte[1024],1024);
            // 3. 調(diào)用DatagramSocket對象的方法接收數(shù)據(jù)
            ds.receive(dp);
            // 4. 解析數(shù)據(jù)包,并把數(shù)據(jù)在控制臺顯示
            byte[] data = dp.getData();
            int length = dp.getLength();
            System.out.println(new String(data,0,length));
            // 5. 關(guān)閉接收端
            ds.close();
        }
    }
    

4.TCP通信程序

  • TCP發(fā)送數(shù)據(jù)

    方法名 說明
    Socket(InetAddress address,int port) 創(chuàng)建流套接字并將其連接到指定IP指定端口號
    Socket(String host, int port) 創(chuàng)建流套接字并將其連接到指定主機上的指定端口號
    InputStream getInputStream() 返回此套接字的輸入流
    OutputStream getOutputStream() 返回此套接字的輸出流
  • TCP接收數(shù)據(jù)

    方法名 說明
    ServletSocket(int port) 創(chuàng)建綁定到指定端口的服務器套接字
    Socket accept() 監(jiān)聽要連接到此的套接字并接受它
  • 注意事項

    1. accept方法是阻塞的,作用就是等待客戶端連接
    2. 客戶端創(chuàng)建對象并連接服務器,此時是通過三次握手協(xié)議,保證跟服務器之間的連接
    3. 針對客戶端來講,是往外寫的,所以是輸出流
      針對服務器來講,是往里讀的,所以是輸入流
    4. read方法也是阻塞的
    5. 客戶端在關(guān)流的時候,還多了一個往服務器寫結(jié)束標記的動作
    6. 最后一步斷開連接,通過四次揮手協(xié)議保證連接終止

一次TCP通信

  • 客戶端

import java.io.*;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建socket 綁定IP和端口
        Socket socket = new Socket("127.0.0.1",10000);

        // 創(chuàng)建socket發(fā)送流
        OutputStream os = socket.getOutputStream();
        os.write("hello".getBytes());
        
        //僅僅關(guān)閉輸出流.并寫一個結(jié)束標記,對socket沒有任何影響
        socket.shutdownOutput();
        
        // 創(chuàng)建socket接收流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = br.readLine())!=null){
            System.out.println(line);
        }
        // 關(guān)閉資源
        br.close();
        os.close();
        socket.close();
    }
}
  • 服務器
public class ServerDemo {
    public static void main(String[] args) throws IOException {
        // 創(chuàng)建socServerSocketket 監(jiān)聽端口
        ServerSocket ss = new ServerSocket(10000);
        //等待客戶端連接
        Socket accept = ss.accept();
        // 輸入流
        InputStream is = accept.getInputStream();
        int b;
        while((b = is.read())!=-1){
            System.out.println((char) b);
        }
        // 輸出流
        System.out.println("看看我執(zhí)行了嗎?");
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream()));
        bw.write("你誰啊?");
        bw.newLine();
        bw.flush();

        // 關(guān)閉資源
        bw.close();
        is.close();
        accept.close();
        ss.close();
    }
}

TCP上傳文件

方法名 說明
void shutdownInput() 將此套接字的輸入流放置在“流的末尾”
void shutdownOutput() 禁止用此套接字的輸出流
  • 客戶端

import java.io.*;
import java.net.Socket;

public class ClientDemo {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("127.0.0.1",10000);

        //是本地的流,用來讀取本地文件的.
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream("1.jpg"));

        //寫到服務器 --- 網(wǎng)絡(luò)中的流
        OutputStream os = socket.getOutputStream();
        BufferedOutputStream bos = new BufferedOutputStream(os);
        int b;
        while((b = bis.read())!=-1){
            bos.write(b);//通過網(wǎng)絡(luò)寫到服務器中
        }
        bos.flush();
        //給服務器一個結(jié)束標記,告訴服務器文件已經(jīng)傳輸完畢
        socket.shutdownOutput();

        //接收流
        BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line;
        while((line = br.readLine()) !=null){
            System.out.println(line);
        }

        bis.close();
        socket.close();
    }
}
  • 服務器

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerDemo {
    public static void main(String[] args) throws IOException {

        ServerSocket ss = new ServerSocket(10000);
        Socket accept = ss.accept();

        //網(wǎng)絡(luò)中的流,從客戶端讀取數(shù)據(jù)的
        BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());

        //本地的IO流,把數(shù)據(jù)寫到本地中,實現(xiàn)永久化存儲
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("3.jpg"));
        int b;
        while((b = bis.read()) !=-1){
            bos.write(b);
        }
        //將字節(jié)輸入流FileInputStream   轉(zhuǎn)成     字符輸入流  Fliereader
        //通過轉(zhuǎn)換流
        InputStreamReader inputStreamReader = new InputStreamReader(accept.getInputStream());
        //通過緩沖輸入字符流
        BufferedReader br = new BufferedReader(inputStreamReader);


        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(accept.getOutputStream());
        BufferedWriter bw = new BufferedWriter(outputStreamWriter);
        bw.write("上傳成功");
        bw.newLine();
        bw.flush();

        bos.close();
        accept.close();
        ss.close();
    }
}

TCP服務端優(yōu)化

  • 異步線程完成socket接收流程

import java.io.*;
import java.net.Socket;
import java.util.UUID;

// 線程任務類
public class ThreadSocket implements Runnable {
    private Socket acceptSocket;

    public ThreadSocket(Socket accept) {
        this.acceptSocket = accept;
    }

    @Override
    public void run() {
        BufferedOutputStream bos = null;
        try {
            //網(wǎng)絡(luò)中的流,從客戶端讀取數(shù)據(jù)的
            BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
            //本地的IO流,把數(shù)據(jù)寫到本地中,實現(xiàn)永久化存儲
            bos = new BufferedOutputStream(new FileOutputStream(  UUID.randomUUID().toString() + ".jpg"));

            int b;
            while((b = bis.read()) !=-1){
                bos.write(b);
            }

            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(acceptSocket.getOutputStream()));
            bw.write("上傳成功");
            bw.newLine();
            bw.flush();
            
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(bos != null){
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            if (acceptSocket != null){
                try {
                    acceptSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

  • 線程池控制socket創(chuàng)建

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class ServerDemo {
    public static void main(String[] args) throws  IOException {
        ServerSocket ss = new ServerSocket(10000);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,//核心線程數(shù)量
                10,   //線程池的總數(shù)量
                60,   //臨時線程空閑時間
                TimeUnit.SECONDS, //臨時線程空閑時間的單位
                new ArrayBlockingQueue<>(5),//阻塞隊列
                Executors.defaultThreadFactory(),//創(chuàng)建線程的方式
                new ThreadPoolExecutor.AbortPolicy()//任務拒絕策略
        );

        while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
            pool.submit(ts);
        }
        //ss.close();
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容