当前位置: 首页 > news >正文

JavaEE 初阶第二十期:网络编程“通关记”(二)

专栏:JavaEE初阶起飞计划

个人主页:手握风云

目录

一、TCP流套接字编程

1.1. ServerSocket

1.2. Socket

1.3. 示例


一、TCP流套接字编程

1.1. ServerSocket

        ServerSocket是创建TCP服务端Socket的API。

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

1.2. Socket

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

  • Socket构造方法
方法签名方法说明
Socket(String host, int port)创建⼀个客户端流套接字Socket,并与对应IP的主机 上,对应端口的进程建立连接
  • Socket方法
方法签名方法说明
InetAddress getInetAddress()返回套接字所连接的地址
InputStream getInputStream()返回此套接字的输⼊流
OutputStream getOutputStream()返回此套接字的输出流

1.3. 示例

        TCP是有连接的,不能一上来就读取数据,需要先处理连接。建立连接的过程,是在操作系统内核里已经完成了,只需要在代码中把操作系统里建立好的连接拿过来用就行。

        SeverSocket是服务端专用的套接字,仅用于在服务端监听客户端的连接请求,是连接的 “管理者”。Socket是通信的端点,既可以作为客户端的套接字(主动发起连接),也可以作为服务端接受连接后生成的套接字(用于与客户端实际通信)。

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 EchoServer {private ServerSocket serverSocket;public EchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器启动!");while (true) {// 如果客户端没有建立连接就会阻塞Socket socket = serverSocket.accept();processConnection(socket);}}/*** 处理客户端连接的方法* @param socket 客户端套接字对象,用于与客户端进行通信*/private void processConnection(Socket socket) {System.out.printf("[%s:%d] 客户端上线!\n", socket.getInetAddress().toString(), socket.getPort());try (InputStream inputStream = socket.getInputStream();  // 从socket获取输入流,用于读取客户端发送的数据OutputStream outputStream = socket.getOutputStream()) {Scanner in = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);while (true) {// 读取请求并解析if (!in.hasNext()) {// 针对客户端下线的逻辑,比如客户端结束了System.out.printf("[%s:%d] 客户端下线!\n", socket.getInetAddress().toString(), socket.getPort());break;}String request = in.next();// 根据请求计算响应String response = process(request);// 把响应写回到客户端writer.println(response);}} catch (IOException e) {e.printStackTrace();}}/*** 处理请求字符串的方法* 该方法接收一个字符串参数,直接返回该参数* * @param request 需要处理的字符串请求* @return 返回与输入相同的字符串*/private String process(String request) {// 直接返回输入的请求字符串return request;}public static void main(String[] args) throws IOException {EchoServer server = new EchoServer(9090);server.start();}
}

        我们前面也写过一个UDP服务器,两个服务器端口号一样,即使同时启动,也不会产生冲突,因为两个服务器的协议不同。

