Johuer's Blog

多学知识,精简代码

0%

Socket如何告知对方已发送完命令

正常来说,scoket客户端打开一个输出流,如果不做约定,也不关闭它,那么服务端永远不知道客户端是否发送完消息,那么服务端会一直等待下去,直到读取超时。
所以怎么告知服务端已经发送完消息就显得特别重要。

It says -1 is returned at end of stream.
End of stream on a socket occurs when the peer closes the connection, or shuts it down for output, and not before.
-1 is not an end of message indicator.

1.通过socket关闭

当Socket关闭的时候,服务端就会收到响应的关闭信号,那么服务端也就知道流已经关闭了,这个时候读取操作完成,就可以继续后续工作。

Server:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class SocketServer1 {

public static void main(String[] args) {

try {

ServerSocket server = new ServerSocket(7777);

System.out.println("等待客户端连接");
Socket socket = server.accept();

// 建立好连接,丛连接中获取输入流
InputStream inputStream = socket.getInputStream();

byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("Get message from client: " + sb);

inputStream.close();
socket.close();
server.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class SocketClient1 {

public static void main(String[] args) {
try {

Socket socket = new Socket("127.0.0.1", 7777);

OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello".getBytes());

outputStream.close();
System.out.println(socket.isClosed()); // true
socket.close();

} catch (IOException e) {
e.printStackTrace();
}
}
}

客户端执行完outputStream.close(),则服务端read结果-1,否则read方法一直阻塞。

注意:只要InputStream和OutputStream有一个调用close()方法,socket也关闭。

但是这种方式有一些缺点:
客户端Socket关闭后,将不能接收服务端发送的消息,也不能再次发送消息; 如果客户端想再次发送消息,需要重现创建Socket连接

2.通过socket关闭输出流

1
socket.shutdownOutput();

注意不是使用以下方法,如果关闭了输出流,那么相应的Socket也将关闭,和直接关闭Socket一个性质。

outputStream为发送消息到服务端打开的输出流

1
outputStream.close();

调用Socket的shutdownOutput()方法,底层会告知服务端我这边已经写完了,那么服务端收到消息后,就能知道已经读取完消息,
如果服务端有要返回给客户的消息那么就可以通过服务端的输出流发送给客户端,如果没有,直接关闭Socket。

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class SocketServer2 {

public static void main(String[] args) {

try {
ServerSocket server = new ServerSocket(7777);

Socket socket = server.accept();

InputStream inputStream = socket.getInputStream();

byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
// 只有当客户端关闭它的输出流的时候(socket.shutdownOutput()、outputStream.close()),服务端才能取得结尾的-1
while ((len = inputStream.read(bytes)) != -1) {
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("Get message from client: " + sb);

OutputStream outputStream = socket.getOutputStream();
outputStream.write("Hello client, I get your message.".getBytes("UTF-8"));

inputStream.close();
outputStream.close();
socket.close();
server.close();

} catch (IOException e) {
e.printStackTrace();
}
}
}

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class SocketClient2 {

public static void main(String[] args) {

try {
Socket socket = new Socket("127.0.0.1", 7777);

OutputStream outputStream = socket.getOutputStream();
outputStream.write("你好,服务端1。".getBytes("UTF-8"));
// 通过shutdownOutput告诉服务器已经发送完数据,后续只能接收数据
socket.shutdownOutput();

InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder sb = new StringBuilder();
while ((len = inputStream.read(bytes)) != -1) {
sb.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("Get message from server: " + sb);

inputStream.close();
outputStream.close();
socket.close();

} catch (IOException e) {
e.printStackTrace();
}
}
}

这种方式通过关闭客户端的输出流,告知服务端已经写完了,虽然可以读到服务端发送的消息,但是还是有一点点缺点:
不能再次发送消息给服务端,如果再次发送,需要重新建立Socket连接

3.通过约定符号

Server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class SocketServer3 {

public static void main(String[] args) {

try {
ServerSocket server = new ServerSocket(7777);

Socket socket = server.accept();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
StringBuilder sb = new StringBuilder();
while ((line = reader.readLine()) != null && !"quit".equals(line)) {
sb.append(line);
System.out.println("line: " + line);
}

System.out.println("quit: " + sb);

} catch (IOException e) {
e.printStackTrace();
}
}
}

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class SocketClient3 {

public static void main(String[] args) {

try {
SocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 7777);
Socket socket = new Socket();
socket.connect(socketAddress);

OutputStream os = socket.getOutputStream();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.print("请输入:");
String next = scanner.next();
os.write((next+"\n").getBytes());
}

} catch (IOException e) {
e.printStackTrace();
}
}
}

优点:不需要关闭流,当发送完一条命令(消息)后可以再次发送新的命令(消息)
缺点:需要额外的约定结束标志,太简单的容易出现在要发送的消息中,误被结束,太复杂的不好处理,还占带宽

4.通过指定长度

长度+类型+数据模式的传输方式

参考