java 网络编程

java 网络编程

1.0 基本通信架构

基本的通信架构有两种,一种是CS架构,即客户端服务端架构,另一种是BS架构,即浏览器服务端架构

1.1 ip地址

java 中使用InetAddress 代表ip地址,常用的方法有:

image-20250727213018883

下面是方法使用

package com.net.test;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class IpTest {
    public static void main(String[] args) throws Exception {
        InetAddress ip=InetAddress.getLocalHost();
        System.out.println(ip.getHostAddress());//获得ip地址
        System.out.println(ip.getHostName());//获得主机名
        InetAddress.getByName("baidu.com");//根据ip地址或者域名获得InetAddress对象
        System.out.println();
        //判断当前书籍是否可以和当前对象对应的服务器联通,传入的时间为连接尝试时间,单位为毫秒
        System.out.println(ip.isReachable(1000));
        System.out.println();
    }
}

1.2 端口

端口是程序进行网络通信的出入口,被规定为一个16位的二进制,范围是0-65535

分类:

周知端口:0-1023 被预先定义的指明应用占用

注册端口:1024-49151 分配给用户进程或者某些应用程序

动态端口: 49152 到65535 动态进行分配

我们自己的程序一般使用注册端口

一个设备中不能出现两个使用同一端口的程序

1.3 UDP通信

在java中,使用DatagramSocket进行udp通信

image-20250727223655766

image-20250727223736184

image-20250727223808467

下面是UDP通信中创建客户端并发送数据的例子

客户端

package com.net.test;

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

public class Client {
    public static void main(String[] args) throws Exception {
        DatagramSocket client=new DatagramSocket();
        byte [] data= "hello".getBytes();
        DatagramPacket packet=new DatagramPacket(data,data.length,
                InetAddress.getLocalHost(),6666);
        client.send(packet);
        System.out.println("发送完毕");
        client.close();
    }
}

其中DatagramPacket 是我们要存储数据的数据包对象 其中有参构造函数的几个参数在这里进行解释:

第一个参数是一个字节数组,存储着我们要发送的数据

第二个参数是数据发送长度,代表着我们要将第一个字节数组的多少数据发送给对方

第三个参数是一个InetAddress对象,代表我们发送数据的对象,我们可以用InetAddress.getByName方法获得

目标对象

第四个参数是我们发送的端口,有了对象我们要指明发送给对象的哪个端口

定义完成后使用send方法发送数据包

服务端:

package com.net.test;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class Server {
    public static void main(String[] args) throws Exception {
        DatagramSocket server = new DatagramSocket(6666);
        byte [] data= new byte[1024*64];//UDP发送数据一次最大64KB
        DatagramPacket packet = new DatagramPacket(data,data.length);
        server.receive(packet);
        String rs=new String(data,0,packet.getLength());
        System.out.println(rs);
        System.out.println(packet.getAddress());
        System.out.println(packet.getPort());
        server.close();
    }
}

这是一个构建服务端的简单程序,现在进行解释,首先通过DatagramSoket 对象代表服务端,不过这时候要指定

端口了,不能再随机分配,因为之前客户端的端口是指定了发送给目标ip的指定端口了,接着创建字节数组来接受

数据,创建DatagramPacket来接受数据包,字节数组用来接受传输的数据,DatagramPacket不光需要接受传输

的数据,还需要接受发送过来的附加信息,比如发送客户端的ip地址,端口等信息,我们再客户端发送

DatagramPacket也是一样的原因,可以看到我们后续的就用到了附加的信息

当我们启动服务端时,在遇到 server.receive(packet); 时产生阻塞,直到接受到数据后才继续执行

执行的输出结果

hello
/198.18.0.1
51573

需要注意我们缓冲区大小设置的问题,如果我们传入的数据包大于我们接收时设置的缓冲区大小,那么超出的数据

就会丢失

1.4 多发多收

服务端:

package com.net.test;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;

public class Server {
    public static void main(String[] args) throws Exception {
        DatagramSocket server = new DatagramSocket(6666);
        while (true) {
            byte [] data= new byte[1024*64];//UDP发送数据一次最大64KB
            DatagramPacket packet = new DatagramPacket(data,data.length);
            server.receive(packet);
            String rs=new String(data,0,packet.getLength());
            if(rs.equals("quit")){
                System.out.println("接收完毕,退出");
                break;
            }
            else {
                System.out.println(rs);
            }
        }
        server.close();
    }
}

客户端:

package com.net.test;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws Exception {
        DatagramSocket client=new DatagramSocket();
        Scanner sc =new Scanner(System.in);
        while (true) {
            System.out.println("输入:");
            String msg=sc.nextLine();
            byte [] data= msg.getBytes();
            DatagramPacket packet=new DatagramPacket(data,data.length,
                    InetAddress.getLocalHost(),6666);
            client.send(packet);
            if(msg.equals("quit")){
                System.out.println("所有数据已发送完成");
                break;
            }
            System.out.println("已发送");
        }
        client.close();
    }
}

