Java第十二課——客戶端與伺服器的單線通訊
Java第十二課——伺服器與客戶端的單線通訊
一、通訊是如何進行的?
平時我們用微信、QQ進行聊天時,我們是如何把訊息傳送出去的?對方為什麼能接到訊息?兩人甚至在地球的兩極也能接到訊息,這是如何實現的?
我們知道,平時日常的對話需要面對面的進行,而其實在網際網路下也是一樣的。在通訊剛開始時,兩人必須在同一網路下才能進行通訊,通過訪問對方的IP地址,找到其開放的埠進行通訊,先來說一下什麼是IP地址?什麼是埠?
百度百科是這麼解釋的:
IP地址是IP協議提供的一種統一的地址格式,它為網際網路上的每一個網路和每一臺主機分配一個邏輯地址,以此來遮蔽實體地址的差異。
埠可分為虛擬埠和物理埠,其中虛擬埠指計算機內部或交換機路由器內的埠,不可見。例如計算機中的80埠、21埠、23埠等。物理埠又稱為介面,是可見埠,計算機背板的RJ45網口,交換機路由器集線器等RJ45埠。電話使用RJ11插口也屬於物理埠的範疇。
簡單的來說,IP地址相當於虛擬的地理位置,每個網路都有自己的IP地址,每個在網路裡的主機會被當前所連線的網路分配一個IP地址,這個IP地址可能隨著每次的連線而有所改變,但在一次連線的過程中是不變的。
埠就相當於對外開放的視窗,通訊上的埠是虛擬的,相當於外界想與當前主機進行交流的視窗,需要開放端口才能與外界進行資料交換。
檢視自己當前IP可以在cmd面板中用ipconfig命令,找到IPv4地址後對應的就是當前的IP地址
二、建立伺服器
建立伺服器就必須開放一個埠,讓外界可以通過訪問埠來訪問伺服器。而靠前的埠基本上都已經被佔用了,像80埠埠是HTTP,即超文字傳輸協議開放的;23埠是telnet的埠等;所以在開放埠時選用較靠後的埠。
建立伺服器的核心語句:
ServerSocket serversocket = new ServerSocket(int port);
port即對應開放的埠
下面是一個完整的伺服器建立
public class Server{
ServerSocket serversocket;
public void create(){
try{
serversocket = new ServerSocket(9876);
System.out.println("ServerSocket: loading successfully");
} catch (IOException e){
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static void main(String[] args){
Server server = new Server();
server.create();
}
}
解釋一下try/catch,在通訊上,因為與IO流有關,所以對IO流的處理都需要加try/catch或throw才能編譯通過
執行一下,輸出了ServerSocket: loading successfully就成功建立了。
三、連線伺服器,並接收訊息
public void conn(){
try {
//等待使用者連線
Socket socket = serversocket.accept();
System.out.println("ServerSocket: user enter");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
這個方法其實就是等待使用者連線,連線上了之後就可以進入下一步的資料獲取和資料處理
public void conn(){
try {
//等待使用者連線
Socket socket = serversocket.accept();
System.out.println("ServerSocket: user has entered");
//獲取輸入輸出流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
當有客戶進入後,就會輸出ServerSocket: user has entered
四、資料讀取和資料處理
因為還沒有寫客戶端,但實際上客戶端和伺服器是兩個專案,所以無論有沒有客戶端,我們先假設有客戶端,並且客戶端已經成功的把訊息傳送過來了,那伺服器要做的就是接訊息即可。
通過檢視OutputStream和InputSream可以看到,每次傳送和接收的都是一個位元組,那我們每次讀取一個位元組
public void conn(){
try {
//等待使用者連線
Socket socket = serversocket.accept();
System.out.println("ServerSocket: user enter");
//獲取輸入輸出流
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
//讀取資料
int onebyte = in.read();
char ch = (char)onebyte;
System.out.println("ServerSocket: has read: " + ch);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
這樣就能讀取到一個位元組的資料了
直到這裡,可以做個小實驗,利用cmd面板telnet命令測試一下伺服器:
開啟cmd面板,輸入telnet+IP地址(如果連線的是自己的電腦可以輸入localhost)+埠號
如:> telnet localhost 9876,再輸入任意一個字元(我輸的是s)就會收到如下輸出
注意:埠號要是伺服器所開放的埠號
到這裡伺服器端的雛形就完成了
五、客戶端的建立
客戶端和伺服器在本質上其實是一樣的
伺服器開放一個埠給伺服器去連線,連線進來的使用者就獲取到輸入輸出流來讀寫資料,進行資料處理
客戶端就是去連線一個主機的埠,連線進取後獲取輸入輸出流來給伺服器發訊息,進行資料處理
連線伺服器其實就一句程式碼:
Socket socket = new Socket(String host, int port);
這裡的host其實就是IP地址,只不過要是String型別,然後客戶端想要與伺服器進行互動就需要有個窗體,所以客戶端要做的就是:連線伺服器——>開啟窗體——>讀寫資料,處理
為什麼先連線再開啟窗體?畢竟如果沒連上伺服器那開啟窗體不就本末倒置了嗎?
先完成窗體:
窗體需要一個文字框JTextField用來輸入內容,需要一個按鈕JButton用來確認傳送,另外利用流式佈局FlowLayout進行佈局,給按鈕新增監聽器ActionListener(下面的程式碼中使用了內部類)
public void userUI(){
JFrame frame = new JFrame();
frame.setSize(300,300);
frame.setLayout(new FlowLayout());
frame.setDefaultCloseOperation(3);
JTextField field = new JTextField(20);//輸入框
JButton btn = new JButton("傳送");
frame.add(field);
frame.add(btn);
frame.setVisible(true);
ActionListener l = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
try {
//建議在用out進行輸出的時候用System.out測試一下
// System.out.println(field.getText());
//由於OutputStream只能傳送byte,需要用getBytes()把內容轉化成byte
//out是OutputStream,作為全域性變數
out.write(field.getText().getBytes());
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
};
btn.addActionListener(l);
}
再完成連線的方法
public void conn(){
try {
Socket socket = new Socket(ip, port);
System.out.println("User: connected");
//in是InputStream,作為全域性變數
in = socket.getInputStream();
out = socket.getOutputStream();
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
最後合併為一個客戶端類
public class UserSocket {
String ip = "0.0.0.0";//IP地址
int port = 9876;
OutputStream out;//客戶端的輸出流
InputStream in;//客戶端輸入流
public void userUI(){...}
public void conn(){...}
public static void main(String[] args) {
// TODO Auto-generated method stub
UserSocket us = new UserSocket();
us.userUI();
us.conn();
}
}
當我們測試客戶端時,記得要先開啟伺服器。另外,在輸入框內只能寫一個字元,畢竟伺服器只獲取了一個字元,寫多了也讀不到
到這裡,客戶端就能與伺服器進行單項通訊了