1. 程式人生 > 其它 >Java第十二課——客戶端與伺服器的單線通訊

Java第十二課——客戶端與伺服器的單線通訊

技術標籤:Java通訊javasocket網路

Java第十二課——伺服器與客戶端的單線通訊

一、通訊是如何進行的?

平時我們用微信、QQ進行聊天時,我們是如何把訊息傳送出去的?對方為什麼能接到訊息?兩人甚至在地球的兩極也能接到訊息,這是如何實現的?
我們知道,平時日常的對話需要面對面的進行,而其實在網際網路下也是一樣的。在通訊剛開始時,兩人必須在同一網路下才能進行通訊,通過訪問對方的IP地址,找到其開放的埠進行通訊,先來說一下什麼是IP地址?什麼是埠?
百度百科是這麼解釋的:

IP地址是IP協議提供的一種統一的地址格式,它為網際網路上的每一個網路和每一臺主機分配一個邏輯地址,以此來遮蔽實體地址的差異。

埠可分為虛擬埠和物理埠,其中虛擬埠指計算機內部或交換機路由器內的埠,不可見。例如計算機中的80埠、21埠、23埠等。物理埠又稱為介面,是可見埠,計算機背板的RJ45網口,交換機路由器集線器等RJ45埠。電話使用RJ11插口也屬於物理埠的範疇。

簡單的來說,IP地址相當於虛擬的地理位置,每個網路都有自己的IP地址,每個在網路裡的主機會被當前所連線的網路分配一個IP地址,這個IP地址可能隨著每次的連線而有所改變,但在一次連線的過程中是不變的。
埠就相當於對外開放的視窗,通訊上的埠是虛擬的,相當於外界想與當前主機進行交流的視窗,需要開放端口才能與外界進行資料交換。
檢視自己當前IP可以在cmd面板中用ipconfig命令,找到IPv4地址後對應的就是當前的IP地址

具體內容可以參考TCP/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)就會收到如下輸出
Server建立連線成功
注意:埠號要是伺服器所開放的埠號
到這裡伺服器端的雛形就完成了

五、客戶端的建立

客戶端和伺服器在本質上其實是一樣的
伺服器開放一個埠給伺服器去連線,連線進來的使用者就獲取到輸入輸出流來讀寫資料,進行資料處理
客戶端就是去連線一個主機的埠,連線進取後獲取輸入輸出流來給伺服器發訊息,進行資料處理
連線伺服器其實就一句程式碼:

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();
	}
}

當我們測試客戶端時,記得要先開啟伺服器。另外,在輸入框內只能寫一個字元,畢竟伺服器只獲取了一個字元,寫多了也讀不到

到這裡,客戶端就能與伺服器進行單項通訊了