JavaEE——No.2 套接字编程(TCP)

JavaEE传送门

JavaEE

JavaEE——网络通信基础

JavaEE——No.1 套接字编程(UDP)


目录

  • 套接字编程
    • TCP 的 socket
      • ServerSocket API
      • Socket API
    • 回显服务器
      • 改进版


套接字编程

TCP 的 socket

TCP socket 要掌握的类:

  1. ServerSokcet , 是创建 TCP 服务器 Socket.
  2. Socket, 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。

ServerSocket API

构造方法:

方法签名 方法说明
ServerSocket(int port) 创建一个服务端流套接字Socket,并绑定到指定端口

方法:

方法签名 方法说明
Socket accept() 开始监听指定端口 (创建时绑定的端口) ,有客户端连接后,返回一个服务端 Socket 对象,并基于该 Socket 建立与客户端的连接,否则阻塞等待
void close( ) 关闭此套接字

Socket API

构造方法:

方法签名 方法说明
Socket(String host, int port) 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接

方法:

方法签名 方法说明
InetAddress getInetAddress() 返回套接字所连接的地址
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流

后两种方法, 通过 socket 可以获取到两个流对象, 分别用来读和写


回显服务器

服务器

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

/**
 * User: gu'jiu
 * Description:服务器
 */
public class TcpEchoServer {
    //代码中会涉及到多个 socket 对象
    private ServerSocket listenSocket = null;

    public TcpEchoServer(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动! ");
        while(true) {
            //先调用 accept 来接受客户端的连接
            Socket clientSocket = listenSocket.accept();
            //再处理这个连接
            processConnection(clientSocket);    
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s, %d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            while(true) {
                //读取请求并解析
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()) {
                    //读完了, 连接可以断开了
                    System.out.printf("[%s, %d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                String request = scanner.next();
                //根据请求计算相应
                String response = process(request);
                //把响应写回给客户端
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();

                System.out.printf("[%s: %d] req: %s; reqs: %s\n", clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(), request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭 socket
            clientSocket.close();
        }

    }

    //回显
    public static String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9092);
        server.start();
    }
}

部分代码解释

  1. JavaEE——No.2 套接字编程(TCP)_第1张图片

  2. 通过 clientSocket 来获取到 inputStream 和 outputStream. JavaEE——No.2 套接字编程(TCP)_第2张图片

  • 由于我们所说的 TCP 是面向字节流的, 和之前所介绍到的文件操作是完全相同的. 所以我们也是通过前面所学习过的这些字节流这些类, 来去完成我们数据的一个读写操作.

  • 这里我们的流对象就不再代表着磁盘文件了, 而是代表着 socket. 换句话说, 我们从这个 inputStream 读取数据, 就是从我们的网卡上读取数据; 从outputStream 写数据, 就是在向网卡中写数据.

  • cilentSocket 代表着的是服务器的网卡, 向 clientSocket 读写数据, 就是相当于向网卡中读写数据, 也就相当于在客户端中读写数据.

  1. JavaEE——No.2 套接字编程(TCP)_第3张图片

    socket 也是一个文件, 我们之前说过, 一个进程能同时打开的文件个数是有上限的.

    • listenSocket 是在 TCP 服务器中, 只有唯一一个对象, 就不太会把文件描述符表沾满(随着进程的退出, 自动释放)
    • 而 clientSocket 是在循环中, 每个客户端连上都要分配一个. 如果不关闭, 就会持续消耗文件描述符, 因此就需要把不再使用的 clientSocket 及时释放掉.

客户端

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

/**
 * User: gu'jiu
 * Description:客户端
 */
public class TcpEchoClient {
    //客户端需要使用这个 socket 对象来建立链接
    Socket socket = null;

    public TcpEchoClient(String serverIP, int serverPort) throws IOException {
        //和服务器建立链接, 需要知道服务器的 ip 和端口号
        socket = new Socket(serverIP, serverPort);
    }

    public void start() {
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream();
            OutputStream outputStream = socket.getOutputStream()) {
            while(true) {
                //从控制台读取数据, 构造成一个请求
                System.out.println("------------------");
                System.out.print("-> ");
                String request = scanner.next();
                //发送请求给服务器
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(request);
                printWriter.flush();
                //从服务器读取响应
                Scanner respScanner = new Scanner(inputStream);
                String response = respScanner.next();
                //把响应显示到界面上
                System.out.println(response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws IOException {
        TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9092);
        client.start();
    }
}

运行结果展示

启动服务器

JavaEE——No.2 套接字编程(TCP)_第4张图片

此时没有客户端连接, accept( ) 是阻塞的状态

启动客户端

JavaEE——No.2 套接字编程(TCP)_第5张图片

启动客户端之后的服务器 (accept( ) 阻塞结束)

JavaEE——No.2 套接字编程(TCP)_第6张图片

在客户端输入"HelloGujiu"

JavaEE——No.2 套接字编程(TCP)_第7张图片

关闭客户端后的服务器

JavaEE——No.2 套接字编程(TCP)_第8张图片


改进版

一个服务器一般情况下, 都给多个客户端提供服务.

我们勾选一下 "Allow multiple instances"

JavaEE——No.1 套接字编程(内含打开方式)

这时我们启动两个客户端, 但是这时的服务器并没有出现两次 “客户端上线!”

JavaEE——No.2 套接字编程(TCP)_第9张图片

为什么会出现这种情况呢?

  • 如果没有客户端建立连接, 服务器就会阻塞到 accept

  • 如果有一个客户端过来了, 此时就会进入 start() 方法

  • 此时代码就阻塞在 .hasNext() 这里

  • 于是, 我们就无法第二次调用到 accept, 也就无法处理第二个客户端了

为什么UDP 没有这个问题, TCP 有这个问题呢?

  • UDP 客户端直接发消息即可

  • TCP 建立连接之后, 要处理客户端的多次请求, 才导致无法快速的调到 accept (长连接)

    如果 TCP 每个连接只处理一个客户端的请求, 也能够保证快速调到 accept (短连接)

解决方案

使用线程池

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * User: gu'jiu
 * Description:服务器
 */
public class TcpEchoServer {
    //代码中会涉及到多个 socket 对象
    private ServerSocket listenSocket = null;

    public TcpEchoServer(int port) throws IOException {
        listenSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动! ");
        ExecutorService service = Executors.newCachedThreadPool();
        while(true) {
            //先调用 accept 来接受客户端的连接
            Socket clientSocket = listenSocket.accept();
            //再处理这个连接
            service.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s, %d] 客户端上线!\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()) {
            while(true) {
                //读取请求并解析
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()) {
                    //读完了, 连接可以断开了
                    System.out.printf("[%s, %d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                String request = scanner.next();
                //根据请求计算相应
                String response = process(request);
                //把响应写回给客户端
                PrintWriter printWriter = new PrintWriter(outputStream);
                printWriter.println(response);
                printWriter.flush();

                System.out.printf("[%s: %d] req: %s; reqs: %s\n", clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(), request, response);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭 socket
            clientSocket.close();
        }

    }

    public static String process(String request) {
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server = new TcpEchoServer(9092);
        server.start();
    }
}

(( ◞•̀д•́)◞⚔◟(•̀д•́◟ ))

以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!
在这里插入图片描述
加粗样式

这里是Gujiu吖!!感谢你看到这里
祝今天的你也
开心满怀,笑容常在。

你可能感兴趣的:(JavaEE,tcp/ip,java-ee,网络)