【Java中的网络编程——超详细】

什么是网络编程?

定义与目的

网络编程的主要目的是实现不同计算机之间的数据交互和通信。通过编写代码,开发者可以创建网络连接,发送和接收数据包,实现网络通信的各种功能。这些功能包括但不限于网页浏览、邮件传输、文件传输、远程登录等。

基本原理

网络编程的基本原理是利用计算机网络的通信协议,将数据分成小的数据包进行传输。这些数据包通过网络中的节点和路由器进行传输和中转,最终到达目标计算机。在传输过程中,数据包会按照网络协议的规定进行封装和解封装,以确保数据的完整性和可靠性。


基本的通信架构 

在Java中,网络通信的基本通信架构主要涉及到两种形式:CS架构(Client/Server,客户端/服务器)和BS架构(Browser/Server,浏览器/服务器)。这两种架构为Java应用程序中的网络通信提供了不同的实现方式和应用场景。

1. CS架构(Client/Server)

CS架构是一种典型的软件系统架构,它通过将任务合理分配到客户端和服务器端,实现了软件的协同工作。在这种架构中,客户端负责与用户进行交互,并向服务器发送请求;服务器则负责处理这些请求,并返回相应的结果给客户端。

特点:

  • 客户端开发:在CS架构中,客户端通常需要由程序员进行开发,并需要用户在自己的计算机上安装。
  • 服务器开发:服务器端同样需要程序员进行开发,它负责处理来自客户端的请求,并提供相应的服务。
  • 网络通信:客户端和服务器之间通过网络通信协议(如TCP/IP)进行数据传输和通信。

应用场景:

  • 实时性要求较高或需要复杂交互的应用程序,如在线游戏、即时通讯软件等。
  • 需要保护数据安全和隐私的应用场景,因为客户端和服务器之间的通信可以加密。

2. BS架构(Browser/Server)

BS架构是一种基于Web的架构模式,它将传统的客户端软件替换为Web浏览器,用户只需通过浏览器即可访问服务器上的应用程序。

特点:

  • 客户端无需开发:在BS架构中,客户端通常是标准的Web浏览器,用户无需安装额外的软件即可访问服务器上的应用程序。
  • 服务器开发:服务器端需要程序员进行开发,它负责处理来自浏览器的请求,并返回相应的Web页面或数据。
  • 网络通信:浏览器和服务器之间通过HTTP等协议进行数据传输和通信。

应用场景:

  • Web应用程序,如电子商务网站、企业内部管理系统等。
  • 需要跨平台访问的应用程序,因为浏览器是跨平台的。

 网络通信的关键三要素

网络通信的关键三要素主要包括IP地址、端口号以及传输协议。这三个要素共同构成了网络通信的基础框架,确保了数据在网络中的准确传输和接收。

1. IP地址

  • 定义与功能IP地址是网络中设备的唯一标识,用于在网络中定位和识别各个设备。它类似于现实生活中的地址或电话号码,使得数据能够准确地发送到目标设备。
  • 分类IP地址主要分为IPv4和IPv6两种类型
      IPv4地址由32位二进制数组成,通常采用点分十进制表示法(如192.168.1.1);
      IPv6地址则更加庞大,由128位二进制数组成,采用冒分十六进制表示法(如        2001:0db8:85a3:0000:0000:8a2e:0370:7334)。
  • 特殊地址:包括回送地址(如127.0.0.1,代表本机地址)和广播地址(如X.X.X.255,用于向同一子网内的所有设备发送数据)。
  • 应用:通过IP地址,网络设备可以相互识别并进行数据交换。例如,在Web浏览中,用户的计算机通过IP地址与服务器建立连接,从而访问网页内容。
  • 补充:
InetAddress

InetAddress 类在 Java 中是用于表示 IP 地址的,无论是 IPv4 还是 IPv6 地址。这个类没有公共的构造方法,因为它包含的信息被认为是不可变的(immutable),即一旦创建了一个 InetAddress 实例,就不能更改其代表的 IP 地址。InetAddress 类提供了多种静态方法来获取和解析 IP 地址信息。 

示例 

import java.net.InetAddress;  
import java.net.UnknownHostException;  
  
