回显服务器(基于TCP)

目录

API学习

ServerSocket

Socket

服务端

思路分析

具体实现

客户端

思路分析

具体实现

运行测试

问题分析 

修改优化

完整代码


在学习了基于UDP实现的回显服务器后,我们学习基于TCP实现的回显服务器

API学习

ServerSocket

ServerSocket是创建TCP服务端Socket的API

构造方法:

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

常用方法:

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

Socket

Socket是客户端Socket或服务端中接收到客户端连接(accept方法)的请求后,返回的服务端Socket

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

构造方法:

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

常用方法:

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

服务端

思路分析

对于服务端,要实现的内容有:

1. 与客户端建立连接

2. 接收客户端发送的请求、读取解析请求

3. 根据请求计算数据响应

4. 将响应返回给客户端

由于服务器要等到客户端发送请求时才能进行接收、解析、计算响应等操作,而服务器不知道客户端什么时候发送请求,因此服务器需要一直“待命”,等待客户端发送请求 

具体实现

1.首先我们需要创建一个ServerSocket对象,并通过构造方法来指定服务器要绑定的端口号

import java.io.IOException;
import java.net.ServerSocket;public class TcpEchoServer {private ServerSocket socket = null;public TcpEchoServer(int port) throws IOException {socket = new ServerSocket(port);}
}

2.接下来,我们实现客户端与服务端连接的建立

TCP是有连接的,因此,在进行通信之前,客户端和服务器之间需要建立连接(就像打电话一样,需要一端拨号,另一端接听后,双方才能进行通话)

