android實現基於TCP和UDP協議的即時通訊,含android端和服務器端

這幾天學習瞭下在android中實現即時通訊的方法,一開始,自然是從基本的網絡協議中開始嘗試瞭,這樣能夠最大化的私人訂制自己的應用,還能學習到更多的知識,好處多多,接下來就簡單介紹下兩種協議的不同點吧

TCP協議:提供IP環境下的數據可靠傳輸,它提供的服務包括數據流傳送、可靠性、有效流控、全雙工操作和多路復用。通過面向連接、端到端和可靠的數據包發送。就如給懸崖上的兩人通信時,他必須先把橋建好,確認橋是沒問題的情況下,才把信件交過去,以後大傢每次通信時,都確認下橋沒什麼問題,再通過這座橋來回通信瞭。

UDP協議:不為IP提供可靠性、流控或差錯恢復功能,在正式通信前不必與對方先建立連接,不管對方狀態就直接發送。這個就是飛鴿傳書瞭~

雖然UDP可靠性不如TCP協議,但是通信效率高於TCP。在網速極差的情況下優先考慮UDP協議,網速好的話TCP還是很方便使用的。

在Java中使用TCP可以通過java.net.Socket;這個類

建立連接
//實例化一個Socket對象
socket = new Socket();
//與對應的ip、端口進行連接,先要把橋建好
socket.connect(new InetSocketAddress(ip, port), 3000);
發送信息

InputStream ois = socket.getInputStream();
DataInputStream dis = new DataInputStream(new BufferedInputStream(ois));
//讀取服務器發過來的信息,如果沒信息將會阻塞線程
msg =  dis.readUTF();
發送信息
//獲得輸出流
DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
//發送數據
dos.writeUTF(msg);

接下來上源碼,為三個Thread的子類,分別對應上面三個

public class SocketThread extends Thread{

	private Socket socket;
	private Client client;
	private String ip;
	private int port;
	private boolean isStart=false;
	private MessageListener mMessageListener;
	
	/**
	 * 
	 * 使用TCP協議,連接訪問
	 * @param ip 目標機器的IP
	 * @param port 端口
	 * @param mMessageListener 收到服務器端數據時將回調該接口內的
	 *  public void Message(String msg)方法
	 */
	public SocketThread(String ip, int port,
			MessageListener mMessageListener) {
		this.ip = ip;
		this.port = port;
		this.mMessageListener = mMessageListener;
	}

	public void run() {
		try {
			//實例化一個Socket對象
			socket = new Socket();
			//與對應的ip、端口進行連接,先要把橋建好
			socket.connect(new InetSocketAddress(ip, port), 3000);
			if (socket.isConnected()) {
				System.out.println("Connected..");
				client = new Client(socket,mMessageListener);
				//打開對應的輸入/輸出流監聽
				client.start();
				isStart=true;
			}
		} catch (IOException e) {
			e.printStackTrace();
			isStart=false;
		}
	}

	// 直接通過client得到讀線程
	public ClientInputThread getClientInputThread() {
		return client.getIn();
	}

	// 直接通過client得到寫線程
	public ClientOutputThread getClientOutputThread() {
		return client.getOut();
	}

	//返回Socket狀態
	public boolean isStart(){
		return isStart;
	}
	
	// 直接通過client停止讀寫消息
	public void setIsStart(boolean isStart) {
		this.isStart = isStart;
		client.getIn().setStart(isStart);
		client.getOut().setStart(isStart);
	}
	
	//發送消息
	public void sendMsg(String msg){
		client.getOut().sendMsg(msg);
	}
	
	public class Client {

		private ClientInputThread in;
		private ClientOutputThread out;

		public Client(Socket socket,MessageListener mMessageListener) {
			//用這個監聽輸入流線程來接收信息
			in = new ClientInputThread(socket);
			in.setMessageListener(mMessageListener);
			//以後就用這個監聽輸出流的線程來發送信息瞭
			out = new ClientOutputThread(socket);
		}

		public void start() {
			in.setStart(true);
			out.setStart(true);
			in.start();
			out.start();
		}

		// 得到讀消息線程
		public ClientInputThread getIn() {
			return in;
		}

		// 得到寫消息線程
		public ClientOutputThread getOut() {
			return out;
		}
	}
}

public class ClientInputThread extends Thread {
	private Socket socket;
	private String msg;
	private boolean isStart = true;
	private InputStream ois;
	private DataInputStream dis;
	private MessageListener messageListener;// 消息監聽接口對象

