本文介紹編寫一個(gè)使用Socket完成Android客戶端與服務(wù)器端通信的簡(jiǎn)單Demo
詳細(xì)代碼地址:github.com/Baolvlv/LearnAndroid/tree/master/MySocketClient
(1)Socket介紹
Socket,又稱套接字,應(yīng)用通過socket向網(wǎng)絡(luò)發(fā)出請(qǐng)求或應(yīng)答網(wǎng)絡(luò)請(qǐng)求
在java中尺碰,Socket與ServerSocket位于java.net包中挣棕。ServerSocket位于服務(wù)器端汇竭,Socket位于客戶端。連接成功時(shí)穴张,兩端都產(chǎn)生一個(gè)Socket實(shí)例,通過操作這個(gè)實(shí)例完成會(huì)話两曼。兩端的實(shí)例平等皂甘,沒有差別,通過Socket與其子類完成所有操作悼凑。
(2)Sokcet鏈接建立過程
1.服務(wù)器監(jiān)聽 ? ?2.客戶端發(fā)出請(qǐng)求 ? ?3.建立鏈接 ? ?4.通信
(3)Socket的特點(diǎn)
Socket基于Tcp鏈接偿枕,數(shù)據(jù)傳輸有保障
Socket適用于建立長(zhǎng)時(shí)間鏈接
Socket編程通常應(yīng)用于即時(shí)通信
Eclipse中新建java工程,包户辫,類渐夸。
創(chuàng)建ServerSocket對(duì)象,自定義通信端口號(hào)(1-65535)65535最大的16位為二進(jìn)制數(shù)渔欢。
// 構(gòu)造函數(shù)參數(shù)為SerSocket偵聽的端口墓塌,取值1-65535
//隨意取值,使用較大的端口奥额,與預(yù)留端口區(qū)分
try{//創(chuàng)建serverSocket對(duì)象
ServerSocket
serverSocket=newServerSocket(12345);
調(diào)用serverSocket.accept方法苫幢,偵聽客戶端的請(qǐng)求,創(chuàng)建socket垫挨。此方法在客戶端請(qǐng)求之前會(huì)導(dǎo)致線程一直為阻塞狀態(tài)韩肝。當(dāng)accept方法執(zhí)行,socket被賦值九榔,則說明鏈接建立哀峻。
//偵聽客戶端的請(qǐng)求,會(huì)阻塞當(dāng)前線程? block
//accept方法執(zhí)行哲泊,socket被賦值剩蟀,則說明鏈接建立
Socket
socket=serverSocket.accept();
建立連接后,服務(wù)器端彈出對(duì)話框攻旦,使用java swing
//建立鏈接后執(zhí)行喻旷,彈出對(duì)話框,第一個(gè)參數(shù)為父級(jí)容器
JOptionPane.showMessageDialog(
null,"有客戶端鏈接到了本機(jī)的12345端口");
此時(shí)運(yùn)行程序牢屋,程序會(huì)一直運(yùn)行且预,直到客戶端發(fā)出請(qǐng)求,建立連接后彈出對(duì)話框結(jié)束
2.使用ServerSocket建立聊天服務(wù)器
會(huì)阻塞主線程的程序應(yīng)該單獨(dú)放在一個(gè)線程中
創(chuàng)建ChatSocket類繼承自Thread烙无,用于執(zhí)行客戶端連接后的輸出操作
//聲明Socket對(duì)象
Socket
socket
;
//構(gòu)造函數(shù)接收外部傳入的SocketpublicChatSocket(Sockets
){
this.socket=s
;
}
創(chuàng)建輸出函數(shù)锋谐,使用輸出流進(jìn)行輸出
publicvoidout(Stringout) {
socket.getOutputStream().write(out.getBytes("UTF-8"));
復(fù)寫Thread類的run方法,執(zhí)行輸出操作截酷,每隔1秒輸出一次
publicvoidrun() {intcount
= 0;
//不斷執(zhí)行輸出操作while(true
){
out(
"loop"+count
);
count
++;
try
{
//每隔1秒輸出一次
sleep(1000);
}
catch(InterruptedExceptione
) {
//TODOAuto-generated catch blocke
.printStackTrace();
}
自定義ServerListener類繼承自Thread涮拗,用于執(zhí)行偵聽客戶端的請(qǐng)求,創(chuàng)建socket,為每個(gè)客戶端進(jìn)行輸出操作
創(chuàng)建ServerSocket,繼承自Thread類
publicclassServerListenerextendsThread {
復(fù)寫run方法三热,規(guī)定端口鼓择,創(chuàng)建ServerSocket對(duì)象
ServerSocketserverSocket=newServerSocket(12345);
不斷循環(huán),每當(dāng)客戶端發(fā)出請(qǐng)求就漾,serverSocket.accept()方法執(zhí)行一次呐能,創(chuàng)建一個(gè)新Socket對(duì)象,將創(chuàng)建的socket傳入抑堡,啟動(dòng)輸出線程
while(true){
Socketsocket=serverSocket.accept();
newChatSocket(socket).start();
}
Telnet是進(jìn)行遠(yuǎn)程登錄的標(biāo)準(zhǔn)協(xié)議和主要方式它為用戶提供了在本地計(jì)算機(jī)上完成遠(yuǎn)程主機(jī)工作的能力摆出。可以用telnet命令來測(cè)試端口號(hào)是否正常打開還是關(guān)閉狀態(tài)首妖。
測(cè)試命令:控制臺(tái)輸入 ?telnet localhost 12345
會(huì)在控制臺(tái)不斷輸出請(qǐng)求結(jié)果
添加一個(gè)客戶端發(fā)出消息偎漫,其他客戶端接收消息的功能
創(chuàng)建ChatManager類,用于管理每個(gè)客戶端對(duì)象創(chuàng)建的用于聊天的ChatSocket線程對(duì)象
由于每個(gè)聊天服務(wù)器只能擁有一個(gè)ChatManger類有缆,所以使用單例模式
創(chuàng)建private的構(gòu)造方法和private對(duì)象象踊,public static返回對(duì)象的方法
//私有化構(gòu)造方法private
ChatManager() {}
//為當(dāng)前的類創(chuàng)建唯一的實(shí)例privatestaticfinalChatManagercm=new
ChatManager();
//創(chuàng)建返回唯一實(shí)例的public static方法publicstatic
ChatManager getChatManager() {
returncm
;
}
創(chuàng)建Vector,范型為ChatSocket妒貌,用于管理客戶端的聊天線程
//創(chuàng)建Vector通危,管理每一個(gè)客戶端創(chuàng)建的ServerSocket線程對(duì)象
Vector
vector=newVector<>();
創(chuàng)建向Vector中添加元素的方法
//Vector的添加方法publicvoidadd(ChatSocketcs
) {
vector.add(cs);
創(chuàng)建發(fā)布消息的方法,參數(shù)為發(fā)布消息的chatSocket和發(fā)布的消息
遍歷Vector的中的所有元素灌曙,對(duì)不是發(fā)布消息的其他ChatSocket執(zhí)行out方法
在發(fā)布消息的ChatSocket內(nèi)部調(diào)用此方法
//其中任意一個(gè)線程可以調(diào)用publish方法向其他的客戶端發(fā)布信息publicvoidpublish(ChatSocketcs,Stringout
) {
//遍歷vector中的元素for(inti= 0;i
++){
//創(chuàng)建方法內(nèi)部的本地對(duì)象
ChatSocket
cSocket=vector.get(i
);
//判斷菊碟,不向自身發(fā)布消息if(!cSocket.equals(cs
)){
//調(diào)用輸出方法,輸出需要發(fā)送的信息cSocket.out(out
);
}
當(dāng)客戶端連接時(shí)在刺,創(chuàng)建socket,創(chuàng)建ChatSocket,將創(chuàng)建的ChatSocket添加到ChatManager中
Socketsocket=serverSocket.accept();
ChatSocketchatSocket=newChatSocket(socket);chatSocket.start();//當(dāng)ChatSocket線程創(chuàng)建時(shí)逆害,將它添加到ChatManager中
ChatManager.getChatManager().add(
chatSocket);
當(dāng)前客戶端獲取輸出流進(jìn)行輸出
//向客戶端發(fā)送數(shù)據(jù)
socket.getOutputStream().write(outline.getBytes("UTF-8"));
在ChatSocket中,socket獲取輸出流蚣驼,out方法將獲取的字符串輸出
//通過輸出流進(jìn)行輸出publicvoidout(Stringout
) {
String
outline=out+"\n"
;
try
{
//向客戶端發(fā)送數(shù)據(jù)socket.getOutputStream().write(outline.getBytes("UTF-8"));
在ChatSocket的run方法中魄幕,將本ChatSocket的數(shù)據(jù),通過socket獲取輸入流
讀取本socket發(fā)送的信息颖杏,調(diào)用ChatManager的publish方法發(fā)送給其他ChatSocket
@Overridepublicvoid
run() {
try
{
//通過socket獲取輸入流纯陨,讀取當(dāng)前socket將要發(fā)送的信息
BufferedReader
reader=new
BufferedReader(
newInputStreamReader(socket.getInputStream(),"UTF-8"
));
String
line
;
//將本ChatSocket發(fā)送的數(shù)據(jù),轉(zhuǎn)發(fā)給其他客戶端while((line=reader.readLine()) !=null
){
ChatManager.getChatManager().publish(
this,line
);
}
3.在Android中創(chuàng)建socket客戶端
socket的創(chuàng)建不能在主線程中留储,新建異步線程翼抠,復(fù)寫doInBackground方法,完成socket創(chuàng)建获讳,連接與數(shù)據(jù)讀取的操作阴颖。
//讀取聊天信息的異步線程AsyncTask read =newAsyncTask() {
@OverrideprotectedVoiddoInBackground(Void... params) {
try{
//socket不能在主線程中//新建socket,參數(shù)為ip與端口號(hào)socket=newSocket(ip,12346);//通過socket的輸入輸出流,創(chuàng)建BufferedWriter與BufferedReaderwriter=newBufferedWriter(newOutputStreamWriter(socket.getOutputStream()));reader=newBufferedReader(newInputStreamReader(socket.getInputStream()));//向外發(fā)布成功的狀態(tài)publishProgress("@success");}catch(IOException e) {
Toast.makeText(getApplicationContext(),"無法建立連接",Toast.LENGTH_SHORT).show();e.printStackTrace();}
try{
String line;//通過reader讀取數(shù)據(jù)并向外發(fā)布while((line =reader.readLine())!=null){
publishProgress(line);}
}catch(IOException e) {
e.printStackTrace();}
return null;
復(fù)寫onProgressUpdate()方法用于接收doInBackground方法向外發(fā)布的數(shù)據(jù),更新主線程的ui
@Overrideprotected voidonProgressUpdate(String... values) {
//接收判斷丐膝,如果是成功量愧,則彈出提示if(values[0].equals("@success")){
Toast.makeText(MainActivity.this,"連接成功",Toast.LENGTH_SHORT).show();}
//在主ui線程中钾菊,更新接收到的消息tvChat.append("某人說:"+values[0]+"\n");super.onProgressUpdate(values);}
};
調(diào)用異步線程執(zhí)行
//調(diào)用異步線程執(zhí)行read.execute();
創(chuàng)建send方法,通過writer向外輸出數(shù)據(jù)
public voidsend(){
try{
tvChat.append("我說:"+etSend.getText().toString()+"\n");//通過writer輸出信息writer.write(etSend.getText().toString()+"\n");writer.flush();//清空ediText的內(nèi)容etSend.setText("");}catch(IOException e) {
e.printStackTrace();}
button的事件監(jiān)聽器中調(diào)用相應(yīng)的方法偎肃,添加互聯(lián)網(wǎng)訪問權(quán)限
@Overridepublic voidonClick(View v) {
switch(v.getId()){
caseR.id.btnConnect:
connect();break;caseR.id.btnSend:
send();break;}