Java网络编程

例子见Github-JavaSE-Day08

基本概念

网络

  1. 多台计算机组成,使用物理线路连接
  2. 交换数据,共享资源

网络编程三要素

  1. IP地址:唯一标识网络上每一台计算机,
    两台计算机间通信必备要素 ipconfig查看
  2. 端口号:计算机中应用程序的编号
    有效端口0~65536
    系统使用或保留端口0~1024
  3. 通信协议:通信的规则 TCP UDP
    国际通用协议TCP/IP协议

网络分层模型
网络模型一:
OSI开发系统互联参考模型

网络模型二:
TCP/IP参考模型:Transfer Control Protocol/Internet Protocol传输控制/网际协议
在这里插入图片描述

IP地址表示方法
IP地址一共32位。
IP地址 = 网络IP + 主机IP

  • 网络ID:标识计算机或网络设备所在网段
  • 主机ID:标识特定主机或网络设备

特殊的IP地址
0.0.0.0:本机 更多是阻断和外网的通信
127.0.0.1:本机回环地址,用于本机测试 localhost 可以通信
255.255.255.255:当前子网,一般用于向当前子网广播信息

IP地址所对应的对象–>InetAddress
Java提供了一个对ip地址的访问类java.net.InetAddress。无构造方法。

InetAddressDemo.java
public class InetAddressDemo {
    public static void main(String[] args) throws UnknownHostException {
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost); // 主机名+IP地址

    // 自己局域网内(同一网段内)的主机名才能拿到ip地址
    InetAddress inetAdd = InetAddress.getByName("LAPTOP-8VFER8KN");
    System.out.println(inetAdd);

    //拿到百度的ip地址
    InetAddress baidu = InetAddress.getByName("www.baidu.com");
    System.out.println(baidu);
    System.out.println(baidu.getHostAddress());
    System.out.println(baidu.getHostName());
}

}

端口:port
端口是虚拟概念,并不是主机上真的有若干端口。通过端口可以在一个主机上运行多个网络应用程序。

传输协议
UDP:

  • 相当于发短信(有字数限制),不需要建立连接,效率较高但容易丢包
  • 数据报大小限制在64k内
    TCP:
  • 相当于打电话,需要建立连接,效率较低但数据传输安全。
  • 三次握手和四次分手。

    为什么要三次握手?
    如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。
    为什么要四次分手?
    A、B通信,如果三次分手可能会造成:A端已经释放了资源,但B端的资源没有释放。这就会造成我们对应资源的一种浪费。

视频是用UDP还是TCP?
可以先用TCP建立连接,再用UDP传输数据。视频掉帧影响不大

Socket套接字

  • 网络上两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端(接收数据的地方,接收IO请求的,可以读写数据的地方)称为一个Socket
  • 比如两台计算机互联,需要个接口,那么这个网线就是socket。两个程序互连,存在网络上,所以是一个逻辑上的概念,相当于虚拟接口。
  • Socket是Java中定义好的一个类,Java中使用Socket完成TCP程序的开发,使用它可以方便地建立可靠双向持续性(消息没发完不可能断)、点对点的通讯连接。
  • 在Socket的程序开发中,服务器端使用ServerSocket等待客户端的连接(ServerSocket相当于是硬件设备)
    在这里插入图片描述

基于TCP协议的Socket编程

进行网络通信时,Socket需要借助数据流来完成数据的传输工作。

  1. 通信双方需要建立连接
  2. 连接建立时双方存在主次之分。(先服务端再客户端)

在这里插入图片描述

Socket类
java.net.Socket
双端通信:
客户端向服务端发送数据

  • 先启动服务端,如果先启动客户端会报拒绝连接的错误
  • 如果启动两次服务端也会报错(地址被占用),因为端口只能绑定一次
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //Client.java
    // 创建客户端的套接字
    Socket client = new Socket("127.0.0.1",10000);
    //-------------向外进行输出----------------
    // 获取输出流对象
    OutputStream outputStream = client.getOutputStream();
    // 数据输出
    outputStream.write("hello java".getBytes());
    /**
    * 或者:
    * // 将输出流对象进行包装
    * DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
    * // 传输数据
    * dataOutputStream.writeUTF("hello,你好");
    */
    //--------------接受服务器端返回的消息----------------------
    InputStream inputStream = client.getInputStream();
    byte[] buf = new byte[1024];
    int length = inputStream.read(buf);
    System.out.println("服务端的响应数据是:"+new String(buf,0,length));

    //关闭流操作
    inputStream.close();
    outputStream.close();
    client.close();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    // Server.java
    // 创建servertsocket对象
    ServerSocket serverSocket = new ServerSocket(10000);
    // 获取服务器的套接字对象
    Socket server = serverSocket.accept();
    //-------------接受客户端的输入----------------
    // 获取输入流对象
    InputStream inputStream = server.getInputStream();
    byte[] buf = new byte[1024];
    int length = inputStream.read(buf);
    System.out.println("客户端传输的数据是:" +new String(buf,0,length));
    /**
    * 或者:
    * // 对输入流做包装,包装成DataInputStream
    * DataInputStream dataInputStream = new DataInputStream*(inputStream);
    * // 读取对应的数据
    * String s = dataInputStream.readUTF();
    * System.out.println(s);
    */
    //-------------向进客户端进行输出----------------
    OutputStream outputStream = server.getOutputStream();
    // 数据输出
    outputStream.write("hello from server".getBytes());

    // 关闭流对象
    outputStream.close();
    inputStream.close();
    server.close();
    serverSocket.close();

  1. 不能自动关闭流,必须手动进行关闭
  2. 当数据比较大的时候,需要添加输入输出流完成的标志
    shutdownOutput()或者shutdownInput()
  3. 如果传输的是对象,需要用到ObjectOutputStream,序列化

IDEA 实现Serializable接口时,自动生成UID值的方法。
File –> Settings –> Editor –> Inspections –> Java –> Serialization issues –> 勾选“Serializable class without ‘serialVersionUID’”
之后选择类名,按”alt + enter”,可以随机生成数字作为UID

基于UDP的网络编程(用的少)

  1. 通信双方不需要建立连接
  2. 通信双方完全平等

同样需要用到套接字,只不过变成了DatagramSocket(此类表示用于发送和接收数据报数据包的套接字)

DatagramPacket:该类表示数据报包。用于实现无连接传送服务。

UDPClient.java
/**
 * 客户端向服务端传送消息
 * 先后启动无顺序
 */
public class UDPClient {
    public static void main(String[] args) throws Exception {
        // 创建UDP通信的socket 自己对应的端口
        DatagramSocket datagramSocket = new DatagramSocket(10000);
        // 从控制台读取数据
        Scanner scanner = new Scanner(System.in);
        String str = scanner.nextLine();
        // 远程的地址和端口
        DatagramPacket datagramPacket = new DatagramPacket(str.getBytes(),str.length(),
                InetAddress.getByName("localhost"),10001);
        // 发送数据
        datagramSocket.send(datagramPacket);
        datagramSocket.close();
    }
}
UDPServer.java
public class UDPServer {
    public static void main(String[] args) throws Exception {
        DatagramSocket datagramSocket = new DatagramSocket(10001);
        //--------接收数据----------
        byte[] buf = new byte[1024];
        // 用来接收传输过来的数据
        DatagramPacket datagramPacket = new DatagramPacket(buf,buf.length);
        // 利用创建好的数据报包来接收数据
        datagramSocket.receive(datagramPacket);
        // 打印输出信息
        System.out.println(new String(datagramPacket.getData(),0,datagramPacket.getLength()));
        datagramSocket.close();
    }
}