android socket聊天室——也不僅僅是聊天室
前提概要
筆者很久之前其實就已經學習過了socket,當然也是用socket做過了聊天室,但是覺得此知識點比較一般,並無特別難的技術點,於是也並未深究。
然而近期一個專案中對socket的使用卻讓筆者感覺socket強大無比,可以實現諸多功能。
個人Socket體驗
專案主要有關智慧家居,需要實現多臺手機同時對燈進行操作(開或者關),主要需要實現以下幾點:
1、進入介面時獲取所有燈的狀態。
2、一臺手機改變了燈的狀態,其他的手機上可以有所顯示。
3、硬體上改變了燈的狀態(手動開關燈),所有手機上要有所顯示。
此功能如果使用HTTP讀取的方式實現就不太合適了。一方面客戶端與伺服器讀取檔案的同步性難以保證,即使保證了,也需要浪費大量效能;另一方面,類似筆者的這種專案功能伺服器和客戶端互動比較頻繁,對“即時性”要求也比較高,用HTTP不僅效能消耗太大,而且難以保證“即時性”。
但是使用Socket就很容易實現了,主要邏輯如下:
1、每次進入介面與伺服器建立Socket連線,並得到此時燈的狀態
2、每次需要對燈進行操作的時候建立一個執行緒把燈的狀態傳遞給伺服器,伺服器接收到之後,把該狀態傳遞給每一個此時與伺服器建立連線的客戶端。
此次體驗也是讓筆者想起了學長之前做的一道筆試題,題目大概如下:
將淘寶網頁和手機版同時開啟賬戶,手機停留在購物車介面,此時網頁上將某一物品加入購物車,如何設計才能讓手機自動重新整理購物車。
如果使用socket,相信是一個不錯的思路。
好了,接下來進入正題,展示socket聊天室demo。
效果(原始碼在文章結尾)
主要思路
Android
1、進入介面客戶端與伺服器建立socket,同時此時開啟一個執行緒一直接收伺服器傳送來的訊息。
2、每次點選button獲取EditText中的字串,呼叫子執行緒把字串傳送給伺服器。
伺服器
1、建立一個ArrayList儲存Socket。
2、迴圈接收請求訪問該埠的客戶端,接收到之後,把該socket儲存到ArrayList中,並且為每一個socket開啟一個執行緒用於通訊。
3、每個socket的執行緒的邏輯如下:迴圈接收客戶端發來的訊息,接收到之後,利用之前的ArrayList,傳送到每一個客戶端。如果某個客戶端返回空值或者無法傳送過去,那麼表示該客戶端已經斷開,就從ArrayList中移除。
程式碼
(借鑑《Android瘋狂講義》)
Android
不要忘記在AndroidManifest裡面加上訪問網路的許可權
MainActivity:
package com.example.double2.sockettesttwo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity {
private EditText etMain;
private Button btnMain;
private TextView tvMain;
private ClientThread mClientThread;
//在主執行緒中定義Handler傳入子執行緒用於更新TextView
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
etMain = (EditText) findViewById(R.id.et_main);
btnMain = (Button) findViewById(R.id.btn_main);
tvMain = (TextView) findViewById(R.id.tv_main);
mHandler=new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 0) {
tvMain.append("\n" + msg.obj.toString());
}
}
};
//點選button時,獲取EditText中string並且呼叫子執行緒的Handler傳送到伺服器
btnMain.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
try {
Message msg = new Message();
msg.what = 1;
msg.obj = etMain.getText().toString();
mClientThread.revHandler.sendMessage(msg);
etMain.setText("");
} catch (Exception e) {
e.printStackTrace();
}
}
});
mClientThread = new ClientThread(mHandler);
new Thread(mClientThread).start();
}
}
ClientThread
package com.example.double2.sockettesttwo;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
/**
* 專案名稱:SocketTestTwo
* 建立人:Double2號
* 建立時間:2016.11.20 9:16
* 修改備註:
*/
public class ClientThread implements Runnable {
private Socket mSocket;
private BufferedReader mBufferedReader = null;
private OutputStream mOutputStream = null;
private Handler mHandler;
public Handler revHandler;
public ClientThread(Handler handler) {
mHandler = handler;
}
@Override
public void run() {
try {
mSocket = new Socket("10.3.20.159", 30003);
Log.d("xjj","connect success");
mBufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
mOutputStream = mSocket.getOutputStream();
new Thread(){
@Override
public void run() {
super.run();
try {
String content = null;
while ((content = mBufferedReader.readLine()) != null) {
Log.d("xjj",content);
Message msg = new Message();
msg.what = 0;
msg.obj = content;
mHandler.sendMessage(msg);
}
}catch (IOException e){
e.printStackTrace();
}
}
}.start();
//由於子執行緒中沒有預設初始化Looper,要在子執行緒中建立Handler,需要自己寫
Looper.prepare();
revHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == 1) {
try {
mOutputStream.write((msg.obj.toString() + "\r\n").getBytes("utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
Looper.loop();
} catch (IOException e) {
e.printStackTrace();
Log.d("xjj","");
}
}
}
activity_main
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_vertical_margin"
android:orientation="vertical"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<EditText
android:id="@+id/et_main"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Button
android:id="@+id/btn_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/send"/>
</LinearLayout>
<TextView
android:id="@+id/tv_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
伺服器:
MySever
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class MySever {
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public static String content="";
public static void main(String[] args) throws IOException {
//建立ServerSocket
ServerSocket ss = new ServerSocket(30003);
//不斷接收此埠的socket,並儲存到socketList中
//並且為每一個socket開啟一個執行緒,用於接收資訊
while (true) {
Socket s = ss.accept();
System.out.println("connect success!");
socketList.add(s);
new Thread(new ServerThread(s)).start();
}
}
}
SeverThread
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.SocketException;
import java.util.Iterator;
public class ServerThread implements Runnable {
private Socket mSocket = null;
private BufferedReader mBufferedReader = null;
// 建構函式中接收socket並且初始化BufferedReader
public ServerThread(Socket socket)
throws UnsupportedEncodingException, IOException {
mSocket = socket;
mBufferedReader = new BufferedReader(
new InputStreamReader(mSocket.getInputStream(), "utf-8"));
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
String content = null;
//迴圈接收來自此客戶端的訊息
//如果接收不到了,表面已經斷開,就將此客戶端從socketList中移除
while ((content = mBufferedReader.readLine()) != null) {
System.out.println(content);
//向連線中的每個客戶端傳送資料
//如果異常,說明斷開,就將該條目從socketList中移除
for (Iterator<Socket> it = MySever.socketList.iterator();
it.hasNext();) {
Socket s = it.next();
try {
OutputStream os = s.getOutputStream();
os.write((content + "\n").getBytes("utf-8"));
} catch (SocketException e) {
e.printStackTrace();
it.remove();
}
}
}
} catch (IOException e) {
e.printStackTrace();
MySever.socketList.remove(mSocket);
}
}
}
原始碼地址:
相關文章
- NoSQL——not onlySQL不僅僅是SQLSQL
- 元素,不僅僅是化學
- Redis不僅僅是快取,還是……Redis快取
- Linux不僅僅是開源Linux
- 電子競技,不僅僅是遊戲遊戲
- 你的工作不僅僅是程式設計程式設計
- iCloud不僅僅是為了雲音樂Cloud
- CDP營銷方案 不僅僅是資料整合!
- 前端魔法堂——異常不僅僅是try/catch前端
- 程式設計師不僅僅是寫程式碼程式設計師
- Kotlin開發Android專案簡單demo,不僅僅是Hello WorldKotlinAndroid
- 資料隱私不僅僅是指機密性
- 為什麼 async/await 不僅僅是句法糖AI
- 實踐敏捷估算(1)——不僅僅是估不準的問題敏捷
- AI是一個真正的系統而不僅僅是軟體AI
- Apache Flink,流計算?不僅僅是流計算!Apache
- DBA不僅僅是管理資料庫--也要管理好需求資料庫
- 拿工資不僅僅是讓你寫程式碼的
- 雲不僅僅是一種全新的IT基礎設施
- Socket.IO打造基礎聊天室
- 不僅僅是前端er——折騰伺服器武裝自己前端伺服器
- 重要 | Spark和MapReduce的對比,不僅僅是計算模型?Spark模型
- 【虹科分享】Redis 不僅僅是記憶體資料庫Redis記憶體資料庫
- DBA不僅僅是管理資料庫--也要管理中介軟體資料庫
- Valve正在招聘各種學術人才,不僅僅是做遊戲遊戲
- 深入理解BERT Transformer ,不僅僅是注意力機制ORM
- SmartCode—不僅僅是功能強大的程式碼生成器
- IBM Watson啟示錄:AI不應該僅僅是炫技IBMAI
- 為什麼說六西格瑪不僅僅是資料運算
- 智慧手機紅辣椒金典評測:不僅僅是實惠
- iPhone5,釋出的不僅僅是一部手機iPhone
- AI之父:大模型不僅僅是預測下一個符號AI大模型符號
- 軟體開發人員需要的不僅是技術,也不是文件,也不是管理,而是……
- Oracle 不再僅僅是資料庫Oracle資料庫
- 不僅僅是因為隨機性強,Roguelike遊戲究竟有啥好玩?隨機遊戲
- 賽博朋克中的設計核心(一):不僅僅是日本文化
- SOLIDWORKS軟體不僅僅是三維機械設計軟體Solid
- 不僅僅是送貨無人機,亞馬遜還要用無人駕駛包攬地面無人機亞馬遜