1.5 TCP通信

在java中,使用Socket类作为客户端进行tcp通信

image-20250728150945916

package com.net.test;

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws Exception {
        Socket client =new Socket("127.0.0.1",6666);
        OutputStream ou=client.getOutputStream();
        DataOutputStream dos=new DataOutputStream(ou);
        dos.writeUTF("你好你好");
        dos.close();
        client.close();
    }
}

使用ServerSocket作为服务端

image-20250728154801567

package com.net.test;

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

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("启动成功");
        ServerSocket server=new ServerSocket(8888);
        Socket serversocket=server.accept();
        DataInputStream din=new DataInputStream(serversocket.getInputStream());
        String str=din.readUTF();
        System.out.println(str);
        din.close();
        server.close();
    }
}

我们使用高级管道DataInputStreamDataOutputStream进行消息的接受和发送,在服务端中,我们通过

ServerSocket类来设置端口并接受连接请求,当连接成功后也就是server.accept();顺利执行后会返回一个

Socket对象,作为管道的一端,然后利用Socket对象进行数据传输

1.6 TCP通信实现多发多收

服务端:

package com.net.test;

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

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("启动成功");
        ServerSocket server=new ServerSocket(8888);
        Socket serversocket=server.accept();
        DataInputStream din=new DataInputStream(serversocket.getInputStream());
        while (true) {
            String str=din.readUTF();
            if(str.equals("quit")){
                System.out.println("传输结束,关闭管道");
                break;
            }
            System.out.println(str);

        }
        din.close();
        server.close();
    }
}

客户端:

package com.net.test;

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws Exception {
        Socket client =new Socket("127.0.0.1",8888);
        OutputStream ou=client.getOutputStream();
        DataOutputStream dos=new DataOutputStream(ou);
        Scanner sc=new Scanner(System.in);
        while (true) {
            System.out.print("请输入:");
            String input=sc.nextLine();
            dos.writeUTF(input);
            if(input.equals("quit")){
                System.out.println("传输结束,关闭管道");
                break;
            }
        }
        dos.close();
        client.close();
    }
}

1.7 与多个客户端同时通信

我们上面写的代码,服务端没办法同时与多个客户端进行通信,接下来学习可以与多个客户端进行通信的方法

使用多线程去解决

package com.net.test;

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

public class Server {
    public static void main(String[] args) throws Exception {
        System.out.println("启动成功");
        ServerSocket server=new ServerSocket(8888);

        Socket serversocket= null;
        while (true) {
            serversocket = server.accept();
            Thread t=new ServerReaderThread(serversocket);
            t.start();
        }
    }
}


package com.net.test;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

