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

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.xdnf.cn/news/18365.html

相关文章:

  • 数字隔离器:新能源系统的安全与效能革命
  • 【GM3568JHF】FPGA+ARM异构开发板 测试命令
  • 从零搭建 React 工程化项目
  • 深入解析鸿蒙 ArkTS 中的 @Local 装饰器
  • 【解决办法】wps的word文档编辑时字体的下方出现灰色的底色如何删除
  • CAM可视化卷积神经网络
  • 深度学习:入门简介
  • AI推理革命:从Sequential Thinking到Agentic AI的演进之路——揭秘大语言模型思维进化的四重奏
  • 上海人工智能实验室开源基于Intern-S1同等技术的轻量化开源多模态推理模型
  • logback-spring.xml 文件
  • 车载 GPS 与手机导航的终极对决:谁在复杂路况下更胜一筹?
  • UE5 将纯蓝图项目转为 C++ 项目
  • MongoDB 完整指南
  • 安全运维过程文档体系规范
  • 如何轻松永久删除 Android 手机上的短信
  • Android音频学习(十四)——加载音频设备
  • 什么是Jmeter?Jmeter使用的原理步骤是什么?
  • day38-HTTP
  • 第41周——人脸图像生成
  • 携程旅游的 AI 网关落地实践
  • 计算机网络技术-第七章
  • Ingress控制器深度解析:Nginx与Traefik实战指南
  • Java的运行时数据区
  • ICMP 协议分析
  • 从零到一:RAGFlow 本地部署全攻略
  • JeeSite V5.13.0 发布,升级 Spring Boot 3.5,Cloud 2025,AI 1.0,Vite 7
  • 数据结构-HashMap
  • Vue2+Vue3前端开发_Day5
  • 【neo4j】安装使用教程
  • lesson44:Redis 数据库全解析:从数据类型到高级应用