TPC回显服务器(Java代码)

一.TPC的Socket API的使用

ServerSocket类

给服务器使用的的类,需手动绑定端口号

Socket类

既要给服务器使用,也给客户端使用

!!这两个类都是表示socket文件,抽象了网卡之类的硬件设备.

二.回显服务器的具体实现

和UDP实现回显服务器一样,我们写一个简单的TCP协议的客户端/服务器,客户端给服务器发送请求,请求就是控制台输入的字符串,服务器收到请求字符串后直接返回请求字符串,这种功能的服务器称为回显服务器.

执行结果:

 服务器代码:

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.ExecutorService;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 86180
 * Date: 2024-01-22
 * Time: 8:47
 */
public class TcpEchoServer {
    //ServerSocket给服务器使用的类,用来绑定端口号
    private ServerSocket serverSocket = null;
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动!!!");
        //ExecutorService
        while (true){
            //通过accept方法,把内核中已经建立好的连接拿到应用程序中
            //把建立好的连接,拿到服务器里
            Socket clientSocket = serverSocket.accept();
            processConnection(clientSocket);
        }
    }

    //处理连接
    public void processConnection(Socket clientSocket){
        //进入方法,先打印一个日志
        System.out.printf("[%s,%d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
        //进入交互
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            //使用try()方式,避免使用完流对象,忘记关闭
            //由于客户端发来的数据可能是多条数据,针对多条数据,就循环的处理
            while (true){
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    System.out.printf("[%s:%d] 客户端下线!!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                //1.读取请求并解析,以next来作为读取请求的方式
                String request = scanner.next();
                //2.根据请求,计算响应
                String response = process(request);
                //3.把响应写回客户端
                //  可以把String转成字节数组,写入到OutputStream字符串
                //  也可以使用PrintWriter把OutputStream包裹一下,来写入字符串
                PrintWriter printWriter = new PrintWriter(outputStream);
                //此处的println不是打印到控制台,而是写入outputStream对应的流对象,也就是写入clientSocket里面
                //这个数据通过网络发送出去
                //此处使用println,带有\n也是为了后续
                printWriter.println(response);
                //此处还要刷新缓冲区,刷新内存
                printWriter.flush();
                //4.打印一次这次请求交互过程的内容
                System.out.printf("[%s:%d] req = %s,resp = %s\n",clientSocket.getInetAddress(),
                        clientSocket.getPort(),request,response);
                }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //关闭clientSocket

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

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

 客户端代码:

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

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 86180
 * Date: 2024-01-22
 * Time: 8:47
 */
public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp,int serverPort) throws IOException {
        socket = new Socket(serverIp,serverPort);
    }
    public void start(){
        System.out.print("客户端启动");
        Scanner scanner = new Scanner(System.in);
        try (InputStream inputStream = socket.getInputStream();
             OutputStream outputStream = socket.getOutputStream()) {
            //
            PrintWriter writer = new PrintWriter(outputStream);
            Scanner scannerNetwork = new Scanner(inputStream);
            //

            while (true){
                //1.从控制台读取用户输入的内容
                System.out.print("->");
                String request = scanner.next();
                //2.把字符串作为请求,发送给服务器
                writer.println(request);
                writer.flush();
                //3,读取服务器返回的响应
                String response = scannerNetwork.next();
                //4.在界面上显示内容
                System.out.println(response);
            }

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

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

重点注意的问题:

1.文件资源泄露

虽然我们对字节流文件都进行了关闭处理,但这只是对clientSocket上的自带的流对象关闭,

但是还有一个文件没有close,就是服务器中的clientSocket对象没有进行close

如果不进行关闭,那么大量的客户端访问就会造成文件资源泄露,我们的操作是 在循环执行一次处理客户端请求的方法后及时关闭,确保每一次方法执行完都进行文件的关闭,这样问题得到解决!!

TPC回显服务器(Java代码)_第1张图片

 2.多个客户端访问故障

虽然当前客户端执行情况一切顺利,但是在我们再开一个客户端时,程序出现BUG!!新开的客户端并未得到回显响应

TPC回显服务器(Java代码)_第2张图片

这个情况是因为,处理连接方法中又有一次循环,第一个客户建立连接后,服务器开始循环等待客户端的请求,阻塞在scanner.hasNext这里,如果客户端不退出,永远不出循环,这样就无法再建立一个新的socket和新的客户连接,我们这里采用的办法是多线程,创建新的线程来处理新来的客户端请求,也就是新来一个客户端,就新建立一个线程来进行分配!!!

TPC回显服务器(Java代码)_第3张图片

这回可以看到,客户端可以正常访问了 

TPC回显服务器(Java代码)_第4张图片

三.客户端代码改进

经过上诉分析,我们对客户端代码进行修改,并且采用线程池来改进代码:

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.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 86180
 * Date: 2024-01-22
 * Time: 8:47
 */
public class TcpEchoServer {
    //ServerSocket给服务器使用的类,用来绑定端口号
    private ServerSocket serverSocket = null;
    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        ExecutorService service = Executors.newCachedThreadPool();
        System.out.println("服务器启动!!!");
        //ExecutorService
        while (true){
            //通过accept方法,把内核中已经建立好的连接拿到应用程序中
            //把建立好的连接,拿到服务器里
            Socket clientSocket = serverSocket.accept();

            //创建新的进程调用prpcessConnection方法
            //线程开始单独存活循环处理一个客户端请求
//            Thread t = new Thread(()->{
//                processConnection(clientSocket);
//            }) ;
//            t.start();
             //更好一点的办法, 是使用线程池.
            service.submit(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }

    //处理连接
    public void processConnection(Socket clientSocket){
        //进入方法,先打印一个日志
        System.out.printf("[%s,%d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());
        //进入交互
        try(InputStream inputStream = clientSocket.getInputStream();
            OutputStream outputStream = clientSocket.getOutputStream()){
            //使用try()方式,避免使用完流对象,忘记关闭
            //由于客户端发来的数据可能是多条数据,针对多条数据,就循环的处理
            while (true){
                Scanner scanner = new Scanner(inputStream);
                if(!scanner.hasNext()){
                    System.out.printf("[%s:%d] 客户端下线!!\n",clientSocket.getInetAddress(),clientSocket.getPort());
                    break;
                }
                //1.读取请求并解析,以next来作为读取请求的方式
                String request = scanner.next();
                //2.根据请求,计算响应
                String response = process(request);
                //3.把响应写回客户端
                //  可以把String转成字节数组,写入到OutputStream字符串
                //  也可以使用PrintWriter把OutputStream包裹一下,来写入字符串
                PrintWriter printWriter = new PrintWriter(outputStream);
                //此处的println不是打印到控制台,而是写入outputStream对应的流对象,也就是写入clientSocket里面
                //这个数据通过网络发送出去
                //此处使用println,带有\n也是为了后续
                printWriter.println(response);
                //此处还要刷新缓冲区,刷新内存
                printWriter.flush();
                //4.打印一次这次请求交互过程的内容
                System.out.printf("[%s:%d] req = %s,resp = %s\n",clientSocket.getInetAddress(),
                        clientSocket.getPort(),request,response);
                }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                //关闭clientSocket
                clientSocket.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }


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

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

你可能感兴趣的:(网络通信,服务器,java,运维)