本文首发于微信公众号「后厂技术官」
1.Socket简介
Socket也称作“套接字“,是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。它分为流式套接字和数据包套接字,分别对应网络传输控制层的TCP和UDP协议。TCP协议是一种面向连接的、可靠的、基于字节流的传输层通信协议。它使用三次握手协议建立连接,并且提供了超时重传机制,具有很高的稳定性。UDP协议则是是一种无连接的协议,且不对传送数据包进行可靠性保证,适合于一次传输少量数据,UDP传输的可靠性由应用层负责。在网络质量令人十分不满意的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多。
从上图我们也可以看出,不同的用户进程通过Socket来进行通信,所以Socket也是一种IPC方式,接下来我们用TCP服务来实现一个简单的聊天程序。
2.实现聊天程序服务端
配置
首先我们来实现服务端,当然要使用Socket我们需要在AndroidManifest.xml声明如下的权限:
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
我们需要实现一个远程的Service来当作聊天程序的服务端,AndroidManifest.xml文件中配置service:
<service android:name=".SocketServerService" android:process=":remote" />
|
实现Service
接下来我们在Service启动时,在线程中建立TCP服务,我们监听的是8688端口,等待客户端连接,当客户端连接时就会生成Socket。通过每次创建的Socket就可以和不同的客户端通信了。当客户端断开连接时,服务端也会关闭Socket并结束结束通话线程。服务端首先会向客户端发送一条消息:“您好,我是服务端”,并接收客户端发来的消息,将收到的消息进行加工再返回给客户端。
package com.example.liuwangshu.moonsocket; import android.app.Service; import android.content.Intent; import android.os.IBinder; import android.text.TextUtils; import android.util.Log; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; public class SocketServerService extends Service { private boolean isServiceDestroyed = false; @Override public void onCreate() { new Thread(new TcpServer()).start(); super.onCreate(); } @Override public IBinder onBind(Intent intent) { throw new UnsupportedOperationException("Not yet implemented"); } private class TcpServer implements Runnable { @Override public void run() { ServerSocket serverSocket; try { serverSocket = new ServerSocket(8688); } catch (IOException e) {
return; } while (!isServiceDestroyed) { try { final Socket client = serverSocket.accept(); new Thread() { @Override public void run() { try { responseClient(client); } catch (IOException e) { e.printStackTrace(); } } }.start(); } catch (IOException e) { e.printStackTrace(); } } } } private void responseClient(Socket client) throws IOException { BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream())); PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())), true); out.println("您好,我是服务端"); while (!isServiceDestroyed) { String str = in.readLine(); Log.i("moon", "收到客户端发来的信息" + str); if (TextUtils.isEmpty(str)) { Log.i("moon", "客户端断开连接"); break; } String message = "收到了客户端的信息为:" + str; out.println(message); } out.close(); in.close(); client.close(); } @Override public void onDestroy() { isServiceDestroyed = true; super.onDestroy(); } }
|
3.实现聊天程序客户端
客户端Activity会在onCreate方法中启动服务端,并开启线程连接服务端Socket。为了确保能连接成功,采用了超时重连的策略,每次连接失败时都会重新建立连接。连接成功后,客户端会收到服务端发送的消息:“您好,我是服务端”,我们也可以在EditText输入字符并发送到服务端。
package com.example.liuwangshu.moonsocket; import android.content.Intent; import android.os.SystemClock; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; public class SocketClientActivity extends AppCompatActivity { private Button bt_send; private EditText et_receive; private Socket mClientSocket; private PrintWriter mPrintWriter; private TextView tv_message; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_socket); initView(); Intent service = new Intent(this, SocketServerService.class); startService(service); new Thread() { @Override public void run() { connectSocketServer(); } }.start();
} private void initView() { et_receive= (EditText) findViewById(R.id.et_receive); bt_send= (Button) findViewById(R.id.bt_send); tv_message= (TextView) this.findViewById(R.id.tv_message); bt_send.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final String msg = et_receive.getText().toString(); if(!TextUtils.isEmpty(msg)&&null!=mPrintWriter) { mPrintWriter.println(msg); tv_message.setText(tv_message.getText() + "\n" + "客户端:" + msg); et_receive.setText(""); } } }); } private void connectSocketServer() { Socket socket = null; while (socket == null) { try { socket = new Socket("localhost", 8688); mClientSocket = socket; mPrintWriter = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true); } catch (IOException e) { SystemClock.sleep(1000); } } try { BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream())); while (!isFinishing()) { final String msg = br.readLine(); if (msg != null) { runOnUiThread(new Runnable() { @Override public void run() { tv_message.setText(tv_message.getText() + "\n" + "服务端:" + msg); } } ); } } mPrintWriter.close(); br.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); } }
}
|
布局很简单(activity_socket.xml):
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/tv_message" android:layout_width="match_parent" android:layout_height="400dp" />
<LinearLayout android:layout_width="match_parent" android:layout_height="50dp" android:layout_alignParentBottom="true" android:orientation="horizontal"> <EditText android:id="@+id/et_receive" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="2" /> <Button android:id="@+id/bt_send" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:text="向服务器发消息" /> </LinearLayout> </RelativeLayout>
|
4.运行聊天程序
运行程序,我们可以看到客户端和服务端是两个进程:
客户端首先会收到服务端的信息:”您好,我是服务端”,接下来我们向服务端发送“我想要怒放的生命”。这时候服务端收到了这条信息并返回给客户端加工后的这条信息:
github源码下载