在Android聊天程序的實(shí)現(xiàn)中趾代,通常是通過http請(qǐng)求拉取最新聊天信息,由于http請(qǐng)求是無狀態(tài)(Stateless)的撒强,無法隨時(shí)獲知新消息的到達(dá),通常采用推送的方式告知客戶端有新的消息胚想。除了這種http+推送的方式外,也可以通過Socket編程實(shí)現(xiàn)聊天程序浊服。本文將對(duì)網(wǎng)絡(luò)層次進(jìn)行簡(jiǎn)單的講解胚吁,并結(jié)合一個(gè)簡(jiǎn)單的demo講解Socket編程的實(shí)現(xiàn)牙躺。
計(jì)算機(jī)網(wǎng)絡(luò)基礎(chǔ)知識(shí)
計(jì)算機(jī)網(wǎng)絡(luò)用于連接不同的計(jì)算機(jī)腕扶,并實(shí)現(xiàn)計(jì)算機(jī)間的數(shù)據(jù)傳輸。整個(gè)數(shù)據(jù)傳輸是個(gè)很復(fù)雜的過程乓搬,需要很多步驟進(jìn)行,這些步驟被劃分成了不同的層級(jí)进肯,所以計(jì)算機(jī)網(wǎng)絡(luò)有標(biāo)準(zhǔn)的層級(jí)結(jié)構(gòu),從底層到高層依次是物理層江掩,數(shù)據(jù)鏈路層,網(wǎng)絡(luò)層策泣,傳輸層抬吟,應(yīng)用層萨咕,如下圖:
分層的優(yōu)點(diǎn)在于各層級(jí)之間是獨(dú)立的危队,靈活性好,在結(jié)構(gòu)上可分隔開茫陆,易于實(shí)現(xiàn)和維護(hù)擎析,同時(shí)能促進(jìn)每層的標(biāo)準(zhǔn)化工作。每個(gè)層次都有運(yùn)行在該層的一系列網(wǎng)絡(luò)協(xié)議揍魂,如網(wǎng)絡(luò)層的IP協(xié)議,傳輸層的TCP/UDP協(xié)議讨盒,應(yīng)用層的Http/SMTP/DNS協(xié)議步责,如下圖:
其中Http協(xié)議運(yùn)行在應(yīng)用層,典型的應(yīng)用是網(wǎng)絡(luò)瀏覽器蔓肯。Http協(xié)議是無狀態(tài)的,同一個(gè)客戶多次訪問同一個(gè)服務(wù)器上的頁面時(shí)秉扑,服務(wù)器的響應(yīng)時(shí)間跟第一次訪問時(shí)是相同的,因?yàn)榉?wù)器并不記得曾經(jīng)訪問過的這個(gè)客戶舟陆,也不記得上次訪問的內(nèi)容。Http協(xié)議的無狀態(tài)特性簡(jiǎn)化了服務(wù)器的設(shè)計(jì)秦躯,使得服務(wù)器更容易支持大量并發(fā)的Http請(qǐng)求。
因?yàn)镠ttp協(xié)議完成一次數(shù)據(jù)交互后就斷開連接倡缠,所以服務(wù)器無法通過Http請(qǐng)求主動(dòng)告知客戶端某條新消息的到達(dá)茎活。而Tcp/Udp協(xié)議可以做到了這一點(diǎn)。Tcp/Udp協(xié)議是傳輸層協(xié)議载荔,可以進(jìn)行全雙工通信。這樣就使得服務(wù)器端可以在有新消息到達(dá)的時(shí)候主動(dòng)通知客戶端懒熙,讓客戶端拉取最新消息。
Tcp和Udp兩個(gè)協(xié)議的區(qū)別在于,Tcp是面向連接的泌豆,在傳輸數(shù)據(jù)區(qū)必須先建立連接,數(shù)據(jù)傳輸結(jié)束后要釋放連接蔬浙。能保證數(shù)據(jù)的可靠傳輸贞远,建立連接的過程較復(fù)雜,占用較多的處理機(jī)資源蓝仲。而Udp是無連接的,不需要建立連接袱结,不提供可靠交付,但是處理機(jī)開銷小溢吻,效率高果元∠耍基于這些特點(diǎn),Udp協(xié)議通常用于對(duì)傳輸數(shù)據(jù)容錯(cuò)性高阅畴、時(shí)效性強(qiáng)的場(chǎng)景题翰,如語音電話數(shù)據(jù)的傳輸;而Tcp協(xié)議通常用于對(duì)傳輸數(shù)據(jù)完整性和順序要求高的場(chǎng)景豹障,如文件傳輸。
在我們的聊天程序中昵仅,我們將利用Tcp協(xié)議進(jìn)行聊天內(nèi)容的傳輸累魔。
使用Tcp協(xié)議的Socket編程
使用Tcp協(xié)議的Socket編程主要用到兩個(gè)類,Socket和ServerSocket垦写。ServerSocket本身也是一個(gè)Socket,只是它同時(shí)包含了一些額外的服務(wù)器終端的功能梯投,比如監(jiān)聽端口,等待客戶端Socket前來建立連接等尔艇。通過accept方法一旦和客戶端建立起連接么鹤,就會(huì)返回一個(gè)普通的Socket和客戶端Socket進(jìn)行對(duì)等的通信。
本文的Demo實(shí)現(xiàn)的功能很簡(jiǎn)單蒸甜,用戶輸入消息,發(fā)送給服務(wù)器昧辽,服務(wù)器原封不動(dòng)返回給客戶端。原理是:?jiǎn)?dòng)一個(gè)服務(wù)器線程和兩個(gè)客戶端線程搅荞。服務(wù)器線程監(jiān)聽2000端口,等待客戶端進(jìn)程的連接咕痛。客戶端線程包括一個(gè)發(fā)送線程和一個(gè)接收線程塞栅,發(fā)送線程連接服務(wù)器的2000端口腔丧,將用戶輸入的消息發(fā)送給服務(wù)器線程,接收線程監(jiān)聽2001端口愉粤,等待服務(wù)器返回的數(shù)據(jù)。服務(wù)器通過2000端口收到客戶端發(fā)來的數(shù)據(jù)后衣厘,將數(shù)據(jù)原封不動(dòng)發(fā)送到客戶端的2001端口,完成通信错邦。
由于通過Socket進(jìn)行收發(fā)消息是阻塞式的型宙,也就是說在等待接收消息的時(shí)候不能同時(shí)發(fā)送消息,所以客戶端啟用了兩個(gè)線程分別進(jìn)行收消息和發(fā)消息的操作妆兑。代碼結(jié)構(gòu)如下圖:
服務(wù)器收發(fā)線程關(guān)鍵代碼:
@Override
public void run() {
try {
// 服務(wù)器線程通過2000端口監(jiān)聽客戶端發(fā)來的消息
serverSocket = new ServerSocket(2000);
serverReceiveSocket = serverSocket.accept();
// 和客戶端2001端口進(jìn)行通信,向客戶端發(fā)送消息
serverSendSocket = new Socket("127.0.0.1", 2001);
dataInputStream = new DataInputStream(serverReceiveSocket.getInputStream());
printStream = new PrintStream(serverSendSocket.getOutputStream());
bufferedReader = new BufferedReader(new InputStreamReader(dataInputStream, "UTF-8"));
while (!Thread.currentThread().isInterrupted()) {
try {
String line = bufferedReader.readLine();
printStream.println(line);
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
return;
}
cleanUpJob();
}
客戶端發(fā)送線程關(guān)鍵代碼:
@Override
public void run() {
try {
// 客戶端連接服務(wù)器端2000端口,通過該端口向服務(wù)器發(fā)消息
clientSocket = new Socket("127.0.0.1", 2000);
outputStream=new PrintStream(clientSocket.getOutputStream());
while (!Thread.currentThread().isInterrupted()) {
if (toSendList != null && toSendList.size() > 0) {
outputStream.println(toSendList.get(0));
toSendList.clear();
}
}
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
cleanUpJob();
}
客戶端收消息關(guān)鍵代碼:
@Override
public void run() {
try {
// 客戶端監(jiān)聽2001端口,等待服務(wù)器的連接,接收服務(wù)器發(fā)來的消息
server = new ServerSocket(2001);
socket = server.accept();
inputStream = new DataInputStream(socket.getInputStream());
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
while (!Thread.currentThread().isInterrupted()) {
String line = bufferedReader.readLine();
if (!TextUtils.isEmpty(line)) {
strList.add(line);
handler.sendEmptyMessage(-1);
}
}
} catch (IOException e) {
e.printStackTrace();
return;
}
cleanUpJob();
}
參考:
http://stackoverflow.com/questions/2004531/what-is-the-difference-between-socket-and-serversocket
http://stackoverflow.com/questions/5764065/the-difference-between-inputstream-and-inputstreamreader-when-reading-multi-byte