import java.net.Socket;public class EchoClient {private Socket socket;public EchoClient(String severIP, int serverIP) {socket = new Socket();}
}

        这里的构造方法与UDP不同的是,因为UDP协议本身是无连接的,不记录对端的信息。而TCP是一上来需要连接的,但连接的过程在操作系统内核完成的,但还是需要告诉操作系统服务器的端口和IP是什么。

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;/*** @author gao* @date 2025/8/21 15:28*/public class EchoClient {private Socket socket;public EchoClient(String severIP, int serverPort) throws IOException {socket = new Socket(severIP, serverPort);}public void start() {System.out.println("客户端启动!");Scanner in = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {Scanner scannerNet = new Scanner(inputStream);PrintWriter writer = new PrintWriter(outputStream);while (true) {// 从控制台读取用户的输入System.out.print("> ");String request = in.next();// 构造请求发送给服务器writer.println(request);// 读取服务器的响应if (!scannerNet.hasNext()) {System.out.println("服务器断开了连接!");break;}String response = scannerNet.next();// 响应显示到控制台上System.out.println(response);}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {EchoClient client = new EchoClient("127.0.0.1", 9090);client.start();}
}

        虽然服务器和客户端都写完了,但我们一启动并输入之后,就会发现,服务器这边并没有任何响应。

        这时我们要分析到底是客户端没把数据发送出去,还是服务器收到了没有正确处理。我们可以在服务器代码里面的阻塞后面加上个打印,再运行观察,还是没有。说明上面的hasNext()没有解除阻塞,大概率就是客户端没发来数据。

// 发送数据的代码
writer.println(request);

        第一个问题,此处的代码执行到了,但是此处的println只是写到了缓冲区,没有写到网卡,也就没有真正发送。缓冲区,英文名称"buffer",通常情况下就是一个“内存空间”,计算机读取内存比读取网卡要快很多。假设要很多次写入,就要把多次的数据一次写入网卡。如果缓冲区满了,就会自动传到网卡,或者刷新缓冲区以强行切入到外设。

while (true) {// 读取请求并解析if (!in.hasNext()) {// 针对客户端下线的逻辑,比如客户端结束了System.out.printf("[%s:%d] 客户端下线!\n", socket.getInetAddress().toString(), socket.getPort());break;}System.out.println("服务器收到了数据");String request = in.next();// 根据请求计算响应String response = process(request);// 把响应写回到客户端writer.println(response);writer.flush();
}
while (true) {// 从控制台读取用户的输入System.out.print("> ");String request = in.next();// 构造请求发送给服务器writer.println(request);writer.flush();// 读取服务器的响应if (!scannerNet.hasNext()) {System.out.println("服务器断开了连接!");break;}String response = scannerNet.next();// 响应显示到控制台上System.out.println(response);
}

        第二个问题,当前程序中,存在“文件泄露”。每循环一次,就会触发一次打开文件的操作。一个服务器,不知道要处理多少个客户端,导致打开操作频繁。我们只需要在processConnection最后加上finally代码块。

public void start() throws IOException {System.out.println("服务器启动!");while (true) {// 如果客户端没有建立连接就会阻塞Socket socket = serverSocket.accept();processConnection(socket);}
}
try {socket.close();
} catch (IOException e) {e.printStackTrace();
}

        但在UDP中的socket对象只有一个,不会频繁创建销毁,生命周期很长,只要1服务器运行,就随时能使用。

        第三个问题,程序运行效果的问题。如果在启动多个客户端的情况下就会出现只有一个客户端有连接。在IntelliJ IDEA中要想启动多个实例,需要设置一下。下拉右上角的框,点击"Edit Configurations",再点击Modify options,再勾选上“Allow multiple instances”。

        这样我们就可以启动两个客户端,当我们在第一个客户端里面输入任意内容会得到返回结果,但第二个客户端输入内容就没有结果。

        这是因为服务器里面的start方法里面有一层循环,而start方法里面的porcessConnection方法里面还有一层循环。只要第一个客户端不退出,processConnection就不会退出。而UDP里面只有一层循环就不会出现上述问题。

        我们可以利用之前的多线程知识,把porcessConnection方法放在其他线程里面。

public void start() throws IOException {System.out.println("服务器启动!");while (true) {// 如果客户端没有建立连接就会阻塞Socket socket = serverSocket.accept();Thread t = new Thread(() ->{processConnection(socket);});t.start();}
}

        其实多线程最初被发明出来的概念就是为了给每个客户端分配线程,由这个线程负责客户端的请求和响应。

http://www.dtcms.com/a/343798.html

相关文章:

  • 微前端qiankun框架,子页面图标样式错乱问题,显示为X
  • Halcon那些事:什么是动态阈值,如何用dyn_threshold分割图片
  • Elasticsearch Rails 实战全指南(elasticsearch-rails / elasticsearch-model)
  • 集成电路学习:什么是K-NN最近邻算法
  • Seaborn数据可视化实战:Seaborn图表定制与数据可视化入门
  • AI+虚拟仿真:以科技之光照亮希望的田野
  • 课小悦系列智能耳机上市,用硬核科技为教育赋能
  • 学习嵌入式第二十三天——数据结构——栈
  • Qt5 文件与数据处理详解
  • NETSDK1045 当前 .NET SDK 不支持将 .NET 8.0 设置为目标。请将 .NET 5.0 或更低版本设置为目标,或使用支持
  • 【FPGA Interlaken协议】
  • 服务器与客户端
  • AI服务器介绍
  • FPGA设计中的信号完整性量化与优化:探索高速数字系统的关键路径
  • 20.9 QLoRA微调实战:1.5B参数Whisper-large-v2在24GB显存实现中文语音识别,CER骤降50%!
  • 企业微信新版搞了个AI功能
  • 构效关系(Structure-Activity Relationship, SAR)分析的标准方法:R基团结构解析
  • Amazon Lambda:无服务器时代的计算革命,解锁多样化应用场景
  • MATLAB入门教程
  • 【PSINS工具箱】MATLAB例程,二维平面上的组合导航,EKF融合速度、位置和IMU数据,4维观测量
  • 如何创建一个Cloudfalare worker项目?
  • 当下一次攻击发生前:微隔离如何守护高敏数据,防范勒索攻击下的数据泄露风险!
  • 【变压器老化仿真】matlab实现变压器老化过程的行为模拟与源码编写
  • 24.解构赋值
  • Qt5 的跨平台开发详细讲解
  • 嘉立创eda元件导出到kicad库以使用
  • Spring Ai 1.0.1中存在的问题:使用MessageChatMemoryAdvisor导致System未被正确的放在首位
  • 拆解本地组策略编辑器 (gpedit.msc) 的界面和功能
  • 相似度、距离
  • 【C++】--函数参数传递:传值与传引用的深度解析