public class InetAddressExample {  
    public static void main(String[] args) {  
        try {  
            // 通过主机名获取 InetAddress 实例  
            InetAddress addressByName = InetAddress.getByName("www.example.com");  
            System.out.println("Host Name: " + addressByName.getHostName());  
            System.out.println("Host Address: " + addressByName.getHostAddress());  
  
            // 通过 IP 地址字符串获取 InetAddress 实例  
            InetAddress addressByString = InetAddress.getByName("192.168.1.1");  
            System.out.println("Host Address from String: " + addressByString.getHostAddress());  
  
            // 获取本地主机的 InetAddress 实例  
            InetAddress localHost = InetAddress.getLocalHost();  
            System.out.println("Local Host Name: " + localHost.getHostName());  
            System.out.println("Local Host Address: " + localHost.getHostAddress());  
  
        } catch (UnknownHostException e) {  
            e.printStackTrace();  
        }  
    }  
}

2. 端口号

  • 定义与功能:端口号用于标识运行在计算机设备上的程序或进程。每个程序或进程在运行时都会占用一个或多个端口号,以便网络通信时能够准确找到对应的应用程序。
  • 分类:端口号被规定为一个16位的二进制数,范围从0~65535。
    0到1023为周知端口,被预先定义的知名应用占用(如HTTP占用80端口,FTP占用21端口);
    1024到49151为注册端口,分配给用户进程或某些特定应用程序;
    49152到65535为动态端口,一般不固定分配给某种进程,而是动态分配。
    注意:我们自己开发的程序一般使用注册端口,且在一个设备中,不能出现两个程序的端口号一样,否则出错。
  • 应用:在网络通信中,客户端和服务器通过指定的端口号进行连接和数据交换。例如,在Web服务中,服务器通常会在80端口上监听HTTP请求;而在FTP服务中,服务器则会在21端口上监听FTP请求。

3. 传输协议

  • 定义与功能:传输协议是网络通信中用于规定数据传输格式、顺序、错误检测和纠正等规则的集合。它确保了数据在网络中的可靠传输和接收。
  • 主要类型:常见的传输协议包括TCP(传输控制协议)和UDP(用户数据报协议)。TCP是一种面向连接的协议,提供可靠的数据传输服务;而UDP则是一种无连接的协议,提供快速但可能不可靠的数据传输服务。
  • 应用:不同的应用场景需要选择不同的传输协议。例如,在需要确保数据完整性和可靠性的场景中(如文件传输、金融交易等),通常会选择TCP协议;而在对实时性要求较高且可以容忍一定数据丢失的场景中(如在线视频、语音通话等),则可能会选择UDP协议。
TCP协议 
TCP通信过程详解

在Java中,TCP(Transmission Control Protocol,传输控制协议)通信的过程可以被生动地描述为一场精心策划的“对话”,其中涉及到了两个主要角色:客户端(Client)和服务器端(Server)。这个过程可以分为几个关键步骤,每个步骤都承载着特定的信息和任务,确保了数据能够在两者之间可靠地传输。

1. 握手建立连接

第一步:打招呼(客户端发起连接请求)

想象一下,客户端就像是一个初次来到陌生城市的人,它想要与服务器(这个城市中的某个特定建筑,比如一家图书馆)建立联系。于是,客户端向服务器发送了一个“你好,我想和你聊聊”的信号,这个信号就是TCP连接请求。这个过程被形象地称为“第一次握手”。

第二步:确认身份并邀请(服务器回复)

服务器收到客户端的请求后,它会确认自己的身份(确保自己是客户端想要连接的那个服务器),并发送一个“你好,我收到了你的请求,请继续”的回复。这个回复标志着服务器已经准备好与客户端进行通信,这个过程被称为“第二次握手”。

第三步:确认接收(客户端再次确认)

客户端收到服务器的回复后,它会再次发送一个“好的,我已经准备好开始对话了”的确认信息给服务器。这个确认信息确保了双方都已经为数据传输做好了准备,也标志着TCP连接的正式建立。这个过程就是“第三次握手”。

经过这三次握手,客户端和服务器之间就建立起了一条可靠的通信链路,就像两个人已经面对面坐好,准备开始一场深入的对话。

2. 数据传输

