LongJL 发表于 2021-7-22 19:04

【自学笔记】 Java基础 - 网络基础

本帖最后由 LongJL 于 2021-7-22 19:24 编辑

# 网络编程

## 概述
- 在网络通信协议下,不同计算机上运行的程序,可以进行数据传输。

## 三要素
### IP地址
- 设备在网络中的地址,是唯一的标识
- 全称"互联网协议地址",也称IP地址。是分配给上网设备的数字标签。常见的IP分类为:ipv4和ipv6
### 端口
- 应用程序在设备在唯一的标识

#### 端口号
用俩个字节表示的整数,它的取值范围是0~65535,其他0~1023之间的端口号用于一些知名的网络服务或者应用,一遍使用1024以上的端口就行。** 一个端口号只能被一个应用程序使用。 **


### 协议
- 数据在网络中传输的规则,常见的协议又UDP协议和TCP协议

#### UDP协议
- 用户数据报协议(User Datagram Protocal)
- UDP是面向无连接通信协议,速度快,有大小限制一次最多发送64K,数据不安全,易丢失

#### TCP协议
- 传输控制协议(Transmission Control Protocal)
- TCP协议是面向连接的通信协议。速度慢,没有大小限制,数据安全。

## InetAddress类

### 方法
- `public static InetAddress getByName(String host)`:确定主机名称的IP地址。
- `public String getHostName() `:获取此IP地址的主机名。
- `public String getHostAddress()`:返回文本显示中的IP地址字符串

```Java
public class InetAddressDemo1 {
    public static void main(String[] args) throws UnknownHostException {
      InetAddress address = InetAddress.getByName("LAPTOP-FHMDM194");

      // 主机名
      String hostName = address.getHostName();
      System.out.println(hostName);

      // ip
      String ip = address.getHostAddress();
      System.out.println(ip);
    }
}

```

## UDP通信程序

### UDP的三种通信方式
- 单播
- 组播
- 广播

### 单播
#### 发送步骤
1. 创建发送端的DatagramSocket对象
      `DatagramSocket() `构造数据报套接字并将其绑定到本地主机上的任何可用端口。
2. 创建数据,并把数据打包(DatagramPacket)
      `DatagramPacket(byte[] buf, int length, InetAddress address, int port) `构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
      参数介绍:
                buf:要发送的数据
                length:你要把数组中多少个数据发送过去
                address:接收端
                port:接收端的端口号
3. 调用DatagramSocket对象的方法发送数据
4. 释放数据

```Java
public class ClientDemo {
    public static void main(String[] args) throws IOException {
      DatagramSocket ds = new DatagramSocket();

      byte[] bytes = "发送的信息".getBytes();
      InetAddress address = InetAddress.getByName("127.0.0.1");
      int port = 10000;
      DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,port);
      
      ds.send(dp);

      ds.close();
    }
}

```

#### 接收步骤
1. 创建接收端的DatagramSocket对象
      `DatagramSocket(int port)`构造数据报套接字并将其绑定到本地主机上的指定端口。
2. 创建个变量,接收数据
      `DatagramPacket(byte[] buf, int length)`构造一个 DatagramPacket用于接收长度的数据包 length 。
3. 调用DatagramSocket的方法接收数据并把数据放入箱子中
4. 解析数据包
5. 释放资源

```Java
public class ServerDemo {
    public static void main(String[] args) throws IOException {
      // 指定一个端口号,接收发送端发送的数据
      DatagramSocket ds = new DatagramSocket(10000);

      byte[] bytes = new byte;
      DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

      ds.receive(dp);

      byte[] data = dp.getData();
      int length = dp.getLength();

      System.out.println(new String(data,0,length));

      ds.close();
    }
}

```

##### 注意事项
1. 在运行的过程中,需要先运行接收端,然后再去运行发送端
2. 接收端启动后,如果发送端没有发送数据,就会处于等待状态
3. 在接收数据的时候,可以调用getLength的方法,可以获取接收数据的长度


### 组播

组播地址:224.0.0.0 ~ 239.255.255.255,其中224.0.0.0 ~ 224.0.0.255为预留的组播地址
#### 发送步骤
1. 创建发送端的DatagramSocket对象
      `DatagramSocket() `构造数据报套接字并将其绑定到本地主机上的任何可用端口。
2. 创建数据,并把数据打包(DatagramPacket)
      `DatagramPacket(byte[] buf, int length, InetAddress address, int port) `构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
      参数介绍:
                buf:要发送的数据
                length:你要把数组中多少个数据发送过去
                address:接收端
                port:接收端的端口号
3. 调用DatagramSocket对象的方法发送数据,发送给组播地址
4. 释放数据
```Java
public class ClientDemo {
    public static void main(String[] args) throws IOException {
      DatagramSocket ds = new DatagramSocket();

      byte[] bytes = "组播发送".getBytes();
      InetAddress address = InetAddress.getByName("224.0.1.0");
      int post = 8888;
      DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,post);

      ds.send(dp);

      ds.close();
    }
}
```

#### 接收步骤
1. 创建接收端的MulticastSocket对象
      `MulticastSocket(int port)`创建组播套接字并将其绑定到特定端口。
