JavaSE-网络编程


Java为网络提供了java.net包,该包下的URL和URLConnection等类提供了以编程方式访问Web服务的功能,而URLDecoder和URLEncoder则提供了普通字符串和application/x-www-form-urlencoded MIME字符串相互转换的静态方法

网络相关API

  • InetAddress:代表IP地址,Inet4Address、Inet6Address分别代表了IPV4和IPV6
InetAddress常用方法
常用方法 返回值类型 说明
getByName(String host) static InetAddress 根据IP地址字符串或主机名获得对应的IP地址对象
getByAddress(byte[] addr) static InetAddress 根据原始IP地址来获取对应的InetAddress对象
getLocalHost() static InetAddress 获得本地主机IP地址对象
getAllByName(String host) static InetAddress[] 给定主机的名称,根据系统上配置的名称服务返回其IP地址数组
getHostName() String 获取主机名
getHostAddress() String 获取IP地址字符串
getCanonicalHostName() String 获取此IP地址的完全限定域名
isReachable(int timeout) boolean 测试该地址是否能ping通
// 获取本机地址对象
InetAddress address1 = InetAddress.getLocalHost();
System.out.println(address1); // zhanghy/192.168.0.105
// 获取域名IP对象
InetAddress address2 = InetAddress.getByName("www.baidu.com");
System.out.println(address2); // www.baidu.com/110.242.68.3
// 获取公网IP对象
InetAddress address3 = InetAddress.getByName("110.242.68.3");
System.out.println(address3); // /110.242.68.3

System.out.println(address1.getHostName()); // zhanghy
System.out.println(address1.getHostAddress()); // 192.168.0.105
System.out.println(address2.isReachable(5000)); // true 判断在5秒内是否能PING通

基于UDP协议的网络编程

UDP协议是一种不可靠的网络协议,它在通信实例的两端各建立一个Socket,但这两个Socket之间并没有虚拟链路,这两个Socket只是发送、接收数据报的对象。Java提供了DatagramSocket对象作为基于UDP协议的Socket,使用DatagramPacket代表DatagramSocket发送、接收的数据报

UDP协议基础

UDP协议是英文User Datagram Protocal的缩写,即用户数据报协议,主要用来支持那些需要在计算机之间传输数据的网络连接。UDP协议从问世到至今已经被使用了很多年,虽然UDP协议目前应用不如TCP协议广泛,但UDP协议依然是一个非常实用和可行的网络传输层协议,尤其是在一些实时性很强的应用场景中,比如网络游戏、视频会议等,UDP协议的快速更具有独特的魅力
UDP协议是一种面向非连接的协议,面向非连接指的是在正式通信前不必与对方先建立连接,不管对方状态就直接发送。至于对方是否可以接收到这些数据内容,UDP协议无法控制,因此说UDP协议是一种不可靠的协议。UDP协议适用于一次只传送少量数据、对可靠性要求不高的应用环境
UDP协议直接位于IP协议之上,实际上IP协议属于OSI参考模型的网络层协议,而UDP协议和TCP协议都属于传输层协议
UDP协议的主要作用是完成网络数据流和网络报之间的转换——在信息的发送端,UDP协议将网络数据流封装成数据报,然后将数据报发送出去;在信息的接收端,UDP协议将数据报转换成实际数据内容

基于UDP协议的发送接收数据

Java使用DatagramSocket代表UDP协议的Socket,DatagramSocket本身只是码头,不维护状态,不能产生IO流,它的唯一作用就是接收和发送数据报.Java使用DatagramPacket代表数据报,DatagramSocket接收和发送的数据都是通过DatagramPacket对象完成的
DatagramSocket发送数据报时,DatagramSocket并不知道将该数据报发送到哪里,而是由DatagramPacket自身决定数据报的目的地

  • 使用DatagramSocket作为发送端和接收端的对象
    • public DatagramSocket():创建发送端的Socket对象,系统会将该对象绑定到本机默认IP地址,本机所有可用端口中随机选择的某个端口号
    • public DatagramSocket(int port):创建接收端的Socket对象并指定端口号(将本机对象绑定到本机默认IP地址/指定端口)
    • public DatagramSocket(int port,InetAddress Iaddr):创建一个DatagramSocket实例,并将该对象绑定到指定的IP地址和指定的端口号
    • public void send(DatagramPacket dp):发送数据包
    • public void receive(DatagramPacket dp) :接收数据包
  • 使用DatagramPacket封装数据包对象
    • public DatagramPacket(byte[] buf, int length, InetAddress address, int port):创建发送端数据包对象
    • public DatagramPacket(byte[] buf, int length):创建接收端的数据包对象
    • public DatagramPacket(byte[] buf, int offset, int length, InetAddress address, int port):创建发送端数据包对象(指定发送buf数组从offset开始,总共length个字节)
    • InetAddress getAddress():当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的IP地址
    • int getPort():当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的端口
    • SocketAddress getSocketAddress():当程序刚接收到一个数据报时,该方法返回该数据报的发送主机的SocketAddress