一旦连接建立,客户端和服务器就可以开始传输数据了。这就像两个人开始交谈,他们轮流说话,每个人都在等待对方说完后再继续。在TCP通信中,数据被分割成多个小的数据包进行传输,每个数据包都包含了序号,以确保接收方能够按照正确的顺序重新组合这些数据。

同时,TCP还提供了错误检测和重传机制。如果某个数据包在传输过程中丢失或损坏,接收方会发送一个错误消息给发送方,要求重新发送该数据包。这种机制确保了数据的完整性和可靠性。

3. 挥手断开连接

当数据传输完成后,客户端和服务器需要断开连接以释放资源。这个过程被形象地称为“四次挥手”。

第一次挥手:提出分手(客户端发送断开请求)

客户端会向服务器发送一个“我已经说完了,想结束对话”的请求。这就像一个人在交谈结束时告诉对方“我要走了”。

第二次挥手:同意分手但还有话说(服务器回复并等待)

服务器收到客户端的请求后,会回复一个“好的,我收到了你的请求,但请等一下,我还有一点话要说”。这个过程表示服务器已经同意断开连接,但可能还需要发送一些额外的数据给客户端。

第三次挥手:确认收到并说再见(服务器发送断开请求)

当服务器完成所有需要发送的数据后,它会向客户端发送一个“我也说完了,现在可以正式结束了”的请求。这个过程就像服务器在告诉客户端“我已经没有话要说了,我们可以正式分手了”。

第四次挥手:正式分手(客户端确认)

客户端收到服务器的断开请求后,会回复一个“好的,我也准备好了,正式分手吧”的确认信息。这个过程标志着TCP连接的正式断开。就像两个人在确认彼此都已经准备好离开后,正式结束了这场对话。

TCP客户端开发

Java中的Socket类来开发TCP客户端程序。

  1. Socket类
    Socket类是Java中用于表示TCP客户端和服务器端之间的连接的一个类。它封装了TCP连接的细节,提供了发送和接收数据的接口。
  2. 构造器
    public Socket(String host, int port)这个构造器用于创建一个新的套接字,连接到指定的主机(由IP地址或主机名指定)和端口号。一旦连接成功,就建立了一个到服务器的TCP连接。
  3. 方法
    public OutputStream getOutputStream()这个方法用于获取一个输出流,客户端可以通过这个输出流向服务器发送数据。
    public InputStream getInputStream(),这个方法用于获取一个输入流,客户端可以通过这个输入流从服务器接收数据。

代码演示

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

public class TcpClient {
    public static void main(String[] args) throws Exception {
            // 连接到服务器
            Socket socket = new Socket("localhost", 8888); // 假设服务器运行在localhost的8888端口

            // 获取输出流,用于发送数据到服务器
            OutputStream outputStream = socket.getOutputStream();
            PrintWriter printWriter = new PrintWriter(outputStream, true);
            printWriter.println("Hello, Server!"); // 发送消息到服务器

            // 获取输入流,用于接收服务器返回的数据
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String response = bufferedReader.readLine(); // 读取服务器响应

            // 输出服务器响应
            System.out.println("Server Response: " + response);

            // 关闭资源
            bufferedReader.close();
            printWriter.close();
            socket.close();
    }
}

 TCP服务端开发

ServerSocket 类用于服务器端来监听来自客户端的连接请求

ServerSocket 类的构造器和方法

构造器

  • public ServerSocket(int port):此构造器用于创建服务器套接字,并将其绑定到指定的端口号上。服务器套接字将等待来自客户端的连接请求。

方法

  • public Socket accept():此方法用于侦听并接受到此套接字的连接。此方法在连接到达之前一直处于阻塞状态。当连接到达时,它返回一个新的 Socket 对象,该对象用于与连接的客户端进行通信。

 代码演示

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class TcpServer {
    public static void main(String[] args) {
        int port = 8888; // 服务器监听的端口号
        try (ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("Server is running on port " + port);

            // 等待客户端连接
            Socket socket = serverSocket.accept(); // 此处会阻塞,直到有客户端连接
            System.out.println("Client connected!");

            // 创建输入流和输出流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);

            // 读取客户端发送的消息并回显
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                System.out.println("Received from client: " + line);
                printWriter.println("Echo: " + line); // 将接收到的消息回显给客户端
            }

            // 注意:在实际应用中,不应该依赖于客户端关闭连接来退出循环。
            // 这里只是为了简化示例而这样做。

        } catch (Exception e) {
            e.printStackTrace();
        }

        // 注意:在try-with-resources语句中,serverSocket会在离开try块时自动关闭。
        // 但是,对于socket,由于它是在循环外部创建的,并且我们需要等待客户端发送数据,
        // 所以我们不会在这里关闭它(在这个简单的示例中)。
        // 在实际应用中,你可能需要在另一个循环中处理多个客户端连接,并在适当的时机关闭每个socket。
    }
}
UDP协议 

