前言:在当今数字化时代,网络编程已成为软件开发者必备的一项技能。 

目录

一、网络编程基础概念

二、简单的 TCP 客户端 - 服务端通信示例

服务端代码

客户端代码

三、简单的 UDP 通信示例

服务端代码

客户端代码

四、多线程 TCP 服务端

五、Java NIO 非阻塞 IO

简单的非阻塞 TCP 服务端示例

总结


一、网络编程基础概念

在开始我们的网络编程之旅前,先了解一下网络编程的基础概念。网络通信主要基于 TCP/IP 协议,它包含多个层次的协议:

  1. 传输层协议 :TCP(面向连接、可靠传输)和 UDP(无连接、不可靠传输但低延迟)是最关键的两个协议。TCP 好比快递专车,先规划好路线,每送一个包裹都会确认对方收到,保证数据安全准确到达;UDP 则像群发短信,只管发送,不确认对方是否收到,虽然可靠性不高,但速度快,适合对实时性要求高的场景,如直播、在线游戏。

  2. 应用层协议 :HTTP、HTTPS 等是我们日常开发中常用的协议。HTTP 是浏览器和服务器之间进行数据传输的语言,HTTPS 是加密版的 HTTP,更安全。

在网络通信中,有两个主要角色:服务端和客户端。服务端就像一个商店,负责提供服务,放在那里等待顾客上门;客户端就是顾客,主动联系服务端寻求服务,比如我们用手机打开网页,手机就是客户端,联系服务器请求网页内容。

二、简单的 TCP 客户端 - 服务端通信示例

服务端代码

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

public class SimpleTCPServer {
    public static void main(String[] args) {
        try {
            // 创建服务端套接字,监听 12345 端口,这相当于服务端商店拉了一面大旗,上面写着 “我在这儿等你,端口是 12345”
            ServerSocket serverSocket = new ServerSocket(12345);
            System.out.println("服务端已启动,等待客户端连接...");

            // 阻塞等待客户端连接,服务端就一直在这儿等着,直到有客户端来敲门
            Socket socket = serverSocket.accept();
            System.out.println("客户端已连接");

            // 获取输入流,读取客户端消息,服务端伸出手接收客户端传来的消息
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String clientMsg = in.readLine();
            System.out.println("收到客户端消息:" + clientMsg);

            // 获取输出流,向客户端发送消息,服务端也给客户端回复消息
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            out.println("服务端已收到消息,欢迎连接!");

            // 关闭资源,用完的东西要收拾好,不然会占用空间
            in.close();
            out.close();
            socket.close();
            serverSocket.close();
        } catch (IOException e) {
            // 如果过程中有啥问题,就直接打印错误信息,方便我们检查问题出在哪
            e.printStackTrace();
        }
    }
}

客户端代码

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

