import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class MySocketServer {
public static void main(String[] args) throws IOException {
// 创建一个serverSocket,监听一个端口号并创建通信socket
ServerSocket serverSocket = new ServerSocket(8888);
// 当有客户端连接时创建一个通信socket,没有连接时会阻塞
Socket socket = serverSocket.accept();
// 打印客户端信息
System.out.println("客户端" + socket.getInetAddress().getLocalHost() + "连接到服务器");
// 输入流,用于读取客户端信息
InputStream in = socket.getInputStream();
// 良好习惯,关闭输出流
in.close();
// 输出流,用于给客户端返回信息
OutputStream out = socket.getOutputStream();
/* 读取客户端信息 */
byte[] buffer = new byte[1024];
int len;
StringBuilder msgBuilder = new StringBuilder();
while ((len = in.read(buffer)) != -1) {
msgBuilder.append(new String(buffer, 0, len));
}
// 打印从客户端收到的信息
System.out.println("服务端接收到消息:" + msgBuilder.toString());
// 给客户端返回信息
out.write(("服务端收到消息:" + msgBuilder.toString()).getBytes(StandardCharsets.UTF_8) );
// 输出缓冲数据
out.flush();
// 关闭输出流
out.close();
}
}
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
public class MySocketClient {
public static void main(String[] args) throws IOException {
// 指定ip和端口号,创建socket连接
Socket socket = new Socket("127.0.0.1", 8888);
/* 发送消息 */
String msg = "客户端发送了一条消息,现在是北京时间" + new Date();
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
out.write(msg.getBytes(StandardCharsets.UTF_8));
out.flush();
// 关闭输出流
out.close();
/* 读取信息 */
byte[] buffer = new byte[1024];
int len;
StringBuilder msgBuilder = new StringBuilder();
while ((len = in.read(buffer)) != -1) {
msgBuilder.append(new String(buffer, 0, len));
}
// 打印收到信息
System.out.println("客户端收到服务端回信:" + msgBuilder);
// 关闭输入流
in.close();
}
}
1、步骤:先启动服务端,再启动客户端
2、服务端打印:
客户端LAPTOP-EECN3AOI/192.168.31.39连接到服务器
Exception in thread "main" java.net.SocketException: Socket is closed
at java.net.Socket.getOutputStream(Socket.java:943)
at MySocketServer.main(MySocketServer.java:19)
3、客户端打印:
Exception in thread "main" java.net.SocketException: socket closed
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at java.net.SocketInputStream.read(SocketInputStream.java:127)
at MySocketClient.main(MySocketClient.java:24)
4、错误分析:socket被关闭
5、原因:分析代码,未对socket进行关闭,但是客户端在通信完成前提前关闭了out流,服务端提前关闭了in流,查询资料得知关闭流会导致socket关闭
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class MySocketServer {
public static void main(String[] args) throws IOException {
// 创建一个serverSocket,监听一个端口号并创建通信socket
ServerSocket serverSocket = new ServerSocket(8888);
// 当有客户端连接时创建一个通信socket,没有连接时会阻塞
Socket socket = serverSocket.accept();
// 打印客户端信息
System.out.println("客户端" + socket.getInetAddress().getLocalHost() + "连接到服务器");
// 输入流,用于读取客户端信息
InputStream in = socket.getInputStream();
// 输出流,用于给客户端返回信息
OutputStream out = socket.getOutputStream();
/* 读取客户端信息 */
byte[] buffer = new byte[1024];
int len;
StringBuilder msgBuilder = new StringBuilder();
while ((len = in.read(buffer)) != -1) {
msgBuilder.append(new String(buffer, 0, len));
}
// 打印从客户端收到的信息
System.out.println("服务端接收到消息:" + msgBuilder.toString());
// 给客户端返回信息
out.write(("服务端收到消息:" + msgBuilder.toString()).getBytes(StandardCharsets.UTF_8) );
}
}
public class MySocketClient {
public static void main(String[] args) throws IOException {
// 指定ip和端口号,创建socket连接
Socket socket = new Socket("127.0.0.1", 8888);
/* 发送消息 */
String msg = "客户端发送了一条消息,现在是北京时间" + new Date();
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
out.write(msg.getBytes(StandardCharsets.UTF_8));
out.flush();
/* 读取信息 */
byte[] buffer = new byte[1024];
int len;
StringBuilder msgBuilder = new StringBuilder();
while ((len = in.read(buffer)) != -1) {
msgBuilder.append(new String(buffer, 0, len));
}
// 打印收到信息
System.out.println("客户端收到服务端回信:" + msgBuilder);
}
}
1、服务端:打印连接信息后无响应
客户端LAPTOP-EECN3AOI/192.168.31.39连接到服务器
2、客户端:无响应
3、原因:分析代码,服务端没有打印出客户端发送消息,猜测是以下代码陷入死循环。
byte[] buffer = new byte[1024];
int len;
StringBuilder msgBuilder = new StringBuilder();
while ((len = in.read(buffer)) != -1) {
msgBuilder.append(new String(buffer, 0, len));
}
查资料得知,在普通流当中,这个方法可行。但是在socket中,只有当对方将输出流关闭后才会以-1作为结束标志,故而陷入死循环,推断正确。
4、解决方法:需要关闭流的同时,不关闭socket,可使用Socket::shutdownOutput()方法和Socket::shutdownInput()方法实现
public class MySocketServer {
public static void main(String[] args) throws IOException {
// 创建一个serverSocket,监听一个端口号并创建通信socket
ServerSocket serverSocket = new ServerSocket(8888);
// 当有客户端连接时创建一个通信socket,没有连接时会阻塞
Socket socket = serverSocket.accept();
// 打印客户端信息
System.out.println("客户端" + socket.getInetAddress().getLocalHost() + "连接到服务器");
// 输入流,用于读取客户端信息
InputStream in = socket.getInputStream();
// 输出流,用于给客户端返回信息
OutputStream out = socket.getOutputStream();
/* 读取客户端信息 */
byte[] buffer = new byte[1024];
int len;
StringBuilder msgBuilder = new StringBuilder();
while ((len = in.read(buffer)) != -1) {
msgBuilder.append(new String(buffer, 0, len));
}
// 关闭输入流
socket.shutdownInput();
// 打印从客户端收到的信息
System.out.println("服务端接收到消息:" + msgBuilder.toString());
// 给客户端返回信息
out.write(("服务端收到消息:" + msgBuilder.toString()).getBytes(StandardCharsets.UTF_8) );
// 关闭输出流同时关闭socket
out.close();
}
}
public class MySocketClient {
public static void main(String[] args) throws IOException {
// 指定ip和端口号,创建socket连接
Socket socket = new Socket("127.0.0.1", 8888);
/* 发送消息 */
String msg = "客户端发送了一条消息,现在是北京时间" + new Date();
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
out.write(msg.getBytes(StandardCharsets.UTF_8));
out.flush();
// 关闭输出流
socket.shutdownOutput();
/* 读取信息 */
byte[] buffer = new byte[1024];
int len;
StringBuilder msgBuilder = new StringBuilder();
while ((len = in.read(buffer)) != -1) {
msgBuilder.append(new String(buffer, 0, len));
}
// 打印收到信息
System.out.println("客户端收到服务端回信:" + msgBuilder);
// 关闭输入流同时关闭socket
in.close();
}
}
1、服务端
客户端LAPTOP-EECN3AOI/192.168.31.39连接到服务器
服务端接收到消息:客户端发送了一条消息,现在是北京时间Sun Jan 07 16:46:26 GMT+08:00 2024
2、客户端
客户端收到服务端回信:服务端收到消息:客户端发送了一条消息,现在是北京时间Sun Jan 07 16:46:26 GMT+08:00 2024
之前的代码,只进行了一次通信就将输出流关闭,实际应用中会存在多次通信,需要进行进一步优化。要进行多次通信,就需要制定每次发送消息的结束标识,有以下两种方式可以实现:
(1) 仅使用字符通信,制定一个结束标识,比如 “END” 字符串。当读取到"EDN"字符串时,就说明读取到了一个完整的消息,但有一个弊端,就是消息里可能存在与结束标识同样的内容,会干扰消息的接收,解决方法可以是在与结束标识相同内容前,加入转义字符,但处理起来比较麻烦,因此本次不此本方法实践
(2)在消息头部,加入n个字节,用于表示本次发送消息大小。通信双方约定好这个规范,接收方先读取前n个字节获取大小,再从输入流中读取对应数目的字节,就可以读取到对应的消息。本次实践使用此方法
public static void main(String[] args) throws IOException {
// 指定ip和端口号,创建socket连接
Socket socket = new Socket("127.0.0.1", 8888);
/* 发送消息 */
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
for (int i = 1; i <= 3; i++) {
String msg = "客户端发送第"+ i +"条消息,现在是北京时间" + new Date();
byte[] msgBytes = msg.getBytes(StandardCharsets.UTF_8);
// 发送本次消息长度
out.write(msgBytes.length);
// 发送本次消息
out.write(msgBytes);
}
out.flush();
socket.shutdownOutput();
// 关闭输入流同时关闭socket
in.close();
socket.close();
}
public static void test01() throws IOException{
// 创建一个serverSocket,监听一个端口号并创建通信socket
ServerSocket serverSocket = new ServerSocket(8888);
// 当有客户端连接时创建一个通信socket,没有连接时会阻塞
Socket socket = serverSocket.accept();
// 打印客户端信息
System.out.println("客户端" + socket.getInetAddress().getLocalHost() + "连接到服务器");
// 输入流,用于读取客户端信息
InputStream in = socket.getInputStream();
// 初始化大于消息最大字节数数组,可以复用
byte[] buffer = new byte[1024];
while (true) {
// 读取第一个字节获取消息长度
int len = in.read();
// 当客户端关闭输出流时停止读取
if (len == -1) break;
// 读取数据
int readLen = in.read(buffer);
if (readLen == -1) break;
// 打印读取到的数据
System.out.println(new String(buffer, 0, len));
}
// 关闭输入流
socket.shutdownInput();
socket.close();
}
客户端LAPTOP-EECN3AOI/192.168.31.39连接到服务器
客户端发送第1条消息,现在是北京时间Sun Jan 14 12:54:57 GMT+08:00 2024
客户端发送第2条消息,现在是北京时间Sun Jan 14 12:54:57 GMT+08:00 2024
由于TCP是可靠传输,因此可以排除消息传输丢失的可能性,那么问题就出现在服务端读取上面:
while (true) {
// 读取第一个字节获取消息长度
int len = in.read();
// 当客户端关闭输出流时停止读取
if (len == -1) break;
// 读取数据
int readLen = in.read(buffer);
if (readLen == -1) break;
// 打印读取到的数据
System.out.println(new String(buffer, 0, len));
}
在读取完一条消息的大小后,将输入流中数据全部读入数组,而实际上此时后续消息可能也已经到达,也一并被读取到buffer中,而打印数据的代码只截取了一条消息的大小的数据,剩余后续数据在下次读取中被舍弃,因此出现了信息缺漏
解决方法,应该在读取进buffer时只截取对应长度的数据,再将数据输出
只对服务端进行修改,客户端代码保持和之前一致
public static void test02() throws IOException {
// 创建一个serverSocket,监听一个端口号并创建通信socket
ServerSocket serverSocket = new ServerSocket(8888);
// 当有客户端连接时创建一个通信socket,没有连接时会阻塞
Socket socket = serverSocket.accept();
// 打印客户端信息
System.out.println("客户端" + socket.getInetAddress().getLocalHost() + "连接到服务器");
// 输入流,用于读取客户端信息
InputStream in = socket.getInputStream();
// 初始化大于消息最大字节数数组,可以复用
byte[] buffer = new byte[1024];
int len;
// 每次读取一字节获取消息长度,直至客户端关闭输出流
while ((len = in.read()) != -1) {
// 只读取消息长度的数据
int readLen = in.read(buffer, 0, len);
// 客户端关闭输出流时停止读取
if (readLen == -1) break;
System.out.println(new String(buffer, 0, len));
}
// 关闭输入流
socket.shutdownInput();
socket.close();
}
读取到所有数据:
客户端LAPTOP-EECN3AOI/192.168.31.39连接到服务器
客户端发送第1条消息,现在是北京时间Sun Jan 14 13:14:30 GMT+08:00 2024
客户端发送第2条消息,现在是北京时间Sun Jan 14 13:14:30 GMT+08:00 2024
客户端发送第3条消息,现在是北京时间Sun Jan 14 13:14:30 GMT+08:00 2024