Java 网络编程(二)—— TCP流套接字编程

TCP 和 UDP 的区别

在传输层,TCP 协议是有连接的,可靠传输,面向字节流,全双工
而UDP 协议是无连接的,不可靠传输,面向数据报,全双工

有连接和无连接的区别是在进行网络通信的时候,通信双方有没有保存对端的地址信息,即假设 A 和 B 进行通信,A 保存了 B 的地址信息,B 也保存了 A 的地址信息,此时双方都知道和谁建立了连接,这就是有连接的通信,在之前的 UDP 数据报套接字编程中就提到过 UDP 是无连接的,所以在发送数据报的时候要加上对端的信息,防止丢包。

可靠传输是通过各种手段来防止丢包的出现,而不可靠传输则没有做任何处理直接把数据报传输过去,但是可靠传输不意味着能 100% 把数据报完整无误地传输给对方,只是尽可能降低丢包发生的概率,并且可靠传输是要使用很多手段来保持的,所以付出的代价相比于不可靠传输要大。

面向字节流就是以字节为单位来进行数据的传输,面向数据报就是以数据报为单位进行数据的传输。

全双工就是通信的双发可以同时给对方发送数据,但是半双工是指双方只有一方可以发送数据。

TCP流套接字 API 介绍

ServerSocket

ServerSocket 是TCP服务端Socket 的API

构造方法:

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

ServerSocket 方法:

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

Socket

Socket 是客户端Socket 或者是 服务端那边收到客户端建立连接的请求(通过 accept() 方法)返回的Socket 对象。

不管是客户端还是服务端的Socket 对象,他们都保留了对端的地址信息,这也是TCP协议有连接的体现。

Socket 构造方法:

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

Socket 方法:

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

回显服务器

首先在回显服务器的构造方法里初始化我们的ServerSocket

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

