JavaEE 第20节 用TCP套接字实现简单回显服务器

这里写目录标题

  • 一、API介绍
    • ServerSocket
    • Socket
  • 二、创建简单的回显服务器
    • 服务器端
    • 客户端

一、API介绍

ServerSocket

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

关于此构造方法的注意事项:
Server的构造方法不止这一个,但是表格中的是最常用的。 ServerSocket(int
port) 不用指定服务器的IP地址,它会自动监听所有网络接口,在运行之前它的IP地址用通配符表示,服务器实际运行的IP地址取决于服务器的运行环境。 如果是在本地电脑,那么IP地址就是“127.0.0.1”,如果使用的是公网,那么IP地址就是公网IP,因此程序用就不用自己手动设置服务器的IP地址了

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

注意:
只要使用套接字就会消耗文件资源,也就是会占用文件资源描述符。如果使用完套接字,不手动close()关闭,有时出现大量文件资源申请,文件资源描述符被使用完就可能出现文件资源泄露。为了保证代码的健壮性,一定要记得判断是否需要手动关闭文件资源。

Socket

  • 构造方法
方法签名 方法说明
Socket(String host, int port) 创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的进程建立连接
  • 类方法
方法签名 方法说明
InetAddress getInetAddress() 返回套接字所连接的地址
in getPort() 返回套接字所连接的端口号
InputStream getInputStream() 返回此套接字的输入流
OutputStream getOutputStream() 返回此套接字的输出流

二、创建简单的回显服务器

回显服务器(Echo Server)是一种简单的服务器应用程序,它的功能是接收客户端发送的数据,并将这些数据原样返回给客户端。这种服务器通常用于测试和调试网络应用程序。

服务器端

public class Server {

    //用于与客户端建立连接
    private ServerSocket serverSocket = null;

    public Server(int port) throws IOException {

        //这里只用设置端口号即可,不用设置IP地址,IP地址根据运行环境而定
        //因为是在本地运行,所以运行时IP地址时“127.0.0.1”
        serverSocket = new ServerSocket(port);
    }

    public void start() throws IOException {
        System.out.println("服务器启动!");

        while (true) {
            //与UDP协议不同,TCP协议需要和客户端建立连接
            Socket clientSocket = serverSocket.accept();
            System.out.printf("服务器和客户端建立连接 [%s:%d]\n",
                    clientSocket.getInetAddress(),
                    clientSocket.getPort());

            //使用线程池,确保一个服务器可以同时处理多个客户端发来的请求
            Thread thread = new Thread(() -> {

                try {
                    //正式进行数据传输
                    processConnection(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });

            //记得启动线程!
            thread.start();
        }
    }

    private void processConnection(Socket clientSocket) throws IOException {

        //TC面向字节流,进行传输,所以使用OutputStream/InputStream
        try(OutputStream outputStream=clientSocket.getOutputStream();
            InputStream inputStream=clientSocket.getInputStream()) {

            //用于获取客户端的请求
            Scanner scanner=new Scanner(inputStream);

            //用于发送响应给客户端
            PrintWriter printWriter=new PrintWriter(outputStream);

            //循环处理请求与响应
           while(true){
           
               //1、获取客户端请求
               if(!scanner.hasNext()){

                   //如果没有得到客户端的请求,说明客户端下线了
                   break;
               }

               //得到的请求放到request中
               String request=scanner.next();

               //2、根据请求,生成响应
               String response=process(request);

               //3、把响应发送给客户端
               printWriter.println(response);
                printWriter.flush();//记得刷新缓冲区!!!详细解释见代码块下方注意事项

               //4、打印工作日志
               System.out.printf("[%s:%d] 请求=%s 响应=%s\n",
                       clientSocket.getInetAddress(),
                       clientSocket.getPort(),
                       request,
                       response);
           }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {

            //打印客户端下线信息
            System.out.printf("客户端下线 [%s:%d]\n",
                    clientSocket.getInetAddress(),
                    clientSocket.getPort());

            //只要是套接字,就会消耗资源占位符,如果用完没有及时关闭,可能出现文件资源泄露。
            if(clientSocket!=null&&!clientSocket.isClosed()){
                clientSocket.close();
            }
        }
    }

    //因为是回显服务器,所以这里直接返回相同的内容,在实际开发中这一步可能是比较复杂的
    private String process(String request){return request;}

    public static void main(String[] args) throws IOException {

        //自己指定一个端口号最好是大于1023的(当然也要小于65535)
        Server server=new Server(9090);
        server.start();
    }
}

注意事项:
PrintWriter的发送数据设置了缓冲区。 设置一个缓冲区的原因是避免频繁进行数据IO,因为这样消耗大量资源。通过缓冲区,数据堆积到一定大小在进行IO那么消耗成本就会小很多。但是在我们这个回显服务器中,需要立刻看到响应,所以需要用到PrintWriter中的flush()方法,调用这个方法可以刷新缓冲区,把所有的数据都进行IO。
 
另外, 除了用PrintWriter进行数据IO ,还可以使用outputStreamwrite()方法,但是需要把数据转化成字节数组:在这里插入图片描述

客户端

public class Client {

    //用于接收服务器的响应,或者发送请求给服务器
    private Socket socket=null;

    //两个参数分别是,需要连接的服务器的IP地址以及端口号,端口号刚才设置了是9090,由于是在本地运行所以ip默认是“127.0.0.1”
    public Client(String serverIP,int port) throws IOException {

        //和指定的服务器建立连接
        socket=new Socket(serverIP,port);
    }

    public void start() throws IOException {
        System.out.println("客户端启动!");

        //与UDP不同,TCP面向字节流进行传输
        try(OutputStream outputStream=socket.getOutputStream();
            InputStream inputStream=socket.getInputStream()){

            //用户通过控制台输入,提供请求
            Scanner userIN=new Scanner(System.in);

            //用于发送请求
            PrintWriter printWriter=new PrintWriter(outputStream);

            //用于接收响应
            Scanner recieve=new Scanner(inputStream);

            //循环运行
            while(true){
                //1、获取用户请求
                System.out.print("->");
                String request=userIN.next();

                //2、把请求发送给客户端
                printWriter.println(request);
                printWriter.flush();//注意记得刷新缓冲区!

                //3、接收服务器发来的响应
                if(!recieve.hasNext()){
                    //没有获取到服务器的响应,可能是网络出现问题,或者服务器挂了
                    break;
                }
                 	//正确获取到响应
                String response=recieve.next();

                //4、把响应呈现给用户(打印)
                System.out.printf("响应:%s\n",response);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

最后,如果有任何关于代码或概念上的问题,欢迎私信或者评论交流。关于TCP协议的相关机制,可以参阅: 网络编程专栏 大家的支持是我前行的巨大动力!

你可能感兴趣的:(JavaEE基础,#,JavaEE,网络编程,服务器,java-ee,tcp/ip,tcp,网络)