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

Java 网络编程

文章目录

  • UDP Socket API
    • DatagramSocket
    • DatagramPacket
    • 例子:UDP版本的回显服务器-客户端
  • TCP Socket API
    • ServerSocket
    • Socket
    • 例子:TCP版本的回显服务器-客户端

UDP Socket API

DatagramSocket

这是一个 socket 类,本质上相当于一个文件,在系统中,还有一种特殊的 socket 文件,对应到网卡设备。

构造一个 DatagramSocket 对象,就相当于打开了一个内核中的 socket 文件

构造方法:

构造方法说明
DatagramSocket()构造一个数据报套接字,并将其绑定到本地主机上的任何可用端口
DatagramSocket(int port)构造一个数据报套接字,并将其绑定到本地主机上的指定端口

普通方法:

方法说明
void receive(DatagramPacket p)从该套接字接收数据报,此方法会一直阻塞,直到接收到数据报为止
void send(DatagramPacket p)从此套接字发送数据报
void close()关闭此数据报套接字

DatagramPacket

表示一个 UDP 数据报,UDP 是面向数据报的协议,传输数据就是以 DatagramPacket 为基本单位

构造方法说明
DatagramPacket(byte buf[], int length)构造一个 DatagramPacket,用于接收长度为 length 的数据包,length 参数必须小于或等于 buf.length
DatagramPacket(byte buf[], int length, SocketAddress address)构造一个数据报,用于将长度为 length 的数据报发送到指定主机上的指定端口号。length 参数必须小于或等于 buf.length
DatagramPacket(byte buf[], int offset, int length, SocketAddress address)构造一个数据报,用于将偏移量为 ioffsetlength 长度的数据报发送到指定主机上的指定端口号。length 参数必须小于或等于 buf.length
DatagramPacket(byte buf[], int length, InetAddress address, int port)构造一个数据报,用于将长度为 length 的数据包发送到指定主机上的指定端口号。length 参数必须小于或等于 buf.length
方法说明
InetAddress getAddress()返回发送此数据报或接收数据报的机器的 IP 地址
SocketAddress getSocketAddress()获取此数据包发送到或来自的远程主机的 SocketAddress(通常为IP地址+端口号)
int getPort()返回发送的数据报中的接收端主机端口号,或者接收的数据报中的发送端主机端口号
byte[] getData()返回数据缓冲区
int getLength()返回要发送的数据长度或接收的数据长度

InetSocketAddress

创建 DatagramPacket 时,需要 SocketAddress,该对象通过 InetSocketAddress 创建。

构造方法说明
InetSocketAddress(InetAddress addr, int port)根据 IP 地址和端口号创建套接字地址

例子:UDP版本的回显服务器-客户端

