我昨天在写java网络编程的时候想到这样几个问题,现在总结一下。
一:TCP编程是面向字节流的
我用socket编程,是基于TCP/IP的,而TCP协议是传输层的协议,它是面向连接的,与UDP很大的不同在于前者是面向字节流的协议,而后者是用户数据报协议(User Datagram Protocol ),面向字节流有一个很大的好处,那就是可以进行拥塞控制,进行流量控制,进行差错控制。
为什么UDP不行呢?是因为UDP应用层给传输层什么数据报,传输层就发送什么数据报,根本不管它有多少个字节,传输太多是不是会造成阻塞。而TCP就不一样了,应用层让传输层传输数据的时候,传输层并不会直接发送,而是从缓冲区一个字节一个字节的拿出来,TCP发送端是有发送窗口的,这个窗口就是TCP进行流量控制,差错控制的核心。而接收端又是有接收窗口的,总之通过窗口直接的滑动(或者快了,或者顺序乱了,或者帧丢失了),来进行以上的控制。
那么java在进行socket编程的时候只能使用字节流了?答案并不是这样
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.io.InputStream; public class Server1 { /**服务器端*/ public static void main(String[] args) throws IOException{ ServerSocket ss = new ServerSocket(5678); Socket socket = ss.accept(); PrintWriter out = new PrintWriter(socket.getOutputStream()); BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream())); while(true) { String str=in.readLine(); System.out.println(str); out.println("hello world"); out.flush(); } } } import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; /** * socket编程的客户端 * */ public class Client1 public static void main(String[] args)throws Exception{ Socket server=new Socket("localhost",5678); BufferedReader in=new BufferedReader(new InputStreamReader(server.getInputStream())); PrintWriter out=new PrintWriter(server.getOutputStream()); BufferedReader wt=new BufferedReader(new InputStreamReader(System.in)); while(true){ String str=wt.readLine(); out.println(str); out.flush(); if(str.equals("end")){ break; } System.out.println(in.readLine()); } server.close(); } }
以上的代码给人一种使用了面向字符流的假象,其实我们可以看到这里的字符流其实是对字节流进行了封装,这里应该是适配器模式,把一个字节流转化成了一个字符流 ,server.getInputStream()方法的源码是,可以看到它实际返回的是字节流。
public InputStream getInputStream() throws IOException
二:print和println的区别到底是什么
在socket编程中,如果使用PrintWriter类作为输出流,那么为什么一定要println(String str),而不是print()。
在我的印象中println只是在print的后面加上一个换行符,估计大部分人跟我的想法一样,可是,看了一下源码发现
/** * Prints a String and then terminate the line. This method behaves as * though it invokes <code>{@link #print(String)}</code> and then * <code>{@link #println()}</code>. * * @param x The <code>String</code> to be printed. */ public void println(String x) { synchronized (this) { print(x); newLine(); } } public void print(String s) { if (s == null) { s = "null"; } write(s); }
从上面的两个方法至少可以看到两点1:println方法是线程安全的:2:多了一个newLine()方法。
那么newLine()是个什么方法呢?
/** private BufferedWriter textOut; */ private void newLine() { try { synchronized (this) { ensureOpen(); textOut.newLine(); //在BufferedWriter类中 newLine方法只是写一个换行符 /** * Writes a line separator. The line separator string is defined by the * system property <tt>line.separator</tt>, and is not necessarily a single * newline ('\n') character. * * @exception IOException If an I/O error occurs * public void newLine() throws IOException { write(lineSeparator); } */ /** * Flushes the output buffer to the underlying character stream, without * flushing the stream itself. This method is non-private only so that it * may be invoked by PrintStream. 这里的意思是说刷新输出流到存在下面的字符流,而不需要刷新这个流本身 */ textOut.flushBuffer(); /* 那么这里到底做了什么呢? */ charOut.flushBuffer(); if (autoFlush) out.flush(); } } catch (InterruptedIOException x) { Thread.currentThread().interrupt(); } catch (IOException x) { trouble = true; } }
也就是说println方法比print做的事情多得多。它有终止改行,刷新缓冲区的功能,而print却没有,当使用print输出的时候,没有刷新,也没有终止,而只是单独的写入进去。
那么,刷新一个缓冲区到底在底层是做了什么呢。