2. 创建个变量,接收数据
      `DatagramPacket(byte[] buf, int length)`构造一个 DatagramPacket用于接收长度的数据包 length 。
3. 调用DatagramSocket的方法接收数据并把数据放入箱子中
4. 解析数据包
5. 释放资源

```Java
public class ServerDemo {
    public static void main(String[] args) throws IOException {
      MulticastSocket ms = new MulticastSocket(8888);

      DatagramPacket dp = new DatagramPacket(new byte,1024);

      // 把当前计算机绑定一个组播地址,表示添加到这一组中
      ms.joinGroup(InetAddress.getByName("224.0.1.0"));

      ms.receive(dp);

      byte[] data = dp.getData();
      int length = dp.getLength();

      System.out.println(new String(data,0,length));

      ms.close();
    }
}

```

### 广播
广播地址:255.255.255.255
#### 发送步骤
1. 创建发送端的DatagramSocket对象
      `DatagramSocket() `构造数据报套接字并将其绑定到本地主机上的任何可用端口。
2. 创建数据,并把数据打包(DatagramPacket)
      `DatagramPacket(byte[] buf, int length, InetAddress address, int port) `构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号。
      参数介绍:
                buf:要发送的数据
                length:你要把数组中多少个数据发送过去
                address:接收端
                port:接收端的端口号
3. 调用DatagramSocket对象的方法发送数据,发送给广播地址
4. 释放数据
```Java
public class ServerDemo {
    public static void main(String[] args) throws IOException {
      // 指定一个端口号,接收发送端发送的数据
      DatagramSocket ds = new DatagramSocket(8888);

      byte[] bytes = new byte;
      DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

      ds.receive(dp);

      byte[] data = dp.getData();
      int length = dp.getLength();

      System.out.println(new String(data,0,length));

      ds.close();
    }
}

```


#### 接收步骤
1. 创建接收端的DatagramSocket对象
      `DatagramSocket(int port)`构造数据报套接字并将其绑定到本地主机上的指定端口。
2. 创建个变量,接收数据
      `DatagramPacket(byte[] buf, int length)`构造一个 DatagramPacket用于接收长度的数据包 length 。
3. 调用DatagramSocket的方法接收数据并把数据放入箱子中
4. 解析数据包
5. 释放资源

```Java
public class ClientDemo {
    public static void main(String[] args) throws IOException {
      DatagramSocket ds = new DatagramSocket();

      byte[] bytes = "广播发送".getBytes();
      InetAddress address = InetAddress.getByName("255.255.255.255");
      int post = 8888;
      DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,post);

      ds.send(dp);

      ds.close();
    }
}

```

## TCP通信程序
TCP通信协议是一个可靠的网络协议,它在通信的俩端各简历一个Socket对象
客户端用的类为`Socket`,服务端用的类为`ServerSocket`


### 发送数据步骤
1. 创建客户端的Socket对象与指定服务器连接
      `Socket(String host, int port) `:创建流套接字并将其连接到指定主机上的指定端口号。
2. 获取输出流,写数据
      `public OutputStream getOutputStream()`:返回此套接字的输出流。
3. 释放资源
      `void close()`

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

      OutputStream outputStream = socket.getOutputStream();

      outputStream.write("hello".getBytes());


      // 释放资源
      outputStream.close();
      socket.close();
    }
}
```

### 接收数据步骤
1. 创建服务器端的Socket对象(ServerSocket)
      `ServerSocket(int port)`创建绑定到指定端口的服务器套接字。
2. 监听客户端连接,返回一个Socket对象
      `Socket accept()`
      注:执行到这一步的时候,如果没有客户端来连接就会发送阻塞。
3. 获取输入流,读数据,并把数据显示在控制台
      `InputStream getInputStream()`
```Java
public class ServerDemo {
    public static void main(String[] args) throws IOException {
      ServerSocket ss = new ServerSocket(8888);

      Socket accept = ss.accept();

      InputStream inputStream = accept.getInputStream();

      int b;
      while ((b= inputStream.read()) != -1){
            System.out.println((char)b);
      }

      inputStream.close();
      ss.close();
    }
}

```


#### 注意事项
1. accept方法是阻塞的,它的作用就等待客户端连接。
2. 客户端创建对象并连接服务器,此时是通过三次握手协议保证跟服务器之间的连接。
3. 对于客户端而言,是往外写的,所以是输出流,对于服务器而言,是往里读的,所以是输入流
4. read方法也是阻塞的
5. 在关闭客户端输出流的时候,会自动往服务器写一个结束标记的动作。
6. 通过四次挥手保证连接终止


#### 三次握手
1. 客户端向服务器发送链接请求(等待服务器确认)
2. 服务器向客户端返回一个响应(告诉客户端收到了请求)
3. 客户端向服务器再次发送确认信息(建立连接)

#### 四次挥手
1. 客户端向服务器发出取消连接请求
2. 服务器向客户端返回一个响应(表示收到客户端取消请求)
3. 第二次发送完得时候会去处理数据,处理完了再次向客户端发送确认取消信息
4. 客户端再次发送确认消息(连接取消)


#### TCP小练习

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

      OutputStream outputStream = socket.getOutputStream();
      outputStream.write("hello".getBytes());
      socket.shutdownOutput();// 仅仅关闭输出流,并写一个结束标记,对socket没有任何影响

      BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 字节转字符
      String line;
      while ((line = br.readLine())!=null){
            System.out.println(line);
      }
      br.close();
      outputStream.close();
      socket.close();
    }
}

public class ServerDemo {
    public static void main(String[] args) throws IOException {
      ServerSocket ss = new ServerSocket(8888);

      Socket accept = ss.accept();

      InputStream inputStream = accept.getInputStream();

      int b;

      while ((b= inputStream.read())!=-1){
            System.out.println((char)b);
      }

      BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(accept.getOutputStream())); // 字节转字符
      bw.write("你谁啊");
      bw.newLine();
      bw.flush();

      bw.close();
      inputStream.close();
      accept.close();
      ss.close();
    }
}
```

文件上传

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

      // 本地流,读取本地文件
      BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\FileTest\\1.mp4"));

      OutputStream outputStream = socket.getOutputStream();
      BufferedOutputStream bos = new BufferedOutputStream(outputStream);

      int b;
      while ((b=bis.read())!= -1){
            bos.write(b); // 通过网络写到服务器
      }

      // 给服务器结束标记
      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();


    }
}
public class ServerDemo {
    public static void main(String[] args) throws IOException {
      ServerSocket ss = new ServerSocket(8888);

      Socket accept = ss.accept();

      // 网络中的流
      BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
      BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("D:\\FileTest\\copy\\1.mp4"));

      int b;

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

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


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

```


# 服务端优化
- 使用死循环让服务器保持一直运行
- 使用UUID来确保文件的唯一性
- 使用多线程可以处理多个客户端请求
- 在使用线程池优化多线程的资源消耗过大的问题

## UUID
一个表示不可变的通用唯一标识符(UUID)的类。 UUID表示128位值。
### 方法
- `public static UUID randomUUID()` 静态工厂检索一个类型4(伪随机生成)的UUID。 UUID是使用加密强伪随机数生成器生成的。
- `String toString()` 返回代表这个 UUID的 String对象


```Java
public class ServerDemo {
    public static void main(String[] args) throws IOException {
      ServerSocket ss = new ServerSocket(8888);
      ThreadPoolExecutor pool = new ThreadPoolExecutor(
                3,// 核心线程数量
                10,// 线程池的总数量
                60,// 临时线程的空闲时间
                TimeUnit.SECONDS,// 临时线程的空闲时间单位
                new ArrayBlockingQueue<>(5),//阻塞队列
                Executors.defaultThreadFactory(),//创建线程的方式
                new ThreadPoolExecutor.AbortPolicy()// 任务拒绝策略
      );
      while (true) {
            Socket accept = ss.accept();
            ThreadSocket ts = new ThreadSocket(accept);
//            new Thread(ts).start();
            pool.submit(ts);
      }
//      ss.close();
    }
}
public class ThreadSocket implements Runnable {
    private Socket acceptSocket;
    public ThreadSocket(Socket accept) {
      this.acceptSocket = accept;
    }

    @Override
    public void run() {
      BufferedOutputStream bos = null;
      try {
            // 网络中的流
            BufferedInputStream bis = new BufferedInputStream(acceptSocket.getInputStream());
            bos = new BufferedOutputStream(new FileOutputStream("D:\\FileTest\\copy\\"+ UUID.randomUUID().toString().replace("-","") +".mp4"));
            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 (acceptSocket != null){
                try {
                  acceptSocket.close();
                } catch (IOException e) {
                  e.printStackTrace();
                }
            }
            if (bos != null){
                try {
                  bos.close();
                } catch (IOException e) {
                  e.printStackTrace();
                }
            }


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

      // 本地流,读取本地文件
      BufferedInputStream bis = new BufferedInputStream(new FileInputStream("D:\\FileTest\\1.mp4"));

      OutputStream outputStream = socket.getOutputStream();
      BufferedOutputStream bos = new BufferedOutputStream(outputStream);

      int b;
      while ((b=bis.read())!= -1){
            bos.write(b); // 通过网络写到服务器
      }

      // 给服务器结束标记
      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();


    }
}
```

好萌一只大辉宝 发表于 2021-7-22 19:10

有用,刚好在学

qiokio 发表于 2021-7-22 19:25

谢谢,有帮助

偶尔平凡 发表于 2021-7-22 19:26

letoto 发表于 2021-7-22 20:04

web前端想转java

daytripper 发表于 2021-7-22 20:15

刚好需要学习下java,感谢楼主

gaifan 发表于 2021-7-22 21:06

谢谢分享

MCQI 发表于 2021-7-22 21:08

想看看,想看看

KevINBy 发表于 2021-7-22 21:24

受教了,感谢分享

SuBaiQiao 发表于 2021-7-22 21:24

谢谢分享,很与价值!
页: [1] 2 3
查看完整版本: 【自学笔记】 Java基础 - 网络基础