	public ClientInputThread(Socket socket) {
		this.socket = socket;
		try {
			ois = socket.getInputStream();
			dis = new DataInputStream(new BufferedInputStream(ois));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 提供給外部的消息監聽方法
	 * 
	 * @param messageListener
	 *            消息監聽接口對象
	 */
	public void setMessageListener(MessageListener messageListener) {
		this.messageListener = messageListener;
	}

	public void setStart(boolean isStart) {
		this.isStart = isStart;
	}

	@Override
	public void run() {
		try {
			while (isStart) {
				//讀取信息,如果沒信息將會阻塞線程
				msg =  dis.readUTF();
				// 每收到一條消息,就調用接口的方法Message(String msg)
				Log.v("收到消息", msg);
				messageListener.Message(msg);
			}
			ois.close();
			if (socket != null)
				socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	BufferedReader reader=null;
	public String getInputStreamString() {
		/*
		 * To convert the InputStream to String we use the
		 * BufferedReader.readLine() method. We iterate until the BufferedReader
		 * return null which means there's no more data to read. Each line will
		 * appended to a StringBuilder and returned as String.
		 */
		if (ois != null) {
			reader = new BufferedReader(new InputStreamReader(ois));
		}
		StringBuilder sb = new StringBuilder();

		String line = null;
		try {
			while ((line = reader.readLine()) != null) {
				sb.append(line + "\n");
			}
		} catch (IOException e) {
			e.printStackTrace();
		} 

		return sb.toString();
	}
}

public class ClientOutputThread extends Thread {
	private Socket socket;
	private DataOutputStream dos;
	private boolean isStart = true;
	private String msg;

	public ClientOutputThread(Socket socket) {
		this.socket = socket;
		try {
			dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void setStart(boolean isStart) {
		this.isStart = isStart;
	}

	// 這裡處理跟服務器是一樣的
	public void sendMsg(String msg) {
		this.msg = msg;
		synchronized (this) {
			notifyAll();
		}
	}

	@Override
	public void run() {
		try {
			while (isStart) {
				if (msg != null) {
					dos.writeUTF(msg);
					dos.flush();
					msg=null;
					synchronized (this) {
						wait();// 發送完消息後,線程進入等待狀態
					}
				}
			}
			dos.close();// 循環結束後,關閉輸出流和socket
			if (socket != null)
				socket.close();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
//定義接收到消息時的,處理消息的接口
public interface MessageListener {
	public void Message(String msg);
}

主界面,感覺很醜,將就吧



    

        
    

    

    
"

MainActivity代碼

public class MainActivity extends Activity {

	EditText etMessage;
	TextView tvSend, tvMessage;
	SocketThread client;
	MyHandler myHandler;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		setup();
	}

	public void setup() {
		etMessage = (EditText) findViewById(R.id.etMessage);
		tvSend = (TextView) findViewById(R.id.tvSend);
		tvMessage = (TextView) findViewById(R.id.tvMessage);
		tvSend.setOnClickListener(onClick);
		myHandler = new MyHandler();

		//初始化
		client = new SocketThread("10.21.56.226", 8888,new MessageListener() {

					//收到消息後調用此方法
					@Override
					public void Message(String msg) {
						// TODO Auto-generated method stub
						// tvMessage.append(msg);
						Bundle bundle = new Bundle();
						bundle.putString("input", msg);
						Message isMessage = new Message();
						isMessage.setData(bundle);
						//使用handler轉發
						myHandler.sendMessage(isMessage);
					}
				});
		//正式啟動線程
		client.start();

	}

	OnClickListener onClick = new OnClickListener() {
		public void onClick(android.view.View v) {
			String message = etMessage.getText().toString();
			Log.v("發送消息", message);
			if (client.isStart()) {
				client.sendMsg(message);
			}
		};
	};

	private class MyHandler extends Handler {
		@Override
		public void handleMessage(Message msg) {
			// TODO Auto-generated method stub
			Log.v("處理收到的消息", "  ");
			tvMessage.append(msg.getData().getString("input"));
		}
	}
}

服務器端的代碼,主要是使用ServerSocket監聽一個端口,來與客戶端鏈接和收發信息

public class ChatServer {
    boolean started = false;
    ServerSocket ss = null;

    List clients = new ArrayList();

    public static void main(String[] args) {
	new ChatServer().start();
    }

    public void start() {
	try {
	    //ServerSocket監聽8888端口
	    ss = new ServerSocket(8888);
	    started = true;
	} catch (BindException e) {
	    System.out.println("start....");
	    System.out.println("有問題");
	    e.printStackTrace();
	    System.exit(0);
	} catch (IOException e) {
	    e.printStackTrace();
	}

	try {

	    while (started) {
		Socket s = ss.accept();
		Client c = new Client(s);
		System.out.println("a client connected!");
		new Thread(c).start();
		clients.add(c);
		// dis.close();
	    }
	} catch (IOException e) {
	    e.printStackTrace();
	} finally {
	    try {
		ss.close();
	    } catch (IOException e) {
		// TODO Auto-generated catch block
		e.printStackTrace();
	    }
	}
    }

    class Client implements Runnable {
	private Socket s;
	private DataInputStream dis = null;
	private DataOutputStream dos = null;
	private boolean bConnected = false;

	public Client(Socket s) {
	    this.s = s;
	    try {
		dis = new DataInputStream(s.getInputStream());
		dos = new DataOutputStream(s.getOutputStream());
		bConnected = true;
	    } catch (IOException e) {
		e.printStackTrace();
	    }
	}

	public void send(String str) {
	    try {
		dos.writeUTF(str);
	    } catch (IOException e) {
		clients.remove(this);
		System.out.println("關閉一個連接");
		// e.printStackTrace();
	    }
	}

	public void run() {
	    try {
		while (bConnected) {
		    String str = dis.readUTF();
		    System.out.println(str);
		    for (int i = 0; i < clients.size(); i++) {
			Client c = clients.get(i);
			c.send(str);
			// System.out.println(" a string send !");
		    }
		    /*
		     * for(Iterator it = clients.iterator();
		     * it.hasNext(); ) { Client c = it.next(); c.send(str); }
		     */
		    /*
		     * Iterator it = clients.iterator();
		     * while(it.hasNext()) { Client c = it.next(); c.send(str);
		     * }
		     */
		}
	    } catch (EOFException e) {
		System.out.println("Client closed!");
	    } catch (IOException e) {
		e.printStackTrace();
	    } finally {
		try {
		    System.out.println("close All !");
		    if (dis != null)
			dis.close();
		    if (dos != null)
			dos.close();
		    if (s != null) {
			s.close();
			// s = null;
		    }

		} catch (IOException e1) {
		    e1.printStackTrace();
		}

	    }
	}

    }
}

接下來先運行服務器代碼,再運行手機端就可以瞭,多臺手機可以互相發送信息瞭。
晚點再寫UDP的

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *