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

news/2025/3/15 3:42:25/

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

🛰️🛰️系列专栏:【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.ppmy.cn/news/12685.html

相关文章

《Buildozer打包实战指南》第二节 安装Kivy和Buildozer

目录 2.1 安装Kivy 2.2 安装Buildozer 2.3 验证安装 2.4 一点建议 Python是Ubuntu系统中自带的&#xff0c;我们在桌面上右键打开终端&#xff0c;然后输入python3 --version就可以看到Ubuntu系统中的Python版本了。 可以看到&#xff0c;Python的版本是3.10.6。虽然Python…

python中split()函数的用法详解

一、split()函数的简单应用 1.split()函数 split() 通过指定分隔符对字符串进行切片&#xff0c;如果参数 num 有指定值&#xff0c;则分隔 num1 个子字符串。它是按指定的分隔符&#xff0c;把一个字符串分隔成指定数目的子字符串&#xff0c;然后把它们放入一个列表中&…

基础数学(二)两数之和 三数之和

目录 两数之和_牛客题霸_牛客网 三数之和_牛客题霸_牛客网 两数之和_牛客题霸_牛客网 给出一个整型数组 numbers 和一个目标值 target&#xff0c;请在数组中找出两个加起来等于目标值的数的下标&#xff0c;返回的下标按升序排列。 &#xff08;注&#xff1a;返回的数组下标从…

一行代码加速Pytorch推理速度6倍

一行代码加速Pytorch推理速度6倍 Torch-TensorRT 是 PyTorch 的集成&#xff0c;它利用 NVIDIA GPU 上的 TensorRT 推理优化。 只需一行代码&#xff0c;它就提供了一个简单的 API&#xff0c;可在 NVIDIA GPU 上提供高达 6 倍的性能加速。 话不多说, 线上代码, 再解释原理!!…

LeetCode 334. 递增的三元子序列(C++)

思路&#xff1a; 1.对于任何位置&#xff0c;只需要满足i < j < k &#xff0c;使得 nums[i] < nums[j] < nums[k]&#xff1b;所以只需要记录每个元素的左边最小值leftMin[i]和右边最大值rightMax[i]&#xff0c;满足条件leftMin[i-1]<nums[i]<rightMax[i1…

SSL协议未开启是什么意思?

SSL协议未开启是指服务器中的服务没有开启或者没有SSL模块。在互联网中&#xff0c;数据交互传输基于http明文协议&#xff0c;随着互联网的不断发展&#xff0c;http协议展现出它的缺陷&#xff0c;通过http协议传输的数据容易被攻击者窃取、篡改或仿冒。为适应新形势下的网络…

MyBatis 详解 (2) -- 增删改操作

MyBatis 详解 2 -- 增删改操作前言一、准备工作1.1 创建数据库和表1.2 添加实体类1.3 添加 mapper 接口 (数据持久层)1.4 创建与接口对应的 xml 文件二、增加操作2.1 默认返回受影响的行数2.2 特殊的新增&#xff1a;返回自增 id三、删除操作四、修改操作五、实现完整交互5.1 添…

【Python学习】条件和循环

前言 往期文章 【Python学习】列表和元组 【Python学习】字典和集合 条件控制 简单来说&#xff1a;当判断的条件为真时&#xff0c;执行某种代码逻辑&#xff0c;这就是条件控制。 那么在讲条件控制之前&#xff0c;可以给大家讲一个程序员当中流传的比较真实的一个例子…