InetAddress

Java中表示IP地址的类,常用方法:

InetAddress ip = InetAddress.getByName("www.example.com");
System.out.println(ip.getHostName());  // 主机名
System.out.println(ip.getHostAddress()); // IP地址

UDP通信

无连接协议,速度快但不可靠

一发一收

发送端:

DatagramSocket socket = new DatagramSocket();
byte[] data = "Hello".getBytes();
DatagramPacket packet = new DatagramPacket(
    data, data.length, InetAddress.getLocalHost(), 6666);
socket.send(packet);

接收端:

DatagramSocket socket = new DatagramSocket(6666);
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
socket.receive(packet);
System.out.println(new String(packet.getData(), 0, packet.getLength()));

多发多收

只需在循环中重复发送/接收逻辑即可

TCP通信

面向连接的可靠协议

一发一收

客户端:

Socket socket = new Socket("127.0.0.1", 8888);
OutputStream os = socket.getOutputStream();
os.write("Hello".getBytes());

服务端:

ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
InputStream is = socket.getInputStream();
byte[] buffer = new byte[1024];
int len = is.read(buffer);
System.out.println(new String(buffer, 0, len));

多发多收

使用循环处理多次通信,使用flush()刷新管道

支持多个客户端消息

服务端主线程只负责接收连接,每个客户端交给新线程处理

  1. 单独的ClientHandler线程类处理每个客户端连接
  2. 服务端主循环持续接收新连接
  3. 每个连接都有独立的线程处理通信
// 客户端处理线程
class ClientHandler implements Runnable {
    private Socket socket;
    
    public ClientHandler(Socket socket) {
        this.socket = socket;
    }
    
    @Override
    public void run() {
        try (InputStream is = socket.getInputStream()) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                System.out.println(Thread.currentThread().getName() 
                    + "收到消息:" + new String(buffer, 0, len));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
 
// 服务端主程序
ServerSocket server = new ServerSocket(8888);
while (true) {
    Socket socket = server.accept();  // 接收新连接
    new Thread(new ClientHandler(socket)).start();  // 为每个客户端创建新线程
}

使用线程池优化

public class Server {
    public static void main(String[] args) throws Exception {
        // 目标:实现TCP通信下多发多收:服务端开发。支持多个客户端开发。
        System.out.println("服务端启动了...");
        // 1. 创建一个服务器ServerSocket,指定端口
        ServerSocket ss = new ServerSocket(8888);
 
        // 创建线程池
        ExecutorService es = new ThreadPoolExecutor(5, 10, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(5), new ThreadPoolExecutor.DiscardPolicy());
 
        while (true) {
            // 2. 调用accept()监听,等待客户端连接
            Socket s = ss.accept();
            System.out.println("一个客户端连接了:" + s.getInetAddress().getHostAddress());
            // 3. 使用线程池优化
            es.execute(new ServerReader(s));
        }
    }
}
public class ServerReader implements Runnable {
 
    private Socket socket;
 
    public ServerReader(Socket socket) {
        this.socket = socket;
    }
    @Override
    public void run() {
        try {
            while (true) {
                // 给当前对应的浏览器管道响应一个网页数据回去。
                OutputStream os = socket.getOutputStream();
                // 通过字节输出流包装写出去数据给浏览器
                // 包装为打印流
                PrintWriter pw = new PrintWriter(os);
                pw.write("HTTP/1.1 200 OK\r\n");
                pw.write("Content-Type: text/html\r\n");
                pw.write("\r\n");
                pw.write("<html><body><h1>Hello World</h1></body></html>");
                pw.close();
                socket.close();
 
            }
        } catch (Exception e) {
            System.out.println("客户端下线了:"+socket.getInetAddress().getHostAddress());
        }
        
    }
}