2018-01-11AndriodStudio長連接封裝游戲客戶端和服務器超詳細的注釋

客戶端

客戶端的連接需要準備的東西:gson-2.3.1.jar包用于json解析

先看整個工程目錄,其次service需要在xml中注冊


與服務器的連接類:

public class Connector {

//設置端口號和IP地址

? ? protected static final StringdstName ="192.168.3.40";

protected static final int dstPort =7896;

private SocketmSocket;

public Connector(){

}

//-單例-----------------------------------------------

? ? private static Connectorinstance;

public static Connector getInstance() {

if (instance ==null) {

synchronized (Connector.class) {

if (instance ==null) {

instance =new Connector();

}

}

}

return instance;

}

//-監聽-----------------------------------

? ? protected ConnectorListenermListener;

public void setConnectorListener(ConnectorListener listener) {

this.mListener = listener;

}

public interface ConnectorListener {

void pushData(String data);

}

public void connect(){

//在這里發送數據和接收數據是兩個單獨的線程

? ? ? ? try {

if (mSocket ==null ||mSocket.isClosed()) {

//如果Socket對象為空則新建一個,并給定端口號和IP地址

? ? ? ? ? ? ? ? mSocket =new Socket(dstName,dstPort);

}

//發送數據

? ? ? ? ? ? new Thread(new RequestWorker()).start();

//接受數據

? ? ? ? ? ? new Thread(new ReceiveWorker()).start();

}catch (UnknownHostException e) {

e.printStackTrace();

}catch (IOException e) {

e.printStackTrace();

}

}

//-一般使用這個方法----------------------------------------------------

? ? public void connect(AuthRequest auth){

connect();//調用連接線程

? ? ? ? putRequest(auth);//發送認證信息

? ? }

//消息隊列

? ? private ArrayBlockingQueuequeue =new ArrayBlockingQueue(8);

//發送的線程,只要消息隊列中有數據就發送出去

? ? private class RequestWorkerimplements Runnable{

@Override

? ? ? ? public void run() {

OutputStream out =null;

try {

out =mSocket.getOutputStream();

while(true){

String content =queue.take();

out.write(content.getBytes());

}

}catch (IOException e) {

e.printStackTrace();

}catch (InterruptedException e) {

e.printStackTrace();

}

}

}

public void putRequest(String content){

try {

queue.put(content);//將消息丟到消息隊列里面

? ? ? ? }catch (InterruptedException e) {

e.printStackTrace();

}

}

public void putRequest(Request request) {

//將需要發送的消息放到消息隊列,發送的線程會自動監測,一旦有消息進來我們就將它發出去

? ? ? ? putRequest(request.getData());

}

//接收數據的線程

? ? private class ReceiveWorkerimplements Runnable{

@Override

? ? ? ? public void run() {

try {

InputStream inputStream =mSocket.getInputStream();

byte[] buffer =new byte[1024];

int len = -1;

while((len = inputStream.read(buffer))!=-1){

//一旦有消息進來我們就把消息放到隊列中,在廣播出去

? ? ? ? ? ? ? ? ? ? String text =new String(buffer,0, len);

if (mListener !=null) {

mListener.pushData(text);

}

}

}catch (IOException e) {

e.printStackTrace();

}

}

}

//銷毀連接

? ? public void disConn(){

try {

if (mSocket !=null && !mSocket.isClosed()) {

mSocket.close();

mSocket =null;

}

}catch (IOException e) {

e.printStackTrace();

}

}

}

然后是連接管理類:

