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

news2024/11/27 2:48:54

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

🛰️🛰️系列专栏:【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五层协议栈的详解,感谢小伙伴的一键三连支持!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/167037.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE初阶】第二节.进程篇

文章目录 前言 一、操作系统 二、进程 2.1 进程的概念 2.2 进程的管理​​​​​​​​​​​​​​ 2.3 PCB 2.3.1 PCB里面的一些属性 2.3.2 进程的调度 2.3.3 进程的虚拟地址空间 2.3.4 进程间通信 总结 前言 本节内容我们继续对JavaEE的有关内容进行学习&#xff0c;…

汽车智能化,集度做加法

CES2023刚刚落下帷幕&#xff0c;这场名为“国际消费电子展”的业界盛会&#xff0c;近几年重心正明显转向智能汽车及其周边产业链。在2022年的CES上&#xff0c;集度与英伟达宣布合作&#xff0c;也透露了智能汽车研发的相关计划。而在本届CES之前、2022年末的广州车展上&…

一个关于image访问图片跨域的问题

一、背景 项目中遇到一个问题&#xff0c;同一个图片在 dom 节点中使用了 img 标签来加载&#xff0c;同时由于项目使用了 ThreeJS 3D 渲染引擎&#xff0c;在加载纹理时使用了 TextureLoader 来加载了同一张图片&#xff0c;而由于图片是在阿里云服务器上的&#xff0c;所以最…

SourceTree 拉取、重置提交、回滚、变基与合并

SourceTree的重置当前分支到此次提交 使用场景&#xff1a;“我想把已提交未推送的修改撤销” 使用模式说明软合并软合并是指将此次提交回滚到指定提交位置&#xff0c;但这个过程中会将修改过的文件暂存到暂存区。混合合并混合合并是指将此次提交回滚到指定的位置&#xff0c…

本来挺喜欢刷《剑指offer》的.......(第十一天)

跟着博主一起刷题 这里使用的是题库&#xff1a; https://leetcode.cn/problem-list/xb9nqhhg/?page1 目录剑指 Offer 66. 构建乘积数组剑指 Offer 68 - I. 二叉搜索树的最近公共祖先剑指 Offer 68 - II. 二叉树的最近公共祖先剑指 Offer 66. 构建乘积数组 剑指 Offer 66. 构建…

使用react-bmapgl绘制区域并判断是否重叠

需求如下&#xff1a; 在react项目中使用百度地图实现区域&#xff08;电子围栏&#xff09;的绘制绘制的区域类型为&#xff1a;1、多边形 2、圆形可绘制多个区域区域不能有重叠可重新编辑区域 代码如下: index.tsx import { useCallback, useEffect, useState } from rea…

Python入门实践(二)——变量的使用

文章目录变量1、变量的命名和使用1.1、避免命名错误2、字符串2.1、修改字符串大小写2.2、合并&#xff08;拼接&#xff09;字符串2.3、使用制表符或换行符来添加空白2.4、删除空白3、数字3.1、整数3.2、浮点数3.3、使用str()避免类型错误4、注释变量是对一种数据结构的命名&am…

2023年基建工程(设计规划施工)经验分享,超多干货

为了彻底打通从工程外业勘探调查、数据资料整理&#xff0c;到内业详细设计之间的一系列障碍&#xff0c;结合工程外业调查的特点&#xff0c;基于安卓&#xff08;Android&#xff09;操作系统&#xff0c;精心打磨推出了“外业精灵”移动端应用软件。 该系统把工程外业探勘、…

MPP数据库简介及架构分析

目录什么是MPP&#xff1f;特性并行处理超大规模数据仓库真正适合什么典型的分析工作量数据集中化线性可伸缩性MPP架构技术特性数据库架构分析Shared EverythingShared DiskShare MemoryShared NothingShared Nothing数据库架构优势什么是MPP&#xff1f; MPP (Massively Paral…

分享88个C源码,总有一款适合您

C源码 分享88个C源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c;大家下载后可以看到。 源码下载链接&#xff1a;https://pan.baidu.com/s/1TT87gt66kn5BtLqgRUTlUQ?pwdwje5 提取码…

Java图形化界面---JOptionPane

目录 一、JOptionPane的介绍 二、JOptionalPane的使用 &#xff08;1&#xff09;消息对话框 &#xff08;2&#xff09; 确认对话框 &#xff08;3&#xff09;输入对话框 &#xff08;4&#xff09;选项对话框 一、JOptionPane的介绍 通过JOptionPane可以非常方便地创建…

SpringCloud复习之Sleuth+Zipkin链路追踪实战

文章目录写作背景为什么要有链路监控SpringCloud SleuthZipkin能做什么上手实战启动一个Zipkin Server微服务集成SleuthZipkin写作背景 前面复习了SpringCloud Netflix的几个核心组件&#xff0c;包括Eureka、Ribbon、Feign、Hystrix、Zuul&#xff0c;并进行了Demo级别的实战…

高精度减法【c++】超详细讲解

前言 大家学过高精度加法之后&#xff0c;可能已经知道高精度减法的实现方法了吧 如果你还没有学过高精度加法的话&#xff0c;请点击这里&#xff08;很详细的&#xff09;—>高精度加法【C实现】详解 最大的问题 最大的问题莫过于负数问题了。其他方法和加法一样。 负…

4.二级缓存解析

文章目录1. 二级缓存配置2. 二级缓存结构3. 二级缓存命中条件4. 缓存空间的理解5. 二级缓存执行流程二级缓存也称作是应用级缓存&#xff0c;与一级缓存不同的&#xff0c;是它的作用范围是整个应用&#xff0c;而且可以跨线程使用。所以二级缓存有更高的命中率&#xff0c;适合…

从南丁格尔图到医学发展史

可视化中&#xff0c;前端用于表现不同类目的数据在总和中的占比的场景&#xff0c;往往会采用饼图。 针对数据大小相近&#xff0c;南丁格尔图的呈现会更加美观。 南丁格尔图&#xff0c;又称玫瑰图&#xff0c;是由弗罗伦斯南丁格尔发明。 弗洛伦斯南丁格尔 开创了护理事业…

二、django中的路由系统

django中的路由系统 django中路由的作用和路由器类似&#xff0c;当一个用户请求Django站点的一个页面时&#xff0c;是路由系统通过对url的路径部分进行匹配&#xff0c;一旦匹配成功就导入并执行对应的视图来返回响应。 django如何处理请求 当一个请求来到时&#xff0c;d…

SpringSecurityOauth2架构Demo笔记

总体分为SpringSecurityOauth2授权码模式演示和密码模式演示 一直下一步,依赖手动导入,SpringBoot版本改成2.2.5.RELEASE,JDK版本1.8 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xml…

Open3D 点云投影至指定球面(Python版本)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 假设球体的相关参数:中心为 C ( x c , y c , z c ) C(x_c,y_c,z_c)

【数据结构和算法】栈—模拟实现Stack和栈相关算法题

文章目录栈的定义Stack模拟实现相关算法题1.栈的压入弹出序列2.逆波兰表达式(后缀表达式)⭐1.什么是逆波兰表达式?如何转换成逆波兰表达式逆波兰表达式如何计算3.有效的括号总结栈的定义 栈作为一种数据结构&#xff0c;是一种只能在一端进行插入和删除操作的特殊线性表。它按…

华为MPLS跨域C2方案实验配置

MPLS隧道——跨域解决方案C1、C2讲解_静下心来敲木鱼的博客-CSDN博客_route-policy rr permit node 10 if-match mpls-labelhttps://blog.csdn.net/m0_49864110/article/details/127634890?csdn_share_tail%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId…