四、Socket

Socket通信原理与使用

一、什么是Socket
两个程序直接的双向通讯连接实现数据的交换,这个双向链路的一端成为Socket。Socket通常用来实现客户端和服务端的连接。
Socket是TCP/IP四层介于应用层和传输层中间的软件抽象层,他是一组接口,他把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部。
在这里插入图片描述
二、工作原理
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
在这里插入图片描述

三、Socket使用

package com.test;

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

public class MyServerSocket_TCP {
    public static void main(String[] args) throws IOException {
        // 监听9000端口
        ServerSocket serverSocket = new ServerSocket(9000);
        System.out.println("服务器启动,监听9000端口");
        // 接收客户端连接 阻塞直到有客户端连接
        Socket clientSocket = serverSocket.accept();
        System.out.println("有客户端连接" + clientSocket.getInetAddress());
        // 处理客户端连接
        BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true);
        String message;
        while ((message = reader.readLine()) != null) {
            System.out.println("收到客户端消息:" + message);
            //回发响应
            writer.println("服务器收到消息:" + message);
        }
        // 关闭连接
        writer.close();
        reader.close();
        clientSocket.close();
        serverSocket.close();
    }
}
package com.test;

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

public class MyClientSocket_TCP {
    public static void main(String[] args) throws IOException {
        //创建客户端对象 连接本机的IP地址和端口号
        Socket socket = new Socket("127.0.0.1", 9000);
        System.out.println("客户端已连接");
        PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
        printWriter.println("hello server");
        // 读取 服务端返回的数据
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String line = bufferedReader.readLine();
        System.out.println("服务端返回的数据:" + line);
        bufferedReader.close();
        printWriter.close();
        socket.close();
    }
}

在这里插入图片描述
在这里插入图片描述

package test;

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

public class MyServerSocket_UDP {
    public static void main(String[] args) throws IOException {
        DatagramSocket serverSocket = new DatagramSocket(9000);
        System.out.println("服务启动,监听9000端口");

        byte[] buffer= new byte[1024];

        DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
        //阻塞等待客户端发送数据
        serverSocket.receive(packet);
        //
        InetAddress clientIP = packet.getAddress();
        int clientPort = packet.getPort();
        String msg = new String(packet.getData(),0,packet.getLength(), "UTF-8");
        System.out.println("收到客户端 " + clientIP + ":" + clientPort + " 的消息:" + msg);


        // 回发响应
        String response = "服务器收到:"+msg;
        byte[] responseBytes = response.getBytes();
        DatagramPacket responsePacket = new DatagramPacket(
                responseBytes,
                responseBytes.length,
                packet.getAddress(),
                packet.getPort()
        );
        // 发送响应
        serverSocket.send(responsePacket);
        serverSocket.close();
    }
}
package test;

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

public class MyClientSocket_UDP {
    public static void main(String[] args) throws IOException {
        DatagramSocket clientSocket = new DatagramSocket();
        String msg="hello server";
        byte[] data=msg.getBytes();
        DatagramPacket packet=new DatagramPacket(
                data,
                data.length,
                InetAddress.getByName("127.0.0.1"),//ip地址
                9000 //端口号
        );
        clientSocket.send(packet);

        //接收服务端响应
        byte[] buffer=new byte[1024];
        DatagramPacket responsePacket=new DatagramPacket(buffer,buffer.length);
        clientSocket.receive(responsePacket);
        String response=new String(responsePacket.getData(),0,responsePacket.getLength());
        System.out.println("服务端返回的数据:"+response);
    }
}

在这里插入图片描述
在这里插入图片描述
以上是Socket的通过TCP和UDP的收发数据的简单使用。

package test;

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


public class MyServerSocket_TCP {
    public static void main(String[] args) throws IOException {
        // 监听9000端口
        ServerSocket serverSocket = new ServerSocket(9000);
        System.out.println("服务器启动,监听9000端口");
        while (true) {
            // 接收客户端连接 阻塞直到有客户端连接
            Socket clientSocket = serverSocket.accept();
            System.out.println("有客户端连接" + clientSocket.getInetAddress());
            new Thread(() -> handleClient(clientSocket)).start();
        }
    }

    /**
     * 处理客户端连接
     * @param clientSocket 客户端连接
     */
    private static void handleClient(Socket clientSocket) {
        // 处理客户端连接 BufferedReader  PrintWriter 都实现了 AutoCloseable 的close() 方法 所以 可以 try-with-resources
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
             PrintWriter writer = new PrintWriter(clientSocket.getOutputStream(), true)) {
            String message;
            while ((message = reader.readLine()) != null) {
                System.out.println("收到客户端消息:" + message);
                //回发响应
                writer.println("服务器收到消息:" + message);
            }
        } catch (IOException e) {
            System.out.println("客户端:" + clientSocket.getInetAddress() + " 错误:" + e.getMessage());
        }
    }
}

以上写法典型的阻塞式多客户端写法,使用场景和优势如下:
主线程只负责监听端口并接受连接。
每个客户端都在独立线程中处理。
适合客户端少,逻辑简单的场景。
缺点是线程数量过多时候,会消耗大量的资源。

PrintWriter printWriter = new PrintWriter(socket.getOutputStream(), true);
printWriter.print("hello server");
printWriter.flush(); // 强制把缓冲区数据发送

PS:有个小注意项,虽然 autoFlush=true,但是对print无效,还得需要手动.flush以下。=true仅对 println();printf();format();有效。

package test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;


public class MyServerSocket_TCP {
    public static void main(String[] args) throws IOException {
        // 创建serverSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 绑定端口
        serverSocketChannel.bind(new InetSocketAddress(9000));
        // 设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);
        // 创建选择器 用于监听多个通道
        Selector selector = Selector.open();

        // 注册到选择器上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("服务器启动,监听9000端口");
        handle(selector);

    }

    /**
     *  处理客户端连接
     * @param selector 选择器
     * @throws IOException IO异常
     */
    private static void handle(Selector selector) throws IOException {
        while (true) {
            //创建 Selector
            selector.select();

            // 遍历发生事件的 SelectionKey
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                //Iterator的删除的是当前迭代的元素必须紧跟next()之后  必须remove 否则会重复处理
                keyIterator.remove();
                // 处理连接事件
                if (key.isAcceptable()) {
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                    // 接收客户端连接
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    // 注册读事件
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    System.out.println("客户端连接:" + socketChannel.getRemoteAddress());
                }
                // 客户端可读事件
                else if (key.isReadable()) {
                    SocketChannel client = (SocketChannel) key.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    // 非阻塞
                    int bytesRead = client.read(buffer);
                    if (bytesRead == -1) {
                        // 客户端关闭
                        client.close();
                        System.out.println("客户端已关闭");
                    } else {
                        buffer.flip(); // 准备读取
                        String msg = new String(buffer.array(), 0, buffer.limit());
                        System.out.println("收到客户端消息:" + msg);

                        // 回发数据
                        String response = "服务器收到:" + msg;

                        // 非阻塞写
                        client.write(ByteBuffer.wrap(response.getBytes(StandardCharsets.UTF_8)));
                    }
                }
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值