IPC 之 Socket 的使用
一、概述
我们知道在开发中,即时通讯、设备间的通信都是使用 Socket 实现,那当然用它来实现进程间通信更是不成问题。Socket 即套接字,是一个对 TCP / IP协议进行封装 的编程调用接口(API) 。通过Socket
,我们才能在 Andorid 平台上通过 TCP/IP
协议进行开发。Socket
不是一种协议,而是一个编程调用接口(API
),属于传输层(主要解决数据如何在网络中传输)。
Socket
的使用类型主要有两种:
- 流套接字(
streamsocket
) :基于TCP
协议,采用 流的方式 提供可靠的字节流服务 - 数据报套接字(
datagramsocket
):基于UDP
协议,采用 数据报文 提供数据打包发送的服务
对于 TCP 和 UDP 的讲述,这里就不做说明。
二、Socket 实现 IPC
IPC 中两个重要角色: 客户端和服务端,示例工程仍旧是两个工程。我们先看服务端如何使用 Socket :
1. 服务端
服务端 Service 完整代码:
public class SocketService extends Service { private String[] mDefinedMsg = new String[]{"你好啊,哈哈", "请问你叫什么名字啊?", "今天北京天气不错啊", "你知道吗,我可以和多个人同时聊天", "给你讲个笑话吧,爱笑的人运气不会太差。"}; int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); int KEEP_ALIVE_TIME = 1; TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>(); ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES * 2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, taskQueue); private boolean isServiceDestroy = false; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onCreate() { super.onCreate(); executorService.execute(new TcpServer()); } @Override public void onDestroy() { super.onDestroy(); isServiceDestroy = true; } private class TcpServer implements Runnable { @Override public void run() { ServerSocket serverSocket = null; try { //接听本地8688接口 serverSocket = new ServerSocket(8688); } catch (IOException e) { e.printStackTrace(); } while (!isServiceDestroy && serverSocket != null) { //接受客户端请求 try { Socket client = serverSocket.accept(); executorService.execute(new TalkWithClient(client)); } catch (IOException e) { e.printStackTrace(); } } } } private class TalkWithClient implements Runnable { private Socket socket; TalkWithClient(Socket socket) { this.socket = socket; } @Override public void run() { try { //用于接收客户端信息 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); //用于向客户端发送信息 PrintWriter out = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); out.println("欢迎来到聊天室"); while (!isServiceDestroy) { String info = in.readLine(); if (info == null) { //客户端断开连接 break; } Log.i("CLIENT_INFO", info); int i = new Random().nextInt(mDefinedMsg.length); String msg = mDefinedMsg[i]; out.println(msg); Log.i("Server_INFO", msg); } out.close(); in.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
在服务端通过循环的方式轮询是否有客户端接入,并判断服务一旦停止,停止循环。 accpet() 方法在没有客户端接入时,会阻塞当前线程,等到有客户端接入时会继续执行。
while (!isServiceDestroy && serverSocket != null) { //接受客户端请求 try { Socket client = serverSocket.accept(); executorService.execute(new TalkWithClient(client)); } catch (IOException e) { e.printStackTrace(); } }
TalkWithClient 是一个 Runnable 实现类,用来与客户端进行交互。通过循环的方式读取客户端传递过来的信息,代码中 readLine() 方法也会阻塞,直到客户端有消息过来继续执行。
while (!isServiceDestroy) { String info = in.readLine(); if (info == null) { //客户端断开连接 break; } Log.i("CLIENT_INFO", info); int i = new Random().nextInt(mDefinedMsg.length); String msg = mDefinedMsg[i]; out.println(msg); Log.i("Server_INFO", msg); }
2. 客户端
客户端完整代码:
public class MainActivity extends AppCompatActivity { private static final int MESSAGE_SOCKET_CONNECTED = 1; private static final int MESSAGE_RECEIVE_NEW_MSG = 2; int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors(); int KEEP_ALIVE_TIME = 1; TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS; BlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<Runnable>(); ExecutorService executorService = new ThreadPoolExecutor(NUMBER_OF_CORES, NUMBER_OF_CORES * 2, KEEP_ALIVE_TIME, KEEP_ALIVE_TIME_UNIT, taskQueue); private Socket mClientSocket; private PrintWriter mPrintWriter; @SuppressLint("HandlerLeak") private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_SOCKET_CONNECTED: btnSend.setEnabled(true); break; case MESSAGE_RECEIVE_NEW_MSG: msgContainer.setText(String.format("%s%s", msgContainer.getText(), msg.obj)); break; } } }; private TextView msgContainer; private EditText etMsg; private TextView btnSend; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); msgContainer = findViewById(R.id.msg_container); etMsg = findViewById(R.id.msg); btnSend = findViewById(R.id.btn_send); btnSend.setEnabled(false); executorService.execute(new Runnable() { @Override public void run() { connectTCPServer(); } }); } @Override protected void onDestroy() { if (mClientSocket != null) { try { mClientSocket.shutdownInput(); mClientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } super.onDestroy(); } public void sendMsg(View view) { final String msg = etMsg.getText().toString(); if (!TextUtils.isEmpty(msg) && mPrintWriter != null) { executorService.execute(new Runnable() { @Override public void run() { mPrintWriter.println(msg); } }); etMsg.setText(""); String time = formatTimes(System.currentTimeMillis()); String showMsg = "client" + time + ":" + msg + "\n"; msgContainer.setText(String.format("%s%s", msgContainer.getText(), showMsg)); } } private void connectTCPServer() { Socket socket = null; while (socket == null) { try { socket = new Socket("localhost", 8688); mClientSocket = socket; mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mClientSocket.getOutputStream())), true); mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED); } catch (IOException e) { SystemClock.sleep(1000); //retry } } //接收服务器消息 try { BufferedReader br = new BufferedReader(new InputStreamReader(mClientSocket.getInputStream())); while (!MainActivity.this.isFinishing()) { String msg = br.readLine(); Log.i("CLIENT_RECEIVE", msg); if (msg != null) { String time = formatTimes(System.currentTimeMillis()); String showMsg = "server" + time + ":" + msg + "\n"; mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showMsg).sendToTarget(); } } mPrintWriter.close(); br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } } @SuppressLint("SimpleDateFormat") private String formatTimes(long millis) { return "(" + new SimpleDateFormat("HH:mm:ss").format(new Date(millis)) + ")"; } }
连接服务端:
connectTCPServer() 方法是用来连接服务端以及接收服务器消息,接收服务器消息仍然通过循环方式轮询。
注意:
Socket 实现 IPC 是网络操作,所以一定记得声明权限:
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
最终能够进行传输数据,这里数据可以是任意数据,示例中只是传递了字符串。效果:
示例工程代码:
链接: https://pan.baidu.com/s/1Dq710Z4-vVsrNvgGUky09w 密码: 52fv