public class SimpleTCPClient {
    public static void main(String[] args) {
        try {
            // 创建客户端套接字,连接到本地主机的 12345 端口,客户端就是要找服务端要东西的人,根据服务端的地址和端口号去找服务端
            Socket socket = new Socket("localhost", 12345);

            // 获取输出流,向服务端发送消息,客户端说 “服务端啊,我有个消息要给你”
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
            out.println("你好,服务端!");

            // 获取输入流,读取服务端响应,客户端静静等待服务端回话
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String serverMsg = in.readLine();
            System.out.println("收到服务端消息:" + serverMsg);

            // 关闭资源,用完后要把资源释放掉
            in.close();
            out.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在这个简单示例中,我们成功实现了基于 TCP 协议的服务端和客户端通信。服务端启动后耐心聆听,客户端主动联系,双方通过输入输出流就像写信一样进行文字交流。

三、简单的 UDP 通信示例

UDP 是一种无连接的通信方式,适用于对实时性要求较高的场景。

服务端代码

import java.net.*;

public class SimpleUDPServer {
    public static void main(String[] args) {
        try {
            // 创建服务端套接字,绑定到 12345 端口,服务端就在这儿等着接收消息
            DatagramSocket serverSocket = new DatagramSocket(12345);
            System.out.println("UDP 服务端已启动,等待接收数据...");

            // 创建缓冲区,用于接收数据,这就像服务端准备了一个大袋子,用来装收到的消息
            byte[] buffer = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);

            // 接收数据,服务端就等着消息飞过来,装到袋子里
            serverSocket.receive(receivePacket);
            String clientMsg = new String(buffer, 0, receivePacket.getLength());
            System.out.println("收到客户端消息:" + clientMsg);

            // 获取客户端地址和端口,用于回传数据,服务端记下是谁发来的消息,从哪发来的,好给人家回信
            InetAddress clientAddress = receivePacket.getAddress();
            int clientPort = receivePacket.getPort();

            // 回传数据给客户端,服务端也把消息发回去
            String responseMsg = "UDP 服务端已收到消息";
            byte[] responseBuffer = responseMsg.getBytes();
            DatagramPacket sendPacket = new DatagramPacket(responseBuffer, responseBuffer.length, clientAddress, clientPort);
            serverSocket.send(sendPacket);

            // 关闭套接字,用完之后要关闭,不然会一直占着这个端口,别人就没法用了
            serverSocket.close();
        } catch (SocketException e) {
            System.out.println("Socket 创建失败");
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端代码

import java.net.*;

public class SimpleUDPClient {
    public static void main(String[] args) {
        try {
            // 创建客户端套接字,客户端准备发消息啦
            DatagramSocket clientSocket = new DatagramSocket();

            // 准备要发送的数据,客户端把想说的话写好
            String sendMsg = "你好,UDP 服务端!";
            byte[] sendBuffer = sendMsg.getBytes();

            // 创建数据报包,包含要发送的数据、目标地址和端口,把消息装进信封,写上地址和门牌号(端口号)
            InetAddress serverAddress = InetAddress.getLocalHost();
            DatagramPacket sendPacket = new DatagramPacket(sendBuffer, sendBuffer.length, serverAddress, 12345);

            // 发送数据,把信封扔进邮箱,等着邮递员(网络)去送
            clientSocket.send(sendPacket);

            // 准备接收服务端返回的数据,客户端也准备一个袋子,等着服务端回的消息
            byte[] receiveBuffer = new byte[1024];
            DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);

            // 接收数据,看看服务端回了啥
            clientSocket.receive(receivePacket);
            String responseMsg = new String(receiveBuffer, 0, receivePacket.getLength());
            System.out.println("收到服务端消息:" + responseMsg);

            // 关闭套接字,用完之后关闭,不然资源会被占着
            clientSocket.close();
        } catch (SocketException e) {
            System.out.println("Socket 创建失败");
            e.printStackTrace();
        } catch (UnknownHostException e) {
            System.out.println("未知主机");
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

UDP 通信就像朋友之间互相扔纸飞机,不需要提前建立稳定路线,直接把消息写在飞机上,扔过去就完事儿了,速度快,但偶尔会丢消息。

四、多线程 TCP 服务端

在实际应用中,服务端通常需要同时处理多个客户端连接。通过多线程技术,可以实现这一功能。

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

public class MultiThreadTCPServer {
    public static void main(String[] args) {
        try {
            // 创建服务端套接字,监听 12345 端口,服务端开张营业
            ServerSocket serverSocket = new ServerSocket(12345);
            System.out.println("多线程服务端已启动,等待客户端连接...");

            while (true) {
                // 阻塞等待客户端连接,服务端就一直在这儿等着,来一个客户接待一个
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端已连接");

                // 为每个客户端创建一个线程,服务端给每个客户安排一个服务员(线程),专门负责这个客户
                ClientHandler clientHandler = new ClientHandler(socket);
                new Thread(clientHandler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 客户端处理类
    static class ClientHandler implements Runnable {
        private Socket socket;

        public ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                // 获取输入流,读取客户端消息,服务员仔细听客户说的话
                BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String clientMsg = in.readLine();
                System.out.println("收到客户端消息:" + clientMsg);

                // 获取输出流,向客户端发送消息,服务员给客户回复消息
                PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
                out.println("服务端已收到消息,欢迎连接!");

                // 关闭资源,招待完客户之后,要收拾好桌子(释放资源)
                in.close();
                out.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

这个多线程服务端就像一个餐厅,可以同时服务多位客户,每个客户都有一个专属服务员(线程),保障服务不断。

五、Java NIO 非阻塞 IO

Java NIO(New IO)从 Java 1.4 版本开始引入,提供了非阻塞 IO 和多路复用的功能,极大地提高了网络编程的效率和可扩展性。

简单的非阻塞 TCP 服务端示例

import java.io.*;
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;

public class NonBlockingTCPServer {
    public static void main(String[] args) {
        try {
            // 创建选择器,这就像一个超级管家,能同时盯着多个门口,看有没有客户来
            Selector selector = Selector.open();

            // 创建服务端通道,绑定到 12345 端口,服务端开了一扇门,门牌号是 12345
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.bind(new InetSocketAddress(12345));
            serverSocketChannel.configureBlocking(false);

            // 将服务端通道注册到选择器,监听接受事件,告诉管家,这扇门要是有人敲,就提醒我
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("非阻塞服务端已启动,等待客户端连接...");

            while (true) {
                // 选择一组键集,管家开始巡逻,查看有哪些事儿需要处理
                selector.select();

                // 获取选择键集合的迭代器,这就像管家拿着一摞需要处理的事务清单
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();

                    // 判断是否是可接受事件,看看是不是新客户敲门了
                    if (key.isAcceptable()) {
                        // 接受新的客户端连接,开门迎接新客户
                        ServerSocketChannel server = (ServerSocketChannel) key.channel();
                        SocketChannel socketChannel = server.accept();
                        socketChannel.configureBlocking(false);

                        // 注册到选择器,监听读事件,把客户安排好,告诉管家,要是这个客户有消息发过来,你也提醒我
                        socketChannel.register(selector, SelectionKey.OP_READ);
                        System.out.println("一个客户端已连接");
                    }

                    // 判断是否是可读事件,看看是不是客户发消息过来了
                    else if (key.isReadable()) {
                        // 读取数据,查看客户发了啥消息
                        SocketChannel socketChannel = (SocketChannel) key.channel();
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        int bytesRead = socketChannel.read(buffer);

                        if (bytesRead > 0) {
                            buffer.flip();
                            byte[] data = new byte[buffer.limit()];
                            buffer.get(data);
                            String clientMsg = new String(data).trim();
                            System.out.println("收到客户端消息:" + clientMsg);

                            // 发送响应,回复客户消息
                            String response = "服务端已收到消息";
                            ByteBuffer outBuffer = ByteBuffer.wrap(response.getBytes());
                            socketChannel.write(outBuffer);
                        }

                        // 如果读取到 -1,表示连接已关闭,客户走了,把相关东西收拾一下
                        else if (bytesRead == -1) {
                            socketChannel.close();
                        }
                    }

                    // 移除已处理的键,把处理完的事务从清单上划掉
                    iterator.remove();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

总结

Java 网络编程就像在数字世界里搭建通信桥梁,从基础的 TCP/UDP 通信到多线程服务端,再到 Java NIO 的非阻塞 IO,为开发者提供了丰富的工具和方法。通过本文的示例和讲解,希望能帮助你更好地理解和掌握 Java 网络编程,这样你就能在网络世界的海洋里自由航行啦。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