背景
这几天在学习安卓的进程间通信,而socket也可以实现这一功能,以可靠连接协议TCP为例,实现一个服务端和客户端的应答应用
功能:客户端发消息,服务端把消息原样返回,如果收到小写over,退出
步骤
1、服务端,在Service的onCreate()里面,新建一个线程创建服务端的socket,并实现客户端的应答
public class PeopleService extends Service { private boolean isOut = false; private boolean isOver = false; @Override public void onCreate() { new Thread(new Runnable() { @Override public void run() { try { final ServerSocket serverSocket = new ServerSocket(12345); System.out.println("等待客户端连接"); while (!isOut) { // 服务端不退出,这个线程一直执行 final Socket client = serverSocket.accept(); // 阻塞在此,直到客户端连接 System.out.println("客户端已连接,ip地址:" + client.getInetAddress().getHostAddress() + "端口号:" + client.getLocalPort()); // 客户端连接后,等待1s以便客户端发消息 Thread.sleep(1000); try { BufferedInputStream inputStream = new BufferedInputStream(client.getInputStream()); BufferedOutputStream outputStream = new BufferedOutputStream(client.getOutputStream()); System.out.println("开始和客户端通信"); while (!isOver) { // 消息不为over,线程一直执行 while (inputStream.available() <= 0); // 过滤空消息 byte[] bytes = new byte[1024]; int len; StringBuffer stringBuffer = new StringBuffer(); while (inputStream.available() > 0 && (len = inputStream.read(bytes)) != -1) { stringBuffer.append(new String(bytes, 0, len)); } // 下面就不对stringBuffer的内容判空了,因为是空的话,根本跳不出上面的过滤 String fromClient = stringBuffer.toString(); System.out.println("客户端信息:" + fromClient); outputStream.write(fromClient.getBytes()); outputStream.flush(); isOver = fromClient.equals("over"); isOut = isOver; } System.out.println("over.."); inputStream.close(); outputStream.close(); } catch (Exception e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } } }).start(); } @Override public IBinder onBind(Intent intent) { logger("有连接请求"); logger(intent.toString()); return null; } }
整个过程在一个新线程的原因是service里不让进行耗时操作,所以才新建线程
在利用BufferedOutputStream读消息时,一定先要利用available()过滤空消息。
这和文件IO不一样,文件IO的话输入流的长度是固定的,所以只用read(byte[])就可以读到输入流的末尾,但如果是通信的话,似乎客户端不关闭socket,输入流就一直不到头,所以得用available()得到可读字节数,从而进行消息过滤
2、客户端
布局的话,就是上面一个EditText输入消息,下面一个Button发送,消息,布局文件略过
连接服务器、读取服务器消息放在onStart()里面,同样要新建一个线程
@Override protected void onStart() { super.onStart(); if (!isConnected) { final Intent intent = new Intent(this, PeopleService.class); intent.setAction("com.example.songzeceng"); bindService(intent, connection, BIND_AUTO_CREATE); new Thread(new Runnable() { @Override public void run() { // 等待1s,以便服务端创建socket try { Thread.sleep(1000); server = new Socket("localhost", 12345); inputStream = new BufferedInputStream(server.getInputStream()); while (!isOver) { // 只要不结束,这个线程一直运行 while (inputStream.available() <= 0); // 过滤空消息 byte[] bytes = new byte[1024]; int len; StringBuffer stringBuffer = new StringBuffer(); while (inputStream.available() > 0 && (len = inputStream.read(bytes)) != -1) { stringBuffer.append(new String(bytes, 0, len)); } String fromServer = stringBuffer.toString(); System.out.println(fromServer); isOver = fromServer.equals("over"); } System.out.println("over.."); inputStream.close(); outputStream.close(); } catch (InterruptedException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }).start(); } }
在发送按钮的监听里面,发送信息,当然也是一个新线程,在onCreate()里面
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); et_input = (EditText) findViewById(R.id.client_input); findViewById(R.id.client_send).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { new Thread(new Runnable() { @Override public void run() { try { outputStream = new BufferedOutputStream(server.getOutputStream()); String toServer = et_input.getText().toString(); outputStream.write(toServer.getBytes()); outputStream.flush(); } catch (IOException e) { e.printStackTrace(); } } }).start(); } }); }
运行结果
服务端
客户端
结语
Socket通信应该是安卓IPC里仅有的不用Binder的方式了(忽略共享文件),而java的TCP/UDP比C的要简洁一些