案例GitHub地址
- 在博客Android | UDP的C/S通信實戰(zhàn)案例的基礎(chǔ)上,繼續(xù)進行開發(fā)斋陪;
創(chuàng)建TCP服務(wù)端
-
在sample模塊下朽褪,
新建一個名為tcp的package,
創(chuàng)建TcpServer:
- 指定服務(wù)端端口號(ip 默認為本機ip)
啟動循環(huán)讀取消息隊列的子線程无虚,
死循環(huán)缔赠,不斷等待客戶端請求連接,
一旦連接上骑科,
直接新建一個子線程(丟給ClientTask)去處理這個socket橡淑,
于是主線程又可以回到accept() 阻塞,等待下一個連接請求咆爽;
同時梁棠,將連接上的socket 對應(yīng)的線程類置森,注冊為消息隊列的觀察者,
讓線程類擔任觀察者符糊,負責接收被觀察者的通知信息并做socket 通信凫海。
/**
* <pre>
* author : 李蔚蓬(簡書_凌川江雪)
* time : 2019/10/30 16:57
* desc :指定服務(wù)端端口號(ip 默認為本機ip)
* 啟動循環(huán)讀取消息隊列的子線程,
* 死循環(huán)男娄,不斷等待客戶端請求連接行贪,
* 一旦連接上,直接新建一個子線程(丟給ClientTask)去處理這個socket模闲,
* 于是主線程又可以回到accept() 阻塞建瘫,等待下一個連接請求;
* 同時尸折,將連接上的socket 對應(yīng)的線程類啰脚,注冊為消息隊列的觀察者,
* 讓線程類擔任觀察者实夹,負責接收被觀察者的通知信息并做socket 通信
* </pre>
*/
public class TcpServer {
public void start() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(9090);
MsgPool.getInstance().start();//啟動讀消息的子線程
while (true) {
// /*
// 阻塞的方法i吓ā!亮航! 等待(客戶端的) TCP 連接請求
// 客戶端有 TCP 請求并連接上了 ServerSocket荸实,.
// 那 accept() 就會返回一個 同一連接上 對應(yīng) 客戶一端socket 的 服務(wù)一端socket
// */
Socket socket = serverSocket.accept();
//客戶端連接之后,打印相關(guān)信息
// System.out.println("ip: " + socket.getInetAddress().getHostAddress() +
// ", port = " + socket.getPort() + " is online...");
System.out.println("ip = " + "***.***.***.***" +
", port = " + socket.getPort() + " is online...");
// /*
// 連接上了之后不能直接拿IO流去讀寫缴淋,
// 因為getInputStream() 和 getOutputStream() 都是阻塞的W几!Q缁圆存!
// 如果直接拿IO 流,不做其他處理仇哆,
// 那么Server端的處理流程是這樣的:
// accept()-- getInputStream()處理第一個客戶端 -- 處理完畢,accept()-- getInputStream()處理第二個客戶端....
// 所以必須開啟子線程去讀寫客戶端沦辙,才能做成聊天室
//
// 針對每一個連接上來的客戶端去單獨起一個線程,跟客戶端進行通信
//
// 過程:客戶端連上之后讹剔,打印其信息油讯,
// 然后直接新建一個子線程(丟給ClientTask)去處理這個socket,
// 于是主線程又可以回到accept() 阻塞延欠,等待下一個連接請求
// */
ClientTask clientTask = new ClientTask(socket);
MsgPool.getInstance().addMsgComingListener(clientTask);
clientTask.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new TcpServer().start();
}
}
- 針對每一個連接上來的客戶端去單獨起一個線程陌兑,跟客戶端進行通信,
準備一個線程類,名為ClientTask由捎,
針對每一個連接上來的客戶端去單獨起一個線程兔综,跟客戶端進行通信,
這里便是線程類;
run()中死循環(huán)不斷讀取本類實例對應(yīng)的客戶端發(fā)來的信息,
或者發(fā)送給對應(yīng)的連接對面客戶端(服務(wù)端)要發(fā)送的信息软驰;
實現(xiàn)MsgPool.MsgComingListener涧窒, 成為消息隊列的觀察者!6Э鳌纠吴!
/**
* <pre>
* author : 李蔚蓬(簡書_凌川江雪)
* time : 2019/10/30 17:23
* desc :針對每一個連接上來的客戶端去單獨起一個線程,跟客戶端進行通信,
* 這里便是線程類慧瘤;
* run()中死循環(huán)不斷讀取客戶端發(fā)來的信息戴已,發(fā)送給客戶端(服務(wù)端)要發(fā)送的信息;
* 實現(xiàn)MsgPool.MsgComingListener锅减, 成為消息隊列的觀察者L抢堋!上煤!
* </pre>
*/
public class ClientTask extends Thread implements MsgPool.MsgComingListener {
private Socket mSocket;
private InputStream mIs;
private OutputStream mOs;
public ClientTask(Socket socket) {
try {
mSocket = socket;
mIs = socket.getInputStream();
mOs = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
BufferedReader br = new BufferedReader(new InputStreamReader(mIs));
String line = null;
/*
讀取并輸出客戶端信息休玩。
如果沒有客戶端發(fā)送信息著淆,readLine() 便會阻塞在原地
*/
try {
while ((line = br.readLine()) != null) {
System.out.println("read " + mSocket.getPort() + " = " + line);
//把信息發(fā)送加入到消息隊列劫狠,
// 借助消息隊列的被觀察者通知方法,
// 將消息轉(zhuǎn)發(fā)至其他Socket(所有socket都在創(chuàng)建ClientTask的時候永部,
// 備注成為MsgPool 的觀察者)
MsgPool.getInstance().sendMsg(mSocket.getPort() + ": " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
//作為消息隊列的觀察者對應(yīng)的更新方法,
// 消息隊列中最新的消息會推送通知到這里的msg參數(shù)独泞,
// 這里拿到最新的推送消息后,寫進輸出流苔埋,
// 推到TCP 連接的客戶一端的 socket
@Override
public void onMsgComing(String msg) {
try {
mOs.write(msg.getBytes());
mOs.write("\n".getBytes());
mOs.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
- 準備一個消息隊列懦砂,
每一個Client發(fā)送過來的消息,
都會被加入到隊列當中去组橄,
隊列中默認有一個子線程荞膘,
專門從隊列中,死循環(huán)玉工,不斷去取數(shù)據(jù)(取出隊列的隊頭)羽资,
取到數(shù)據(jù)就做相關(guān)處理,比如分發(fā)給其他的socket遵班;
/**
* <pre>
* author : 李蔚蓬(簡書_凌川江雪)
* time : 2019/10/30 17:45
* desc :每一個Client發(fā)送過來的消息屠升,
* 都會被加入到隊列當中去,
* 隊列中默認有一個子線程狭郑,
* 專門從隊列中腹暖,死循環(huán),不斷去取數(shù)據(jù)翰萨,
* 取到數(shù)據(jù)就做相關(guān)處理脏答,比如分發(fā)給其他的socket;
* </pre>
*/
public class MsgPool {
private static MsgPool mInstance = new MsgPool();
/*
這里默認消息是String類型,
或者可以自行封裝一個Model 類殖告,存儲更詳細的信息
block n.塊糙麦; 街區(qū);障礙物丛肮,阻礙
顧名思義赡磅,這是一個阻塞的隊列,當有消息過來時宝与,就把消息發(fā)送給這個隊列焚廊,
這邊會起一個線程專門從隊列里面去取消息,
如果隊列中沒有消息习劫,就會阻塞在原地
*/
private LinkedBlockingQueue<String> mQueue = new LinkedBlockingQueue<>();
public static MsgPool getInstance() {
return mInstance;
}
private MsgPool() {
}
//這是一個阻塞的隊列咆瘟,
// 當有消息過來時,即客戶端接收到消息時诽里,
// 就把消息發(fā)送(添加)到這個隊列中
//現(xiàn)在所有的客戶端都可以發(fā)送消息到這個隊列中
public void sendMsg(String msg) {
try {
mQueue.put(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//要一早就調(diào)用本方法袒餐,
// 啟動這個讀取消息的線程,在后臺不斷運行
public void start() {
//開啟一個線程去讀隊列的數(shù)據(jù)
new Thread() {
@Override
public void run() {
//無限循環(huán)讀取信息
while (true) {
try {
//取出并移除隊頭谤狡;沒有消息時灸眼,take()是阻塞的
String msg = mQueue.take();
notifyMsgComing(msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
}
//被觀察者方法,遍歷所有已注冊的觀察者墓懂,一次性通知更新
private void notifyMsgComing(String msg) {
for (MsgComingListener listener : mListeners) {
listener.onMsgComing(msg);
}
}
//觀察者接口
public interface MsgComingListener {
void onMsgComing(String msg);//更新方法
}
//被觀察者焰宣,存放觀察者
private List<MsgComingListener> mListeners = new ArrayList<>();
//被觀察者方法,添加觀察者到列表
public void addMsgComingListener(MsgComingListener listener) {
mListeners.add(listener);
}
}
所有的客戶端都可發(fā)送消息到隊列中捕仔,
然后所有的客戶端都在等待
消息隊列的消息新增(mQueue.put())這個時刻匕积,
消息隊列一新增消息,
即一接收到某個客戶端發(fā)送過來消息(mQueue.put())榜跌,
則消息都會一次性轉(zhuǎn)發(fā)給所有客戶端闪唆,
所以這里涉及到一個觀察者設(shè)計模式,
消息隊列(MsgPool)或消息(Msg)是被觀察者钓葫,
所有客戶端處理線程(ClientTask)都是觀察者
觀察者模式實現(xiàn)小結(jié):
觀察者接口準備更新(數(shù)據(jù)或UI的)方法悄蕾;
被觀察者接口準備三個抽象方法;
觀察者實現(xiàn)類具體實現(xiàn)更新邏輯瓤逼,可以有參數(shù)笼吟,參數(shù)為更新需要的數(shù)據(jù);
被觀察者實現(xiàn)類準備一個觀察者List以及實現(xiàn)三個方法:
1.觀察者注冊方法:
參數(shù)為某觀察者霸旗,功能是把觀察者參數(shù)加到觀察者List中贷帮;
2.注銷觀察者方法:
參數(shù)為某觀察者,功能是把觀察者參數(shù)從觀察者List中移除诱告;
3.通知觀察者方法:無參數(shù)或者把需要通知的數(shù)據(jù)作為參數(shù)撵枢,
功能是遍歷所有已注冊的觀察者,
即遍歷 注冊添加到 觀察者List中的觀察者,逐個調(diào)用List中所有觀察者的更新方法锄禽;即一次性更新所有已注冊的觀察者潜必!
使用時,
實例化一個被觀察者和若干個觀察者沃但,
將所有觀察者注冊到被觀察者處磁滚,
調(diào)用被觀察者的通知方法,一次性更新所有已注冊的觀察者宵晚!
創(chuàng)建TCP客戶端
-
創(chuàng)建兩個Package垂攘,整理一下項目架構(gòu),創(chuàng)建一個TcpClient:
/**
* <pre>
* author : 李蔚蓬(簡書_凌川江雪)
* time : 2019/10/31 15:36
* desc :
* </pre>
*/
public class TcpClient {
private Scanner mScanner;
public TcpClient() {
mScanner = new Scanner(System.in);
mScanner.useDelimiter("\n");
}
/**
* 配置socket
* 準備IO 流淤刃,
* 主線程寫晒他,子線程讀
*
*/
public void start() {
try {
Socket socket = new Socket("***", 9090);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
final BufferedReader br = new BufferedReader(new InputStreamReader(is));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os));
/*
實現(xiàn):
通過 reader,
在任何時候 能夠讀到 Server端 發(fā)來的數(shù)據(jù)
通過 writer逸贾,
在任何時候 能夠向 Server端 去寫數(shù)據(jù)
*/
//在等待客戶端 發(fā)送消息過來的話陨仅,這里是需要阻塞的,
// 阻塞的時候又沒有辦法向客戶端發(fā)送數(shù)據(jù)铝侵,所以讀寫?yīng)毩⒌脑捵粕耍隙ㄊ且鹁€程的
//起一個線程,專門用于
// 讀Server 端 發(fā)來的數(shù)據(jù)哟沫,數(shù)據(jù)一過來就讀然后輸出,
// 輸出服務(wù)端發(fā)送的數(shù)據(jù)
new Thread() {
@Override
public void run() {
try {
String line = null;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
}
}
}.start();
//給Server端 發(fā)送數(shù)據(jù)
while (true) {
//next() 是阻塞的饺蔑,不斷地讀控制面板,有數(shù)據(jù)就會通過bufferWriter,
// 即outputStream 寫給Server
String msg = mScanner.next();
bw.write(msg);
bw.newLine();
bw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new TcpClient().start();
}
}
-
反復(fù)測試:
移植客戶端到Android移動端
-
復(fù)制TcpClient到biz包下迭代嗜诀,名為TcpClientBiz:
/**
* <pre>
* author : 李蔚蓬(簡書_凌川江雪)
* time : 2019/10/31 15:36
* desc : 定義接口勤众,完成客戶端的收發(fā)邏輯
* </pre>
*/
public class TcpClientBiz {
private Socket mSocket;
private InputStream mIs;
private OutputStream mOs;
/**
* Looper.getMainLooper()绵估,將主線程中的 Looper 扔進去了,
* 也就是說 handleMessage 會運行在主線程中亲族,
* 4藁邸7餍!;淌摇N伦浴!;食5棵凇!夹界!
* 這樣可以在主線程中更新 UI 而不用把 Handler 定義在主線程中馆里。
* !!p佟1摺!S堋P得健!F捞滥沫!
*/
private Handler mUiHandler = new Handler(Looper.getMainLooper());
// /*
// 注意,因為UdpClient 的send 和 receive 是綁定的键俱,
// 所以其 返回信息的處理接口 是作為 發(fā)送信息方法 的參數(shù)的兰绣,由此產(chǎn)生綁定邏輯
//
// 但是這里 TcpClient 就不是send 和 receive 一一綁定了,
// 其沒有數(shù)量的對應(yīng)關(guān)系编振,只是一個持續(xù)的 任意數(shù)據(jù)包數(shù)量的 全雙工的連接缀辩,
// 無需Udp 的綁定邏輯, Listener 由此不使用跟send 方法綁定的邏輯踪央,
// 使用單獨set 的邏輯表達方式
// */
public interface onMsgComingListener {
void onMsgComing(String msg);
void onError(Exception ex);
void popToast();
}
private onMsgComingListener mListener;
public void setOnMsgComingListener(onMsgComingListener listener) {
mListener = listener;
}
//------------------------------------------------------------------------
public TcpClientBiz() {
// //socket 的new 到 IO 流的獲取 這幾行代碼是已經(jīng)做了網(wǎng)絡(luò)操作的臀玄,
// // 所以必須開一個子線程去進行,3濉=∥蕖!液斜!
// // 畢竟 TcpClientBiz() 在調(diào)用的時候肯定是在UI 線程進行的
//
// /*
// 另外需要注意一點@巯汀!少漆!
// 下面的socket 和 IO 流初始化是在子線程中進行的臼膏,
// 所以我們不知道什么時候會完成初始化,
// 因此在使用的時候是需要進行一個UI 交互提醒的示损,
// 比如loading 動畫渗磅,啟動頁面時使用loading動畫,初始化完成之后再取消loading 動畫检访,
//
// */
new Thread() {
@Override
public void run() {
try {
mSocket = new Socket("172.18.1.59", 9090);//連接到 Server端
mIs = mSocket.getInputStream();
mOs = mSocket.getOutputStream();
mUiHandler.post(new Runnable() {
@Override
public void run() {
mListener.popToast();
}
});
//讀到消息則 借用回調(diào) 回到MainActivity 進行UI 更新
readServerMsg();
} catch (final IOException e) {
mUiHandler.post(new Runnable() {
@Override
public void run() {
if (mListener != null) {
mListener.onError(e);
}
}
});
}
}
}.start();
}
/**
* 一旦本類被實例化始鱼,馬上啟動
* 不斷阻塞等待Server端 信息
* readLine() 沒有消息時阻塞,
* 一有消息脆贵,馬上發(fā)給接口處理邏輯
*
* @throws IOException
*/
private void readServerMsg() throws IOException {
final BufferedReader br = new BufferedReader(new InputStreamReader(mIs));
String line = null;
while ((line = br.readLine()) != null) {
final String finalLine = line;
/*
R角濉!5べ鳌W辞凇P场!3炙选C芩啤!:巍2须纭!F兜肌E酌ā!孩灯!
基于主線程MainLooper 以及 回調(diào)機制
在 業(yè)務(wù)類內(nèi)部 調(diào)用 外部實現(xiàn)的處理邏輯方法
9虢稹!7宓怠0芷ァ!<パ病O颇丁!;肚辍槽棍!
*/
mUiHandler.post(new Runnable() {
@Override
public void run() {
//讀到消息則 借用回調(diào) 回到MainActivity 進行UI 更新
if (mListener != null) {
mListener.onMsgComing(finalLine);
}
}
});
}
}
/**
* 把參數(shù)msg 寫入BufferWriter(O流),發(fā)送給Server端,
* 一般這個msg 消息 是EditText 中的內(nèi)容抬驴,
*
* 調(diào)用時機:一般是EditText 右邊的按鈕被點擊的時候
*
* 調(diào)用時炼七,封裝輸出流,
* 把參數(shù)msg 寫入BufferWriter(O流)怎爵,發(fā)送給Server端,
*
* 在要發(fā)送消息給Server 的時候調(diào)用
* 發(fā)送的消息會在Server 端的 ClientTask 類中
* 的run() 中的while ((line = br.readLine()) != null) 處被讀取到特石,
* 并通過 MsgPool.getInstance().sendMsg() 被添加到消息隊列中
*
* @param msg 要發(fā)送的信息
*/
public void sendMsg(final String msg) {
//開一個線程去做輸出,完成任務(wù)之后線程就自動回收
new Thread(){
@Override
public void run() {
try {
//一有消息過來鳖链,就封裝輸出流,寫入并 發(fā)送信息到 Server端
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(mOs));
bw.write(msg);
bw.newLine();
bw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
public void onDestroy() {
//6漳\轿!狂秦!
// 獨立地try...catch...的原因:
// 9嗦隆!A盐省侧啼!
// 如果把三個close 都放在同一個try 塊里面
// 那假如第一個close 出現(xiàn)了異常牛柒,
// 后面兩個close 即使沒異常,
// 也處理不了了痊乾,這顯然是不符合條件的
// Fけ凇!D纳蟆6昶恰!
try {
if (mIs != null) {
mIs.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (mOs != null) {
mOs.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (mSocket != null) {
mSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
-
rename一下MainActivity為UdpActivity:
復(fù)制UdpActivity一份湿滓,原地粘貼滴须,命名為TcpActivity:
public class TcpActivity extends AppCompatActivity {
private EditText mEtMsg;
private Button mBtnSend;
private TextView mTvContent;
private TcpClientBiz mTcpClientBiz = new TcpClientBiz();
public Context getTcpActivityContext() {
return getApplicationContext();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
mTcpClientBiz.setOnMsgComingListener(new TcpClientBiz.onMsgComingListener() {
@Override
public void onMsgComing(String msg) {
appendMsgToContent("Server:" + msg);
}
@Override
public void onError(Exception ex) {
ex.printStackTrace();
}
@Override
public void popToast() {
Toast.makeText(TcpActivity.this, "初始化完成!_窗隆H铀!可以開始發(fā)送信息了3ァD小!", Toast.LENGTH_SHORT).show();
}
});
}
private void initViews() {
mEtMsg = findViewById(R.id.id_et_msg);
mBtnSend = findViewById(R.id.id_btn_send);
mTvContent = findViewById(R.id.id_tv_content);
mBtnSend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String msg = mEtMsg.getText().toString();
if (TextUtils.isEmpty(msg)) {
return;
}
//發(fā)送后清除編輯框文本
mEtMsg.setText("");
//msg 負責發(fā)送數(shù)據(jù)膀篮,onMsgReturnedListener() 則負責處理對應(yīng)的返回的信息
mTcpClientBiz.sendMsg(msg);
}
});
}
private void appendMsgToContent(String msg) {
mTvContent.append(msg + "\n");
}
/*
回收資源
*/
@Override
protected void onDestroy() {
super.onDestroy();
mTcpClientBiz.onDestroy();
}
}
更改啟動頁面:-
反復(fù)測試(一個模擬機和兩臺真機的聊天哈哈哈):
-
最終Server端聊天記錄:
服務(wù)端諸類代碼實現(xiàn)概述(TcpServer嘹狞、ClientTask、MsgPool)
TcpServer:
死循環(huán)誓竿,阻塞磅网,等待客戶端請求連接,while (true)
&.accept();
一旦連接上筷屡,獲取對應(yīng)的socket對象并
把它丟給ClientTask的構(gòu)造方法涧偷,new ClientTask(socket)
直接新建一個子線程,去處理這個socket(.start()
)毙死,
將連接上的socket 對應(yīng)的線程類燎潮,注冊到消息隊列類中的隊列中,
成為消息隊列的觀察者扼倘;MsgPool.getInstance().addMsgComingListener(clientTask)
啟動消息隊列讀讀隊列的線程确封,
MsgPool.getInstance().start();
-
ClientTask:
public class ClientTask extends Thread implements MsgPool.MsgComingListener
讓線程類作為消息隊列的觀察者,
負責接收被觀察者的通知信息并做socket 通信再菊;
類中:1/3 構(gòu)造方法:
接收TcpServer對過來的socket對象爪喘,
用之初始化其IO流;2/3
run()
:<讀取Client的 I流纠拔,加入 MsgPool.mQueue>
封裝輸入流秉剑,
讀取客戶端發(fā)送過來的信息
并輸出:
while ((line = br.readLine()) != null){...}
System.out.println(...);
把信息發(fā)送加入到消息隊列:MsgPool.getInstance().sendMsg(...);
如果沒有客戶端發(fā)送信息,
readLine() 便會阻塞(注意這里會阻塞稠诲!所以要放在子線程U炫簟)在原地
3/3
onMsgComing(String msg)
:<取出 MsgPool.mQueue诡曙,寫入Client的 O流>
作為消息隊列的觀察者對應(yīng)的更新方法,
消息隊列中最新的消息會推送通知到這里的msg參數(shù),
(消息隊列類有一個子線程死循環(huán)阻塞讀取隊頭略水,
String msg = mQueue.take();
notifyMsgComing(msg);
notifyMsgComing
中遍歷所有已注冊的觀察者,
遍歷時調(diào)用觀察者的onMsgComing(msg)
价卤,
正是本方法!>矍搿\瘛)
本方法中拿到最新的推送消息后,
寫進輸出流驶赏,
發(fā)送給對應(yīng)的 TCP 連接的客戶一端
的socket
-
class MsgPool
消息列表類實現(xiàn)單例模式
private static MsgPool mInstance = new MsgPool();
public static MsgPool getInstance() { return mInstance; }
private MsgPool() { }
準備消息列表底層數(shù)據(jù)結(jié)構(gòu):
private LinkedBlockingQueue<String> mQueue = new LinkedBlockingQueue<>();
sendMsg(String msg):
當有消息過來時炸卑,即客戶端接收到消息時,
就把消息發(fā)送(添加)到消息隊列中:mQueue.put(msg);
在ClientTask
的run()
中調(diào)用
本方法C喊8俏摹!蚯姆;start()
啟動讀取消息的子線程
五续,在后臺不斷運行,
死循環(huán) 阻塞 讀取隊頭龄恋,
一有消息取出就通知所有已注冊的觀察者疙驾,
String msg = mQueue.take();
notifyMsgComing(msg);
在TcpServer中一開始配置好服務(wù)ip和端口就調(diào)用了;實現(xiàn)被觀察者通知方法:
notifyMsgComing(String msg)
實現(xiàn)被觀察者方法郭毕,添加觀察者到列表:
public void addMsgComingListener(MsgComingListener listener)
觀察者接口MsgComingListener
被觀察者列表private List<MsgComingListener> mListeners = new ArrayList<>();
客戶端諸類代碼實現(xiàn)概述(TcpClientBiz它碎、TcpActivity)
-
TcpClientBiz:
連接Server端,
后臺子線程不斷接收Server端發(fā)送過來的信息显押,
前臺封裝扳肛、提供向Server端發(fā)送信息的方法準備一個綁定了
mainLooper
的Handler
定義
<回調(diào)機制>
回調(diào)接口及其抽象方法;
聲明 全局 回調(diào)接口變量乘碑;
回調(diào)接口置入函數(shù)挖息;
setOnMsgComingListener(onMsgComingListener listener) { mListener = listener; }
-
構(gòu)造方法:
開啟子線程!J薹簟套腹!,
配置連接到Server端
的socket资铡;mSocket = new Socket("***.**.*.**", 9090);
通過socket獲得IO流沉迹;
(以上,socket害驹,IO流都初始化給全局變量)使用全局 回調(diào)接口變量,
抽象調(diào)用業(yè)務(wù)方法蛤育;(Toast提醒宛官、Error處理之類)調(diào)用
readServerMsg()
:伞!底洗!;
readServerMsg()
一旦本類被實例化腋么,就會被啟動!:ヒ尽珊擂!
開啟一個子線程,
拿著全局變量I流费变,封裝成BufferReader摧扇,
死循環(huán) 阻塞等待 讀取Server端信息
while ((line = br.readLine()) != null)
一旦有信息,
借助Handler.post()挚歧,
使用全局 回調(diào)接口變量抽象調(diào)用接口方法onMsgComing()
通過回調(diào)機制交給Activity層處理扛稽;sendMsg(final String msg)
開啟一個子線程,
拿著全局變量O流滑负,封裝成BufferWriter在张,
把參數(shù)msg 寫入BufferWriter(O流),發(fā)送給Server端矮慕;
調(diào)用時機:在要發(fā)送消息給Server 的時候調(diào)用帮匾,
一般是EditText 右邊的按鈕被點擊的時候onDestroy():
回收socket、IO流
-
TcpActivity
主要是各種組件的配置痴鳄,
注意幾點即可:需要實例化一個全局TcpClientBiz實例
然后用匿名內(nèi)部類實現(xiàn)回調(diào)接口及其方法瘟斜,
再set 給TcpClientBiz實例;點擊按鈕時把EditText的內(nèi)容發(fā)送給Server端夏跷;
msg = mEtMsg.getText().toString();
mTcpClientBiz.sendMsg(msg);
onDestroy()中調(diào)用
mTcpClientBiz.onDestroy();
回收資源
所有的客戶端都可發(fā)送消息到隊列中哼转,
然后所有的客戶端都在等待
消息隊列的消息新增(mQueue.put())這個時刻,
消息隊列一新增消息槽华,
即一接收到某個客戶端發(fā)送過來消息(mQueue.put())壹蔓,
則消息都會一次性轉(zhuǎn)發(fā)給所有客戶端,
所以這里涉及到一個觀察者設(shè)計模式猫态,
消息隊列(MsgPool)或消息(Msg)是被觀察者佣蓉,
所有客戶端處理線程(ClientTask)都是觀察者
觀察者模式實現(xiàn)小結(jié):
觀察者接口準備更新(數(shù)據(jù)或UI的)方法;
被觀察者接口準備三個抽象方法亲雪;
觀察者實現(xiàn)類具體實現(xiàn)更新邏輯勇凭,可以有參數(shù),參數(shù)為更新需要的數(shù)據(jù)义辕;
被觀察者實現(xiàn)類準備一個觀察者List以及實現(xiàn)三個方法:
1.觀察者注冊方法:
參數(shù)為某觀察者虾标,功能是把觀察者參數(shù)加到觀察者List中;
2.注銷觀察者方法:
參數(shù)為某觀察者灌砖,功能是把觀察者參數(shù)從觀察者List中移除璧函;
3.通知觀察者方法:無參數(shù)或者把需要通知的數(shù)據(jù)作為參數(shù)傀蚌,
功能是遍歷所有已注冊的觀察者,
即遍歷 注冊添加到 觀察者List中的觀察者蘸吓,逐個調(diào)用List中所有觀察者的更新方法善炫;即一次性更新所有已注冊的觀察者!
使用時库继,
實例化一個被觀察者和若干個觀察者箩艺,
將所有觀察者注冊到被觀察者處,
調(diào)用被觀察者的通知方法宪萄,一次性更新所有已注冊的觀察者艺谆!