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();

}

}

}

结果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,406评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,976评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,302评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,366评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,372评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,457评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,872评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,521评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,717评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,523评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,590评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,299评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,859评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,883评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,127评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,760评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,290评论 2 342