Java - 网络编程

一、UDP

UDP:无连接,不可靠传输,面向数据报,全双工。

1.无连接:UDP 客户端没有建立连接,先是 new 了一个 DatagramSocket 对象,然后就直接开始发送请求了。

2.不可靠传输:代码层次看不出来,在内核中才体现出来。

3.面向数据报:不管是服务器还是客户端,发送和接收都是以数据报(DatagramPacket)为基本单位进行的。

4.全双工:一个 scoket 既可以发送,又可以接收。

1.UdpEchoServer 代码(服务器)

public class UdpEchoServer {
    // 要想创建 UDP 服务器,首先要打开一个 socket 文件(构造方法中实例化)
    private DatagramSocket socket = null;

    public UdpEchoServer(int port) throws SocketException {
        // 实例化的时候需要关联(绑定)一个端口号
        socket = new DatagramSocket(port);
    }

    // 启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动!");
        while (true) {
            DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096);
            // 1.读取客户端发来的请求
            socket.receive(requestPacket);
            // 2.对请求进行解析,把 DatagramPacket 转成一个 String
            String request = new String(requestPacket.getData(), 0, requestPacket.getLength());
            // 3.根据请求,计算响应
            String response = process(request);
            // 4.把响应构造成 DatagramPacket 对象
            DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),
                    response.getBytes().length, requestPacket.getSocketAddress());
            // 5.把这个 DatagramPacket 对象返回给客户端
            socket.send(responsePacket);
            System.out.printf("[%s:%d] request = %s; response = %s\n", requestPacket.getAddress(),
                    requestPacket.getPort(), request, response);
        }
    }

    // 通过这个方法,实现根据请求计算响应这个过程
    // 如果是其他服务器,就可以在 process 里面,加上一些其他的逻辑处理
    public String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        // 真正启动服务器
        UdpEchoServer server = new UdpEchoServer(8000);
        server.start();
    }
}

2.强化process,升级为“自定义翻译”服务器

public class UdpDictServer extends UdpEchoServer{
    private Map<String,String> dict = new HashMap<>();
    public UdpDictServer(int port) throws SocketException {
        super(port);
        // 构造词汇
        dict.put("hello","你好");
        dict.put("monkey","孙悟空");
        dict.put("fuck","卧槽");
        dict.put("dog sun","狗日");
    }
    // 重写 process
    @Override
    public String process(String request) {
        return dict.getOrDefault(request,"这个问题俺也不会!");
    }
 
    public static void main(String[] args) throws IOException {
        UdpDictServer server = new UdpDictServer(8000);
        server.start();
    }
}

3.UdpEchoClient 代码(客户端)

public class UdpEchoClient {
    private DatagramSocket socket = null;

    public UdpEchoClient() throws SocketException {
        // 客户端的端口号,一般都是由操作系统自由分配的。
        socket = new DatagramSocket();
    }

    public void start() throws IOException {
        Scanner scanner = new Scanner(System.in);
        while (true) {
            // 1.让客户端从控制台读取一个请求数据
            System.out.println("> ");
            String request = scanner.next();
            // 2.把这个字符串请求发送给服务器,构造 DatagramPacket
            DatagramPacket requestPacket = new DatagramPacket(request.getBytes(),
                    request.getBytes().length, InetAddress.getByName("127.0.0.1"), 8000);
            // 3.把数据报发送给服务器
            socket.send(requestPacket);
            // 4.从服务器读取响应数据
            DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);
            socket.receive(responsePacket);
            // 5.把响应的数据获取出来,转成字符串
            String response = new String(responsePacket.getData(), 0, responsePacket.getLength());

            System.out.printf("request: %s; response: %s\n", request, response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client = new UdpEchoClient();
        client.start();
    }
}

二、TCP

1.TcpEchoServer代码(服务器)

public class TcpEchoServer {
    public ServerSocket serverSocket = null;
 
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
 
    public void start() throws IOException {
        System.out.println("服务器启动!");
        // 此处最好使用自动扩容版本的线程池
        ExecutorService service = Executors.newCachedThreadPool();
        while(true) {
            // 如果当前没有客户端来建立连接,就会阻塞等待!
            Socket clientSocket = serverSocket.accept();
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnect(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
 
    // 通过这个方法,给当前连上的这个客户端,提供服务
    // 一个连接过来了,服务方式可能有两种:
    // 1.一个连接只进行一次数据交互(一个请求 + 一个响应),也叫做短链接
    // 2.一个连接进行多次数据交互(N 个请求 + N 个响应),也叫做长链接
    public void processConnect(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 建立连接!\n", clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
 
            // 这里是长连接的写法,需要通过循环来获取多次交互
            while(true) {
                if(!scanner.hasNext()) {
                    // 断开连接
                    System.out.printf("[%s:%d] 断开连接!\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                // 1.读取请求并解析
                String request = scanner.next();
                // 2.根据请求计算响应
                String response = process(request);
                // 3.把响应写回给客户端
                printWriter.println(response);
                // 刷新缓冲区
                printWriter.flush();
                System.out.printf("[%s:%d] request: %s; response: %s\n",
                        clientSocket.getInetAddress().toString(), clientSocket.getPort(),request,response);
            }
        } finally {
            // 建立连接后,就没用了,需要关闭资源,避免资源泄露
            clientSocket.close();
        }
    }
 
    // 回显
    public String process(String request) {
        return request;
    }
 
    public static void main(String[] args) throws IOException {
        TcpEchoServer tcpEchoServer = new TcpEchoServer(8000);
        tcpEchoServer.start();
    }
}

2.TcpEchoClient代码(客户端)

public class TcpEchoClient {
    public Socket socket = null;
 
    public TcpEchoClient() throws IOException {
        // new 这个对象,需要和服务器建立连接,建立连接,就需要知道服务器在哪
        socket = new Socket("127.0.0.1",8000);
    }
 
    public void start() throws IOException {
        // 由于实现的是长连接,一个连接会处理 N 个请求和响应
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            // 包装一下输入输出流
            Scanner scannerNet = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
 
            while(true) {
                // 从控制台读取用户的输入
                System.out.println("> ");
                String request = scanner.next();
                // 2.向服务器发送请求
                printWriter.println(request);
                printWriter.flush();
                // 3.从服务器读取响应
                String response = scannerNet.next();
                // 4.显示服务器响应
                System.out.printf("request: %s; response: %s\n",request,response);
            }
        }
    }
 
    public static void main(String[] args) throws IOException {
        TcpEchoClient tcpEchoClient = new TcpEchoClient();
        tcpEchoClient.start();
    }
}

 

三、需要注意的点

在使用原生字节流的时候,记得包装成 Scanner , PrintWriter 形式(或者字符流)。

此处还要注意的地方就是,我们将输出流包装成 PrintWriter ,如果使用其中的 writer 方法进行发送数据,上面的代码就会有问题,因为客户端要从控制台读取用户的输入,输入完成后的回车被 Scanner(System.in) 给读取走了,那么服务这边一定会在读取请求并解析这里阻塞(String request = scanner.next() 阻塞),就会导致程序卡死。所以保险起见,我们这里在使用 PrintWriter 的时候,使用它的 println 方法,自动换行,就不会幺蛾子了。
 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值