用java實現socket C/S通信很簡單,很多教科書上都有。但是這些通信模型大都是阻塞式的,其弊端也很明顯:一方必須要接收的到對方的消息后,才能編輯自己的消息發出。同樣對方也要一直等待這條消息收到后才能發送新的消息。用網絡通信的知識講,大概就是半雙工通信吧。這就好比聊天的時候,兩個人只能一人一句的聊天。不能一個人連着發送多句話。
而要實現非阻塞通信呢,也就是實現全雙工通信。我不想使用java的NIO包。因為那樣有點小題大做了。其實只要使用多線程就能實現了。
Socket和ServerSocket
//服務端: ServerSocket ss = new ServerSocket(5432); //客戶端: Socket s = new Socket("127.0.0.1",5432);
//服務端: Socket s = ss.accept();注意以上的代碼要捕獲相應的異常。這就不多說了,eclipse會幫助我們。
IO操作
socket的IO流
InputStream in = s.getInputStream(); OutputStream out = s.getOutputStream();由此獲得的兩個字節IO流就是接下來所有數據交互的基礎了。然后為了提高效率要進行一下包裝。我是用的是DataInputStream/DataOutputStream。你也可以轉換為字符流。
DataInputStream din = new DataInputStream(in); DataOutputStream dout = new DataOutputStream(out);
很多人可能會把輸入流和輸出流搞混淆掉哦。

標准輸入的包裝
DataInputStream dis = new DataInputStream(System.in)); String msg = dis.readUTF(); dout.writeUTF(msg);但是當程序運行的時候,就會發現在一方的控制台窗口無論輸入多少行,對方都無法收到。也就是這里也陷入了阻塞,百度下,究其原因呢,是因為在控制台輸入的字符並不是UTF格式的。所以readUTF根本無法返回字符串。
DataInputStream/DataOutputStream提供了對於基本數據類型的寫入與讀取方法如:
DataInputStream | DataOutputStream |
readInt() | writeInt() |
readChar() | writeChar() |
readDouble() | writeDouble() |
接着我對控制台的標准輸入的包裝使用了BufferedReader(也可以使用Scanner包裝哦)。
BufferedReader bf = new BufferedReader(new InputStreamReader(System.in)); String msg = bf.readLine(); dout.writeUTF(msg);
多線程的使用
- 一個是從socket的輸入流中獲取對方的消息並打印在屏幕上。
- 從標准輸入中鍵入的消息要通過socket的輸出流發送給對方。
以上行為要開辟新的線程來完成來避免阻塞。為此我定義兩個類,取名為SendThread和PrintThread
為了便於操作,我將socket的包裝后的輸出流作為參數傳遞給SendThread。同樣PrintThread中也會傳如socket的包裝后的輸入流參數
import java.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; public class SendThread implements Runnable { private DataOutputStream dout; public SendThread(DataOutputStream dout){ super(); this.dout= dout; } @Override public void run() { // TODO Auto-generated method stub while(true){ String msg; try { BufferedReader bf = new BufferedReader( new InputStreamReader(System.in)); //注意不能直接用DataInputStream來封裝標准輸入,原因前文已提到 msg = bf.readLine(); dout.writeUTF(msg); } catch (IOException e) { // TODO Auto-generated catch block break; } } } }
為了增強可讀性,我也把IP地址傳入了PrintThread中。大家也可無視
import java.io.DataInputStream; import java.io.IOException; class PrintThread implements Runnable{ private DataInputStream din; private String ip; public PrintThread(DataInputStream din,String ip) { super(); this.din = din; this.ip = ip; } @Override public void run() { // TODO Auto-generated method stub while(true){ try { String msg = din.readUTF(); System.out.println("["+ip+"]"+":"+msg); } catch (IOException e) { // TODO Auto-generated catch block break; } } } }
接着在主類Server和Client中要做的事就很簡單了。
//Server import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class Server { private static String ip; public static void main(String[] args) throws IOException { // TODO Auto-generated method stub @SuppressWarnings("resource") ServerSocket ss = new ServerSocket(5432); Socket s = ss.accept(); ip = s.getInetAddress().toString(); ip = ip.substring(ip.indexOf("/")+1); System.out.println(ip+"上線了"); InputStream in = s.getInputStream(); OutputStream out = s.getOutputStream(); DataInputStream din = new DataInputStream(in); DataOutputStream dout = new DataOutputStream(out); SendThread it = new SendThread(dout); PrintThread ot = new PrintThread(din,ip); new Thread(ot).start(); new Thread(it).start(); } }
//Client import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class Client { private static String ip = "127.0.0.1"; public static void main(String[] args) throws IOException{ @SuppressWarnings("resource") Socket s = new Socket(ip,5432); OutputStream out = s.getOutputStream(); InputStream in = s.getInputStream(); DataOutputStream dout = new DataOutputStream(out); DataInputStream din = new DataInputStream(in); SendThread it = new SendThread(dout); PrintThread ot = new PrintThread(din,ip); new Thread(ot).start(); new Thread(it).start(); } }
基本的代碼就是這些,當然這是簡單實現,還可以有很多繼續完善的地方。比如使Server可以監聽多個Client的連接請求,或者你可以加個Swing的界面(個人比較不在乎界面)。細節我也有很多未仔細處理的地方,比如里面的Socket和各種IO流,都沒有寫關閉,(囧,沒有找到個合適的地方)所以代碼中會有一句
@SuppressWarnings("resource")
來忽略流未關閉的警告。。哈,有點水啊。
————————————————————后記的瑣事———————————————————
這段程序,我成功在電腦和虛擬機上實現了通信。虛擬機的原理就是把你的一台電腦當成兩台電腦來用了。只需要修改上述代碼中的IP地址就行了,虛擬機相當於和你處在一個局域網里,你可以使用ipconfig命令查看在這個局域網之下的虛擬機的ip地址,通常host-only的就是虛擬機的ip了。比如在這個虛擬的局域網中我本身的電腦IP地址是192.168.56.101,而虛擬機是192.168.56.1。
還有就是那天數據結構上機課的時候,我發現機房電腦里裝了java(但是沒eclipse)。。於是我就用記事本手寫了上面兩個程序,果然用慣了IDE,記事本會很不習慣啊。但最后還是完成了,我把Client的程序通過U盤傳給了旁邊的同學,最后兩台電腦實現了通信,呵呵。雖然是小事,還是蠻驕傲的呢。
————————————————————————————————————————————