然后就是服务器启动运行的代码了:在面对多个客户端的时候,我们可以使用线程池来进行处理。
这里使用Executors.newCachedThreadPool()是不固定线程的个数的线程池,这样可以灵活地处理多个客户端的请求。

    public void start() throws IOException {System.out.println("服务器启动...");ExecutorService executorService = Executors.newCachedThreadPool();while(true) {//与客户端建立连接Socket clientSocket = serverSocket.accept();//处理客户端发出的多个请求executorService.submit(() -> {try {processClient(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});}}

处理请求

我们通过了一个方法processClient来封装了处理请求的逻辑

如何进行数据的获取和写入操作?
可以通过输入流和输出流来处理getInputStreamgetOutputStream

try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) 

为了更加方便地使用这两个流对象,我们进行了进一步的封装:

//对输入流和输出流进行进一步的封装,方便我们的使用
Scanner scanner = new Scanner(inputStream);
PrintWriter writer = new PrintWriter(outputStream);

由于客户端可能发来的不止一个请求,我们可以使用循环来处理一下,在循环体中,我们处理请求有三个步骤,首先获取请求解析请求,然后计算响应,最后发送响应

            while(true) {if(!scanner.hasNext()) {System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}//解析请求String request = scanner.next();//计算响应String response = process(request);//发送响应writer.println(response);//因为此时的响应数据还在缓存区里,所以需要使用 flush 来将内存的数据发送出去writer.flush();System.out.printf("[%s:%d] request:%s response:%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);}

由于这里是回显服务器,所以计算响应的代码是直接返回字符串就可以了

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

最后当客户端没有请求的时候,我们需要断开此次连接,释放资源,避免资源的泄漏

 finally {//当请求处理完的时候记得关闭服务器与客户端的连接,防止资源泄漏clientSocket.close();}

最终代码

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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class TcpEchoServer {private ServerSocket serverSocket;public TcpEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动...");ExecutorService executorService = Executors.newCachedThreadPool();while(true) {//与客户端建立连接Socket clientSocket = serverSocket.accept();//处理客户端发出的多个请求executorService.submit(() -> {try {processClient(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});}}private void processClient(Socket clientSocket) throws IOException {//获取输入流和输出流try(InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {System.out.printf("[%s:%d] 客户端上线\n",clientSocket.getInetAddress(),clientSocket.getPort());//对输入流和输出流进行进一步的封装,方便我们的使用Scanner scanner = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);while(true) {if(!scanner.hasNext()) {System.out.printf("[%s:%d] 客户端下线\n",clientSocket.getInetAddress(),clientSocket.getPort());break;}//解析请求String request = scanner.next();//计算响应String response = process(request);//发送响应writer.println(response);//因为此时的响应数据还在缓存区里,所以需要使用 flush 来将内存的数据发送出去writer.flush();System.out.printf("[%s:%d] request:%s response:%s\n",clientSocket.getInetAddress(),clientSocket.getPort(),request,response);}} catch (IOException e) {throw new RuntimeException(e);} finally {//当请求处理完的时候记得关闭服务器与客户端的连接,防止资源泄漏clientSocket.close();}}private String process(String request) {return request;}public static void main(String[] args) throws IOException {TcpEchoServer server = new TcpEchoServer(9090);server.start();}
}

客户端

首先在客户端构造方法建立于服务器的连接:

    public TcpEchoClient(String serverIP, int port) throws IOException {//与服务器建立连接socket = new Socket(serverIP,port);}

运行逻辑

首先用户从控制台输入数据,然后发送请求,接着等待服务器的响应并接收响应然后打印响应的内容即可。

    public void start() {try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {//对输入流和输出流进行进一步的封装Scanner scanner = new Scanner(System.in);Scanner scanner2 = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);while(true) {//发送多个请求和接收多个响应if(!scanner.hasNext()) {break;}//发送请求String request = scanner.next();writer.println(request);writer.flush();//接收响应String response = scanner2.next();System.out.println(response);}} catch (IOException e) {throw new RuntimeException(e);}}

这里要注意用户通过控制台输入数据,我们要使用的是Scanner(System.in)
当我们要发送数据的时候是使用 Socket 的 getOutputStream 方法来获取对应的输出流对象,为了便于使用所以我们又使用 PrintWriter 来进一步封装输出流,来打印响应
在发送请求的时候我们需要使用 Socket 的 getInputStream 方法来获得输入流对象,为了方便使用,所以使用Scanner(inputStream)进一步封装。

最终代码

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;public TcpEchoClient(String serverIP, int port) throws IOException {//与服务器建立连接socket = new Socket(serverIP,port);}public void start() {try(InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {//对输入流和输出流进行进一步的封装Scanner scanner = new Scanner(System.in);Scanner scanner2 = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);while(true) {//发送多个请求和接收多个响应if(!scanner.hasNext()) {break;}//发送请求String request = scanner.next();writer.println(request);writer.flush();//接收响应String response = scanner2.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", 9090);client.start();}
}

细节说明

在我们使用PrintWriter 的 writer.println(xxx)之后,我们的数据其实还保留在缓存区中,也就是还没发出去,我们需要通过flush() 方法来刷新缓存区的数据,才能将数据真正发送到对端去。


我们不可以使用writer.print这种没有自动添加换行符的方法,因为我们在接收数据的时候,使用的是Scanner 的 next()方法,next() 是要接收到空白符(包括换行符,制表符,翻页符…)才停止接收的,如果你使用 print 来发送数据,这时候的数据是没有带任何空白符的,那么就不会停止接收数据而是继续等待空白符的到来,这时候服务器就无法处理客户端的请求:如下图:

服务器就阻塞在 下图标红的代码里:
在这里插入图片描述

客户端被阻塞在接收响应的代码里:
在这里插入图片描述


你在客户端的控制台输入的回车不算进数据的换行符里,控制台输入的回车时,只是将数据交给了客户端程序,并不会自动将这些数据转换为网络流中的换行符。

换一句话说,控制台的回车只是结束你在控制台的输入,并不会自动在数据末尾加上换行符

效果展示

在这里插入图片描述
在这里插入图片描述

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

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

相关文章

机器学习—正则化和偏差或方差

正则化参数的选择对偏差和方差的影响 用一个四阶多项式,要用正则化拟合这个模型,这里的lambda的值是正则化参数,它控制着你交易的金额,保持参数w与训练数据拟合,从将lambda设置为非常大的值的示例开始,例如…

【笔记】企业架构TOGAF 10的架构从4A增加至6A

背景 谈谈学习TOGAF 10的总结和笔记,说说较9.2版本有哪些变化。最直观的当属从原来的4A架构升级到6A架构,单独从原来的4A中提炼形成了安全架构、系统架构两个概念,谈谈理解并回顾总结一下学习笔记。 TOGAF 10 将安全架构单独列为一种架构&…

AI写作(十)发展趋势与展望(10/10)

一、AI 写作的崛起之势 在当今科技飞速发展的时代,AI 写作如同一颗耀眼的新星,迅速崛起并在多个领域展现出强大的力量。 随着人工智能技术的不断进步,AI 写作在内容创作领域发挥着越来越重要的作用。据统计,目前已有众多企业开始…

【模块一】kubernetes容器编排进阶实战之资源管理核心概念

kubernetes 资源管理核心概念 k8s的设计理念—分层架构 CRI-container runtime interface-容器运行接口 CNI-container network interface-容器网络接口 CSI-container storage interface-容器存储接口 k8s的设计理念—API设计原则 https://www.kubernetes.org.cn/kubernete…

DBeaver中PostgreSQL数据库显示不全的解决方法

本文介绍在DBeaver中,连接PostgreSQL后,数据库显示不全的解决方法。 最近,在DBeaver中连接了本地的PostgreSQL数据库。但是连接后打开这个数据库时发现,其所显示的Databases不全。如下图所示,Databases只显示了一个pos…

ElasticSearch学习笔记二:使用Java客户端

一、前言 在上一篇文章中&#xff0c;我们对ES有了最基本的认识&#xff0c;本着实用为主的原则&#xff0c;我们先不学很深的东西&#xff0c;今天打算先学习一下ES的Java客户端如何使用。 二、创建项目 1、普通Maven项目 1、创建一个Maven项目 2、Pom文件 <dependenc…

MySQL8 安装教程

一、从官网下载mysql-8.0.18-winx64.zip安装文件&#xff08; 从 https://dev.mysql.com/downloads/file/?id484900 下载zip版本安装包 mysql-8.0.18-winx64.zip 解压到本地磁盘中&#xff0c;例如解压到&#xff1a;D盘根目录&#xff0c;并改名为MySQL mysql-8.0.34-winx6…

如何将LiDAR坐标系下的3D点投影到相机2D图像上

将激光雷达点云投影到相机图像上做数据层的前融合&#xff0c;或者把激光雷达坐标系下标注的物体点云的3d bbox投影到相机图像上画出来&#xff0c;都需要做点云3D点坐标到图像像素坐标的转换计算&#xff0c;也就是LiDAR 3D坐标转像素坐标。 看了网上一些文章都存在有错误或者…

【Pikachu】XML外部实体注入实战

若天下不定&#xff0c;吾往&#xff1b;若世道不平&#xff0c;不回&#xff01; 1.XXE漏洞实战 首先写入一个合法的xml文档 <?xml version "1.0"?> <!DOCTYPE gfzq [<!ENTITY gfzq "gfzq"> ]> <name>&gfzq;</name&…

游戏引擎学习第13天

视频参考:https://www.bilibili.com/video/BV1QQUaYMEEz/ 改代码的地方尽量一张图说清楚吧,懒得浪费时间 game.h #pragma once #include <cmath> #include <cstdint> #include <malloc.h>#define internal static // 用于定义内翻译单元内部函数 #…

(一)Ubuntu20.04服务器端部署Stable-Diffusion-webui AI绘画环境

一、说明 cup型号&#xff1a; Intel(R) Celeron(R) CPU G1610 2.60GHz 内存大小&#xff1a; 7.5Gi 356Mi 4.6Gi 1.0Mi 2.6Gi 6.8Gi Swap: 4.0Gi 0B 4.0Gi 显卡型号&#xff1a;NVIDIA P104-100 注意&#xff1a…

Python Tornado框架教程:高性能Web框架的全面解析

Python Tornado框架教程&#xff1a;高性能Web框架的全面解析 引言 在现代Web开发中&#xff0c;选择合适的框架至关重要。Python的Tornado框架因其高性能和非阻塞I/O特性而备受青睐。它特别适合处理大量并发连接的应用&#xff0c;比如聊天应用、实时数据处理和WebSocket服务…

ubuntu20.04安装anaconda

在anaconda的官网&#xff08;Anaconda | The Operating System for AI&#xff09;或者清华镜像源网站&#xff08;Index of /anaconda/archive/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror&#xff09;中下载对应的anaconda版本 可以在网页直接下载或者通过命…

平衡二叉搜索树之 红黑 树的模拟实现【C++】

文章目录 红黑树的简单介绍定义红黑树的特性红黑树的应用 全部的实现代码放在了文章末尾准备工作包含头文件类的成员变量和红黑树节点的定义 构造函数和拷贝构造swap和赋值运算符重载析构函数findinsert【重要】第一步&#xff1a;按照二叉搜索树的方式插入新节点第二步&#x…

【设计模式】行为型模式(四):备忘录模式、中介者模式

行为型模式&#xff08;四&#xff09;&#xff1a;备忘录模式、中介者模式 7.备忘录模式&#xff08;Memento&#xff09;7.1 通俗易懂的解释7.2 具体步骤7.3 代码示例7.3.1 定义发起人7.3.2 定义备忘录7.3.3 定义管理者7.3.4 客户端7.3.5 输出 7.4 总结 8.中介者模式&#xf…

Thinkpad E15 在linux下升级 bios

安装xubuntu 24.04后&#xff0c;发现键盘的Fn按键全都无法使用&#xff0c;在Windows环境下是正常的&#xff0c;按说是驱动的问题&#xff0c;网上也有说可以通过升级BIOS解决&#xff0c;所以打算升级看看&#xff0c;升级有风险。 参考&#xff1a; https://blog.stigok.c…

Java学习Day61:薄纱王灵官!(Nginx review)

1.Nginx是什么 Nginx是一款轻量级、高性能&#xff0c;并发性好的HTTP和反向代理服务器 2.功能 2.1反向代理 正向代理是指客户端向代理服务器发送请求&#xff0c;代理服务器代表客户端去访问目标服务器。简单来说&#xff0c;正向代理是客户端的代理&#xff0c;客户端通过…

MATLAB用到的矩阵基础知识(矩阵的乘和矩阵的逆)

1. 矩阵乘法 方法: 设第一个矩阵为 A A A,第二个矩阵为 B B B,则 A A A的第一行乘 B B B的第一列,先想乘再相加,作为目标矩阵的一个元素。 前提条件: 所以我们可以看到矩阵相乘的前提条件:第一个矩阵的列数等于第二个矩阵的行数。否则,我们就无法进行行和列的相乘。 最…

Oracle OCP认证考试考点详解082系列22

题记&#xff1a; 本系列主要讲解Oracle OCP认证考试考点&#xff08;题目&#xff09;&#xff0c;适用于19C/21C,跟着学OCP考试必过。 105. 第105题&#xff1a; 题目 解析及答案&#xff1a; 题目翻译&#xff1a; 关于Oracle数据库中的事务请选择两个正确的陈述&#xf…