书上示例
在第一章《基本套接字》中,作者给出了一个TCP Socket通信的例子——反馈服务器,即服务器端直接把从客户端接收到的数据原原本本地反馈回去。
书上客户端代码如下:
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
34
35
36
37
38
39
40
41
42
|
import
java.net.Socket;
import
java.net.SocketException;
import
java.io.IOException;
import
java.io.InputStream;
import
java.io.OutputStream;
public
class
TCPEchoClient {
public
static
void
main(String[] args)
throws
IOException {
if
((args.length <
2
) || (args.length >
3
))
// Test for correct # of args
throw
new
IllegalArgumentException(
"Parameter(s): <Server> <Word> [<Port>]"
);
String server = args[
0
];
// Server name or IP address
// Convert argument String to bytes using the default character encoding
byte
[] data = args[
1
].getBytes();
int
servPort = (args.length ==
3
) ? Integer.parseInt(args[
2
]) :
7
;
// Create socket that is connected to server on specified port
Socket socket =
new
Socket(server, servPort);
System.out.println(
"Connected to server...sending echo string"
);
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
out.write(data);
// Send the encoded string to the server
// Receive the same string back from the server
int
totalBytesRcvd =
0
;
// Total bytes received so far
int
bytesRcvd;
// Bytes received in last read
while
(totalBytesRcvd < data.length) {
if
((bytesRcvd = in.read(data, totalBytesRcvd,data.length - totalBytesRcvd)) == -
1
)
throw
new
SocketException(
"Connection closed prematurely"
);
totalBytesRcvd += bytesRcvd;
}
// data array is full
System.out.println(
"Received: "
+
new
String(data));
socket.close();
// Close the socket and its streams
}
}
|
书上的服务器端代码如下:
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
34
35
36
37
38
39
|
import
java.net.*;
// for Socket, ServerSocket, and InetAddress
import
java.io.*;
// for IOException and Input/OutputStream
public
class
TCPEchoServer {
private
static
final
int
BUFSIZE =
32
;
// Size of receive buffer
public
static
void
main(String[] args)
throws
IOException {
if
(args.length !=
1
)
// Test for correct # of args
throw
new
IllegalArgumentException(
"Parameter(s): <Port>"
);
int
servPort = Integer.parseInt(args[
0
]);
// Create a server socket to accept client connection requests
ServerSocket servSock =
new
ServerSocket(servPort);
int
recvMsgSize;
// Size of received message
byte
[] receiveBuf =
new
byte
[BUFSIZE];
// Receive buffer
while
(
true
) {
// Run forever, accepting and servicing connections
Socket clntSock = servSock.accept();
// Get client connection
SocketAddress clientAddress = clntSock.getRemoteSocketAddress();
System.out.println(
"Handling client at "
+ clientAddress);
InputStream in = clntSock.getInputStream();
OutputStream out = clntSock.getOutputStream();
// Receive until client closes connection, indicated by -1 return
while
((recvMsgSize = in.read(receiveBuf)) != -
1
) {
out.write(receiveBuf,
0
, recvMsgSize);
}
clntSock.close();
// Close the socket. We are done with this client!
}
/* NOT REACHED */
}
}
|
示例程序当然运行无误,运行结果如下:
问题的引出
首先明确几点:
1、客户端与服务器端在接收和发送数据时,read()和write()方法不一定要对应,比如,其中一方可以一次发送多个字节的数据,而另一方可以一个字节一个字节地接收,也可以一个字节一个字节地方送,而多个字节多个字节地接收。因为TCP协议会将数据分成多个块进行发送,而后在另一端会从多个块进行接收,再组合在一起,它并不仅能确定read()和write()方法中所发送信息的界限。
2、read()方法会在没有数据可读时发生阻塞,直到有新的数据可读。
注意客户端中下面部分代码
1
2
3
4
5
|
while
(totalBytesRcvd < data.length) {
if
((bytesRcvd = in.read(data, totalBytesRcvd,data.length - totalBytesRcvd)) == -
1
)
throw
new
SocketException(
"Connection closed prematurely"
);
totalBytesRcvd += bytesRcvd;
}
// data array is full
|
客户端从Socket套接字中读取数据,直到收到的数据的字节长度和原来发送的数据的字节长度相同为止,这里的前提是已经知道了要从服务器端接收的数据的大小,如果现在我们不知道要反馈回来的数据的大小,那么我们只能用read方法不断读取,直到read()返回-1,说明接收到了所有的数据。我这里采用一个字节一个字节读取的方式,代码改为如下:
1
2
3
4
|
while
((bytesRcvd = in.read())!= -
1
){
data[totalBytesRcvd] = (
byte
)bytesRcvd;
totalBytesRcvd++;
}
|
这时问题就来了,输出结果如下:
问题的分析
客户端没有数据打印出来,初步推断应该是read()方法始终没有返回-1,导致程序一直无法往下运行,我在客客户端执行窗口中按下CTRL+C,强制结束运行,在服务器端抛出如下异常:
1
2
3
4
5
|
while
((bytesRcvd = in.read())!= -
1
){
data[totalBytesRcvd] = (
byte
)bytesRcvd;
System.out.println((
char
)data[totalBytesRcvd]);
totalBytesRcvd++;
}
|
此时运行结果如下:
问题的解决
查阅相关资料,仔细阅读了书上的每个细节,在通过对比书上的代码和自己的代码,发现了问题所在。问题就出现在read()方法上,这里的重点是read()方法何时返回-1,在一般的文件读取中,这代表流的结束,亦即读取到了文件的末尾,但是在Socket套接字中,这样的概念很模糊,因为套接字中数据的末尾并没有所谓的结束标记,无法通过其自身表示传输的数据已经结束,那么究竟什么时候read()会返回-1呢?答案是:当TCP通信连接的一方关闭了套接字时。
1
2
|
out.write(data);
// Send the encoded string to the server
socket.shutdownOutput();
|
总结
由于read()方法只有在另一端关闭套接字的输出流时,才会返回-1,而有时候由于我们不知道所要接收数据的大小,因此不得不用read()方法返回-1这一判断条件,那么此时,合理的程序设计应该是先关闭网络输出流(亦即套接字的输出流),再关闭套接字。
http://www.importnew.com/20151.html