用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盤傳給了旁邊的同學,最后兩台電腦實現了通信,呵呵。雖然是小事,還是蠻驕傲的呢。
————————————————————————————————————————————
