news 2026/4/20 2:52:15

java之网络编程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
java之网络编程

由于我们之前已经写多了socket对udq的实现,所以我们这节,主要将重心放在Tcp之上

socket对tcp

ServerSocket

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

ServerSocket 构造方法:

ServerSocket 方法:

因为tcp是有连接的,所以在一个客户端给服务器传输信息前,我们需要先建立连接,这样才能传输信息,这时候就需要用到servesocket了。

当创建好servesocket后,如果有客户端想和服务器建立连接,发送了连接请求,这个时候服务器的应用程序是不需要做出任何操作,由服务器的系统内核直接就完成了连接建立的流程,这样客户端和服务器之间建立了逻辑上的连接,这个连接在内核中以连接对象的形式存在。我们就将该连接对象放入已完成连接队列中(accept队列)。创建好的连接对象就会在该内核的队列中(这个队列是每个 serverSocket 都有一个这样的队列) 排队.
而该服务器要想和已经连接好的客户端进行通讯的话,还需要通过 accept函数从accept队列中取出连接对象,通过该对象就创建socket用于通信。
当我们accept时 如果连接对象在队列中不存在则会堵塞,有的话则会接受且创建一个socket对象用于通讯。我们可以认为上述的模型是一个生产者消费者模型。

socket

Socket 可以是客户端Socket,也可以是服务器中通过连接对象建立的 Socket。 不管是客户端还是服务端的Socket,都是双方建立连接以后,用来双方进行通讯的工具

这个不仅是客户端的socket的构造方法,还是客户端的连接建立请求

以下是它的普通方法

这里的输入流和输出流就是我们发送消息的工具,它们是普通的字节流,我们之前文件io里学到的所有方法在这里也全能用,通过该输入流读取数据就能读取到另一个主机发送的数据,同理输出流就往里面写数据就能发送到另一个主机的输入流里。

实现回显服务器和客户端

服务器端的逻辑如下:
1. 服务器启动:
1. 创建一个 ServerSocket 对象,绑定到指定的端口(10002),并监听客户端连接。
2. 打印 "服务器启动" 表示服务器已启动并正在等待客户端连接。
2. 接受客户端连接:
1. 调用 serverSocket.accept() 方法等待并接受客户端的连接请求。
2. 创建一个新的 Socket 对象,表示与客户端的连接。
3. 处理客户端任务:
1. 调用 startTask 方法处理客户端的请求。
2. 在 startTask 方法中,获取客户端的输入流和输出流。
4. 读取客户端请求并发送响应:
1. 使用 Scanner 从输入流读取客户端发送的数据。
2. 使用 PrintWriter 向输出流发送响应数据。
3. 在循环中不断读取客户端发送的数据,直到 scanner.hasNext() 返回 false(相当于客户端断开连接,之后会特殊说明这里的细节)。
4. 对于每次读取的数据,调用 process 方法进行处理(在这个例子中,process 方法只是简单地返回传入的字符串)。
5. 将处理后的字符串发送回客户端。
5. 关闭连接:
1. 在 finally 块中关闭客户端套接字,确保资源被正确释放。

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;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class TcpServe {
public static void main(String[] args) throws IOException {
System.out.println("服务器启动");
ServerSocket serverSocket = new ServerSocket(10002);
Socket socket = serverSocket.accept();
startTask(socket);
}

public static void startTask(Socket socket) throws IOException {
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while (true) {
if (!scanner.hasNext())
break;
String string = scanner.next();
String string1 = process(string);
printWriter.println(string1);
printWriter.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
socket.close();
}
}

public static String process(String string) {
return string;
}

}

客户端的逻辑如下:

客户端启动:

1.创建一个 Socket 对象,连接到指定的服务器地址(192.168.50.173)和端口(10002)。

打印 "客户端上线" 表示客户端已启动并正在连接服务器。

2.获取输入输出流:

获取 Socket 对象的输入流和输出流,用于接收服务器的响应和发送客户端的请求。

读取用户输入并发送请求:

使用 Scanner 从控制台读取用户输入的字符串。

使用 PrintWriter 将用户输入的字符串发送到服务器。

3.接收服务器响应:

使用 Scanner 从输入流读取服务器发送的响应。

将服务器的响应打印到控制台。

4.循环处理:

在 while (true) 循环中不断读取用户输入,发送请求,接收响应,直到程序被手动终止或出现异常。

5.关闭连接:

在 finally 块中关闭客户端套接字,确保资源被正确释放。

public class TcpClient {
public static void main(String[] args) throws IOException {
System.out.println("客户端上线");
Socket socket = new Socket("10.90.113.182", 10002);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
Scanner scanner = new Scanner(System.in);
Scanner scanner1 = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
while (true) {
String string = scanner.next();
printWriter.println(string);
printWriter.flush();
System.out.println(scanner1.next());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
socket.close();
}
}
}

这里服务器的ip地址可以用root 的ip地址也可以用你自己主机的ip地址。

但这里还是要说几个特殊的点:

1.这里由于我们采用的是printwrite,所以要用flush冲刷数据,否则可能有bug;

这里是输入流部分的知识点(不清楚的可以看前面io文件操作那节博客)

2.在 TCP 网络编程中,Scanner.hasHext() 方法的行为与它在处理本地输入(如控制台输入或文件输入)时的行为有所不同。
这里由于scanner是跟另一个主机的输出流联系在一块,所以没有明确的消息边界。因此,在执行Scanner.hasnext()时如果该输入流没数据那么该方法就会阻塞,直到输入流中有数据可用则返回出true。如果客户端断开了连接,连接的输出流则结束了,断开了连接,此时就有明确的消息边界,Scanmer.hasNext()方法就因为没数据会返回 false,所以退出。(有消息边界时没数据则返回false,有数据则返回true;没消息边界时没数据则堵塞,有数据则返回true,不可能返回false)

3.对于该程序,只支持单个客户端,不支持多个客户端,如果想要达到多个,则要用多线程。

public class TcpServe {
public static void main(String[] args) throws IOException {
System.out.println("服务器启动");
ServerSocket serverSocket = new ServerSocket(10010);
while (true) {
Socket socket = serverSocket.accept();
Thread thread = new Thread(() -> {
try {
TcpServe.startTask(socket);
} catch (Exception e) {
e.printStackTrace();
}
});
thread.start();
}
}

public static void startTask(Socket socket) throws IOException {
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.flush();
while (true) {
if (!scanner.hasNext())
break;
String string = scanner.next();
String string1 = process(string);
printWriter.println(string1);
printWriter.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
socket.close();
}
}

public static String process(String string) {
return string;
}

}

4.对于socket以及它带的两个字节流文件我们都需要手动close,因为它们并不是全程跟随整个程序,有可能中途这些文件就不用了,所以就需要close对应的socket文件和字节流文件,否则一直不关而我们一直使用该程序,文件就会持续累积导致文件泄露问题。
而servesocket就不需要,因为它跟之前的Datagramsocket是全程跟随的,所以没必要多此一举。

5.对于这双方通信我们现在还是只限于一个主机内部,如果要两个主机进行交流就需要部署一个云服务器或者在一个局域网内部才能进行交流。(大概讲一下思路:先把你写的IDEA文件打包为一个jar包,然后在Xshell上打开(需要有云服务器)开始运行服务器,然后将你的客户端中的服务器ip地址改为云服务器的ip地址,这样就可以远程的实现俩个主机的交互了)

6.如果我们程序线程创建销毁太频繁了,还可以用线程池,这样能提高效率

public class TcpServe {
public static void main(String[] args) throws IOException {
System.out.println("服务器启动");
ServerSocket serverSocket = new ServerSocket(10017);
ExecutorService executorService = Executors.newCachedThreadPool();
while (true) {
Socket socket = serverSocket.accept();
executorService.submit(() -> {
try {
TcpServe.startTask(socket);
} catch (Exception e) {
e.printStackTrace();
}
});
}
}

public static void startTask(Socket socket) throws IOException {
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()) {
Scanner scanner = new Scanner(inputStream);
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.flush();
while (true) {
if (!scanner.hasNext())
break;
String string = scanner.next();
String string1 = process(string);
printWriter.println(string1);
printWriter.flush();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
socket.close();
}
}

public static String process(String string) {
return string;
}

}

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/20 2:50:14

手把手教你:在UVM验证环境中安全使用disable fork管理并发线程

UVM验证环境中精准管理并发线程的实践指南 在复杂芯片验证场景中,UVM验证平台往往需要同时运行数十个并发线程——从激励生成到数据采集,从协议检查到覆盖率收集。这些线程如同交响乐团中的不同乐器,需要指挥家精准控制每个声部的起止时机。而…

作者头像 李华
网站建设 2026/4/20 2:13:17

CLAUDE.md:90%人用错了

CLAUDE.md:90%人用错了 我用这个文件,让AI记住我项目的所有秘密。先讲个故事 上周,团队新来一个实习生。 我让他帮改个功能,3小时没搞定。后来我自己上,10分钟改完了。 差距在哪? 不是我比他强,…

作者头像 李华
网站建设 2026/4/20 2:12:24

Ostrakon-VL像素终端部署:离线环境无网络依赖运行方案

Ostrakon-VL像素终端部署:离线环境无网络依赖运行方案 1. 项目背景与特点 1.1 像素特工终端简介 Ostrakon-VL像素终端是一款专为零售与餐饮场景设计的离线多模态识别系统。它基于Ostrakon-VL-8B模型开发,采用独特的8-bit像素风格界面,将复…

作者头像 李华
网站建设 2026/4/20 2:05:20

MySQL触发器实现级联删除效果_MySQL触发器替代外键操作

在 MySQL 中,订单表的 DELETE 触发器无法删除关联的订单项,因触发器禁止修改自身触发表;唯一可行方案是在 orders 表上创建 AFTER DELETE 触发器执行子表删除,但其不参与事务回滚,易致数据不一致,故推荐优先…

作者头像 李华