跳过正文
  1. 文章/
  2. Java/
  3. JavaSE/
  4. JavaSE高级/

5、网络编程

·4732 字·10 分钟· loading · loading · ·
Java JavaSE JavaSE高级
GradyYoung
作者
GradyYoung
JavaSE高级 - 点击查看当前系列文章
§ 5、网络编程 「 当前文章 」

实现网络通信需要解决的两个问题
#

  1. 如何准确地定位网络上一台或多台主机;并且定位主机上的特定的应用?
  2. 找到主机后如何可靠高效地进行数据传输?

网络通信的两个要素
#

  1. IP和端口号,解决上面问题1
  2. 网络通信协议,例如TCP/IP参考模型(应用层、传输层、网络层、物理+数据链路层),解决上面问题2

通信要素一:IP和端口号
#

IP
#

  • IP:唯一的标识 Internet 上的计算机(通信实体)
  • 在Java中使用InetAddress类代表IP
  • IP分类:IPv4和 IPv6
  • 域名: www.baidu.comwww.mi.comwww.sina.comwww.jd.com
    • 域名解析:域名容易记忆,当在连接网络时输入一个主机的域名后,域名服务器(DNS)负责将域名转化成IP地址,这样才能和主机建立连接。
  • 本地回路地址:127.0.0.1对应着localhost
InetAddress类
#
  • 此类的一个对象就代表着一个具体的IP地址,没有公共的构造方法,需要使用以下静态方法实例化
    • public static InetAddress getLocalHost()
    • public static InetAddress getByNames(String host)
  • 常用方法
    • public String getHostAddress():返回IP地址字符串
    • public String getHostName():返回此IP地址主机名
    • public boolean isReachable(int timeout):测试是否可以连通此地址
//1.获取本机地址ip对象
InetAddress ip1 = InetAddress.getLocalHost();
System.out.println(ip1.getHostName());//获取主机名字
System.out.println(ip1.getHostAddress());//获取ip地址
//2.获取域名ip对象
InetAddress ip2 = InetAddress.getByName("www.baidu.com");
System.out.println(ip2.getHostName());//获取域名
System.out.println(ip2.getHostAddress());//获取域名的ip地址
//3.获取公网对象
InetAddress ip3 = InetAddress.getByName("112.80.248.76");
System.out.println(ip3.getHostName());//获取公网名字
System.out.println(ip3.getHostAddress());//获取公网ip地址
//判断网络是否能连接通信 ping 5s之前测试是否能通过
System.out.println(ip3.isReachable(5000));//通过会返回true

端口号
#

  • 正在计算机上运行的进程。

  • 要求:不同的进程不同的端口号

  • 范围:被规定为一个 16 位的整数0~65535

  • 端口号与IP地址的组合得出一个网络套接字:Socket

通信要素二:网络通信协议
#

分层模型
#

说明: untitle.png

TCP和UDP
#

  • TCP协议
    • 使用TCP协议前,必须先建立TCP连接,形成数据传输通道
    • 传输前,采用三次握手方式,点对点通信,可靠
    • TCP协议进行通信的两个进程称为:客户端服务端
    • 在连接中可进行大数据量传输
    • 传输完毕,需要释放建立的连接效率低
    • 使用场景:看重数据完整性,许多高级协议都是建立在TCP协议之上的,例如HTTP、SMTP等。
  • UDP协议
    • 将数据源IP、目的地IP、端口封装成数据报,不需要建立连接
    • 每个数据报大小限制在64K
    • 发送时不管对方是否准备好,接收方收到也不确认,不可靠
    • 可以广播发送
    • 发送数据结束无需释放资源效率高
    • 使用场景:传输的数据通常是能容忍丢失的,例如一些语音视频通信的应用会选择UDP 协议。

TCP
#

TCP三次握手和四次挥手
#

  • TCP的三次握手和四次挥手实质就是TCP通信的连接和断开。

    • 三次握手:为了对每次发送的数据量进行跟踪与协商,确保数据段的发送和接收同步,根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系,并建立虚连接。
    • 四次挥手:即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。

img