DatagramSocket 类

DatagramSocket类用于表示一个UDP套接字,它可以是客户端套接字,也可以是服务器端套接字。客户端套接字用于发送和接收数据包,而服务器端套接字则绑定到一个特定的端口上,以便接收来自客户端的数据包。

  • 构造方法
    • DatagramSocket()创建一个客户端的DatagramSocket,系统会为其分配一个随机的端口号。
    • DatagramSocket(int port):创建一个服务器端的DatagramSocket,并绑定到指定的端口号上。
  • 主要方法
    • void send(DatagramPacket p):发送一个数据包。
    • void receive(DatagramPacket p):接收一个数据包。此方法会阻塞,直到接收到数据包为止。
DatagramPacket 类

DatagramPacket类用于表示一个数据包,它包含了要发送或接收的数据,以及发送方或接收方的地址和端口号。

  • 构造方法
    • DatagramPacket(byte[] buf, int length, InetAddress address, int port):创建一个用于发送的数据包,其中buf是包含要发送数据的字节数组,length是数据的长度,address是服务端的IP地址,port是服务端程序的端口号
    • DatagramPacket(byte[] buf, int length):创建一个用于接收的数据包,其中buf是接收数据的缓冲区,length是缓冲区的长度。
  • 其他方法
    • InetAddress getAddress():获取数据包的发送方或接收方的地址。
    • int getPort():获取数据包的发送方或接收方的端口号。
    • int getLength():获取实际接收到的字节数(仅对接收数据包有效)。

代码示例: 

客户端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UDPClient {
    public static void main(String[] args) throws Exception {
        //1.创建客户端对象(扔韭菜出去的人)
            DatagramSocket socket = new DatagramSocket() ;
            String message = "我是快乐的客户端";
            //调用了String的getBytes()方法,将字符串转换为一系列的字节,并将这些字节存储在字节数组中
            byte[] buffer = message.getBytes();

            InetAddress address = InetAddress.getByName("localhost");
            int port = 8888;
        //2.创建数据包对象封装要发出去的数据(创建一个韭菜盘子)
            DatagramPacket packet = new DatagramPacket(buffer,
                    buffer.length, address, port);
        //3.开始正式发送这个数据包的数据出去了。 (扔出韭菜)
            socket.send(packet);
            System.out.println("客户端数据发送完毕~~~");
        // 接收响应
            buffer = new byte[1024];
            packet = new DatagramPacket(buffer, buffer.length);
            socket.receive(packet);

            String response = new String(packet.getData(), 0, packet.getLength());
            System.out.println("Response from server: " + response);
        }
}

 服务端

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

public class UDPServer {
    public static void main(String[] args) throws Exception {
        System.out.println("-----服务端正式启动-----");
        //1.创建一个服务端对象(接韭菜的人)
        DatagramSocket socket = new DatagramSocket(8888);
        //2.创建一个数据包对象用于接收数据的。(韭菜盘子)
        byte[] buffer = new byte[1024];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

        System.out.println("Server is listening on port 8888...");
        //3.开始正式使用数据包来接收客户端发来的数据。(开始接收韭菜)
        socket.receive(packet);

        String received = new String(packet.getData(), 0, packet.getLength());
        System.out.println("Received: " + received);

        InetAddress address = packet.getAddress();
        int port = packet.getPort();
        String capitalized = received.toUpperCase();

        // 发送响应
        byte[] response = capitalized.getBytes();
        DatagramPacket responsePacket = new DatagramPacket(response, response.length, address, port);
        socket.send(responsePacket);
    }
}

以上代码只是一发一收,怎么实现多发多收呢? 

