偉大的西西子出的作業組員們一個個放棄掉了這個作業,我寫的好孤單寂寞啊←

主要的要求就是利用老師基於Linux的Server,在上面run Java,和Client端互相溝通,可接受的實作方式有Java Swing、Java、Android,而我選了Android...

Server端就只是命令模式而已,廢話不多說先上code。

 

public class SocketServer {
	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		ServerSocket ss = null;
		Socket socket = null;
		BufferedReader in;
		PrintWriter out;
		BufferedReader keyboardInput;
		Thread serverthread;

來說明一下變數吧。

ServerSocket 建立一個 ServerSocket 類,在伺服器端打開端口,不指定其位址(因為它等別人來連)。而指定其位址的狀況出現在於該台 Server 有多張網路介面卡,超連結內有解決方案,需要使用的朋友可以參考。

Socket 類別,則用於在 ServerSocket 物件建立連接後接收建立好的連接。

可以說, ServerSocket 負責在 Server 端開個洞,等 Client 連接上之後就像是連好了水管(Socket)。當然,那個洞開在哪裡,自然就是指定埠號的事了。

BufferdReader 繼承自 Reader 類別,吃的是字元。由於電腦、網路傳送的是編碼,我們看的是字元,所以必經過一番處理才能使用。

in 這邊負責接收由Client 端送來的訊息。KeyboardInput 則是接收我們操縱 Server 端發出的訊息,要給 Client 端看的。

PrintWriter 繼承自 Writer類別,送出的也是字元。

Thread 部分,負責以迴圈聆聽Client端傳送的訊息,等等另外一個檔案會講到。

		try {
			ss = new ServerSocket(5023);// sever端開啟了5023port
			System.out.println("waitting...");
			socket = ss.accept(); 

這邊的 ss.accept() 會癡癡的一直等,等到有個迷途羔羊 Client 端連上他,才會進行做下一個動作。

要注意的是建立連線後這邊會阻塞,如果有Server需要聆聽多個 Client 端的連接,則必須多寫一個執行緒來進行這個 ss.accept() 的動作。

			System.out.println("client connected");
		} catch (IOException e) {
			e.printStackTrace();
		}

		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		serverthread = new ServerThread(in);
		serverthread.start();// 開啟聆聽服務
		if (serverthread.isAlive())
			System.out.println("執行緒開啟成功");

		// send msg to client
		while (true) {
			keyboardInput = new BufferedReader(new InputStreamReader(System.in));

System.in 是鍵盤所輸入的串流,經由InputReader之後成為人能閱讀,再經由 BufferedReader 去拆分、利用。

			String str=keyboardInput.readLine();
			System.out.println("伺服器端說:" + str);

			out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8")), true);// 指定輸出編碼為UTF-8
			out.println(str);
			out.flush();
		}

		keyboardInput.close();
		out.close();
		socket.close();
		ss.close();
	}

}

執行緒的部分,以一個死迴圈來不斷接收由Android端傳送而來的訊息。

public class ServerThread extends Thread {

	private String usermsg = "";
	private BufferedReader clientin;

	@SuppressWarnings("unused")
	private ServerThread() {
		// can't use Constructor
	}

	public ServerThread(BufferedReader in) {
		clientin = in;// 接進來處理
	}

