背景
这几天在学习安卓的进程间通信,而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的要简洁一些