UDP的三种通信方式:

  • 单播:单台主机与单台主机之间的通信
  • 广播:当前主机与所在网络中的所有主机通信
    • 广播地址:255.255.255.255
    • 发送端发送的数据包的目的地写的是广播地址,且指定端口;本机所在网段的其他主机的程序只要匹配端口成功就可以收到消息了
  • 组播:当前主机与选定的一组主机的通信
    • 组播地址:224.0.0.0~239.255.255.255
    • 发送端的数据包的目的地是组播IP(例如:224.0.1.1,端口8888)
    • 接收端必须绑定该组播(224.0.1.1),端口还要对应发送端的目的端口8888,这样就能接收该组播消息
    • DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP
代码案例:使用UDP通信实现-发送消息/接收消息[一发一接]
public class Client {
    public static void main(String[] args) throws Exception {
        System.out.println("==============客户端启动=================");
        DatagramSocket socket = new DatagramSocket();
        byte[] buffer = "hello 不二橘子酱".getBytes();
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 8888);
        socket.send(packet);
        socket.close();
    }
}

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("==============服务端启动=================");
        DatagramSocket socket = new DatagramSocket(8888);
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        socket.receive(packet);
        String result = new String(buffer, 0, packet.getLength()); // 获取发送的内容
        String ip = packet.getSocketAddress().toString(); // /192.168.0.105:56762
        int port = packet.getPort(); // 56762
        socket.close();
    }
}
代码案例:使用UDP通信实现-发送消息/接收消息[多发多收]
public class Client {
    public static void main(String[] args) throws Exception {
        System.out.println("==============客户端启动=================");
        DatagramSocket socket = new DatagramSocket();
        Scanner sc = new Scanner(System.in);
        while (true) {
            String msg = sc.nextLine();
            if ("exit".equals(msg)) {
                System.out.println("离线成功!");
                socket.close();
                break;
            }
            byte[] buffer = msg.getBytes();
            DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getLocalHost(), 8888);
            socket.send(packet);
        }
    }
}

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("==============服务端启动=================");
        DatagramSocket socket = new DatagramSocket(8888);
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        while (true) {
            socket.receive(packet);
            String result = new String(buffer, 0, packet.getLength()); // 获取发送的内容
            System.out.println("收到了来自" + packet.getAddress() + ":" + packet.getPort() + "的消息:" + result);
        }
    }
}
代码案例:使用UDP通信实现-发送消息/接收消息[广播]
public class Client {
    public static void main(String[] args) throws Exception {
        System.out.println("==============客户端启动=================");
        DatagramSocket socket = new DatagramSocket();
        byte[] buffer = "hello 不二橘子酱".getBytes();
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("255.255.255.255"), 8888);
        socket.send(packet);
        socket.close();
    }
}

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("==============服务端启动=================");
        DatagramSocket socket = new DatagramSocket(8888);
        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        socket.receive(packet);
        String result = new String(buffer, 0, packet.getLength()); // 获取发送的内容
        socket.close();
    }
}
代码案例:使用UDP通信实现-发送消息/接收消息[组播]
public class Client {
    public static void main(String[] args) throws Exception {
        System.out.println("==============客户端启动=================");
        DatagramSocket socket = new DatagramSocket();
        byte[] buffer = "hello 不二橘子酱".getBytes();
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length, InetAddress.getByName("224.0.1.1"), 8888);
        socket.send(packet);
        socket.close();
    }
}

    public static void main(String[] args) throws Exception {
        System.out.println("==============服务端启动=================");
        MulticastSocket socket = new MulticastSocket(8888);
        // 把当前接收端加入到一个组播组中去,绑定对应的组播消息的组播IP
        // socket.joinGroup(InetAddress.getByName("224.0.1.1")); // 该方法JDK14开始过时
        socket.joinGroup(new InetSocketAddress(InetAddress.getByName("224.0.1.1"), 8888), NetworkInterface.getByInetAddress(InetAddress.getLocalHost()));

        byte[] buffer = new byte[1024 * 64];
        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        socket.receive(packet);
        String result = new String(buffer, 0, packet.getLength()); // 获取发送的内容
        System.out.println(result);
        socket.close();
    }
}