	@Override
	public void run() {
		// loop to listen User's input
		while (true) {
			try {
				usermsg = clientin.readLine();
			} catch (IOException e) {
				e.printStackTrace();
			}
			System.out.println("使用者說:" + usermsg);
			
			if (usermsg=="再見"||usermsg=="bye"){
				System.out.println("中斷連接");
				try {
					clientin.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
			try {
				Thread.sleep(500); // 休息0.5秒
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}
	}

}

接下來是Android Client的部分。

 

android client  

@SuppressLint("HandlerLeak")
public class Main extends Activity {

	/* Controler Declaration */
	private Button btn_connect, btn_send;
	private EditText et_client, et_input, et_server;

	/* Socket Declaration */
	static Socket socket;
	private final String LOCALHOST = "61.224.164.232"; // 綁定連接的 Server 之 IP 位址,在做應用的時候還是建議別寫死...
	private final int PORT = 5023;
	private PrintWriter out;
	public BufferedReader in;

	/* the OTHER Variable */
	private String usermsg = null;
	public static Handler receiveServer = null;

	Thread thread;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		initial();
		setAction();
	}

	private void initial() {
		btn_connect = (Button) findViewById(R.id.btn_connect);
		btn_send = (Button) findViewById(R.id.btn_send);
		et_server = (EditText) findViewById(R.id.et_server);
		et_client = (EditText) findViewById(R.id.et_client);
		et_input = (EditText) findViewById(R.id.et_input);
	}

	private void setAction() {
		Log.i("Chat", "setAction()");
		et_server.setText("請按下Connect連接");

		btn_send.setEnabled(false);

		// 取得焦點後清空提示文字
		et_input.setOnFocusChangeListener(new OnFocusChangeListener() {
			public void onFocusChange(View v, boolean hasFocus) {
				et_input.setText("");
			}
		});

		// 取得連結,實作socket對象
		btn_connect.setOnClickListener(new OnClickListener() {

			@Override
			public void onClick(View v) {
				et_server.setText("稍等一下,連接中....");
				try {
					socket = new Socket(LOCALHOST, PORT);
					if (socket.isConnected()) Log.i("Chat", "Socket Connected");
					et_server.setText(et_server.getText().toString() + "連接成功!");
					in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
					// in的部分給另外一個執行緒去弄
					out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"BIG5")), true);

					btn_send.setEnabled(true);// 連接上之後,send Button才能夠使用

					thread = new SocketClient(in);
					thread.start();

					if (thread.isAlive())Log.i("Chat", "thread建立成功");

					Log.i("Chat", "try區段成功");

接來這邊很重要。由於 Android 系統不允許主執行緒負擔太重,若是停留太久會造成異常中止,因此如果有負擔重的背景運作,我們可以交給 Thread 來做;但 Android 系統又為了安全性不允許副執行緒更動主執行緒的 UI ,所以 Android API 第八層就生出了這個 Handler 類別,用於監聽副執行緒所傳送過來的 Message 類別,來給主執行緒更新 UI 。

就我的理解來說,在主執行緒中註冊一個 Handler 類別就好比註冊一個聆聽 btn按下的方法。「 Handler 物件」監聽 Message 傳送訊息過來,就好比「聆聽 btn 被按下的方法」則執行方法內的程式碼。如同 btn 長在 layout 上 , Message 則是長在副執行緒中。 

					receiveServer = new Handler() {
						@Override
						public void handleMessage(Message servermsg) {
							super.handleMessage(servermsg);
							
							switch (servermsg.what) {
							case 1:
								Log.i("Chat", "handler執行");
								et_server.setText(et_server.getText() + "\n" + (String)servermsg.obj);
								Log.i("Chat", "handler執行完畢");
								break;
							}
						}
					};
				} catch (UnknownHostException e) {
					et_server.setText(et_server.getText().toString() + "不知名的主機錯誤");
					Log.i("Chat", "UnknownHostException");
					e.printStackTrace();
				} catch (IOException e) {
					et_server.setText(et_server.getText().toString() + "IO錯誤");
					Log.i("Chat", "IOException");
					e.printStackTrace();
				}

			}
		});

		// 按下後將會把EditText框中的文字傳送給server
		btn_send.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				usermsg = et_input.getText().toString();// 取得user欲傳送至Server的訊息
				if (socket.isConnected()) {// 確認socket在連通狀態下
					if (!socket.isOutputShutdown() && usermsg.length() > 0) {// 確認socket要噴出去的東西噴完&&要傳輸的訊息不等於0
						out.println(usermsg);
						out.flush();

						if (et_client.getText().toString().length() == 0) {
							et_client.setText(et_client.getText().toString() + usermsg);// 也要噴一份給介面上的
							et_input.setText("");// 輸入框清空
						} else {// 避免多一行空白
							et_client.setText(et_client.getText().toString() + "\n" + usermsg);// 也要噴一份給介面上的
							et_input.setText("");// 輸入框清空
						}
					} else {
						Toast.makeText(getApplicationContext(), "請確認輸入框是否空白!", 1000).show(); // 只顯示一秒的 Toast
					}
				}
			}
		});

	}
	
	@Override
	protected void onStop() {
		thread.stop();
		out.close();
		try {
			in.close();
			out.close();
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		super.onStop();
	}
}

Android 的另外一個執行緒。

public class SocketClient extends java.lang.Thread {
	BufferedReader serverin;
	String obj;
	Message servermsg = new Message();

	@SuppressWarnings("unused")
	private SocketClient() {
		// 不使用的建構子
	}

	SocketClient(BufferedReader in) {
		if (Main.socket.isConnected()) {
			serverin = in;
			Log.i("Chat", "in創建");
		}
	}

	public void run() {
		while (true) {
			try {
				obj = "" + serverin.readLine();
				servermsg = Main.receiveServer.obtainMessage(1, obj);
			} catch (IOException e) {
				Log.i("Chat", "聆聽Server訊息GG");
				e.printStackTrace();
			}
			Main.receiveServer.sendMessage(servermsg);

			try {
				SocketClient.sleep(500);
			} catch (InterruptedException e) {
				Log.i("Chat", "Thread中斷錯誤");
				e.printStackTrace();
			}// 休息0.5秒
		}

	}
}

簡略的版本以及一些學習上粗淺的心得,如果我的理解有問題,煩請不吝指教:D

arrow
arrow
    全站熱搜

    赭 發表在 痞客邦 留言(0) 人氣()