【JavaEE】TCP回写服务器简易翻译服务器(网络编程)

news2024/12/23 9:09:07

  • 博主简介:想进大厂的打工人
  • 博主主页:@xyk:
  • 所属专栏: JavaEE初阶

上一篇我们讲解了UDP回写服务器和简易翻译服务器,想了解本篇文章,建议先看看上篇文章,学起来会更容易一些~~传送门:(1条消息) 【JavaEE】UDP数据报套接字—实现回显服务器(网络编程)_xyk:的博客-CSDN博客

那么本篇文章我们来讲讲TCP回写服务器和简易翻译服务器~~~


目录

文章目录

一、小技巧

二、TCP网络编程

2.1 ServerSocket API

2.2 Socket API

2.3 长连接和短连接

三、用TCP编写一个客户端服务器程序

3.1 ServerSocket对象创建与构造方法

3.2 start启动方法

3.3 连接成功后的细节处理processConnect

四、TCP客户端编写

4.1 指定服务器Socket对象与构造方法

4.2 start启动方法

4.3 多个客户端运行bug

4.4 测试多个客户端

五、TCP简易翻译服务器

5.1 测试


一、小技巧

先讲个方便计算的小技巧:

  1. Thousand ==> 1KB(千)
  2. Million ==> 1MB(百万)
  3. Billion ==> 1GB(十亿)

对于简易翻译的服务器,要存储成千上万个单词,那么假设一个单词的内存占用为1kb,那么100w个单词 约等于10亿个字节,也就是1GB~~

二、TCP网络编程

TCP也是两个核心的类

ServerSocket:是给服务器用的~

Socket:既会给客户端使用,也会给服务器使用~

2.1 ServerSocket API

ServerSocket 是创建TCP服务端Socket的API

ServerSocket 构造方法:

方法签名方法说明
ServerSocket(int port)创建一个服务端流套接字Socket,并绑定到指定端口

是给服务器端指定一个端口号并绑定~~这个 Socket 和 DatagramSocket定位类似,都是在构造的时候指定一个具体的端口,让服务器绑定该端口

ServerSocket 方法:

方法签
方法说明
Socket
accept()
开始监听指定端口(创建时绑定的端口),有客户端连接后,返回一个服务端Socket
对象,并基于该Socket建立与客户端的连接,否则阻塞等待
void
close()
关闭此套接字

accept 意思就是接受~~

服务器是被动的一方

客户端是主动的一方

客户端主动向服务器发送连接请求,服务器就要同意一下~~

其实tcp连接的接受是在内核里已经完成了,但是实际上这个 accept 是应用层序层面的接受~~

socket对象如果周期很长,一般是不需要关闭的,因为关闭时一般意味着程序终结,资源自动回收

2.2 Socket API

Socket 是客户端Socket,或服务端中接收到客户端建立连接(accept方法)的请求后,返回的服务端Socket。
不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。


Socket 构造方法:

方法签名方法说明
Socket(String host, int
port)
创建一个客户端流套接字Socket,并与对应IP的主机上,对应端口的
进程建立连接

表示服务器的 ip 和 端口号

TCP是有连接的,在客户端 new Socket 对象的时候,就会尝试和指定 ip 和 端口 的目标建立连接了

Socket 方法:

方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

注意这里是字节流!!TCP是面向字节流的!!

从inputStream 这里读数据,就相当于从网卡接受~

从OutputStream 这里写数据,就相当于从网卡发送~

2.3 长连接和短连接

服务器收到一个请求,就返回一个响应,然后断开连接

这就是短连接

服务器收到一个客户端的多条请求,再一起返回响应,然后断开连接

这就是长连接

两者的使用场景有不同:短连接适用于客户端请求频率不高的场景,如浏览网页等。长连接适用于
客户端与服务端通信频繁的场景,如聊天室,实时游戏等。

对于接下来的代码,考虑到多条请求,我做出如下规定:

  • 多条请求之间以空白符分割~
  • 每一个请求都是字符串

对于后面的代码设计,是按长连接来设计的!

三、用TCP编写一个客户端服务器程序