服务器:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;public class UDPEchoServer {private final DatagramSocket socket;public UDPEchoServer(int port) throws SocketException {socket = new DatagramSocket(port);}public void start() throws IOException {System.out.println("服务器 启动!");while (true) {// 读取客户端发来的请求DatagramPacket requestPacket = new DatagramPacket(new byte[4096], 4096); // 空的,用于接收响应socket.receive(requestPacket);// 对请求进行解析,把 DatagramPacket 转成 StringString request = new String(requestPacket.getData(), 0, requestPacket.getLength());// 处理响应String response = process(request);// 把响应构造成 DatagramPacket 对象DatagramPacket responsePacket = new DatagramPacket(response.getBytes(),response.getBytes().length, requestPacket.getSocketAddress());// 把响应发送给客户端socket.send(responsePacket);System.out.printf("[%s:%d] req=%s;resp=%s\n", requestPacket.getAddress().toString(),requestPacket.getPort(), request, response);}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {UDPEchoServer server = new UDPEchoServer(8000);server.start();}
}

客户端:

import java.io.IOException;
import java.net.*;
import java.util.Scanner;public class UDPEchoClient {private final DatagramSocket socket;public UDPEchoClient() throws SocketException {// 客户端端口一般自动分配socket = new DatagramSocket();}public void start() throws IOException {Scanner scanner = new Scanner(System.in);while (true) {// 客户端从控制台读取数据System.out.print("> ");String request = scanner.next();// 构造 DatagramPacketDatagramPacket requestPacket = new DatagramPacket(request.getBytes(),request.getBytes().length, InetAddress.getByName("127.0.0.1"), 8000);// 发送给服务器socket.send(requestPacket);// 从服务器读取响应DatagramPacket responsePacket = new DatagramPacket(new byte[4096], 4096);socket.receive(responsePacket);// 把响应数据转成字符串String response = new String(responsePacket.getData(), 0, responsePacket.getLength());System.out.printf("req: %s; resp: %s\n", request, response);}}public static void main(String[] args) throws IOException {UDPEchoClient client = new UDPEchoClient();client.start();}
}

因为这里的 socket 创建出来就会一直用,所以是伴随程序的整个生命周期的,所以不需要手动调用 close() 去关闭

技巧:Windows 使用 netstat -ano | findstr "端口号",可以查找占用该端口的进程pid

TCP Socket API

ServerSocket

ServerSocket 是创建 TCP 服务端 Socket 的 API

构造方法说明
ServerSocket(int port)创建绑定到指定端口的服务器套接字
方法说明
Socket accept()侦听要与此套接字建立的连接并接受该连接。该方法将阻塞,直到建立连接为止。
void close()关闭此套接字,相当于发送 FIN

Socket

构造方法说明
Socket(String host, int port)创建流套接字并将其连接到命名主机上的指定端口号
方法说明
InetAddress getInetAddress()返回套接字所连接的地址
int getPort()返回此套接字所连接的远程端口号
InputStream getInputStream()返回此套接字的输入流
OutputStream getOutputStream()返回此套接字的输出流

例子:TCP版本的回显服务器-客户端

服务端:

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 final ServerSocket serverSocket;public TCPEchoServer(int port) throws IOException {serverSocket = new ServerSocket(port);}public void start() throws IOException {System.out.println("服务器 启动!");while (true) {Socket clientSocket = serverSocket.accept();// 创建新线程去完成工作,主线程继续acceptThread t = new Thread(() -> {try {processConnect(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});t.start();}}// 短连接:一个连接只进行一次数据交互(一个请求 + 一个响应)// 长连接:一个连接进行多次数据交互(N 个请求 + N 个响应)public void processConnect(Socket clientSocket) throws IOException {System.out.printf("[%s:%d] 建立连接\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());try (InputStream inputStream = clientSocket.getInputStream();OutputStream outputStream = clientSocket.getOutputStream()) {Scanner scanner = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);// 长连接while (true) {if (!scanner.hasNext()) {System.out.printf("[%s:%d] 断开连接\n", clientSocket.getInetAddress().toString(), clientSocket.getPort());break;}// 读取请求并解析String request = scanner.next();// 根据请求计算响应String response = process(request);// 把响应写回客户端printWriter.println(response); // 注意补上空白符,如换行,对面next读的时候要读到空白符才往下走printWriter.flush();System.out.printf("[%s:%d] req: %s, resp: %s\n", clientSocket.getInetAddress().toString(),clientSocket.getPort(), request, response);}} finally {// 一定要记得关闭 clientSocket// 因为它是 accept 创建出来的,每来一个连接就会创建一个,占用文件描述符资源clientSocket.close();}}public String process(String request) {return request;}public static void main(String[] args) throws IOException {TCPEchoServer server = new TCPEchoServer(8000);server.start();}
}

客户端:

package network;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 final Socket socket;public TCPEchoClient() throws IOException {socket = new Socket("127.0.0.1", 8000); // 此时触发三次握手}public void start() throws IOException {Scanner scanner = new Scanner(System.in);try (InputStream inputStream = socket.getInputStream();OutputStream outputStream = socket.getOutputStream()) {Scanner scannerNet = new Scanner(inputStream);PrintWriter printWriter = new PrintWriter(outputStream);while (true) {// 从控制台读取用户输入System.out.print("> ");String request = scanner.next();// 把请求发送给服务器printWriter.println(request); // 注意补上空白符,如换行,对面next读的时候要读到空白符才往下走printWriter.flush();// 从服务器读取响应String response = scannerNet.next();System.out.printf("req: %s; resp: %s\n", request, response);}}}public static void main(String[] args) throws IOException {TCPEchoClient client = new TCPEchoClient();client.start();}
}

上述服务端代码还可以使用线程池改进:

public void start() throws IOException {System.out.println("服务器 启动!");// 使用线程池,适合写自动扩容版本的ExecutorService service = Executors.newCachedThreadPool();while (true) {Socket clientSocket = serverSocket.accept();service.submit(() -> {try {processConnect(clientSocket);} catch (IOException e) {throw new RuntimeException(e);}});}
}

注意

这里的 TCP 服务器之所以使用多线程,是因为处理的是长连接,与客户端建立好连接之后,什么时候断开连接不确定,这一个连接里要处理多少请求,也不确定,单线程处理连接里的循环的时候,就无法 accept 新的连接了。

如果是短连接,每次连接只处理一个请求,就可以不使用多线程了。

相关文章:

  • 2023亚太杯数学建模C题思路分析 - 我国新能源电动汽车的发展趋势
  • EEG 脑电信号处理合集(2): 信号预处理
  • CentOS 7 使用cJSON 库
  • Navicat 技术指引 | 适用于 GaussDB 的模型功能
  • C# APS.NET CORE 6.0 WEB API IIS部署
  • C语言之内存函数
  • 第十二章 : Spring Boot 日志框架详解
  • 云原生Kubernetes系列 | Kubernetes静态Pod的使用
  • 基本数据结构二叉树(1)
  • qml ParticleSystem3D使用介绍
  • 初始化与反初始化
  • Linux学习教程(第八章 Linux用户和用户组管理)三
  • 在ASP.NET Core 中使用 .NET Aspire 消息传递组件
  • rabbitMq确认机制之ConfirmType
  • 数据结构与算法【B树】的Java实现+图解
  • Android开源项目规范总结
  • create-react-app项目添加less配置
  • ECMAScript6(0):ES6简明参考手册
  • java小心机(3)| 浅析finalize()
  • JDK9: 集成 Jshell 和 Maven 项目.
  • LeetCode29.两数相除 JavaScript
  • PermissionScope Swift4 兼容问题
  • Python打包系统简单入门
  • React-生命周期杂记
  • SpringCloud(第 039 篇)链接Mysql数据库,通过JpaRepository编写数据库访问
  • vue学习系列(二)vue-cli
  • 设计模式(12)迭代器模式(讲解+应用)
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 系统认识JavaScript正则表达式
  • UI设计初学者应该如何入门?
  • 翻译 | The Principles of OOD 面向对象设计原则
  • 积累各种好的链接
  • #stm32整理(一)flash读写
  • #传输# #传输数据判断#
  • #微信小程序:微信小程序常见的配置传旨
  • (07)Hive——窗口函数详解
  • (4.10~4.16)
  • (Matalb回归预测)PSO-BP粒子群算法优化BP神经网络的多维回归预测
  • (webRTC、RecordRTC):navigator.mediaDevices undefined
  • (附源码)ssm教材管理系统 毕业设计 011229
  • (南京观海微电子)——I3C协议介绍
  • (转)fock函数详解
  • .gitattributes 文件
  • .NET 4.0网络开发入门之旅-- 我在“网” 中央(下)
  • .NET Conf 2023 回顾 – 庆祝社区、创新和 .NET 8 的发布
  • .net framework4与其client profile版本的区别
  • .考试倒计时43天!来提分啦!
  • @EnableWebMvc介绍和使用详细demo
  • @private @protected @public
  • @reference注解_Dubbo配置参考手册之dubbo:reference
  • @WebService和@WebMethod注解的用法
  • [ vulhub漏洞复现篇 ] GhostScript 沙箱绕过(任意命令执行)漏洞CVE-2019-6116
  • [【JSON2WEB】 13 基于REST2SQL 和 Amis 的 SQL 查询分析器
  • [Ariticle] 厚黑之道 一 小狐狸听故事
  • [corCTF 2022] CoRJail: From Null Byte Overflow To Docker Escape