【JavaEE】基于TCP的客户端服务器程序

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

✨哈喽进来的小伙伴们你们好耶✨

🛰️🛰️系列专栏:【JavaEE】

✈️✈️本篇内容:基于TCP的客户端服务器程序。

🚀🚀代码存放仓库gitee:JavaEE初阶代码存放

⛵⛵作者简介:一名双非本科大三在读的科班Java编程小白道阻且长星夜启程

接着上篇博客我们继续来学习网络编程套接字socket的相关知识点上篇博客写了一个最简单的UDP版本的回显服务那么这里我们来写一个稍微带点业务逻辑的翻译程序(英译汉)。

一、简单翻译程序

即客户端不变把服务代码进行调整关键的逻辑就是把响应写回给客户端。

package NetWork;

import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;

/**
 * 一个翻译功能的服务器客户端程序
 */
public class UdpDictServer extends UdpEchoServer {
    private HashMap<String,String> dict = new HashMap<>();

    public UdpDictServer(int port) throws SocketException {
        super(port);

        //简单构造几个词
        dict.put("cat","小猫");
        dict.put("dog","小狗");
        dict.put("pig","猪猪");
        dict.put("duck","鸭");
    }

    public String process(String requset){
        return  dict.getOrDefault(requset,"该词无法被翻译");
    }

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

OK那么我们首先启动服务器。

然后启动我们的客户端输入相应的英文单词观察服务器的响应。

OK客户端这边没有问题我们在点击到服务器这里观察一下。

 OK那么上一篇博客加上面的内容就是UDP版本的客户端服务器代码的全部内容了今天我们来学习一下TCP版本的客户端服务器代码。

一、TCP API

那么TCP API中也是涉及到两个核心的类SeverSocket(专门给tcp服务器用的) ;Socket(急即要给服务器用又需要给客户端用)。
老样子我们先写tcp版本的 TcpEchoServer 的代码:

public class TcpEchoServer {
    private   ServerSocket serverSocket = null;

    public  TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);//绑定端口号
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            Socket clinetSocket = serverSocket.accept();
            processConnection(clinetSocket);
        }
    }

前面基本都和UDP的差不多区别就是这里的start方法由于tcp是有连接的不是一上来就能读数据需要先建立连接(打电话);accept返回了一个socket对象accept可以理解为接电话那么接电话的前提就是有人给你打电话如果无那么这里accept就会阻塞。

OK接下来我们来写processConnection()方法的代码。

这里也分为三步:

step1: 循环处理每个请求分别返回响应。

step2: 根据请求计算响应。