public class ConnectorManager implements Connector.ConnectorListener {

//連接器

? ? private Connectorconnector;

private ConnectorListenermListener;

private static ConnectorManagerinstance;

private ConnectorManager() {? ? }

public static ConnectorManager getInstance() {

if (instance ==null) {

synchronized (ConnectorManager.class) {

if (instance ==null) {

instance =new ConnectorManager();

}

}

}

return instance;

}

/**

* 創建連接發送驗證

? ? * @param auth

? ? */

? ? public void connnect(String auth) {

connector =new Connector();

connector.setConnectorListener(this);

connector.connect();

//? connector.auth(auth);

? ? }

public void connect(AuthRequest auth) {

connector =new Connector();

connector.setConnectorListener(this);

connector.connect();

// connector.auth(auth.getData());

? ? }

public void putRequest(String request) {

connector.putRequest(request);

}

public void putRequest(Request request) {

connector.putRequest(request.getData());

}

@Override

? ? public void pushData(String data) {

if (mListener !=null) {

mListener.pushData(data);

}

}

public void setConnectorListener(ConnectorListener listener) {

this.mListener = listener;

}

public interface ConnectorListener {

void pushData(String data);

}

服務類

/**

* 繼承了Service,實現了Connector中的接口ConnectorListener

*

* 相信大多數朋友對Service這個名詞都不會陌生,沒錯,一個老練的Android程序員如果連Service都沒聽說過的話,那確實也太遜了。

* Service作為Android四大組件之一,在每一個應用程序中都扮演著非常重要的角色。

* 它主要用于在后臺處理一些耗時的邏輯,或者去執行某些需要長期運行的任務。

* 必要的時候我們甚至可以在程序退出的情況下,讓Service在后臺繼續保持運行狀態。

* Created by Administrator on 2018/1/9.

*/

public class CoreService extends Service implements Connector.ConnectorListener {

private Connectorconnector;//連接器主要用來連接服務器

? ? private ExecutorServicemPools;//線程池

? ? @Override

? ? public IBinder onBind(Intent intent) {

return null;//Client和Server之間的進程間通信通過Binder驅動程序間接實現.不懂的可以百度,這里不做過多講解

? ? }

@Override

? ? public void onCreate() {

super.onCreate();

/*通過Connector.getInstance()我們可以的到一個連接器,單例模式,相信每個程序員都應該了解這個設計模式,

這樣就可以每次得到同一個對象,在我們進行長連接時就避免了每次去新建一個實例化對象*/

? ? ? ? connector = Connector.getInstance();

//監聽器,主要用來監聽從服務器接收到的數據

? ? ? ? connector.setConnectorListener(this);

//設置線程池的初始化大小為3

? ? ? ? mPools = Executors.newFixedThreadPool(3);

//用線程池去開啟線程,這塊不做詳細解釋,

? ? ? ? mPools.execute(new Runnable() {

@Override

? ? ? ? ? ? public void run() {

//每次將認證消息賦空,防止獲取延遲導致的錯誤

? ? ? ? ? ? ? ? AuthRequest request =null;

//在我們自己開發應用過程中,常常使用如下的代碼形式判斷運行新API還是舊的API:

? ? ? ? ? ? ? ? if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {

// 包含新API的代碼塊

? ? ? ? ? ? ? ? ? ? request =new AuthRequest("B","B");//AuthRequest將信息以鍵值對的形式存起來

? ? ? ? ? ? ? ? }else {

// 包含舊的API的代碼塊

? ? ? ? ? ? ? ? ? ? request =new AuthRequest("A","A");

}

connector.connect(request);//重點:連接服務器在這里調用,這樣解耦了客戶端的連接

? ? ? ? ? ? }

});

}

@Override

? ? public void pushData(String data) {

Intent intent =new Intent();

intent.setAction(PushReceiver.ACTION_TEXT);

intent.putExtra(PushReceiver.DATA_KEY, data);

//使用廣播的形式把數據發送到主界面進行處理

? ? ? ? sendBroadcast(intent);

}

}

廣播類------------------------------------------------------------------

//廣播接收器,繼承了BroadcastReceiver

/**

* 知識補充:一來為了自己能更深刻的理解廣播的機制,二是為了方便新手學習

*Android中的廣播使用了設計模式中的觀察者模式:基于消息的發布 / 訂閱事件模型

*因此,Android將廣播的發送者 和 接收者 解耦,使得系統方便集成,更易擴展

*

* 自定義廣播接收者BroadcastReceiver

*

* 繼承BroadcastReceivre基類

必須復寫抽象方法onReceive()方法

廣播接收器接收到相應廣播后,會自動回調 onReceive() 方法

一般情況下,onReceive方法會涉及 與 其他組件之間的交互,如發送Notification、啟動Service等

默認情況下,廣播接收器運行在 UI 線程,因此,onReceive()方法不能執行耗時操作,否則將導致ANR

*/

public abstract class PushReceiverextends BroadcastReceiver {

public static final StringACTION_TEXT ="com.android.action.text";

public static final StringDATA_KEY ="data";

}

消息接收與發送的類-------------------------------------------------------------------------------------

public interface Request {

String getData();

}


public class TextRequest implements Request {

private Mapmap =new HashMap();

public TextRequest(String sender, String token, String receiver,

String content) {

map.put("type","request");

map.put("sequence", UUID.randomUUID().toString());

map.put("action","text");

map.put("sender", sender);

map.put("token", token);

map.put("receiver", receiver);

map.put("content", content);

}

@Override

? ? public String getData() {

return new Gson().toJson(map);

}

}


public class AuthRequest implements Request {

private Mapmap =new HashMap();

public AuthRequest(String sender, String token) {

map.put("type","request");

map.put("sequence", UUID.randomUUID().toString());

map.put("action","auth");

map.put("sender", sender);

map.put("token", token);

}

@Override

? ? public String getData() {

return new Gson().toJson(map);//將map轉成JSON數據

? ? }

}


客戶端Activity代碼-----------------------------------------------------------------

public class ClientActivity extends Activity {

private EditTextmEtContent;//綁定控件主要為了獲取輸入框的內容

? ? @Override

? ? protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mEtContent = findViewById(R.id.edit_query);

//開啟服務需要在app/src/main/AndroidManifest.xml中進行注冊,啟動Service的方法和啟動Activity很類似,都需要借助Intent來實現

? ? ? ? startService(new Intent(this, CoreService.class));

}

/**

* 發送消息:

? ? * @param view

? ? */

? ? public void sendMsg(View view){

String content =mEtContent.getText().toString();

if (TextUtils.isEmpty(content)) {

return;

}

String sender =null;

String token =null;

String receiver =null;

//在我們自己開發應用過程中,常常使用如下的代碼形式判斷運行新API還是舊的API:

? ? ? ? if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB_MR2) {

// 包含新的API的代碼塊

? ? ? ? ? ? sender ="B";

receiver ="A";

token ="B";

}else {

// 包含舊的API的代碼塊

? ? ? ? ? ? sender ="A";

token ="A";

receiver ="B";

}

Request request =new TextRequest(sender, token, receiver, content);

Connector.getInstance().putRequest(request);

}