除了内核建立连接外,还需要服务端进行“接听”(accept操作),才能进行通信

    public void start() throws IOException {System.out.println("启动服务器");while (true){//通过accept方法来“接听”Socket clientSocket = socket.accept();}}

3. 然后通过实现processConnection方法来处理每一次连接建立后的通信(客户端与服务器之间的多次请求响应交互)

此时的实现过程与 基于UDP实现的回显服务器类似,循环读取请求、接收请求并解析、根据请求计算响应最后将响应返回给客户端

需要注意的是,TCP是面向字节流的,传输的基本单位是字节

    //处理连接建立后客户端与服务器之间的多次请求响应private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){//循环读取请求while (true){Scanner scanner = new Scanner(inputStream);if(!scanner.hasNext()){//读取完毕,断开连接System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}//读取请求并解析String request = scanner.next();//根据请求计算响应String response = process(request);//将响应返回给客户端//由于直接通过outputStream进行写入不方便在响应末尾添加\n//因此可以使用PrintWriter进行写入(使用其中的println方法)PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);//不要忘记刷新操作printWriter.flush();//打印日志,观察程序执行效果System.out.printf("[%s:%d] req: %s, resp: %s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request, response);}}catch (IOException e){throw new RuntimeException(e);}finally {clientSocket.close();}}//根据请求计算响应public String process(String request) {return request;}

客户端

思路分析

 对于客户端需要实现的内容有:

1. 从控制台读取用户输入的内容

2. 将内容构造成TCP请求,并发送给服务器

3. 等待服务器响应,当接收到服务器响应时,解析响应内容

4. 显示响应内容

具体实现

1.首先我们创建一个Socket对象,并在构造方法中传入服务器的ip和端口号

import java.io.IOException;
import java.net.Socket;public class TcpEchoClient {private Socket socket = null;public TcpEchoClient(String serverIp, int serverPort) throws IOException {socket = new Socket(serverIp, serverPort);}
}

2. 接下来我们实现客户端的启动(循环读取请求、发送请求、读取响应最后打印响应内容)

    public void start(){System.out.println("启动客户端");try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){Scanner scanner = new Scanner(System.in);//从控制台读取要发送的请求数据Scanner scannerNetwork = new Scanner(inputStream);//从服务器读取响应PrintWriter writer = new PrintWriter(outputStream);//通过PrintWriter进行写入操作while (true){//从控制台读取请求数据System.out.print("请输入:");if(!scanner.hasNext()){//读取完毕,退出循环break;}String request = scanner.next();//读取请求//将请求发送给服务器writer.println(request);//使用println方法来发送数据,使请求末尾带有\nwriter.flush();//刷新缓冲区,使数据及时发送出去//从服务器读取响应String response = scannerNetwork.next();//显示响应内容System.out.println(response);}}catch (IOException e){throw new RuntimeException(e);}}

运行测试

在编写完代码后,我们同时运行服务器和客户端,并输入请求观察代码是否存在问题:

启动服务器:

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

启动客户端:

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

运行测试结果:

服务器:

客户端:

 再运行一个客户端:

运行结果:

此时第二个客户端无响应,当关闭第一个客户端后,此时第二个客户端才能正常工作

问题分析 

为什么会出现这种情况呢?

通过观察服务器代码,我们可以发现:当第一个客户端与服务器建立连接后,服务器就进入processConnection,此时会在scanner.hasNext 阻塞,等待客户端的请求,接收请求后,解析计算响应并返回,然后再次等待请求....,直到该客户端退出后,才能结束processConnection方法,再次进行“接听”

因此,当有新的客户端与服务器建立连接时,虽然新的客户端与服务器在内核层面建立了TCP连接,但服务端未“接听”,因此连接未成功建立,也就无法进行交互。第二个客户端发送的请求存储在服务器的接收缓冲区中,当第一个客户端退出后,服务器就会立即处理第二个客户端之前发送的请求

那应该如何修改代码,使得服务器能够同时与多个客户端建立连接呢?

修改优化

此时使用单线程已经无法满足我们的需求,因此我们考虑使用多线程,主线程负责执行accecpt,每当有一个客户端进行连接,就分配一个新的线程,由这个新线程为客户端提供服务

    public void start() throws IOException {System.out.println("启动服务器");while (true){//通过accept方法来“接听”Socket clientSocket = socket.accept();Thread woker = new Thread(()->{try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});woker.start();}}

此时服务器就能够处理多个客户端的请求了

然而,当客户端比较多时,服务器就会频繁地创建和销毁线程,此时,我们可以考虑使用线程池

完整代码

服务端代码:

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoServer {private ServerSocket socket = null;public TcpEchoServer(int port) throws IOException {socket = new ServerSocket(port);}public void start() throws IOException {System.out.println("启动服务器");ExecutorService pool = Executors.newCachedThreadPool();while (true){//通过accept方法来“接听”Socket clientSocket = socket.accept();/* Thread woker = new Thread(()->{try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});woker.start();*/pool.submit(new Runnable() {@Overridepublic void run() {try {processConnection(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}}});}}//处理连接建立后客户端与服务器之间的多次请求响应private void processConnection(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()){//循环读取请求while (true){Scanner scanner = new Scanner(inputStream);if(!scanner.hasNext()){//读取完毕,断开连接System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}//读取请求并解析String request = scanner.next();//根据请求计算响应String response = process(request);//将响应返回给客户端//由于直接通过outputStream进行写入不方便在响应末尾添加\n//因此可以使用PrintWriter进行写入(使用其中的println方法)PrintWriter printWriter = new PrintWriter(outputStream);printWriter.println(response);//不要忘记刷新操作printWriter.flush();//打印日志,观察程序执行效果System.out.printf("[%s:%d] req: %s, resp: %s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request, response);}}catch (IOException e){throw new RuntimeException(e);}finally {clientSocket.close();}}//根据请求计算响应public String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9019);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;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.println("启动客户端");try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()){Scanner scanner = new Scanner(System.in);//从控制台读取要发送的请求数据Scanner scannerNetwork = new Scanner(inputStream);//从服务器读取响应PrintWriter writer = new PrintWriter(outputStream);//通过PrintWriter进行写入操作while (true){//从控制台读取请求数据System.out.print("请输入:");if(!scanner.hasNext()){//读取完毕,退出循环break;}String request = scanner.next();//读取请求//将请求发送给服务器writer.println(request);//使用println方法来发送数据,使请求末尾带有\nwriter.flush();//刷新缓冲区,使数据及时发送出去//从服务器读取响应String response = scannerNetwork.next();//显示响应内容System.out.println(response);}}catch (IOException e){throw new RuntimeException(e);}}public static void main(String[] args) throws IOException {TcpEchoClient client = new TcpEchoClient("127.0.0.1", 9019);client.start();}
}

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

07:指针

指针 1、什么是指针1.1、地址的定义1.2、指针的作用 2、指针的分类2.1、基本类型指针2.2、指针和数组2.2.1、指针和一维数组2.2.1.1、一维数组名2.2.1.2、下标和指针的关系2.2.1.3、确定一维数组需要几个参数2.2.1.4、指针变量的运算2.2.1.5、指针变量占用几个字节 2.2.2、动态…

2024-02-08 Unity 编辑器开发之编辑器拓展1 —— 自定义菜单栏

文章目录 1 特殊文件夹 Editor2 在 Unity 菜单栏中添加自定义页签3 在 Hierarchy 窗口中添加自定义页签4 在 Project 窗口中添加自定义页签5 在菜单栏的 Component 菜单添加脚本6 在 Inspector 为脚本右键添加菜单7 加入快捷键8 小结 1 特殊文件夹 Editor ​ Editor 文件夹是 …

[职场] 公安管理学就业方向及前景 #媒体#笔记#笔记

公安管理学就业方向及前景 公安管理学是中国普通高等学校本科专业。本专业文理兼收,学制4年,授予法学学士学位。本专业培养掌握马克思主义基本原理,政治坚定,坚持党和国家的路线、方针、政策,具有良好职业素养、科学素…

嵌入式Qt 第一个Qt项目

一.创建Qt项目 打开Qt Creator 界面选择 New Project或者选择菜单栏 【文件】-【新建文件或项目】菜单项 弹出New Project对话框,选择Qt Widgets Application 选择【Choose】按钮,弹出如下对话框 设置项目名称和路径,按照向导进行下一步 选…

应急响应实战笔记01入侵排查篇(5)

第5篇:勒索病毒自救指南 前言 经常会有一些小伙伴问:中了勒索病毒,该怎么办,可以解密吗? 第一次遇到勒索病毒是在早几年的时候,客户因网站访问异常,进而远程协助进行排查。登录服务器&#x…

Python算法题集_排序链表

Python算法题集_排序链表 题148:排序链表1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【冒泡大法】2) 改进版一【列表排序】3) 改进版二【数值归并排序】4) 改进版三【快慢指针归并排序】 4. 最优算法 本文为Python算法题集之一的…

Confluence CVE-2023-22527利用工具

介绍 Confluence CVE 2021,2022,2023 利用工具,支持命令执行,哥斯拉,冰蝎 内存马注入 支持 Confluence 版本:CVE-2021-26084,CVE-2022-26134,CVE_2023_22515,CVE-2023-2…

中年中产程序员从西安出发到海南三亚低成本吃喝万里行:西安-南宁-湛江-雷州-徐闻-博鳌-陵水-三亚-重庆-西安(2.游玩过程)

文章大纲 出发时间:Day1-1月25日星期四,西安飞南宁路途中:Day2-1月26日星期五,南宁-湛江-住雷州(曾经支教过的地方)【晚上买徐闻到海安新港】路途中:Day3-1月27日星期六,雷州-徐闻渡…

前端JavaScript篇之Promise解决了什么问题、Promise.all和Promise.race的区别的使用场景

目录 Promise解决了什么问题Promise.all和Promise.race的区别的使用场景 Promise解决了什么问题 Promise 解决了 JavaScript 中回调地狱的问题。在传统的回调函数中,如果需要依次执行多个异步操作,就需要使用嵌套的回调函数,这样会导致代码难…

UE5 播放本地MP3、MP4

1.创建一个媒体播放器 2.如创建视频,勾选。 它会多一个媒体纹理给你 3.1 设置音频 在一个actor上添加“媒体音频组件” “音频媒体播放器”赋值给它 3.2播放音频 添加一个音频媒体播放器变量, 赋值 地址使用绝对地址 4.1设置视频 UI上创建一个imag…

图表自动化开篇

目录 前言: 使用 Canvas 或者 SVG 渲染 选择哪种渲染器 代码触发 ECharts 中组件的行为 前言: 图表自动化一直以来是自动化测试中的痛点,也是难点,痛点在于目前越来越多公司开始构建自己的BI报表平台但是没有合适的自动化测试…