权限声明
使用Socket通信需要在AndroidManifest中声明以下权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
实现逻辑
其次是不能在主线程中访问网络,因为网络操作很可能是耗时的,从而抛出异常(android.os.NetworkOnMainThreadException)。
因此创建socket连接,发送/接收消息都需要在线程中进行,消息UI更新可以使用handle实现。
聊天室实现
在远程Service建立一个TCP服务,在主界面中连接TCP服务,连接上之后服务端与多个客户端响应。
服务端的代码
通过判断服务端输入流的返回值,当客户端断开连接后,服务端的输入流会返回null,这时就知道客户端已经退出。
public class TCPServerService extends Service {
public final static String TAG = TCPServerService.class.getSimpleName();
private boolean mIsServiceDestroyed = false; // 用来标识服务是否停止向客户端发送消息
private String[] definedMsg = MsgConstants.DEFINED_MSG_LIST;
public TCPServerService() {
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return null;
}
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Override
public void onDestroy() {
// 当服务被销毁时,停止向客户端发送消息
mIsServiceDestroyed = true;
super.onDestroy();
}
private class TcpServer implements Runnable {
@Override
public void run() {
ServerSocket mClientSocket = null;
try {
mClientSocket = new ServerSocket(8688); // 建立服务端
Log.d(TAG, "start server,listener to 8688 port");
} catch (IOException e) {
e.printStackTrace();
}
while (!mIsServiceDestroyed) {
try {
final Socket client = mClientSocket.accept(); // 等待接收请求,创建socket连接
Log.d(TAG, "waiting client connect 8688 port");
new Thread(new Runnable() {
@Override
public void run() {
responseClient(client); // 响应客户端
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) {
BufferedReader reader = null;
PrintWriter writer = null;
try {
// reader接收客户端发送的消息
reader = new BufferedReader(
new InputStreamReader(client.getInputStream()));
// writer向客户端发送消息
writer = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(client.getOutputStream())), true);
// 当服务未被销毁,就持续响应客户端
while (!mIsServiceDestroyed) {
String msg = reader.readLine();
if (msg == null) {
break;
}
if (!TextUtils.isEmpty(msg)) {
writer.println("service received - " + msg + "\n");
Log.d(TAG, "service received - " + msg);
}
int i = new Random().nextInt(definedMsg.length);
writer.println("service send - " + definedMsg[i] + "\n");
Log.d(TAG, "service send - " + definedMsg[i]);
}
writer.println("client quit." + "\n");
Log.d(TAG, "client quit.");
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (client != null) {
client.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客户端示例代码
public class TCPClientActivity extends AppCompatActivity implements View.OnClickListener {
public static final String TAG = TCPClientActivity.class.getSimpleName();
private Button mSendBtn;
private TextView mMsgTextView;
private EditText mMsgEditText;
private Socket mClientSocket = null;
private PrintWriter mPrintWriter; // 客户端发送消息
private BufferedReader mReader; // 客户端接收消息
// 客户端发送或接收的消息在handleMessage中更新UI
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MsgConstants.SOCKET_CONNECT:
mSendBtn.setEnabled(true);
break;
case MsgConstants.SOCKET_RECEIVE_MSG:
mMsgTextView.setText(mMsgTextView.getText() + "\n" + (String) msg.obj);
break;
default:
super.handleMessage(msg);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tcpclient);
mSendBtn = (Button) findViewById(R.id.send_btn);
mMsgEditText = (EditText) findViewById(R.id.edit_msg);
mMsgTextView = (TextView) findViewById(R.id.text_msg);
Intent service = new Intent(this, TCPServerService.class);
startService(service);
new Thread(new Runnable() {
@Override
public void run() {
connectTCPServer();
}
}).start();
mSendBtn.setOnClickListener(TCPClientActivity.this);
}
private void connectTCPServer() {
while (mClientSocket == null) {
try {
// 建立socket连接
mClientSocket = new Socket("127.0.0.1", 8688);
mPrintWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(mClientSocket.getOutputStream())), true);
handler.sendEmptyMessage(MsgConstants.SOCKET_CONNECT);
Log.d(TAG, "connect tcp server success");
} catch (IOException e) {
/**
采用超时重连的策略,每次连接失败后都会重新建立连接。
为降低重试机制的开销,加入休眠机制,每次重试的时间间隔为1000ms。
*/
SystemClock.sleep(1000);
Log.d(TAG, "connect tcp server failed,retry....");
}
}
receiveMsg();
}
private void receiveMsg() {
try {
mReader = new BufferedReader(
new InputStreamReader(mClientSocket.getInputStream()));
while (!TCPClientActivity.this.isFinishing()) {
String msg = mReader.readLine();
if (msg != null && !TextUtils.isEmpty(msg)) {
Log.d(TAG, "client received - " + msg);
String time = formatTime(System.currentTimeMillis());
String showedMsg = "client received " + time + " - " + msg + "\n";
handler.obtainMessage(MsgConstants.SOCKET_RECEIVE_MSG, showedMsg).sendToTarget();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onClick(View view) {
if (view == mSendBtn) {
final String editMsg = mMsgEditText.getText().toString();
if (!TextUtils.isEmpty(editMsg) && mPrintWriter != null) {
mMsgEditText.setText("");
String time = formatTime(System.currentTimeMillis());
final String showedMsg = "client send " + time + " - " + editMsg + "\n";
mMsgTextView.setText(mMsgTextView.getText() + showedMsg);
sendMsg(showedMsg);
Log.d(TAG, "client send - " + editMsg);
}
}
}
private void sendMsg(final String showedMsg) {
new Thread(new Runnable() {
@Override
public void run() {
mPrintWriter.println(showedMsg);
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
if (mPrintWriter != null) {
mPrintWriter.close();
}
if (mReader != null) {
mReader.close();
}
if (mClientSocket != null) {
mClientSocket.shutdownInput();
mClientSocket.shutdownOutput();
mClientSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
private String formatTime(long time) {
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
}