这里是Java网络编程
,Java Socket编程
相关的学习手记
。这里按照官方的Java 8 Toturial
教程的Custom Networking学习路径
,对相关的一些内容进行解读(并不完全,如果有错请联系我,谢谢^ _ ^),同时在学习的过程中加入个人的理解与对代码运行的思考。
下面是整个专栏的文章链接,用于快速的导航。
0
. 整个系列文章介绍1
. Java网络编程假定你需要一些基本的网络知识。2
. Java操作URL;java.net.URL,java.net.URLConnection3
. Java Socket编程;关于Socket的使用; java.net.Socket ,java.net.ServerSocket4
. Java UDP Datagrams数据报编程;Java单播,多播编程;java.net.DatagramPacket,java.net.DatagramSocket,java.net.MulticastSocket5
. 使用Java访问系统(电脑,机器)的网络信息,如网卡信息,网络状态等;java.net.NetworkInterface6
. 在Java客户端上操作,管理Cookie;7
. 在1~6节的编程实践中,我遇到的一些问题;
【网络编程】——Java实现(3)——Java Socket(All About Sockets)实践过程中出现的问题。
我自己按照官方给出的代码实现EchoServer,EchoClient
代码时,使用的I/O类为BufferedReader
,BufferedWriter
。
咱们先贴上两个类的代码实现,然后再分析我遇到的问题在哪里。
EchoServer 类的实现:
public class EchoServer {
public static void main(String[] args) throws Exception {
if(args.length != 1){ // 必须要有一个 端口 参数
System.err.println("Usage: java EchoServer <port number>");
System.exit(1);
}
int portNumber = Integer.parseInt(args[0]);
try(
// 服务端的socket
ServerSocket serverSocket = new ServerSocket(portNumber);
// 通过accept来获取socket
Socket socket = serverSocket.accept();
// 获得输入流,允许读
BufferedReader br = new BufferedReader(
new InputStreamReader( socket.getInputStream() ) );
// 获取输出流,允许写
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter( socket.getOutputStream() ) );
){
String line = null;
while( ( line = br.readLine() ) != null ){
System.out.println("Message from Client : " + line);
// 发送数据返回给客户端:写操作
bw.write( line );
}
} catch ( Exception e ){
System.out.println("Exception caught when trying to listen on port "
+ portNumber + " or listening for a connection");
System.err.println( e.getMessage() );
}
}
}
服务端代码很清晰,BufferedReader br
,BufferedWriter bw
分别负责读取客户端的请求
和发送返回信息给客户端。
主要的通信逻辑如下:
String line = null;
// br接受客户端端的信息
while( ( line = br.readLine() ) != null ){
// 将客户端的信息显示出来
System.out.println("Message from Client : " + line);
// 发送数据返回给客户端:写操作
bw.write( line );
}
下面再看客户端的实现:
public class EchoClient {
public static void main(String[] args) {
if(args.length != 2){
System.err.println("Usage: java EchoClient <hostname> <portnumber>");
System.exit(1);
}
String hostname = args[0];
int portNumber = Integer.parseInt( args[1] );
try(
Socket clientSocket = new Socket(hostname, portNumber);
// socket的输入(读取) *clientSocket.getInputStream()*
BufferedReader socketReader = new BufferedReader(
new InputStreamReader( clientSocket.getInputStream() ));
// socket的输出(写入) *clientSocket.getOutputStream()*
BufferedWriter socketdWriter = new BufferedWriter(
new OutputStreamWriter( clientSocket.getOutputStream() ) );
// 用户的输入 *System.in*
BufferedReader userInput = new BufferedReader(
new InputStreamReader(System.in) );
){
String input = null;
while( (input = userInput.readLine() ) != null ){
// 提示用户输入的信息发送了
System.out.println("Send message: " + input);
// 发送数据:socket写操作
socketdWriter.write(input);
// 取出数据:socket写操作,然后将数据显示出来
System.out.println( "Message from server: " + socketReader.readLine() );
}
} catch (UnknownHostException e) {
System.err.println("Don't know about host " + hostname);
System.exit(1);
} catch (IOException e) {
System.err.println("Couldn't get I/O for the connection to " +
hostname);
System.exit(1);
}
}
}
和服务端一样,客户端也使用BufferedReader
,BufferedWriter
来获取和发送数据。
下面讲解我遇到的问题:
运行服务器,客户端。两端连接成功,客户端开始发消息。
发现没有任何服务端的回应,之后的输入也没有任何反应了。
服务端的控制台,发现没任何反应
下面是分析:
在我们的例子中,我们的socket通信都是,BufferedReader通过readLine()方法来读取信息
,BufferedWriter通过的write方法来写入(返回信息)
。
注意到readLine,查看JDK关于readLine的解释:
/**
* Reads a line of text. A line is considered to be terminated by any one
* of a line feed (’\n’), a carriage return (’\r’), or a carriage return
* followed immediately by a linefeed.
*读取一行文本信息。当文本中包含"\n"或者"\r",亦或者"\n\r",那么我们就认为一行结束了
。也就是说它通过特定的字符来判定一行是否结束了。
再看到我们的客户端程序:
String input = null;
while( (input = userInput.readLine() ) != null ){
// 提示用户输入的信息发送了
System.out.println("Send message: " + input);
// 发送数据:socket写操作
socketdWriter.write(input);
// 取出数据:socket写操作,然后将数据显示出来
System.out.println( "Message from server: " + socketReader.readLine() );
}
这里有个userInput.readLine()
,它自然就会返回一行信息赋值给inpu
。然而此时input中并不会存在换行符
,所以当我们write此input信息到socket缓冲区的时候,并不会包含换行符。所以当我们服务端readLine时,socket缓冲区中全是客户端发来的信息,而找不到换行符。
。
此时的解决方案是,我们可以在文本串的后面添加一个"\n"
。
另外值得一提的是,BufferedWriter提供了一个newLine()方法专门处理此问题
于是可以改进服务端,客户端的代码如下:(注释的代码可选为可选方案)
客户端代码改进:
// 发送数据:socket写操作
//socketdWriter.write(input + "\n");
socketdWriter.newLine();
同理,服务端也相应的改进代码:
// 发送数据返回给客户端:写操作
//bw.write( line + "\n" );
bw.newLine();
此时我觉得代码应没问题了,于是运行,结果。。。。。仍然不能正常运行!!
下面继续分析原因。
BufferedWriter类
内部实现为一段缓冲区
,也就是说,当我们向socket缓冲区写入的数据,没有把缓冲区填满的时候,它是不会将数据发送出去的。
毕竟是Buffer,缓冲作用嘛。
BufferedWriter有一个方法flush()
,一般的翻译是(以水)冲刷,冲洗;冲掉,除掉
。在程序中我们可以理解为,刷新(清除)缓冲区,将数据写出去
。
于是最终的客户端,服务端代码改进如下,终于可以运行了!!
:
服务端:
String line = null;
while( ( line = br.readLine() ) != null ){
System.out.println("Message from Client : " + line);
// 发送数据返回给客户端:写操作
//bw.write( line + "\n" );
bw.newLine();
bw.flush();
}
客户端:
String input = null;
while( (input = userInput.readLine() ) != null ){
// 提示用户发送了什么
System.out.println("Send message: " + input);
// 发送数据:socket写操作
//socketdWriter.write(input + "\n");
socketdWriter.newLine();
//socketdWriter.flush();
// 返回数据
System.out.println( "Message from server: " + socketReader.readLine() );
}
再回头看看官方给出的例子实现是怎样的:
它给出的例子中使用的输出流的实现类为PrintWriter
。
并且它的初始化代码如下:
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
特别注意到PrintWriter的第二个参数,它传入的是true
。
JDK给出PrintWiter的此类型构造函数的解释:
/**
* Creates a new PrintWriter from an existing OutputStream. This
* convenience constructor creates the necessary intermediate
* OutputStreamWriter, which will convert characters into bytes using the
* default character encoding.
*
* @param out An output stream
* @param autoFlush A boolean; if true, the <tt>println</tt>,
* <tt>printf</tt>, or <tt>format</tt> methods will
* flush the output buffer
第二个参数autoFlush: 如果为true,那么调用此PrintWriter对象的println,printf,format方法都会自动flush缓冲区
。
EchoServer,EchoClient类中确实也是这样用的,它每次写操作,都是调用println方法
问题总结
出现此问题,还是我对Java I/O
操作没有完全熟悉。通过此例子,会对Buffer,readLine()
等实现由一个基本的认识。