首先还是创建一个客户端,一个服务器

先来编写服务器端

 3.1 ServerSocket对象创建与构造方法

 这个对象是专门给服务器用的,通过这个对象来获得客户端的发来的连接,可以把他当作售楼处的小哥(专门拉客)~~~

    private ServerSocket serverSocket = null;

    public TcpEchoServer(int port) throws IOException {
        serverSocket = new ServerSocket(port);
    }

3.2 start启动方法

public void start() throws IOException {
    System.out.println("服务器启动!");
    while(true) {
        Socket clientSocket = serverSocket.accept();//建立连接
        processConnect(clientSocket); // 连接成功后进行一些操作~
   }
}

用之前的serverSocket(小哥)来accept连接,此时得到一个新的clientSocket,可以把这个当作售楼处的小姐姐(专业顾问)~~~

注意!!!在这里,每个客户都需要有一个专门的小姐姐来提供服务

此时,会有个疑问:

为什么要返回一个客户端的Socket对象??用之前的那个serverSocket不行吗??

因为服务器的输出流数据来自客户端的输入流,为了获取到数据,所以使用客户端的Socket来接收

不管是客户端还是服务端Socket,都是双方建立连接以后,保存的对端信息,及用来与对方收发数据的。

3.3 连接成功后的细节处理processConnect

private void processConnection(Socket clientSocket) throws IOException {
        System.out.printf("[%s:%d] 客户端上线!\n",clientSocket.getInetAddress().toString(),
                clientSocket.getPort());
   
        try (InputStream inputStream = clientSocket.getInputStream();
             OutputStream outputStream = clientSocket.getOutputStream()) {
            
            Scanner scanner = new Scanner(inputStream);
            PrintWriter printWriter = new PrintWriter(outputStream);
            while (true){
                //1.读取请求
                if (!scanner.hasNext()){
                    // 读取的流到了结尾了(对端关闭了)
                    System.out.printf("[%s:%d] 客户端下线!\n", clientSocket.getInetAddress().toString(),
                            clientSocket.getPort());
                    break;
                }
                // 直接使用 scanner 读取一段字符串.
                String request = scanner.next();
                // 2. 根据请求计算响应
                String response = process(request);
                // 3. 把响应写回给客户端. 不要忘了, 响应里也是要带上换行的.
                printWriter.println(response);
                printWriter.flush();
                System.out.printf("[%s:%d] req: %s; resp: %s\n", clientSocket.getInetAddress().toString(),
                        clientSocket.getPort(), request, response);
            }
        }
    }

1. 客户端上线打印日志

2. 输入输出流处理

try () 这种写法,( ) 中允许写多个流对象,使用 ;来分割

括号内部为“打开文件流”操作,默认加了finally关闭该文件,让程序猿更加方便~~

3. 字节流转换字符流

通过这两个对象,实现传输过程

Scanner作为输入流,数据来源是客户端输入

PrintWriter作为输出流,数据去向是客户端的socket文件

没有这个 scanner 和 printWriter, 完全可以!!但是代价就是得一个字节一个字节扣, 找到哪个是请求的结束标记 \n

  • 多条请求之间以空白符分割~
  • 每一个请求都是字符串

为了简单编码,此处进行字符流转化

4. 建立长连接

hasNext 判定接下来还有没有数据了

如果对端关闭连接,此时hasNext返回false,循环就让它结束

如果对端有数据,就用next方法来读取字符串内容

 5. 计算响应并返回

注意这里的flush()方法,printWrier是将数据写入缓冲区(buffer),而不是写入网卡,此处需要我们进行手动刷新数据,将数据立即被写入网卡,这样才能让客户端读到响应!!!

写网卡操作称作IO操作,为了提高 IO 效率,引入缓冲区,使用缓冲区减少 IO 次数,就可以提高整体的效率~~

假设要写十次网卡,就先要把写的数据放到一个内存构成的缓冲区(buffer)中,再统一把这个缓冲区中的数据写入网卡~~

还需要注意写回的时候,要换行写回!!!

6. 关闭文件

clientSocket只是给一个连接提供服务的,这个东西还是要能够进行关闭的~~