要实现UDP的多发多收,你需要创建一个循环来不断地接收数据包,而不是只接收一次。对于客户端,如果你想要发送多个数据包,你只需在发送第一个数据包后继续发送其他数据包即可。

UDP服务器端(多发多收)

import java.io.IOException;  
import java.net.DatagramPacket;  
import java.net.DatagramSocket;  
import java.net.InetAddress;  
  
public class UDPServer {  
    public static void main(String[] args) {  
        DatagramSocket socket = null;  
        try {  
            socket = new DatagramSocket(8888);  
            byte[] buffer = new byte[1024];  
  
            System.out.println("Server is listening on port 8888...");  
  
            while (true) { // 使用无限循环来持续接收数据包  
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);  
                socket.receive(packet); // 阻塞直到接收到数据包  
  
                String received = new String(packet.getData(), 0, packet.getLength());  
                System.out.println("Received: " + received);  
  
                InetAddress address = packet.getAddress();  
                int port = packet.getPort();  
  
                // 处理接收到的数据(例如,转换为大写)  
                String capitalized = received.toUpperCase();  
  
                // 发送响应  
                byte[] response = capitalized.getBytes();  
                DatagramPacket responsePacket = new DatagramPacket(response, response.length, address, port);  
                socket.send(responsePacket);  
            }  
        } catch (IOException e) {  
            e.printStackTrace();  
        } finally {  
            if (socket != null) {  
                socket.close();  
            }  
        }  
    }  
}

UDP客户端(多发)

import java.io.IOException;  
import java.net.DatagramPacket;  
import java.net.DatagramSocket;  
import java.net.InetAddress;  
  
public class UDPClient {  
    public static void main(String[] args) {  
        DatagramSocket socket = null;  
        try {  
            socket = new DatagramSocket();  
  
            String[] messages = {"Hello, UDP Server!", "This is another message.", "Final message."};  
            InetAddress address = InetAddress.getByName("localhost");  
            int port = 8888;  
  
            for (String message : messages) {  
                byte[] buffer = message.getBytes();  
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length, address, port);  
                socket.send(packet);  
  
                // 注意:在这个简单的客户端示例中,我们并没有等待服务器的响应。  
                // 如果你需要等待并处理响应,你需要编写额外的代码来接收响应数据包。  
  
                System.out.println("Sent: " + message);  
  
                // 这里可以添加一些延迟来模拟发送间隔,但在这个示例中我们省略了。  
            }  
  
            // 注意:在实际应用中,客户端通常也会监听来自服务器的响应。  
            // 但在这个简单的示例中,我们只是为了演示如何发送多个数据包而省略了接收响应的部分。  
  
        } catch (IOException e) {  
            e.printStackTrace();  
        } finally {  
            if (socket != null) {  
                socket.close();  
            }  
        }  
    }  
}

三要素之间的联系

1. IP地址与端口号的联系

  • 唯一标识与具体定位:IP地址作为网络设备的唯一标识,确保了数据能够准确地发送到目标设备。而端口号则进一步在目标设备上定位到具体的程序或进程,使得数据能够被正确的程序所接收和处理。
  • 协同工作:在网络通信中,IP地址和端口号通常是成对出现的。客户端在发起连接请求时,需要指定目标设备的IP地址和端口号,以便服务器能够准确地识别并响应请求。

2. IP地址与传输协议的联系

  • 数据传输的基础:IP地址提供了数据传输的起点和终点,而传输协议则规定了数据传输的方式和规则。无论是TCP还是UDP协议,都需要依赖于IP地址来实现数据的发送和接收。
  • 协议类型与IP地址的关系:不同的传输协议具有不同的特性和应用场景。例如,TCP协议提供可靠的数据传输服务,适用于需要确保数据完整性和可靠性的场景;而UDP协议则提供快速但可能不可靠的数据传输服务,适用于对实时性要求较高且可以容忍一定数据丢失的场景。在选择使用哪种传输协议时,需要考虑到IP地址所在的网络环境、数据传输的需求以及安全性等因素。

3. 端口号与传输协议的联系

  • 数据传输的入口与出口:端口号作为程序或进程的标识,在网络通信中充当了数据传输的入口和出口。不同的程序或进程通常会使用不同的端口号来区分彼此,以便在网络中实现数据的独立传输和处理。

  • 22
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值