javaEE-网络编程4.TCP回显服务器

发布于:2025-02-11 ⋅ 阅读:(60) ⋅ 点赞:(0)

目录

TCP流套接字编程

一.API介绍

ServerSocket类

构造方法:

​编辑方法:

Socket类

构造方法:

方法:

二、TCP连接

三、通过TCP实现回显服务器

TCP服务端:

1.创建Socket对象

2.构造方法

3.start方法

TCP客户端:

1.创建Socket对象

2.构造方法:

3.start方法

服务端参考代码:

客户端参考代码:

参考结果:


TCP流套接字编程

一.API介绍

有两个关键的类:

ServerSocket:是创建TCP服务端Socket的API。给服务器使用的类,使用这个类,绑定端口号.

(马路牙子上的拉客销售)

Socket:既会给客户端使用,又会给服务器使用。是客户端的Socket,又是服务器接收到客户端accept请求后,返回的服务端Socket.

(售楼处的销售顾问)

ServerSocket类

构造方法:

方法:

Socket类

既会给客户端使用,又会给服务器使用

作用:双方建立连接后,保存对端信息,用来与对方收发数据。

构造方法:

方法:

二、TCP连接

对于UDP来说,是无连接的,每次发送数据时,都需要手动在send方法中指定目标地址;

但是TCP是有连接的,需要提前把连接功能建立起来,

连接如何建立,不需要代码干预,是系统内核自动负责完成的。

我们要做的就是:客户端:发起“建立连接”的动作;

                                服务器:把建立好的连接从内核中拿到应用程序里。

三、通过TCP实现回显服务器

TCP服务端

1.创建Socket对象

2.构造方法

3.start方法

要循环的读取客户端的连接.因此要在循环中

任务1:接听连接,通过调用accept方法

任务2:建立连接,通过实现processConnection方法,建立连接

修改1:将连接改成多线程模式

当多个客户端同时发出请求时,由于单线程,这能处理一个客户端请求,将连接改成多线程,同时获取多用户的请求;但是还有一个问题,每来一个客户端,就创建一个线程,客户端结束,就销毁这个线程,当多个客户端发起请求时,就要创建和销毁大量的线程,开销就会较大;

可以用线程池的方法来创建多线程,提前将线程创建好,需要时就直接用,不用时就先放着,不需要创建和销毁。

线程池解决了线程频繁创建和销毁的开销,但是当多个线程被创建,而没有被销毁,就会导致当前服务器上积累了的大量的线程,这对于服务器的负担会非常严重。

为了解决这个问题,还可以引入其他方案:

1、引入协程。(轻量级线程)本质上还是线程,用户可以通过手动调度的方法,让一个线程并发的执行多个任务。(解决了线程调度的开销)

2.IO多路复用。(系统内核级别的机制)让一个线程负责处理多个Socket对象,这里需要Socket数据不是同时需要被处理的。

4.processConnection方法

输出客户端IP和端口号,表示客户端上线

通过调用Socket的getInputStream方法获取客户端请求,和getOutputStream返回响应

任务2.1 读取客户端请求

读取结束,则表明客户端下线,显示客户端IP和端口号

    2.2,请求并解析 注意:next读取到空白字符才结束

       

    2.3根据请求解析响应

   

这里的process方法建议写成public,方便之后子类通过继承TcpEchoServer方法,实现特有的功能

2.4把响应返回给客户端, 通过PrintfWriter的println方法返回.

修改2:手动刷新缓冲区

这里的println会有缓冲区,当读取到客户的请求时,不会计算响应,而是等缓冲区满了,才一起处理,因此,每次请求,要手动刷新缓冲区,才能保证客户端的每次请求都能及时响应

TCP客户端:

1.创建Socket对象

2.构造方法:

3.start方法

因为TCP是以字节流的形式发送请求的,要用InputStream来获取用户的输入; 获取到服务端的响应,也要用OutputStream来接收

任务1:获取用户输入请求

2.将请求发送给服务端 通过PrintWriter的println方法发送,这样可以获取到y用户输入的 "\n"

修改1:手动刷新缓冲区

客户端这里的println也有缓冲区的问题,每次输入后,也要手动刷新,将请求发送给服务端

3.从服务端获取响应 通过Scanner读取OutPutStream的输出流来获取服务器返回的响应

4.显示响应

修改2:对Socket手动关闭

目前这个代码会出现文件资源泄露,创建的Socket,没有进行close(),

这里的关闭,只是关闭了输出流和输入流,并没有关闭Socket

因此,为了避免文件资源泄露,当客户端断来连接后,要在客户端的finally代码块中手动关闭

服务端参考代码:


class TcpEchoServer1{
    //创建ServerSocket对象
    private ServerSocket serverSocket=null;
    //提供客户端端口号
    public TcpEchoServer1(int port) throws IOException {
        serverSocket=new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务端启动");
        ExecutorService pool = Executors.newCachedThreadPool();
        while(true){
            //1.通过accpet方法 接听连接
            Socket clientSocket = serverSocket.accept();
            //2.通过实现processConnection方法 建立连接
//            processConnection(clientSocket);
            pool.submit(new Runnable(){//通过线程池,创建多条线程
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }
    //TCP是以字节流的方式发送请求的,要用InputStream来读取发送的请求,
                        //    用OutputStream来 返回响应
    private void processConnection(Socket clientSocket) {
        System.out.printf("[%s,%d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
        try(InputStream inputStream=clientSocket.getInputStream();
            OutputStream outputStream=clientSocket.getOutputStream()) {
            //循环读取客户端的请求,并返回响应
           while(true){
               //1.读取请求
               Scanner scan=new Scanner(inputStream);
               if(!scan.hasNext()){
                   //读取完毕,客户端断开连接,产生 读取完毕
                   System.out.printf("[%s,%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());
                   break;
               }
               //2,请求并解析 注意:next读取到空白字符才结束
               String request=scan.next();
               //3.根据请求,计算响应
               String response=process(request);
               //4.把响应返回给客户端
               //给outputStream套上一层,可以获取 \n 更方便操作
               PrintWriter printWriter=new PrintWriter(outputStream);
               printWriter.println(response);
               printWriter.flush();//手动刷新缓冲区
               System.out.printf("[%s,%d] req:%s, resp:%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),
                                    request,response);

           }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private String process(String request) {
        //这里还是什么都不做,就返回请求
        return request;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer1 tcpEchoServer1 = new TcpEchoServer1(9090);
        tcpEchoServer1.start();
    }
}

客户端参考代码:


class TcpEchoClient1{
    //1.创建Socket对象
    private Socket clientSocket=null;
    //传入服务端的IP和端口号
    public TcpEchoClient1(String serverIp,int serverPort) throws IOException {
        clientSocket=new Socket(serverIp,serverPort);
    }
    public void start() throws IOException {
        System.out.println("客户端启动");
        try(InputStream inputStream=clientSocket.getInputStream();
            OutputStream outputStream=clientSocket.getOutputStream();
            Scanner scanNetWork=new Scanner(inputStream);
            Scanner scanConsole=new Scanner(System.in);
            PrintWriter printWriter=new PrintWriter(outputStream) ) {

            while(true){
                System.out.print("->");
                if(!scanConsole.hasNext()){
                    break;//客户端请求输入结束
                }
                //1.获取请求
                String request=scanConsole.next();
                //2.将请求发送给服务端 通过PrintWriter的println方法发送
                printWriter.println(request);
                printWriter.flush();
                //3.获取响应
                String response=scanNetWork.next();
                //4.输出响应
                System.out.println(response);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            clientSocket.close();
        }
    }
    public static void main(String[] args) throws IOException {
        TcpEchoClient1 tcpEchoClient1 = new TcpEchoClient1("127.0.0.1", 9090);
        tcpEchoClient1.start();
    }
}

参考结果:

可以同时启动多个客户端,与同一个服务端建立连接

 


网站公告

今日签到

点亮在社区的每一天
去签到