四、TCP客户端编写

4.1 指定服务器Socket对象与构造方法

会用这个对象就行了,至于怎么通过端口获得对应的输入输出流的不重要

public class TcpEchoClient {
    private Socket socket = null;

    public TcpEchoClient(String serverIp,int port) throws IOException {
        // 这个操作相当于让客户端和服务器建立 tcp 连接.
        // 这里的连接连上了, 服务器的 accept 就会返回.
        socket = new Socket(serverIp,port);
    }

4.2 start启动方法

public void start() {
    Scanner scanner = new Scanner(System.in);
    try (InputStream inputStream = socket.getInputStream();
         OutputStream outputStream = socket.getOutputStream()){
        PrintWriter printWriter = new PrintWriter(outputStream);
        Scanner scannerFromServer = new Scanner(inputStream);
        while(true) {
            System.out.print("-> ");
            String request = scanner.nextLine();
            printWriter.println(request);
            printWriter.flush();
            String response = scannerFromServer.next();//如果为空,则需要等待对方flush
            System.out.printf("[%s : %d] 收到请求:%s, 返回响应:%s\n", socket.getInetAddress().toString(),
                    socket.getPort(), request, response);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

1. 获得服务器的输入输出流

同样的方法

2. 字节流转换字符流

3. 请求与响应

注意,这里写的时候要加上换行,因为之前我们有规定

也是需要刷新一下缓冲区,让其立即写入网卡!!!

4.3 多个客户端运行bug

此时如果我们运行多个客户端,上述的代码显然是不可以的,我们可以测试一下

我们启动两个客户端

 第一个客户端输入请求:

第二个客户端输入请求:

此时可以发现第二个客户端的数据请求,服务器没有响应!!!

那么为什么会这样?

因为我们的服务器只accept了一次,只有一个小姐姐在提供服务

 

 processConnect还没有执行完,没有退出

解决方法:

我们可以使用多线程或者线程池来解决,只要让服务器并发的去执行任务就ok了!

1.多线程写法

while (true){
            Socket clientSocket = serverSocket.accept();
            // 如果直接调用, 该方法会影响这个循环的二次执行, 导致 accept 不及时了.
            // 创建新的线程, 用新线程来调用 processConnection
            // 每次来一个新的客户端都搞一个新的线程即可!!
            Thread t = new Thread(()->{
                try {
                    processConnection(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            t.start();

2.线程池写法

ExecutorService executorService = Executors.newCachedThreadPool();
executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnection(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });

主线程只做两件事:

  • 1.accept
  • 2.创建线程

当线程创建好了,就会立即下一次调用accept,与此同时,刚创建出来的新线程,去循环处理客户端请求

4.4 测试多个客户端

 

此时,就解决了多个客户端同时在线~~~

五、TCP简易翻译服务器

package network;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author xyk的电脑
 * @version 1.0
 * @description: TODO
 * @date 2023/4/17 17:26
 */
public class TcpDictServer extends TcpEchoServer{
    private Map<String,String> map = new HashMap<>();

    public TcpDictServer(int port) throws IOException {
        super(port);

        map.put("cat","小猫");
        map.put("dog","小狗");
        map.put("pig","小猪");
    }

    @Override
    public String process(String request){
        return map.getOrDefault(request,"查无此词!");
    }

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

}

5.1 测试

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

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

相关文章

创新案例|香氛新品牌观夏如何以DTC模式仅4年实现年收过亿

国产香氛品牌观夏的DTC战略&#xff0c;正是从产品创新、全渠道布局、社交电商营销等方面创新&#xff0c;实现更高效地直接触达和转化消费者。观夏作为香氛DTC品牌的创新模式值得更多国内新品牌的关注与借鉴&#xff0c;同时也能为传统品牌的DTC转型提供全球化视野的启示。 1.…

改进YOLOv8: | ICLR 2022 | 涨点神器!Intel提出ODConv:即插即用的动态卷积 | 轻量化涨点创新必备

OMNI-DIMENSIONAL DYNAMIC CONVOLUTION ODConv实验核心代码修改文件yaml文件运行论文链接: https://openreview.net/forum?id=DmpCfq6Mg39 本文介绍了一篇动态卷积的工作:ODConv,其通过并行策略采用多维注意力机制沿核空间的四个维度学习互补性注意力。作为一种“即插即用…

3。数据结构(1)

嵌入式软件开发第三部分&#xff0c;各类常用的数据结构及扩展&#xff0c;良好的数据结构选择是保证程序稳定运行的关键&#xff0c;&#xff08;1&#xff09;部分包括数组&#xff0c;链表&#xff0c;栈&#xff0c;队列。&#xff08;2&#xff09;部分包括树&#xff0c;…

使用rt thread studio新建一个rt thread工程的详细操作说明(以stm32F411CEU6)为例

新建工程 这里以rt thread 5.0以及stm32F411CEU6芯片为例&#xff0c;创建工程 先建一个工作空间 .metadata是建工作空间自己生成的。 这时候&#xff0c;工作空间是空的&#xff0c;没有项目: 新建项目 首先下载RT-thread源文件和芯片支持包&#xff1a; 2023.05.02&#x…

NOIP2000 提高组 方格取数 线性DP 坐标优化 全局最优

&#x1f351; 算法题解专栏 &#x1f351; [NOIP2000 提高组] 方格取数 题目描述 设有 N N N \times N NN 的方格图 ( N ≤ 9 ) (N \le 9) (N≤9)&#xff0c;我们将其中的某些方格中填入正整数&#xff0c;而其他的方格中则放入数字 0 0 0。如下图所示&#xff08;见样…

延时队列的三种实现方案

延时队列的三种实现方案 什么是延时队列延时队列的应用场景基于Java DelayQueue的实现源码剖析 基于Redis的zset实现实现步骤Redis延时队列优势Redis延时队列劣势 基于RabbitMQ的延时队列实现TTL DXL(死信队列)插件实现 总结参考文章 什么是延时队列 在分布式系统中&#xff…

Go(二):包管理、通道、协程并发、互斥锁基础

包管理、协程并发基础 生成包管理文件go-mod第一步&#xff08;初始化创建包管理文件&#xff09;第二步&#xff08;导入包&#xff09; 常用命令导入远程包&#xff08;示例&#xff1a;gin&#xff09;第一步&#xff08;导入包&#xff09;第二步&#xff08;安装包&#x…

操作系统之进程同异步、互斥

引入 异步性是指&#xff0c;各并发执行的进程以各自独立的、不可预知的速度向前推进。 但是在一定的条件之下&#xff0c;需要进程按照一定的顺序去执行相关进程&#xff1a; 举例说明1&#xff1a; 举例说明2: 读进程和写进程并发地运行&#xff0c;由于并发必然导致异步性…

【Python】如何在Python中绘制带有连接线的双饼图?

文章目录 一、导入所需的库二、准备数据三、绘制双饼图3.1 创建画布和子图对象3.2 绘制大饼图3.3 绘制小饼图3.4 连接线1&#xff0c;连接大饼图的上边缘和小饼图的饼块3.5 连接线2&#xff0c;连接大饼图的下边缘和小饼图的饼块3.6 添加连接线3.7 调整子图布局 四、源代码 在 …

Linux 内核组织(kernel.org)将关闭 FTP 服务

Linux 内核组织&#xff08;kernel.org&#xff09;是一家建立于 2002 年的加利福尼亚公共福利公司&#xff0c;其目的是公开地免费分发 Linux 内核和其它开源软件。它接受 Linux 基金会的管理&#xff0c;包括技术、资金和人员支持&#xff0c;用以维护kernel.org 的运营。 Li…

2 ROS2话题通讯基础(1)

2 ROS2话题通讯基础 2.1 ROS2话题通讯介绍2.2 ROS2常用的消息类型介绍2.2.1 std_msgs消息类型2.2.2 geometry_msgs消息类型 2.3 使用C/C创建基础消息类型的话题通讯2.3.1 创建C/C发布话题信息的功能包并配置VSCode环境2.3.2 编写ROS2发布话题节点CPP文件2.3.3 配置C/C发布话题功…

【Elasticsearch】SQL操作相关

文章目录 SQL操作数据准备查询索引下的数据SQL转化为DSL(本质)SQL与DSL混合使用查看所有索引查询指定索引查看索引(表)结构where条件过滤group by分组having 对分组后的数据进行过滤order by 排序limit 限制查询数量cursor 游标->为缓存设计聚合操作支持的函数和运算比较运算…

虚拟机和Docker有什么区别?

虚拟机 对于虚拟机&#xff0c;抽象层或抽象软件成为管理程序。管理程序就是帮助虚拟机模拟物理计算机的东西。在管理程序下面&#xff0c;我们有些硬件。管理程序管理单个物理主机上不同虚拟机之间的资源分配。管理程序管理单个物理主机上不同虚拟机之间的资源分配。也就是管…

微信小程序学习实录3(环境部署、百度地图微信小程序、单击更换图标、弹窗信息、导航、支持腾讯百度高德地图调起)

百度地图微信小程序 一、环境部署1.need to be declared in the requiredPrivateInfos2.api.map.baidu.com 不在以下 request 合法域名3.width and heigth of marker id 9 are required 二、核心代码&#xff08;一&#xff09;逻辑层index.js&#xff08;二&#xff09;渲染层…

vue diff算法与虚拟dom知识整理(2) snabbdom简介并搭建开发环境

snabbdom算是diff算法 和 虚拟dom 的一个鼻租了 vue源码借鉴了snabbdom 这个单词翻译出来叫速度 命名还是用了点心的 后面是 dom 这个 我们大概去猜作者的意思 大概想表示的就是 一个比较快的dom操作 snabbdom的get地址如下 https://github.com/snabbdom/snabbdom 这里的简…

「OceanBase 4.1 体验」|快速安装部署[OBD方式]

文章目录 一、Oceanbase数据库简介1.1 核心特性1.2 系统架构1.2.1 存储层1.2.2 复制层1.2.3 均衡层1.2.4 事务层1.2.4.1 原子性1.2.4.2 隔离性 1.2.5 SQL 层1.2.5.1 SQL 层组件1.2.5.2 多种计划 1.2.6 接入层 二、OceanBase 数据库社区版部署2.1 部署方式2.2 基础环境配置2.3 通…

【华为OD机试真题】信号发射和接收(javaC++python)100%通过率 超详细代码注释 代码深度解读

信号发射和接收 知识点数组栈 单调栈时间限制: 1s 空间限制: 256MB 限定语言:不限 题目描述: 有一个二维的天线矩阵&#xff0c;每根天线可以向其他天线发射信号也能接收其他天线的信号&#xff0c;为了简化起见&#xff0c;我们约定每根天线只能向东和向南发射信号&#xf…

【ROS仿真实战】获取机器人在gazebo位置真值的三种方法(三)

文章目录 前言一. 使用ROS tf库二、 使用Gazebo Model Plugin三、 使用libgazebo_ros_p3d插件四、总结 前言 在ROS和Gazebo中&#xff0c;获取机器人的位置信息通常通过ROS消息传递进行。在这篇文章中&#xff0c;我们将介绍三种获取机器人在Gazebo中位置真值的方法&#xff1…

CTF ASCII码 密码解密题 简单

1. 题目 这次的CTF题目就是一张图片如下&#xff0c;并且说有几个蛋被打乱过。明显是一个密码学的解码题。 2. 解题思路 左边表格给出10种颜色&#xff0c;特别是第二列给出了数字0&#xff0c;种种迹象都指向了10进制。每一个蛋都有三种颜色&#xff0c;代表每个蛋都是三位…

【GORM框架】一文学会用gorm实现对单表的增删改查操作

博主简介&#xff1a;努力学习的大一在校计算机专业学生&#xff0c;热爱学习和创作。目前在学习和分享&#xff1a;数据结构、Go&#xff0c;Java等相关知识。博主主页&#xff1a; 是瑶瑶子啦所属专栏: GORM框架学习 近期目标&#xff1a;写好专栏的每一篇文章 文章目录 一、…