/*? ? 這里多說兩句查閱api文檔后自己對于Intent的一些理解和實踐

程序的3個核心組件——Activity、services、廣播接收器——是通過intent傳遞消息的。

intent消息對于運行時綁定不同的組件是很方便的,這些組件可以是同一個程序也可以是不同的。

一個intent對象,是一個被動的數據結構,它保存了一個操作的抽象描述——或通常是一個廣播的實例,一些發生的事情的描述,一個通知。

傳遞intent到不同組件的機制是互不相同的。

Android系統會尋找合適的Activity、service或設置廣播接收器來響應intent,在需要的時候實例化它們。

在消息系統里沒有交疊:廣播intent僅僅分派給廣播接收器,不會分派給Activity或service。一個intent分派給startActivity()僅僅分派給Activity,不會分派給service或廣播接收器,等等。

在這里我們用Intent去告訴廣播群我們接收到了數據,一旦對應的Intent接收到了數據,廣播就會告訴綁定了廣播的Activity去更新UI

*/

? ? @Override

? ? protected void onResume() {

super.onResume();

if (mReceiver !=null) {

//IntentFilter會告訴Android系統我要廣播intent僅僅分派給廣播接收器

// 設置接收廣播的類型為PushReceiver.ACTION_TEXT

? ? ? ? ? ? IntentFilter filter =new IntentFilter(PushReceiver.ACTION_TEXT);

//動態注冊:調用Context的registerReceiver()方法

? ? ? ? ? ? registerReceiver(mReceiver, filter);

}

}

private PushReceivermReceiver =new PushReceiver() {

/*? 實例化了廣播接收器

實現抽象類PushReceiver,重寫了onReceive的方法

復寫onReceive()方法

接收到廣播后,則自動調用該方法

*/

? ? ? ? @Override

? ? ? ? public void onReceive(Context context, Intent intent) {

String action = intent.getAction();

if (PushReceiver.ACTION_TEXT.equals(action)) {

//接收到對應的廣播后執行的操作

? ? ? ? ? ? ? ? String result = intent.getStringExtra(PushReceiver.DATA_KEY);

System.out.println(result);

System.out.println(" ");

Toast.makeText(getApplicationContext(), result,

Toast.LENGTH_SHORT).show();

}

}

};

// 注冊廣播后,要在相應位置記得銷毀廣播

// 即在onDestroy()中unregisterReceiver(mBroadcastReceiver)

// 當此Activity實例化時,會動態將MyBroadcastReceiver注冊到系統中

// 當此Activity銷毀時,動態注冊的MyBroadcastReceiver將不再接收到相應的廣播。

? ? @Override

? ? protected void onDestroy() {

super.onDestroy();

if (mReceiver !=null) {

unregisterReceiver(mReceiver);

}

}

}


服務器端-----------------------------------------------------------------------------------

//服務器端

@SuppressWarnings("all")

public class TcpServer {

public static void main(String[] args) {

//final LinkedList list = new LinkedList();

? ? ? ? final Map map =new HashMap();

int port =7896;

try {

ServerSocket server =new ServerSocket(port);

while (true) {

// 獲得客戶端連接

// 阻塞式方法

? ? ? ? ? ? ? ? System.out.println("準備阻塞...");

final Socket client = server.accept();

System.out.println("阻塞完成...");

// 添加到集合里

//list.add(client);

? ? ? ? ? ? ? ? new Thread(new Runnable() {

@Override

? ? ? ? ? ? ? ? ? ? public void run() {

try {

// 輸入流,為了獲取客戶端發送的數據

? ? ? ? ? ? ? ? ? ? ? ? ? ? InputStream is =client.getInputStream();

// 得到輸出流

? ? ? ? ? ? ? ? ? ? ? ? ? ? OutputStream out =client.getOutputStream();

byte[] buffer =new byte[1024];

int len = -1;

System.out.println("準備read...");

while ((len = is.read(buffer)) != -1) {

String text =new String(buffer,0, len);

if (text.startsWith("#")) {

map.put(text,client);

// 回復認證信息

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? out.write("認證成功,over".getBytes());

}else {

out.write("發送成功,over".getBytes());

System.out.println(text);

}

}

}catch (Exception e) {

e.printStackTrace();

}

}

}).start();

}

}catch (Exception e) {

e.printStackTrace();

}

}

}

結果

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。