这几天学习了下在android中实现即时通讯的方法,一开始,自然是从基本的网络协议中开始尝试了,这样能够最大化的私人订制自己的应用,还能学习到更多的知识,好处多多,接下来就简单介绍下两种协议的不同点吧
TCP协议:提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复用。通过面向连接、端到端和可靠的数据包发送。就如给悬崖上的两人通信时,他必须先把桥建好,确认桥是没问题的情况下,才把信件交过去,以后大家每次通信时,都确认下桥没什么问题,再通过这座桥来回通信了。
UDP协议:不为IP提供可靠性、流控或差错恢复功能,在正式通信前不必与对方先建立连接,不管对方状态就直接发送。这个就是飞鸽传书了~
虽然UDP可靠性不如TCP协议,但是通信效率高于TCP。在网速极差的情况下优先考虑UDP协议,网速好的话TCP还是很方便使用的。
在Java中使用TCP可以通过java.net.Socket;这个类
首先,创建一个Socket实例,并建立连接,该处不使用new Socket(int port)创建实例,该方法将绑定一个端口,以后发送信息,监听都是使用该端口,如果该端口被占用了,就会引发异常。
//实例化一个Socket对象 socket = new Socket(); //与对应的ip、端口的服务器进行连接,先要把桥建好 socket.connect(new InetSocketAddress(ip, port), 3000);
接收信息
InputStream ois = socket.getInputStream(); DataInputStream dis = new DataInputStream(new BufferedInputStream(ois)); //读取服务器发过来的信息,如果没信息将会阻塞线程 msg = dis.readUTF();
发送信息
//获得输出流 DataOutputStream dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); //发送数据 dos.writeUTF(msg);
接下来上源码,为三个Thread的子类,分别对应上面三个
Socket和其他基本信息的初始化,直接使用此类发送消息,设置消息监听
public class SocketThread extends Thread{ private Socket socket; private Client client; private String ip; private int port; private boolean isStart=false; private MessageListener mMessageListener; /** * * 使用TCP协议,连接访问 * @param ip 目标机器的IP * @param port 端口 * @param mMessageListener 收到服务器端数据时将回调该接口内的 * public void message(String msg)方法 */ public SocketThread(String ip, int port, MessageListener mMessageListener) { this.ip = ip; this.port = port; this.mMessageListener = mMessageListener; } public void run() { try { //实例化一个Socket对象 socket = new Socket(); //与对应的ip、端口进行连接,先要把桥建好 socket.connect(new InetSocketAddress(ip, port), 3000); if (socket.isConnected()) { System.out.println("Connected.."); client = new Client(socket,mMessageListener); //打开对应的输入/输出流监听 client.start(); isStart=true; } } catch (IOException e) { e.printStackTrace(); isStart=false; } } // 直接通过client得到读线程 public ClientInputThread getClientInputThread() { return client.getIn(); } // 直接通过client得到写线程 public ClientOutputThread getClientOutputThread() { return client.getOut(); } //返回Socket状态 public boolean isStart(){ return isStart; } // 直接通过client停止读写消息 public void setIsStart(boolean isStart) { this.isStart = isStart; client.getIn().setStart(isStart); client.getOut().setStart(isStart); } //发送消息 public void sendMsg(String msg){ client.getOut().sendMsg(msg); } public class Client { private ClientInputThread in; private ClientOutputThread out; public Client(Socket socket,MessageListener mMessageListener) { //用这个监听输入流线程来接收信息 in = new ClientInputThread(socket); in.setMessageListener(mMessageListener); //以后就用这个监听输出流的线程来发送信息了 out = new ClientOutputThread(socket); } public void start() { in.setStart(true); out.setStart(true); in.start(); out.start(); } // 得到读消息线程 public ClientInputThread getIn() { return in; } // 得到写消息线程 public ClientOutputThread getOut() { return out; } } }接收信息的实现类
public class ClientInputThread extends Thread { private Socket socket; private String msg; private boolean isStart = true; private InputStream ois; private DataInputStream dis; private MessageListener messageListener;// 消息监听接口对象 public ClientInputThread(Socket socket) { this.socket = socket; try { ois = socket.getInputStream(); dis = new DataInputStream(new BufferedInputStream(ois)); } catch (IOException e) { e.printStackTrace(); } } /** * 提供给外部的消息监听方法 * * @param messageListener * 消息监听接口对象 */ public void setMessageListener(MessageListener messageListener) { this.messageListener = messageListener; } public void setStart(boolean isStart) { this.isStart = isStart; } @Override public void run() { try { while (isStart) { //读取信息,如果没信息将会阻塞线程 msg = dis.readUTF(); // 每收到一条消息,就调用接口的方法message(String msg) Log.v("收到消息", msg); messageListener.message(msg); } ois.close(); if (socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } } BufferedReader reader=null; public String getInputStreamString() { /* * To convert the InputStream to String we use the * BufferedReader.readLine() method. We iterate until the BufferedReader * return null which means there's no more data to read. Each line will * appended to a StringBuilder and returned as String. */ if (ois != null) { reader = new BufferedReader(new InputStreamReader(ois)); } StringBuilder sb = new StringBuilder(); String line = null; try { while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } } catch (IOException e) { e.printStackTrace(); } return sb.toString(); } }发送信息的实现类
public class ClientOutputThread extends Thread { private Socket socket; private DataOutputStream dos; private boolean isStart = true; private String msg; public ClientOutputThread(Socket socket) { this.socket = socket; try { dos = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); } catch (IOException e) { e.printStackTrace(); } } public void setStart(boolean isStart) { this.isStart = isStart; } // 这里处理跟服务器是一样的 public void sendMsg(String msg) { this.msg = msg; synchronized (this) { notifyAll(); } } @Override public void run() { try { while (isStart) { if (msg != null) { dos.writeUTF(msg); dos.flush(); msg=null; synchronized (this) { wait();// 发送完消息后,线程进入等待状态 } } } dos.close();// 循环结束后,关闭输出流和socket if (socket != null) socket.close(); } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
//定义接收到消息时的,处理消息的接口 public interface MessageListener { public void message(String msg); }
主界面,感觉很丑,将就吧
<RelativeLayout 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" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.chatclient.MainActivity" > <ScrollView android:id="@+id/svMessage" android:layout_width="fill_parent" android:layout_height="100dp" > <TextView android:id="@+id/tvMessage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="消息内容:\n" /> </ScrollView> <EditText android:id="@+id/etMessage" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_below="@+id/svMessage" /> <TextView android:id="@+id/tvSend" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/svMessage" android:layout_marginLeft="10dp" android:layout_toRightOf="@+id/etMessage" android:text="发送消息" android:textSize="25sp" /> " </RelativeLayout>
MainActivity代码
public class MainActivity extends Activity { EditText etMessage; TextView tvSend, tvMessage; SocketThread client; MyHandler myHandler; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setup(); } public void setup() { etMessage = (EditText) findViewById(R.id.etMessage); tvSend = (TextView) findViewById(R.id.tvSend); tvMessage = (TextView) findViewById(R.id.tvMessage); tvSend.setOnClickListener(onClick); myHandler = new MyHandler(); //初始化 client = new SocketThread("10.21.56.226", 8888,new MessageListener() { //收到消息后调用此方法 @Override public void message(String msg) { // TODO Auto-generated method stub // tvMessage.append(msg); Bundle bundle = new Bundle(); bundle.putString("input", msg); Message isMessage = new Message(); isMessage.setData(bundle); //使用handler转发 myHandler.sendMessage(isMessage); } }); //正式启动线程 client.start(); } OnClickListener onClick = new OnClickListener() { public void onClick(android.view.View v) { String message = etMessage.getText().toString(); Log.v("发送消息", message); if (client.isStart()) { client.sendMsg(message); } }; }; private class MyHandler extends Handler { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub Log.v("处理收到的消息", " "); tvMessage.append(msg.getData().getString("input")); } } }
服务器端的代码,主要是使用ServerSocket监听一个端口,来与客户端链接和收发信息
public class ChatServer { boolean started = false; ServerSocket ss = null; List<Client> clients = new ArrayList<Client>(); public static void main(String[] args) { new ChatServer().start(); } public void start() { try { //ServerSocket监听8888端口 ss = new ServerSocket(8888); started = true; } catch (BindException e) { System.out.println("start...."); System.out.println("有问题"); e.printStackTrace(); System.exit(0); } catch (IOException e) { e.printStackTrace(); } try { while (started) { Socket s = ss.accept(); Client c = new Client(s); System.out.println("a client connected!"); new Thread(c).start(); clients.add(c); // dis.close(); } } catch (IOException e) { e.printStackTrace(); } finally { try { ss.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class Client implements Runnable { private Socket s; private DataInputStream dis = null; private DataOutputStream dos = null; private boolean bConnected = false; public Client(Socket s) { this.s = s; try { dis = new DataInputStream(s.getInputStream()); dos = new DataOutputStream(s.getOutputStream()); bConnected = true; } catch (IOException e) { e.printStackTrace(); } } public void send(String str) { try { dos.writeUTF(str); } catch (IOException e) { clients.remove(this); System.out.println("关闭一个连接"); // e.printStackTrace(); } } public void run() { try { while (bConnected) { String str = dis.readUTF(); System.out.println(str); for (int i = 0; i < clients.size(); i++) { Client c = clients.get(i); c.send(str); // System.out.println(" a string send !"); } /* * for(Iterator<Client> it = clients.iterator(); * it.hasNext(); ) { Client c = it.next(); c.send(str); } */ /* * Iterator<Client> it = clients.iterator(); * while(it.hasNext()) { Client c = it.next(); c.send(str); * } */ } } catch (EOFException e) { System.out.println("Client closed!"); } catch (IOException e) { e.printStackTrace(); } finally { try { System.out.println("close All !"); if (dis != null) dis.close(); if (dos != null) dos.close(); if (s != null) { s.close(); // s = null; } } catch (IOException e1) { e1.printStackTrace(); } } } } }接下来先运行服务器代码,再运行手机端就可以了,多台手机可以互相发送信息了。
接下来写下UDP的发送和接收,服务器端也进行了转发,使得一台手机发送的消息,所有手机都能收到消息
UDP使用的是DatagramSocket类,从该类名字就可以看出该类使用起来类似于电报或是信件这样的形式
(1)创建一个DatagramSocket实例,该处也不固定端口了
client = new DatagramSocket();
(2)发送数据需要先把"信"写好,这里和TCP不太同,使用的是DatagramPacket来包含发送的地址和发送的数据
创建DatagramPacket实例
DatagramPacket(byte[] buf, int offset, int length,InetAddress address, int port)
再调用DatagramSocket.send(DatagramPacket pack);
(3)接收数据,就不用知道对方地址了,所以一般使用DatagramPacket packet = new DatagramPacket(rbyte[] buf, int offset, int length);
然后调用DatagramSocket.receive(DatagramPacket packet);接收数据
Socket和其他基本信息的初始化,直接使用此类发送消息,设置消息监听
/** * @author xuan 2014-10-30 * * 使用UDP发送数据的类,收发数据一般直接使用该类 */ public class UDPThread extends Thread { private String IP; private int port; private DatagramSocket client; private ReceivePacket mReceivePacket; private MessageListener mMessageListener; // 第一次发送给服务器的数据 private String connetString; // 服务器地址 // private InetAddress mInetAddress; byte[] baMessage = null; // 判断服务是否可以使用 private boolean isStart; // 是否使用WifiManager.MulticastLock private boolean useWifyLock = false; public WifiManager.MulticastLock lock; public UDPThread(String IP, int port, String connetString, MessageListener messageListener) { // TODO Auto-generated constructor stub this.IP = IP; this.port = port; this.connetString = connetString; this.mMessageListener = messageListener; } @Override public void run() { // TODO Auto-generated method stub try { // if (useWifyLock) { lock.acquire(); } client = new DatagramSocket(); // client.setBroadcast(true); byte[] msg = String2Byte(connetString); DatagramPacket packet = new DatagramPacket(msg, msg.length, InetAddress.getByName(IP), port); client.send(packet); mReceivePacket = new ReceivePacket(client); mReceivePacket.setMessageListener(mMessageListener); mReceivePacket.lock = this.lock; mReceivePacket.start(); isStart = true; // 本线程以后将等待并响应发送消息的请求(sendMessage(String)) waitMessageToSend(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } if (useWifyLock) { lock.release(); } } public void sendMessage(String msg) { synchronized (this) { baMessage = String2Byte(msg); notifyAll(); } } // 部分手机默认关闭UDP广播功能,此时必须使用该方法 public void useWifyLock(WifiManager.MulticastLock lock) { if (lock != null) { useWifyLock = true; mReceivePacket.useWifyLock=true; this.lock = lock; } } private void waitMessageToSend() { while (isStart) { // 因为只有一条发送线程,所以不用放在synchronized块中,可提高响应度 if (baMessage != null) { DatagramPacket packet; try { if (useWifyLock) { lock.acquire(); } packet = new DatagramPacket(baMessage, baMessage.length, InetAddress.getByName(IP), port); client.send(packet); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("msg长度: " + baMessage.length); baMessage = null; if (useWifyLock) { lock.release(); } } synchronized (this) { try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } if (!client.isClosed()) { client.close(); } } public void setStart(boolean isStart) { this.isStart = isStart; } public void close() { this.isStart = false; mReceivePacket.startReceive(false); } private byte[] String2Byte(String message) { ByteArrayOutputStream ostream = new ByteArrayOutputStream(); DataOutputStream dataStream = new DataOutputStream(ostream); try { dataStream.writeUTF(message); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } try { dataStream.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return ostream.toByteArray(); } }
监听服务端发送过来的数据的实现类
/** * @author xuan 2014-10-30 * * 接收数据的类 */ public class ReceivePacket extends Thread { private DatagramSocket server; private MessageListener mMessageListener; // 最多接受1024byte数据 private byte[] recive = new byte[1024]; private boolean isStart = false; boolean useWifyLock = false; public WifiManager.MulticastLock lock; public ReceivePacket(DatagramSocket server) { // TODO Auto-generated constructor stub this.server = server; } @Override public void run() { // TODO Auto-generated method stub isStart = true; receive(); } private void receive() { try { while (isStart) { DatagramPacket packet = new DatagramPacket(recive, recive.length); // 阻塞接收,直到接收到数据为止才继续 Log.v("等待数据", " \n"); if (useWifyLock) { lock.acquire(); } server.receive(packet); Log.v("收到数据了", " \n"); // 收到数据后,调用MessageListener if(isStart){ mMessageListener.message(byte2String(recive)); } if (useWifyLock) { lock.release(); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } if (!server.isClosed()) { server.close(); } } public void startReceive(boolean start) { this.isStart = start; } /** * 提供给外部的消息监听方法 * * @param messageListener * 消息监听接口对象 */ public void setMessageListener(MessageListener messageListener) { this.mMessageListener = messageListener; } private String byte2String(byte[] msg) { DataInputStream istream = new DataInputStream(new ByteArrayInputStream( msg, 0, msg.length)); String str = null; try { str = istream.readUTF(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return str; } }MainActivity类代码
public class MainActivity extends Activity { EditText etMessage; TextView tvMessage; Button tvSend,btClose,btStart; UDPThread client; // ClientInputThread mClientInputThread; // ClientOutputThread mClientOutputThread; MyHandler myHandler; WifiManager.MulticastLock lock; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); setup(); } public void setup() { etMessage = (EditText) findViewById(R.id.etMessage); tvSend = (Button) findViewById(R.id.tvSend); tvMessage = (TextView) findViewById(R.id.tvMessage); btClose = (Button)findViewById(R.id.btClose); btStart = (Button)findViewById(R.id.btStart); tvSend.setOnClickListener(onClick); btClose.setOnClickListener(onClick); btStart.setOnClickListener(onClick); myHandler = new MyHandler(); WifiManager manager = (WifiManager) this .getSystemService(Context.WIFI_SERVICE); lock= manager.createMulticastLock("myUDP"); // lock.acquire(); client = new UDPThread("10.21.56.226", 7000,"login:xuan,password:{888}", new MessageListener() { @Override public void message(String msg) { // TODO Auto-generated method stub // tvMessage.append(msg); Bundle bundle = new Bundle(); bundle.putString("input", msg); Message isMessage = new Message(); isMessage.setData(bundle); myHandler.sendMessage(isMessage); } }); client.lock = this.lock; client.start(); } OnClickListener onClick = new OnClickListener() { public void onClick(android.view.View v) { switch(v.getId()){ case R.id.tvSend: String message = etMessage.getText().toString(); Log.v("发送消息", message); client.sendMessage(message); break; case R.id.btClose: client.close(); break; // case R.id.btStart: // client.start(); // break; } }; }; private class MyHandler extends Handler { @Override public void handleMessage(Message msg) { // TODO Auto-generated method stub Log.v("处理收到的消息", " "); tvMessage.append(msg.getData().getString("input")); } } @Override public void finish() { // TODO Auto-generated method stub super.finish(); } }xml布局
<RelativeLayout 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" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="com.example.chatclient.MainActivity" > <ScrollView android:id="@+id/svMessage" android:layout_width="fill_parent" android:layout_height="100dp" > <TextView android:id="@+id/tvMessage" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="消息内容:\n" /> </ScrollView> <EditText android:id="@+id/etMessage" android:layout_width="100dp" android:layout_height="wrap_content" android:layout_below="@+id/svMessage" /> <Button android:id="@+id/tvSend" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/svMessage" android:layout_marginLeft="10dp" android:layout_toRightOf="@+id/etMessage" android:text="发送消息" android:textSize="25sp" /> <Button android:id="@+id/btClose" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/etMessage" android:layout_marginLeft="10dp" android:text="关闭连接" android:textSize="25sp" /> <Button android:id="@+id/btStart" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/btClose" android:layout_marginLeft="10dp" android:text="打开连接" android:textSize="25sp" /> </RelativeLayout>android端的代码就是这样子的~
接下来把服务端的简单代码也贴上去,UDP服务端和客户端基本没啥使用上的区别,主要是接收数据,并且转发数据给其他人(包括自己,这样就确定自己真的发送出去了),服务端代码非常简单,只是为了测试android端代码。如有不足,还望赐教~