java socket (回顾)
背景与目标
最近在项目中重温了 Socket 编程,鉴于长期未使用,许多细节已较为模糊,因此通过一个小例子进行回顾。
实现目标
实现一个简单的通信模型:客户端向服务器端发送消息,服务器端读取信息后回复客户端,如此循环往复。
服务端代码实现
服务端监听指定端口,等待客户端连接,接收消息后通过控制台输入回复内容。
package com.dai.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
/**
* 服务端实现
* @author 代长亚
* @version V1.0
*/
public class Server {
private ServerSocket serverSocket = null;
private Socket socket = null;
public Server() {
try {
// 启动服务器监听
serverSocket = new ServerSocket(8888);
System.out.println("服务器端已经启动.....");
while (true) {
// 等待客户端连接
socket = serverSocket.accept();
// 获取输入流
DataInputStream dis = new DataInputStream(socket.getInputStream());
// 获取输出流
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
System.out.println("客户端发来信息:" + dis.readUTF());
System.out.print("请求回复信息:");
Scanner sc = new Scanner(System.in);
dos.writeUTF(sc.nextLine());
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Server();
}
}客户端代码实现
客户端主动连接服务器,发送控制台输入的消息,并接收服务端的回复。
package com.dai.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
/**
* 客户端实现
* @author 代长亚
* @version V1.0
*/
public class Client {
private Socket socket = null;
public Client() {
try {
System.out.println("客户端已经启动.....");
while (true) {
// 连接服务器
socket = new Socket("127.0.0.1", 8888);
// 获取输入流
DataInputStream dis = new DataInputStream(socket.getInputStream());
// 获取输出流
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
System.out.print("请输入要发送的话:");
Scanner sc = new Scanner(System.in);
dos.writeUTF(sc.nextLine());
System.out.println("服务器端:" + dis.readUTF());
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new Client();
}
}概念回顾
Java Socket 简介
所谓 Socket(套接字),通常用于描述 IP 地址和端口,是一个通信链的句柄。应用程序通常通过“套接字”向网络发出请求或者应答网络请求。
以 J2SDK-1.3 为例,Socket 和 ServerSocket 类库位于 java.net 包中:
- ServerSocket:用于服务器端。
- Socket:建立网络连接时使用。
在连接成功时,应用程序两端都会产生一个 Socket 实例,操作这个实例即可完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是 Socket 还是 ServerSocket,它们的工作都是通过 SocketImpl 类及其子类完成的。
核心 API
java.net.Socket 继承于 java.lang.Object,有八个构造器。以下介绍使用最频繁的三个方法(其它方法可参考 JDK 文档):
- accept 方法:用于产生“阻塞”,直到接受到一个连接,并且返回一个客户端的
Socket对象实例。“阻塞”是一个术语,它使程序运行暂时“停留”在这个地方,直到一个会话产生,然后程序继续;通常“阻塞”是由循环产生的。 - getInputStream 方法:获得网络连接输入,同时返回一个
InputStream对象实例。 - getOutputStream 方法:连接的另一端将得到输入,同时返回一个
OutputStream对象实例。
注意:getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。
SocketImpl 与端口说明
抽象类 SocketImpl 是实际实现套接字的所有类的通用超类,创建客户端和服务器套接字都可以使用它。
关于端口分配,需注意以下几点:
- 端口的分配必须是唯一的,因为端口是为了唯一标识每台计算机唯一服务的。
- 端口号范围是 0~65535。
- 前 1024 个端口已经被 TCP/IP 作为保留端口,因此用户分配的端口只能是 1024 之后的。
说明
- 版本时效:本文代码与概念基于较早期的 JDK 版本(如 JDK 1.3/1.6 风格)回顾整理,核心原理在现代 Java 版本中依然适用。
- 资源管理:示例代码为了保持逻辑简洁,未在循环中显式关闭
Socket及流对象。在实际生产环境中,务必使用try-with-resources或在finally块中关闭资源,以防止内存泄漏。 - 并发处理:当前服务端模型为单线程循环处理,同一时间只能服务一个客户端。如需支持多客户端并发,通常需为每个
accept后的Socket开启独立线程或使用线程池。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/java-socket--hui-gu.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。