Android中使用socket通信实现消息推送的方法详解
原理
最近用socket写了一个消息推送的demo,在这里和大家分享一下。
主要实现了:一台手机向另外一台手机发送消息,这两台手机可以随时*发送文本消息进行通信,类似我们常用的qq。
效果图:
原理:手机通过socket发送消息到服务器,服务器每接收到一条消息之后,都会把这条消息放进一个messagelist里面,服务器会不停地检测messagelist是否含有消息,如果有的话就会根据messagelist里面item的数据,推送到相应的另一端手机上面。
下面简单画了一个图来说明这个原理:
演示:手机客户端client1发送消息msg1到手机客户端client2,client2收到消息后回复msg2给client1
1.手机客户端client1发送一条“msg1”的文本消息到服务器;
2.服务器收到来自client1的“msg1”消息后,把它add进messagelist里面;
3.服务器检测到messagelist里面含有消息(开启服务器时就新建里一个检测messagelist的线程,线程里面有一个死循环,用于不停检测messagelist是否含有消息);
4.服务器读取消息数据,如读取到来自client1发给client2的消息“msg1”,那么服务器就把“msg1”推送到client2上;
5.client2检测到服务器推送的消息,做出相应的操作(如:震动、铃声、显示消息等);
6.client2接收到来自服务器推送的“msg1”消息后,client2也回复一条文本消息“msg2”给client1,此过程和client1发送消息给client2一样。
7.最后,client2就可以显示来自client1发送的消息“msg1”,而client1则可以显示来自client2的回复消息“msg2”。
实现过程
根据消息推送的原理图,我们的实现过程主要分为server端和client端,server端采用java的编程,而client端则用android编程。
所以在这里也分别创建了两个工程socketserver和socketclient
我们先来看一下socketmessage.java类:
public class socketmessage { public int to;//socketid,指发送给谁 public int from;//socketid,指谁发送过来的 public string msg;//消息内容 public string time;//接收时间 public socketthread thread;//socketthread下面有介绍 }
该类是一个消息类,用于表示消息是由谁发给谁的、消息内容是什么、接收时间是多少,只有几个属性,比较简单。
而myserver.java类就相对比较多一些代码:
package com.jimstin.server; import java.io.bufferedreader; import java.io.bufferedwriter; import java.io.inputstreamreader; import java.io.outputstreamwriter; import java.net.serversocket; import java.net.socket; import java.text.simpledateformat; import java.util.arraylist; import java.util.date; import org.json.jsonobject; import com.jimstin.msg.socketmessage; public class myserver { private boolean isstartserver; private serversocket mserver; /** * 消息队列,用于保存socketserver接收来自于客户机(手机端)的消息 */ private arraylist<socketmessage> mmsglist = new arraylist<socketmessage>(); /** * 线程队列,用于接收消息。每个客户机拥有一个线程,每个线程只接收发送给自己的消息 */ private arraylist<socketthread> mthreadlist = new arraylist<socketthread>(); /** * 开启socketserver */ private void startsocket() { try { isstartserver = true; int prot = 2000;//端口可以自己设置,但要和client端的端口保持一致 mserver = new serversocket(prot);//创建一个serversocket system.out.println("启动server,端口:"+prot); socket socket = null; int socketid = 0;//android(socketclient)客户机的唯一标志,每个socketid表示一个android客户机 //开启发送消息线程 startsendmessagethread(); //用一个循环来检测是否有新的客户机加入 while(isstartserver) { //accept()方法是一个阻塞的方法,调用该方法后, //该线程会一直阻塞,直到有新的客户机加入,代码才会继续往下走 socket = mserver.accept(); //有新的客户机加入后,则创建一个新的socketthread线程对象 socketthread thread = new socketthread(socket, socketid++); thread.start(); //将该线程添加到线程队列 mthreadlist.add(thread); } } catch (exception e) { e.printstacktrace(); } } /** * 开启推送消息线程,如果mmsglist中有socketmessage,则把该消息推送到android客户机 */ public void startsendmessagethread() { new thread(){ @override public void run() { super.run(); try { /*如果isstartserver=true,则说明socketserver已启动, 用一个循环来检测消息队列中是否有消息,如果有,则推送消息到相应的客户机*/ while(isstartserver) { //判断消息队列中的长度是否大于0,大于0则说明消息队列不为空 if(mmsglist.size() > 0) { //读取消息队列中的第一个消息 socketmessage from = mmsglist.get(0); for(socketthread to : mthreadlist) { if(to.socketid == from.to) { bufferedwriter writer = to.writer; jsonobject json = new jsonobject(); json.put("from", from.from); json.put("msg", from.msg); json.put("time", from.time); //writer写进json中的字符串数据,末尾记得加换行符:"\n",否则在客户机端无法识别 //因为bufferedreader.readline()方法是根据换行符来读取一行的 writer.write(json.tostring()+"\n"); //调用flush()方法,刷新流缓冲,把消息推送到手机端 writer.flush(); system.out.println("推送消息成功:"+from.msg+">> to socketid:"+from.to); break; } } //每推送一条消息之后,就要在消息队列中移除该消息 mmsglist.remove(0); } thread.sleep(200); } } catch (exception e) { e.printstacktrace(); } } }.start(); } /** * 定义一个socketthread类,用于接收消息 * */ public class socketthread extends thread { public int socketid; public socket socket;//socket用于获取输入流、输出流 public bufferedwriter writer;//bufferedwriter 用于推送消息 public bufferedreader reader;//bufferedreader 用于接收消息 public socketthread(socket socket, int count) { socketid = count; this.socket = socket; system.out.println("新增一台客户机,socketid:"+socketid); } @override public void run() { super.run(); try { //初始化bufferedreader reader = new bufferedreader(new inputstreamreader(socket.getinputstream(), "utf-8")); //初始化bufferedwriter writer = new bufferedwriter(new outputstreamwriter(socket.getoutputstream(), "utf-8")); //如果isstartserver=true,则说明socketserver已经启动, //现在需要用一个循环来不断接收来自客户机的消息,并作其他处理 while(isstartserver) { //先判断reader是否已经准备好 if(reader.ready()) { /*读取一行字符串,读取的内容来自于客户机 reader.readline()方法是一个阻塞方法, 从调用这个方法开始,该线程会一直处于阻塞状态, 直到接收到新的消息,代码才会往下走*/ string data = reader.readline(); //讲data作为json对象的内容,创建一个json对象 jsonobject json = new jsonobject(data); //创建一个socketmessage对象,用于接收json中的数据 socketmessage msg = new socketmessage(); msg.to = json.getint("to"); msg.msg = json.getstring("msg"); msg.from = socketid; msg.time = gettime(system.currenttimemillis()); //接收到一条消息后,将该消息添加到消息队列mmsglist mmsglist.add(msg); system.out.println("收到一条消息:"+json.getstring("msg")+" >>>> to socketid:"+json.getint("to")); } //睡眠100ms,每100ms检测一次是否有接收到消息 thread.sleep(100); } } catch (exception e) { e.printstacktrace(); } } } /** * 获取指定格式的时间字符串,通过毫秒转换日期 * @param milltime */ private string gettime(long milltime) { date d = new date(milltime); simpledateformat sdf = new simpledateformat("yyyy-mm-dd hh:mm:ss"); return sdf.format(d); } public static void main(string[] args) { myserver server = new myserver(); server.startsocket(); } }
2.socketclient工程
该工程是一个android的工程,只有一个mainactivity.java和activity_main.xml文件,
先看一下activity_main.xml布局文件:
<linearlayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".mainactivity" android:orientation="vertical" > <linearlayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <edittext android:id="@+id/ip_edt" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="ip" android:text="172.16.1.200"/> <edittext android:id="@+id/port_edt" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="port" android:text="2000"/> </linearlayout> <button android:id="@+id/start_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="start"/> <edittext android:id="@+id/socket_id_edt" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="socketid"/> <edittext android:id="@+id/msg_edt" android:layout_width="match_parent" android:layout_height="wrap_content" android:minlines="5" android:hint="content" android:gravity="top" /> <button android:id="@+id/send_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="send"/> <textview android:id="@+id/console_txt" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <button android:id="@+id/clear_btn" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="clear"/> </linearlayout>
效果图:
mainactivity.java类:
package com.jimstin.socketclient; import java.io.bufferedreader; import java.io.bufferedwriter; import java.io.ioexception; import java.io.inputstreamreader; import java.io.outputstreamwriter; import java.net.socket; import java.net.unknownhostexception; import java.text.simpledateformat; import java.util.date; import org.json.jsonobject; import com.tencent.stat.mtasdkexception; import com.tencent.stat.statconfig; import com.tencent.stat.statservice; import android.r.integer; import android.os.asynctask; import android.os.bundle; import android.os.handler; import android.os.message; import android.util.log; import android.view.view; import android.view.view.onclicklistener; import android.widget.edittext; import android.widget.textview; import android.widget.toast; import android.app.activity; public class mainactivity extends activity implements onclicklistener { private edittext mipedt, mportedt, msocketidedt, mmessageedt; private static textview mconsoletxt; private static stringbuffer mconsolestr = new stringbuffer(); private socket msocket; private boolean isstartrecievemsg; private sockethandler mhandler; protected bufferedreader mreader;//bufferedwriter 用于推送消息 protected bufferedwriter mwriter;//bufferedreader 用于接收消息 @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); initview(); } private void initview() { mipedt = (edittext) findviewbyid(r.id.ip_edt); mportedt = (edittext) findviewbyid(r.id.port_edt); msocketidedt = (edittext) findviewbyid(r.id.socket_id_edt); mmessageedt = (edittext) findviewbyid(r.id.msg_edt); mconsoletxt = (textview) findviewbyid(r.id.console_txt); findviewbyid(r.id.start_btn).setonclicklistener(this); findviewbyid(r.id.send_btn).setonclicklistener(this); findviewbyid(r.id.clear_btn).setonclicklistener(this); mhandler = new sockethandler(); } /** * 初始化socket */ private void initsocket() { //新建一个线程,用于初始化socket和检测是否有接收到新的消息 thread thread = new thread(new runnable() { @override public void run() { string ip = mipedt.gettext().tostring();//ip int port = integer.parseint(mportedt.gettext().tostring());//socket try { isstartrecievemsg = true; msocket = new socket(ip, port); mreader = new bufferedreader(new inputstreamreader(msocket.getinputstream(), "utf-8")); mwriter = new bufferedwriter(new outputstreamwriter(msocket.getoutputstream(), "utf-8")); while(isstartrecievemsg) { if(mreader.ready()) { /*读取一行字符串,读取的内容来自于客户机 reader.readline()方法是一个阻塞方法, 从调用这个方法开始,该线程会一直处于阻塞状态, 直到接收到新的消息,代码才会往下走*/ string data = mreader.readline(); //handler发送消息,在handlemessage()方法中接收 mhandler.obtainmessage(0, data).sendtotarget(); } thread.sleep(200); } mwriter.close(); mreader.close(); msocket.close(); } catch (exception e) { e.printstacktrace(); } } }); thread.start(); } @override public void onclick(view v) { switch (v.getid()) { case r.id.send_btn: send(); break; case r.id.clear_btn: mconsolestr.delete(0, mconsolestr.length()); mconsoletxt.settext(mconsolestr.tostring()); break; case r.id.start_btn: if(!isstartrecievemsg) { initsocket(); } break; default: break; } } /** * 发送 */ private void send() { new asynctask<string, integer, string>() { @override protected string doinbackground(string... params) { sendmsg(); return null; } }.execute(); } /** * 发送消息 */ protected void sendmsg() { try { string socketid = msocketidedt.gettext().tostring().trim(); string msg = mmessageedt.gettext().tostring().trim(); jsonobject json = new jsonobject(); json.put("to", socketid); json.put("msg", msg); mwriter.write(json.tostring()+"\n"); mwriter.flush(); mconsolestr.append("我:" +msg+" "+gettime(system.currenttimemillis())+"\n"); mconsoletxt.settext(mconsolestr); } catch (exception e) { e.printstacktrace(); } } static class sockethandler extends handler { @override public void handlemessage(message msg) { // todo auto-generated method stub super.handlemessage(msg); switch (msg.what) { case 0: try { //将handler中发送过来的消息创建json对象 jsonobject json = new jsonobject((string)msg.obj); mconsolestr.append(json.getstring("from")+":" +json.getstring("msg")+" "+gettime(system.currenttimemillis())+"\n"); //将json数据显示在textview中 mconsoletxt.settext(mconsolestr); } catch (exception e) { e.printstacktrace(); } break; default: break; } } } @override public void onbackpressed() { super.onbackpressed(); isstartrecievemsg = false; } private static string gettime(long milltime) { date d = new date(milltime); simpledateformat sdf = new simpledateformat("yyyy-mm-dd hh:mm:ss"); return sdf.format(d); } }
以上代码的注释都比较详细,就不再多说了。