public class ServerReaderThread extends Thread{
    Socket socket;
    ServerReaderThread(Socket socket){
        this.socket=socket;
    }
    @Override
    public void run()  {
        try(DataInputStream din=new DataInputStream(socket.getInputStream())){
            while (true) {
                String input= din.readUTF();
                if(input.equals("quit")){
                    break;
                }
                System.out.println(input);
            }
            socket.close();
        }
        catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

package com.net.test;

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws Exception {
        Socket client =new Socket("127.0.0.1",8888);
        OutputStream ou=client.getOutputStream();
        DataOutputStream dos=new DataOutputStream(ou);
        Scanner sc=new Scanner(System.in);
        while (true) {
            System.out.print("请输入:");
            String input=sc.nextLine();
            dos.writeUTF(input);
            if(input.equals("quit")){
                System.out.println("传输结束,关闭管道");
                break;
            }
        }
        dos.close();
        client.close();
    }
}

案例 群聊

package com.net.test;

import java.io.DataInputStream;
import java.net.*;
import java.util.ArrayList;
import java.util.List;

public class Server {
    public static List<Socket> sockets=new ArrayList<>();
    public static void main(String[] args) throws Exception {

        System.out.println("启动成功");
        ServerSocket server=new ServerSocket(8888);

        Socket serversocket= null;
        while (true) {
            serversocket = server.accept();
            sockets.add(serversocket);
            Thread t=new ServerReaderThread(serversocket);
            t.start();
        }
    }
}


package com.net.test;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class ServerReaderThread extends Thread{
    Socket socket;
    private static Map<Socket, DataOutputStream> clientOutputStreams = new HashMap<>();
    
    ServerReaderThread(Socket socket){
        this.socket=socket;
        try {
            // 为每个客户端创建并保存输出流
            clientOutputStreams.put(socket, new DataOutputStream(socket.getOutputStream()));
        } catch (IOException e) {
            System.out.println("创建客户端输出流失败: " + e.getMessage());
        }
    }
    @Override
    public void run()  {
        try(DataInputStream din=new DataInputStream(socket.getInputStream())){
            while (true) {
                try {
                    String input= din.readUTF();
                    // 如果客户端发送退出消息,则断开连接
                    if ("quit".equals(input)) {
                        System.out.println(socket.getInetAddress()+"下线");
                        Server.sockets.remove(socket);
                        clientOutputStreams.remove(socket);
                        socket.close();
                        break;
                    }
                    sendMessageToAll(input);
                }
                catch (Exception e){
                    System.out.println(socket.getInetAddress()+"下线");
                    Server.sockets.remove(socket);
                    clientOutputStreams.remove(socket);
                    try {
                        if (!socket.isClosed()) {
                            socket.close();
                        }
                    } catch (IOException ioException) {
                        // 忽略关闭异常
                    }
                    break;
                }
            }
        }
        catch (Exception e) {
            System.out.println("处理客户端消息时出错: " + e.getMessage());
        }
    }

    private void sendMessageToAll(String input) {
        // 创建副本以避免并发修改异常
        for(Socket s: Server.sockets.toArray(new Socket[0])){
            try {
                if (s.isClosed() || !s.isConnected()) {
                    // 移除已关闭的连接
                    Server.sockets.remove(s);
                    clientOutputStreams.remove(s);
                    continue;
                }

                DataOutputStream out = clientOutputStreams.get(s);
                if (out != null) {
                    out.writeUTF(input);
                    out.flush();
                }
            } catch (IOException e) {
                System.out.println("向客户端 " + s.getInetAddress() + " 发送消息失败");
                // 移除发送失败的客户端
                Server.sockets.remove(s);
                clientOutputStreams.remove(s);
                try {
                    if (!s.isClosed()) {
                        s.close();
                    }
                } catch (IOException ioException) {
                    // 忽略关闭异常
                }
            }
        }
    }
}
package com.net.test;

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) {
        Socket client = null;
        DataOutputStream dos = null;
        Scanner sc = null;

        try {
            System.out.println("正在连接服务器...");
            client = new Socket("127.0.0.1", 8888);
            System.out.println("连接服务器成功");

            dos = new DataOutputStream(client.getOutputStream());

            // 启动客户端读取线程
            Thread t = new ClientThread(client);
            t.setDaemon(true);
            t.start();

            sc = new Scanner(System.in);
            System.out.println("输入消息(输入quit退出):");

            while (!client.isClosed() && client.isConnected()) {
                String input = sc.nextLine();
                if (input == null || input.equals("quit")) {
                    System.out.println("传输结束,关闭连接");
                    // 发送退出消息给服务器
                    try {
                        dos.writeUTF("quit");
                        dos.flush();
                    } catch (IOException e) {
                        // 忽略发送退出消息时的异常
                    }
                    break;
                }

                try {
                    dos.writeUTF(input);
                    dos.flush();
                    // System.out.println("消息已发送"); // 不再打印,因为会收到转发的消息
                } catch (IOException e) {
                    System.err.println("发送消息失败: " + e.getMessage());
                    break;
                }
            }
        } catch (ConnectException e) {
            System.err.println("连接被拒绝:请确保服务器已启动");
        } catch (IOException e) {
            System.err.println("IO异常: " + e.getMessage());
        } finally {
            // 关闭资源
            if (sc != null) {
                sc.close();
            }
            if (dos != null) {
                try {
                    dos.close();
                } catch (IOException e) {
                    System.err.println("关闭输出流时出错: " + e.getMessage());
                }
            }
            if (client != null && !client.isClosed()) {
                try {
                    client.close();
                    System.out.println("客户端已关闭");
                } catch (IOException e) {
                    System.err.println("关闭客户端时出错: " + e.getMessage());
                }
            }
        }
    }
}
package com.net.test;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

public class ClientThread extends Thread{
    Socket socket;

    ClientThread(Socket socket){
        this.socket=socket;
    }

    @Override
    public void run(){
        DataInputStream in = null;
        try {
            in = new DataInputStream(socket.getInputStream());
            while (!socket.isClosed() && socket.isConnected()) {
                try {
                    String output = in.readUTF();
                    System.out.println("聊天记录:"+output);
                } catch (IOException e) {
                    // 连接断开时退出循环
                    if (!socket.isClosed()) {
                        System.out.println("与服务器的连接已断开");
                    }
                    break;
                }
            }
        } catch (Exception e) {
            if (!socket.isClosed()) {
                System.out.println("客户端读取线程异常: " + e.getMessage());
            }
        } finally {
            // 不要在这里关闭socket,让主线程关闭
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    // 忽略关闭异常
                }
            }
        }
    }
}

需要注意的是,我们的数据输入输出流和Socket是绑定的,如果我们将其关闭也会带着关闭Socket 所以我们要对

输入输出流进行复用,而不是用完就关闭,在最后断开连接的时候再进行关闭

posted @ 2025-07-28 00:01  折翼的小鸟先生  阅读(3)  评论(0)    收藏  举报