step3:把这个响应返回给客户端。

  private void processConnection(Socket clinetSocket) {
        System.out.printf("[%s:%d客户端建立连接\n",clinetSocket.getInetAddress().toString(),clinetSocket.getPort());
        //接下来处理请求与响应
        try(InputStream inputStream = clinetSocket.getInputStream()){
            try(OutputStream outputStream = clinetSocket.getOutputStream()){
                Scanner scanner = new Scanner(inputStream);//读取请求
                while (true){
                    if(!scanner.hasNext()){
                        System.out.printf("[%s:%d]客户端断开连接\n",clinetSocket.getInetAddress().toString(),clinetSocket.getPort());
                        break;
                    }
                    String request = scanner.next();
                    String response = process(request);
                    //为了方便起见可以使用PrintWriter把OutputStream包裹一下
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    //刷新缓冲区如果没有这个刷新可能客户端就不能第一时间看到响应结果。
                    printWriter.flush();
                    System.out.printf("[%s:%d] rep:%s,resp:%s\n",
                            clinetSocket.getInetAddress().toString(),clinetSocket.getPort(),request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //记得关闭操作
            try {
                clinetSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

注意上述代码特意针对这里的clientSocket关闭了一下但是却没有对SeverSocket关闭那么关闭是在干什么?释放资源释放资源的前提是这个资源不再使用了对于UDP的程序serversocket来说这些socket都是贯穿始终的最迟也是随着进程一起退出但是对于TCP来说这个是每个连接的一个有很多断开也就不在需要了每次都得保证处理完的连接都进行释放。

TcpEchoServer完整代码:

package NetWork;

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;

public class TcpEchoServer {
    private   ServerSocket serverSocket = null;

    public  TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);//绑定端口号
    }

    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true){
            //由于tcp是有连接的不是一上来就能读数据需要先建立连接(打电话)
            //accept返回了一个socket对象
            Socket clinetSocket = serverSocket.accept();
            processConnection(clinetSocket);
        }
    }

    private void processConnection(Socket clinetSocket) {
        System.out.printf("[%s:%d客户端建立连接\n",clinetSocket.getInetAddress().toString(),clinetSocket.getPort());
        //接下来处理请求与响应
        try(InputStream inputStream = clinetSocket.getInputStream()){
            try(OutputStream outputStream = clinetSocket.getOutputStream()){
                Scanner scanner = new Scanner(inputStream);//读取请求
                while (true){
                    //循环的处理每个请求 分别返回响应
                    if(!scanner.hasNext()){
                        System.out.printf("[%s:%d]客户端断开连接\n",clinetSocket.getInetAddress().toString(),clinetSocket.getPort());
                        break;
                    }
                    String request = scanner.next();
                    //2、根据请求计算响应
                    String response = process(request);
                    //3、把这个响应返回给客户端
                    //为了方便起见可以使用PrintWriter把OutputStream包裹一下
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(response);
                    //刷新缓冲区如果没有这个刷新可能客户端就不能第一时间看到响应结果。
                    printWriter.flush();
                    System.out.printf("[%s:%d] rep:%s,resp:%s\n",
                            clinetSocket.getInetAddress().toString(),clinetSocket.getPort(),request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //记得关闭操作
            try {
                clinetSocket.close();
            } 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();
    }

OK接下来我们写 TcpEchoClinet 的代码。

public class TcpEchoClinet {
    private Socket socket = null;
    public TcpEchoClinet(String serverIp,int serverport) throws IOException {
        socket = new Socket(serverIp,serverport);
    }

注意这里传入的IP和端口号的含义表示不是自己绑定而是表示和这个ip端口号建立连接。

start()方法:

    public void start(){
        System.out.println("和服务器连接成功");
        Scanner scanner = new Scanner(System.in);
        try(InputStream inputStream = socket.getInputStream()){
            try(OutputStream outputStream = socket.getOutputStream()){
                while (true){
                    //要做的事情仍然是四个步骤
                    //1.从控制台读取字符串
                    System.out.println("->");
                    String request = scanner.next();
                    //2、根据读取的字符串构造请求发送给服务器
                    PrintWriter printWriter = new PrintWriter(outputStream);
                    printWriter.println(request);
                    printWriter.flush();
                    //3、从服务器读取响应解析
                    Scanner respscanner = new Scanner(inputStream);
                    String response = respscanner.next();
                    //4、把结果显示到控制台上。
                    System.out.printf("req:%s,resp:%s\n",request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

main函数

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

OK老规矩我们先启动服务器再启动客户端。

 服务器启动成功我们启动客户端可以发现和服务器连接成功

 然后我们点到服务器这边观察一下可以发现服务器已经和客户端建立连接系统已经分配了端口号。

 OK我们输入数据测试一下。

看服务器这边的响应没有问题。

 那么我们结束客户端看服务器这边的响应。

 虽然上述代码已经运行起来了但是存在一个问题就是当前的服务器同一时间只能处理一个连接我们看如何验证?

我们再次启动一个客户端看服务器这边有没有响应。

 我们发现服务器这边没有再次出现客户端建立连接的结果只有第一次的客户端程序可以得到响应。

 

那么这是为什么呢?

我们可以发现这里的代码第一次accept结束之后就会进入processConnection在processConnection中又有一个循环若processConnection里面的循环不停processConnection就无法完成就会导致外层循环无法进入下一轮也就无法第二次调用accept了。

 那么如何解决思路是什么呢?这里就得让processConnection的执行和前面的accept的执行互相不干扰。这里就得用到咋们之前学的多线程的知识啦

那么之前为什么UDP版本的程序就不需要多线程就可以处理多个请求呢?

因为UDP不需要连接只需要一个循环就可以处理所有客户端的请求但是TCP即需要处理连接又需要处理一个连接中的多个请求。

解决方案:

让主线程循环调用accept当有客户端连接上来的时候就让主线程创建一个新线程由新线程负责客户端的若干个请求这个时候多个线程看上去是同时执行的。

这里我们新写一个类TcpThreadEchoServer在原有的TcpEchoServer基础上修改以下部分代码即可。

            Thread t = new Thread(()->{
                processConnection(clinetSocket);
            });
            t.start();

TcpThreadEchoServer代码:

package NetWork;

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;

public class TcpThreadEchoServer {
    //    private ServerSocket listenSocket = null;
    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 clinetSocket = serverSocket.accept();
            Thread t = new Thread(()->{
                processConnection(clinetSocket);
            });
            t.start();
        }
    }

    private void processConnection(Socket clinetSocket) {
        System.out.printf("[%s:%d客户端建立连接\n",clinetSocket.getInetAddress().toString(),clinetSocket.getPort());
        try(InputStream inputStream = clinetSocket.getInputStream()){
            try(OutputStream outputStream = clinetSocket.getOutputStream()){
                Scanner scanner = new Scanner(inputStream);//读取请求
                while (true){
                    if(!scanner.hasNext()){
                        System.out.printf("[%s:%d]客户端断开连接\n",clinetSocket.getInetAddress().toString(),clinetSocket.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] rep:%s,resp:%s\n",
                            clinetSocket.getInetAddress().toString(),clinetSocket.getPort(),request,response);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //记得关闭操作
            try {
                clinetSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

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

OK我们再次启动多线程版本的服务器代码然后启动两个客户端发现没有问题。

 OK那么到这里我们的网络编程socket就已经全部学习完毕了下一节博主将会持续更新TCP/IP五层协议栈的详解感谢小伙伴的一键三连支持

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java服务器