基于TCP协议的网络编程

TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在通信的两端之间形成网络虚拟链路.一旦建立了虚拟的网络链路,两端的程序就可以通过虚拟链路进行通信.Java对基于TCP协议的网络通信提供了良好的封装,Java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信

TCP协议基础

要使两台计算机彼此能进行通信,必须使两台计算机使用同一种"语言".IP协议负责将消息从一个主机传送到另一个主机,消息在传送的过程中被分割成一个个的小包
TCP协议被称作一种端对端的协议,这是因为它对两台计算机之间的链接起了重要作用——当一台计算机需要与另一台远程计算机连接时,TCP协议会让它们建立一个连接,用于发送和接收数据的虚拟链路

代码案例:TCP发送接收数据[一发一收]
public class Client {
    public static void main(String[] args) throws Exception {
        System.out.println("==============客户端启动=================");
        Socket socket = new Socket("127.0.0.1", 8888);
        OutputStream out = socket.getOutputStream();
        PrintStream ps = new PrintStream(out);
        ps.println("我是TCP的客户端,我已经与你对接");
        ps.flush();
    }
}

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("==============服务端启动=================");
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket socket = serverSocket.accept();
        InputStream in = socket.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        String msg;
        if ((msg = reader.readLine()) != null) {
            System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
        }
    }
}

代码案例:TCP发送接收数据[多发多收]
public class Client {
    public static void main(String[] args) throws Exception {
        System.out.println("==============客户端启动=================");
        Socket socket = new Socket("127.0.0.1", 8888);
        OutputStream out = socket.getOutputStream();
        PrintStream ps = new PrintStream(out);
        Scanner sc = new Scanner(System.in);
        while (true) {
            String msg = sc.nextLine();
            ps.println(msg);
            ps.flush();
        }
    }
}

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("==============服务端启动=================");
        ServerSocket serverSocket = new ServerSocket(8888);
        Socket socket = serverSocket.accept();
        InputStream in = socket.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(in));
        String msg;
        while ((msg = reader.readLine()) != null) {
            System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
        }
    }
}
代码案例:TCP发送接收数据[多线程多个客户端]
public class Client {
    public static void main(String[] args) {
        System.out.println("==============客户端启动=================");
        try {
            Socket socket = new Socket("127.0.0.1", 8888);
            OutputStream out = socket.getOutputStream();
            PrintStream ps = new PrintStream(out);
            Scanner sc = new Scanner(System.in);
            while (true) {
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

// 实现服务端可以同时处理多个客户端的消息
public class Server {
    public static void main(String[] args) {
        System.out.println("==============服务端启动=================");
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress()+"上线了!");
                new ServerReaderThread(socket).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ServerReaderThread extends Thread {
    private Socket socket;
    public ServerReaderThread(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream in = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            String msg;
            while ((msg = reader.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!");
        }
    }
}
代码案例:TCP发送接收数据[线程池]
public class Client {
    public static void main(String[] args) {
        System.out.println("==============客户端启动=================");
        try {
            Socket socket = new Socket("127.0.0.1", 8888);
            OutputStream out = socket.getOutputStream();
            PrintStream ps = new PrintStream(out);
            Scanner sc = new Scanner(System.in);
            while (true) {
                String msg = sc.nextLine();
                ps.println(msg);
                ps.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public class Server {
    private static ExecutorService pool = new ThreadPoolExecutor(3, 5, 6, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());

    public static void main(String[] args) {
        System.out.println("==============服务端启动=================");
        try {
            ServerSocket serverSocket = new ServerSocket(8888);
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println(socket.getRemoteSocketAddress() + "上线了!");
                Runnable target = new ServerReaderRunnable(socket);
                pool.execute(target);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ServerReaderRunnable implements Runnable {
    private Socket socket;
    public ServerReaderRunnable(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            InputStream in = socket.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            String msg;
            while ((msg = reader.readLine()) != null) {
                System.out.println(socket.getRemoteSocketAddress() + "说了:" + msg);
            }
        } catch (Exception e) {
            System.out.println(socket.getRemoteSocketAddress() + "下线了!");
        }
    }
}