三次握手
#
  • 第1次握手:客户端发送一个带有SYN(synchronize)标志的数据包给服务端;
    • 客户端发送建立TCP连接的请求报文,其中报文中包含seq序列号,是由发送端随机生成的,并且将报文中的SYN字段置为1,表示需要建立TCP连接。(SYN=1seq=xx为随机生成数值)
  • 第2次握手:服务端接收成功后,回传一个带有SYN/ACK标志的数据包传递确认信息,表示我收到了;
    • 服务端回复客户端发送的TCP连接请求报文,其中包含seq序列号,是由回复端随机生成的,并且将SYN置为1,而且会产生ACK字段,ACK字段数值是在客户端发送过来的序列号seq的基础上加1进行回复,以便客户端收到信息时,知晓自己的TCP建立请求已得到验证。(SYN=1ACK=x+1seq=yy为随机生成数值)这里的ack加1可以理解为是确认和谁建立连接
  • 第3次握手:客户端再回传一个带有ACK标志的数据包,表示我知道了,握手结束。
    • 客户端收到服务端发送的TCP建立验证请求后,会使自己的序列号加1表示,并且再次回复ACK验证请求,在服务端发过来的seq上加1进行回复。(SYN=1ACK=y+1seq=x+1

SYN标志位数置1,表示建立TCP连接;ACK标志表示验证字段。

四次挥手
#

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

  • 第1次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入FIN_WAIT_1状态;
    • 客户端发送断开TCP连接请求的报文,其中报文中包含seq序列号,是由发送端随机生成的,并且还将报文中的FIN字段置为1,表示需要断开TCP连接。(FIN=1seq=xx由客户端随机生成)
  • 第2次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),服务端进入CLOSE_WAIT状态;
    • 服务端会回复客户端发送的TCP断开请求报文,其包含seq序列号,是由回复端随机生成的,而且会产生ACK字段,ACK字段数值是在客户端发过来的seq序列号基础上加1进行回复,以便客户端收到信息时,知晓自己的TCP断开请求已经得到验证。(FIN=1ACK=x+1seq=yy由服务端随机生成)
  • 第3次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入LAST_ACK状态;
    • 服务端在回复完客户端的TCP断开请求后,不会马上进行TCP连接的断开,服务端会先确保断开前,所有传输到A的数据是否已经传输完毕,一旦确认传输数据完毕,就会将回复报文的FIN字段置1,并且产生随机seq序列号。(FIN=1ACK=x+1seq=zz由服务端随机生成)
  • 第4次挥手:客户端收到FIN后,客户端进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,服务端进入CLOSED状态,完成四次挥手。
    • 客户端收到服务端的TCP断开请求后,会回复服务端的断开请求,包含随机生成的seq字段和ACK字段,ACK字段会在服务端的TCP断开请求的seq基础上加1,从而完成服务端请求的验证回复。(FIN=1ACK=z+1seq=hh为客户端随机生成) 至此TCP断开的4次挥手过程完毕

其中:FIN标志位数置1,表示断开TCP连接。

ServerSocket
#

  • 构造方法
    • ServerSocket(int port) :创建绑定到特定端口的服务器套接字
    • ServerSocket(int port, int backlog):利用指定的 backlog 创建服务器套接字并将其绑定到指定的本地端口号
    • ServerSocket(int port, int backlog, InetAddress address):使用指定的端口、侦听 backlog 和要绑定到的本地 IP 地址创建服务器
    • ServerSocket():创建非绑定服务器套接字。
  • 常用方法
    • getLocalPort():返回此套接字在其上侦听的端口
    • accept():侦听并接受到此套接字的连接
    • setSoTimeout(int timeout):指定超时值
    • bind(SocketAddress host, int backlog):将 ServerSocket 绑定到特定地址

Socket
#

客户端要获取一个 Socket 对象通过实例化 ,而服务器端获得一个 Socket 对象是通过accept()方法的返回值

  • 构造方法(Socket 构造方法执行完成后,并不只实例化一个 Socket 对象,其会尝试连接到指定的服务器和端口)
    • Socket(String host, int port) :创建一个流套接字并将其连接到指定主机上的指定端口号
    • Socket(InetAddress host, int port):创建一个流套接字并将其连接到指定 IP 地址的指定端口号
    • Socket(String host, int port, InetAddress localAddress, int localPort):创建一个指定主机和端口的套接字并将其连接到指定远程主机上的指定远程端口
    • Socket(InetAddress host, int port, InetAddress localAddress, int localPort):创建一个指定ip和端口的套接字并将其连接到指定远程地址上的指定远程端口
    • Socket():通过系统默认类型的 SocketImpl 创建未连接套接字
  • 常用方法
    • connect(SocketAddress host, int timeout):将此套接字连接到服务器,并指定一个超时值
    • getInetAddress():返回套接字连接的地址
    • getPort():返回此套接字连接到的远程端口
    • getLocalPort():返回此套接字绑定到的本地端口
    • getRemoteSocketAddress():返回此套接字连接的端点的地址,如果未连接则返回 null
    • getInputStream():返回此套接字的输入流
    • getOutputStream():返回此套接字的输出流
    • close():关闭此套接字

TCP Demo
#

服务端
#
public static void main(String[] args) throws Exception{
    //1、创建服务端管道
    ServerSocket serverSocket = new ServerSocket(8080);
    //2、阻塞等待客户端连接
    Socket accept = serverSocket.accept();
    System.out.println("有客户端连接:" + accept.getRemoteSocketAddress());
    //3、从管道获取输入流,接收客户端消息
    InputStream inputStream = accept.getInputStream();
    //4、升级输入流为缓冲流
    BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
    //5、逐行读取数据
    String s;
    while ((s = bufferedReader.readLine()) != null){
        System.out.println("客户端说:" + s);
    }
    bufferedReader.close();
}
客户端
#
public static void main(String[] args) throws Exception{
    //1、创建客户端管道
    Socket socket = new Socket("127.0.0.1",8080);
    //2、从管道获取输出流,发送消息给服务端
    OutputStream outputStream = socket.getOutputStream();
    //3、升级输出流为打印流
    PrintStream printStream = new PrintStream(outputStream);
    //4、发送消息
    printStream.println("你好呀,服务端!!!");
    printStream.flush();
    printStream.close();
}

UDP
#

DatagramSocket
#

  • 构造方法
    • DatagramSocket():绑定到本地地址和一个随机的端口号
    • DatagramSocket(int port):绑定本地地址和一个特定的端口号
    • DatagramSocket(int port, InetAddress iad):绑定到特定的端口号及指定地址
    • DatagramSocket(SocketAddress sad):绑定指定地址和随机端口号
  • 常用方法
    • close():关闭套接字
    • recevie(DatagramPacket dp):接受数据报
    • send(DatagramPacket dp):发送数据报

DatagramPacket
#

java.net包中的 DatagramPacket 类用来表示数据报包,数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。

  • 接收包,(用来接收数据)
    • DatagramPacket(byte[] buf, int length):用来接受长度为length的buf数据(即数据存于字节数组buf中)
  • 发送包(封装数据、目的ip、目的端口,用来发送数据)
    • DatagramPacket(byte[] buf, int length, InetAddress address, int port):将length长的buf数据发送到指定的地址的端口号处
    • DatagramPacket(byte[] buf, int length, SocketAddress address):将length长的buf数据发送到指定的套接字地址处

UDP Demo
#

服务端
#
public static void main(String[] args) throws Exception{
    //1、创建UDP对象,自定义端口
    DatagramSocket datagramSocket = new DatagramSocket(8080);
    //2、创建容器接收数据
    byte[] bytes = new byte[1024];
    //3、创建接收数据包对象,将数据接收到bytes中
    DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length);
    while (true){
        //4、接收数据
        datagramSocket.receive(datagramPacket);
        SocketAddress address = datagramPacket.getSocketAddress();
        //5、获取接收数据的长度
        int length = datagramPacket.getLength();
        System.out.println("收到数据:" + new String(bytes,0,length) + ",来自:" + address);
    }
}
客户端
#
public static void main(String[] args) throws Exception{
    //1、创建UDP对象
    DatagramSocket datagramSocket = new DatagramSocket();
    //发送五次数据
    for (int i = 0; i < 5; i++) {
        String s = "来自客户端的数据,第" + i + "次";
        byte[] bytes = s.getBytes(StandardCharsets.UTF_8);
        //2、创建发送数据包对象
        DatagramPacket datagramPacket = new DatagramPacket(bytes,bytes.length, InetAddress.getByName("127.0.0.1"),8080);
        //3、发送数据
        datagramSocket.send(datagramPacket);
    }
    //4、关闭资源
    datagramSocket.close();
}
JavaSE高级 - 点击查看当前系列文章
§ 5、网络编程 「 当前文章 」