前言
TCP
通讯是一个比较重要也是一种比较稳定的通讯方式,在Java
项目上我们可以比较快捷的实现一个TCP
通讯客户端,但是基于Android和java还是有些区别的,故而tcp通讯在Android端的实现右许多需要注意的地方。这节就让我们来学习下TCP
客户端在Android上的实现吧。
今天涉及的内容:
- tcp通讯基础简介
1.1 关于ip
1.2 关于端口 - tcp客户端的封装类TcpClient简介
- TcpClient在MainActivity中的使用
- 封装类TcpClient涉及到的几个其他类介绍
- tcp通讯客户端使用注意事项及问题排查参考
5.1 通讯权限
5.2 问题排查参考 - 效果图和项目结构图
- tcp客户端的封装类TcpClient源码
先来波效果图
一.tcp通讯基础简介
TCP是一种比较常见和基础的通讯方式,关于比较细的东西这里就不做详细介绍。这里只提及几个比较重要的,容易被忽略的点。
1.1 关于ip
tcp通讯时的客户端socket
在与服务端建立连接时,会涉及到ip
和端口
,这里的ip
要设置的是服务端ip
,而不是客户端设备的ip
.
1.2 关于端口
端口(port),端口范围是0-65535
,一般端口会取该范围内较大的数值。这样可以大概率避免端口使用冲突。
二.tcp客户端的封装类TcpClient简介
为了让tcp客户端socket连接在android
上使用更加方便,我封装了一个工具类——TcpClient
,下面介绍下TcpClient
的几个主要方法:
/**设置连接超时时间**/
public TcpClient setConnectTimeOut(int timeOut)
/**
* 设置建立连接的参数
* @param ip 如: "192.168.1.1"
* @param port 范围在 0-65535 之间,一般取比较大的值
*/
public TcpClient setSocket(String ip,int port)
/**
* 发送消息
* @param message 字符串消息
* @param charsetName 字符集
*/
public void sendMessage(String message,String charsetName)
/**
* 接收消息
*
* @param charsetName 字符集
* @return
*/
public String receiveMessage(String charsetName)
/**关闭连接**/
public void close()
其在Android
中的使用流程大致如下:
//声明
private TcpClient mTcpClient;
//初始化并设置连接参数
mTcpClient=new TcpClient();
mTcpClient.setConnectTimeOut(5000)//设置连接超时
.setSocket("192.168.50.152",12345);//设置连接ip和端口
//点击按钮时发送消息并接收返回数据
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1://测试
test();
break;
default:
break;
}
}
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
String message="我是客户端a";
message= SocketHelper.getMessageByReadLine(message);
mTcpClient.sendMessage(message, SocketConfig.UTF_8);
String result = mTcpClient.receiveMessage(SocketConfig.UTF_8);
runOnUiThread(new Runnable() {
@Override
public void run() {
LogUtil.i("=======我来了=====");
mTextView.setText(result);
}
});
LogUtil.i("=====客户端收到结果===result=" + result);
}
}).start();
}
三.TcpClient在MainActivity中的使用
下面贴出TcpClient
在MainActivity
中使用代码:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final int PERMISSION_CODE=1234;
private TextView mTextView;
private Button mButton1;
private TcpClient mTcpClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化控件
initView();
//设置监听
setListener();
//申请权限
requestPermission(MainActivity.PERMISSION_CODE);
}
private void requestPermission(int requestCode) {
String permissions[] = {
Manifest.permission.CAMERA,
Manifest.permission.INTERNET,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE};
PermissionHelper.getInstance().checkPermissions(permissions, requestCode, MainActivity.this);
}
@PermissionSuccess(requestCode = MainActivity.PERMISSION_CODE)
public void requestSuccess() {
//申请到权限后的处理
//......
LogUtil.i("=====权限申请成功======");
}
@PermissionFail(requestCode = MainActivity.PERMISSION_CODE)
public void requestFail() {
//未获取到权限的处理
//......
LogUtil.i("=====权限申请失败======");
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
PermissionHelper.getInstance().onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void initView(){
mTextView=findViewById(R.id.tv);
mButton1=findViewById(R.id.btn1);
mTcpClient=new TcpClient();
mTcpClient.setConnectTimeOut(5000)//设置连接超时
.setSocket("192.168.50.152",12345);//设置连接ip和端口
}
private void setListener(){
mButton1.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn1://测试
test();
break;
default:
break;
}
}
private void test() {
new Thread(new Runnable() {
@Override
public void run() {
String message="我是客户端a";
message= SocketHelper.getMessageByReadLine(message);
mTcpClient.sendMessage(message, SocketConfig.UTF_8);
String result = mTcpClient.receiveMessage(SocketConfig.UTF_8);
runOnUiThread(new Runnable() {
@Override
public void run() {
LogUtil.i("=======我来了=====");
mTextView.setText(result);
}
});
LogUtil.i("=====客户端收到结果===result=" + result);
}
}).start();
}
}
四.封装类TcpClient涉及到的几个其他类介绍
封装类TcpClient
使用过程中会涉及到以下两个类:
- SocketConfig : 主要提供通讯的常用配置和常量等
- SocketHelper:通讯帮助类,用于提供通讯所需的一些常用方法
下面贴出SocketConfig
类代码:
/**
* Title:socket通讯配置类
* description:
* autor:pei
* created on 2020/5/6
*/
public class SocketConfig {
//字符集
public static final String UTF_8="UTF-8";
public static final String GBK="GBK";
}
SocketHelper
代码如下:
/**
* Title:socket帮助类
* description:
* autor:pei
* created on 2020/5/7
*/
public class SocketHelper {
/**
* 消息转换
*
* 注:当接收消息的方法是以 (result = bufferedReader.readLine()) != null 做判断读取stream的时候,
* 则发送的消息要以 "\n" 结尾
*
* @param message
* @return
*/
public static String getMessageByReadLine(String message){
message=message+"\n";
return message;
}
}
五.tcp通讯客户端使用注意事项及问题排查参考
5.1 通讯权限
tcp在Android
上的使用仍然需要注意网络权限问题。
你需要在AndroidManifast.xml
中加入以下网络权限:
<uses-permission android:name="android.permission.INTERNET" />
如有必要,你还需要在你Android项目中加入Android 6.0+
手动权限。这里就不细讲了。
5.2 问题排查参考
- 写的tcp客户端socket代码无法运行问题。在
android
中tcp客户端socket发送消息的逻辑只能放到线程中执行(如点击按钮开启一条线程,然后在线程中执行tcp通讯逻辑),不能放到主线程中执行tcp通讯逻辑。 - 在tcp客户端socket与服务端建立连接的时候,经常会出现以下几种错误:
1.客户端socket连接超时
2.客户端socket结束收据时出现socket对象为空
3.socket已经关闭
鉴于各种socket连接失败的情况,我们可以按照以下几条来排查问题:
1. 服务端未开启或者服务端停止
2. 服务端客户端ip地址不一致
3. 服务端客户端port端口不一致
4. 服务端客户端ip与port均一致,但是网络不在一个网段
- 当socket建立ok后,可能还会出现客户端
socket
接收不到数据的问题。这里需要注意的是TcpClient
的接收消息的方法receiveMessage(String charsetName)
中是以(result = bufferedReader.readLine()) != null
做判断读取stream
的,所以服务端向TcpClient
发送消息时,需要在结尾加上\n
,这样TcpClient
的receiveMessage(String charsetName)
方法才能将传过来的数据接收完整。 - 接收数据乱码问题。
TcpClient
的发送和接收方法中均有一个字符集参数,为了保证数据不乱码,需要客户端和服务端使用相同的字符编码集。
六.效果图和项目结构图
由于篇幅原因,这里就只介绍tcp客户端
,然后也张贴tcp服务端
效果。下节我们再讲tcp服务端
。下面贴出效果图
七.tcp客户端的封装类TcpClient源码
下面贴出tcp客户端的封装类TcpClient
源码: