基于TCP协议实现回显服务器和客户端

前言

关于回显服务器的概念在上篇博客中有解释

一、TCP和UDP实现的回显服务器有什么区别?

(1)TCP是面向字节流的,UDP是面向数据报的;
(2)TCP的服务器需要与操作系统内核建立连接,UDP不需要
(3)TCP是可靠传输,UDP是不可靠传输
(4)UDP效率较高,TCP相对UDP来说效率稍微逊;

二、如何构建基于TCP协议的回显服务器

1.实例化serverSocket对象

代码如下:

public class TCPEchoServer {
    private ServerSocket serverSocket = null;

    //初始化serverSocket时需要绑定端口号
    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
}

2.进入主循环

	进入主循环需要完成的事情:
	 - 与TCP建立连接
	 - 处理TCP连接
		 - 获取请求并解析
		 - 根据请求计算响应
		 - 将响应写回客户端

代码如下:

public void start() throws IOException {
        System.out.println("服务器启动");
        while(true){
            //负责与客户端交互
            //与TCP建立连接
            Socket clientSocket = serverSocket.accept();
            //处理连接
            processConnection(clientSocket);
        }


    }

    private void processConnection(Socket clientSocket) {
        System.out.printf("客户端上线 [%s:%d]",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        //TCP是面向字节流的,由于是回显服务器主要针对字符流文件,因此这里需要将字节流文件字符流化
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))
        ){
            while(true){
                //获取请求并解析
                //注意此处readLine()规定了读取是按照行读取的,同样写数据必须按行写,相当于自定义协议
                String request = bufferedReader.readLine();
                //根据请求计算响应
                String response = process(request);
                //将响应写回客户端
                //这里之所以加上换行符的原因是因为要按行写数据,需要遵循自定义协议
                bufferedWriter.write(response+"\n");
                //由于 bufferedWriter是带有缓冲区的,没有刷新的话数据就在缓冲区中,没有真正写到socket中
                //需要手动刷新
                bufferedWriter.flush();
                //打印日志
                System.out.printf("[%s:%d] req:%s resp:%s\n",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }

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

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

完整代码如下:

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

/**
 * Author:ZouDouble
 * Description:
 * 天气:晴天
 * 目标:Good Offer
 * Date    2021-01-05 17:20
 */
public class TCPEchoServer {
    private ServerSocket serverSocket = null;

    //初始化serverSocket时需要绑定端口号
    //serverSocket负责与客户端进行连接
    public TCPEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        while(true){
            //负责与客户端交互
            //与TCP建立连接
            Socket clientSocket = serverSocket.accept();
            //处理连接
            processConnection(clientSocket);
        }


    }

    private void processConnection(Socket clientSocket) {
        System.out.printf("客户端上线 [%s:%d]",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        //TCP是面向字节流的,由于是回显服务器主要针对字符流文件,因此这里需要将字节流文件字符流化
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))
        ){
            while(true){
                //获取请求并解析
                //注意此处readLine()规定了读取是按照行读取的,同样写数据必须按行写,相当于自定义协议
                String request = bufferedReader.readLine();
                //根据请求计算响应
                String response = process(request);
                //将响应写回客户端
                //这里之所以加上换行符的原因是因为要按行写数据,需要遵循自定义协议
                bufferedWriter.write(response+"\n");
                //由于 bufferedWriter是带有缓冲区的,没有刷新的话数据就在缓冲区中,没有真正写到socket中
                //需要手动刷新
                bufferedWriter.flush();
                //打印日志
                System.out.printf("[%s:%d] req:%s resp:%s\n",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }

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

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

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

三、如何构建基于TCP协议的回显服务器的客户端

1.实例化Socket对象

代码如下:

public class TCPEchoClientServer {
    private Socket socket = null;
    //注意:这里用的是Socket类,这里的serverIp和serverPort仅仅只是属性,初始化的时候并没有绑定端口号
    //客户端初始化的时候不需要绑定端口号
    public TCPEchoClientServer(String serverIp,int serverPort) throws IOException {
        socket = new Socket(serverIp,serverPort);
    }

2.进入主循环

	进入主循环需要完成的事情:
		 - 读取客户请求并解析
		 - 构造请求发给服务器
		 - 从服务器读取响应
		 - 将响应写回客户端

代码如下:

 public void start(){
        System.out.println("客户端来咯");
        Scanner scanner = new Scanner(System.in);
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
                ){
            while(true){
                //读取用户数据
                System.out.println("输入请求->");
                String request = scanner.nextLine();
                if ("exit".equals(request)){
                    System.out.println("退出");
                    break;
                }
                //构造请求数据
                bufferedWriter.write(request+"\n");
                bufferedWriter.flush();
                //从服务器读取响应
                String response = bufferedReader.readLine();
                //将响应写回客户端
                System.out.println(response);

            }

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

    }

完整代码如下:

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

/**
 * Author:ZouDouble
 * Description:
 * 天气:晴天
 * 目标:Good Offer
 * Date    2021-01-05 17:42
 */
public class TCPEchoClientServer {
    private Socket socket = null;
    //注意:这里用的是Socket类,这里的serverIp和serverPort仅仅只是属性,初始化的时候并没有绑定端口号
    //客户端初始化的时候不需要绑定端口号
    public TCPEchoClientServer(String serverIp,int serverPort) throws IOException {
        socket = new Socket(serverIp,serverPort);
    }
    public void start(){
        System.out.println("客户端来咯");
        Scanner scanner = new Scanner(System.in);
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()))
                ){
            while(true){
                //读取用户数据
                System.out.println("输入请求->");
                String request = scanner.nextLine();
                if ("exit".equals(request)){
                    System.out.println("退出");
                    break;
                }
                //构造请求数据
                bufferedWriter.write(request+"\n");
                bufferedWriter.flush();
                //从服务器读取响应
                String response = bufferedReader.readLine();
                //将响应写回客户端
                System.out.println(response);

            }

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

    }

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

运行截图:
基于TCP协议实现回显服务器和客户端_第1张图片
基于TCP协议实现回显服务器和客户端_第2张图片

这样写出的代码有一个bug,它的建立连接和处理连接是串行执行的,代码中的while()循环是客户端下线才会触发,因此这个代码的局限性就是仅支持单用户使用,改进方法就是让它并发执行!如果使用多线程的话,由于多线程中也涉及到线程的创建与销毁,也是一些开销~最直接一步到位的方法就是利用线程池。接下来,我把多线程版本的和线程池版本的代码都放在下面,有需要的朋友可以看看!

基于多线程实现

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

/**
 * Author:ZouDouble
 * Description:
 * 天气:晴天
 * 目标:Good Offer
 * Date    2021-01-05 18:02
 */
public class TCPThreadEchoServer {
    private ServerSocket serverSocket = null;

    public TCPThreadEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        while(true){
            Socket clientSocket = serverSocket.accept();
            Thread thread = new Thread(){
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            };
            thread.start();
        }
    }
    private void processConnection(Socket clientSocket){
        System.out.printf("[%s:%d]客户端上线~\n",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))
        ) {
            while(true){
                //1)读取请求并建立连接
                String request =  bufferedReader.readLine();
                //2)根据请求计算响应
                String response = process(request);
                //3)将响应写回客户端
                bufferedWriter.write(response+"\n");
                bufferedWriter.flush();

                System.out.printf("[%s:%d] req:%s resp:%s\n",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }
        } catch (IOException e) {
            System.out.printf("[%s:%d]客户端下线~\n",clientSocket.getInetAddress().toString(),
                    clientSocket.getPort());
        }
    }

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

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

基于线程池实现

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Author:ZouDouble
 * Description:
 * 天气:晴天
 * 目标:Good Offer
 * Date    2021-01-05 19:58
 */
public class TCPEThreadPoolEchoServer {
    private ServerSocket serverSocket = null;

    public TCPEThreadPoolEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService executorService = Executors.newCachedThreadPool();
        while(true){
            Socket clientSocket = serverSocket.accept();
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    processConnection(clientSocket);
                }
            });
        }
    }
    private void processConnection(Socket clientSocket){
        System.out.printf("客户端上线[%s:%d]",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
        try(BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()))
        ) {
            while(true){
                //1)接收请求并解析
                String request = bufferedReader.readLine();
                //2)根据请求计算响应
                String response = process(request);
                //3)将响应写回客户端
                bufferedWriter.write(response+"\n");
                bufferedWriter.flush();
                //打印日志
                System.out.printf("[%s:%d] req:%s resp:%s\n",clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(),request,response);
            }

        } catch (IOException e) {
            System.out.printf("客户端下线[%s:%d]",clientSocket.getInetAddress().toString(),
                    clientSocket.getPort());
        }
    }
    private String process(String request){
        return request;
    }

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

你可能感兴趣的:(JavaWeb,socket,多线程)