大坑
最近有个项目需要用到TCP Socket通讯,遇到了一个大坑,所以做了这个Demo。
读取socket输入流的时候很多代码都会这么写,一般也不会有什么问题,但是readLine()方法读取不到换行和回车时会阻塞!
String line = null;
while ((line = br.readLine()) != null) {
}
readLine()是个阻塞函数,可以点进去查看源码,只有遇到换行或者回车回车符(’\n’,’\r’ 对应十六进制是 0D 0A)才会返回,而有些模块返回给你的数据不带这两个结束符,所以导致阻塞,程序不能走下去。这种情况一般如果模块那边愿意修改加上结束符,那就没问题。如果模块那边又不方便改,那就不能用这种方式了,得换种写法。
调试工具
调试的实时我们可以借助一些PC端的工具,TCP调试助手、TCP测试工具类似的,度娘下很多,操作也是傻瓜式的,填写IP和端口然后建立或者连接。
Demo演示及实现
废话少说,直接 进入今天的主题,下面就看看如何在Android上利用TCP/IP协议使用Socket与Server进行通讯吧!
一、权限
<uses-permission android:name="android.permission.INTERNET"/>
二、服务端实现,简单来说就3步
1、 创建ServerSocket ,监听端口,等待客户端Socket连接
2、从Socket里获取的客户端发来的消息
3、通过Socket给客户端发消息
核心代码如下
class ServerSocketThread extends Thread {
@Override
public void run() {
try {
// 创建ServerSocket
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("--开启服务器,监听端口 9999--");
// 监听端口,等待客户端连接
while (true) {
System.out.println("--等待客户端连接--");
Socket socket = serverSocket.accept(); //等待客户端连接
System.out.println("得到客户端连接:" + socket);
startReader(socket);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 从参数的Socket里获取消息
*/
private static void startReader(final Socket mSocket) {
new Thread(){
@Override
public void run() {
try {
// 获取读取流
BufferedReader in = new BufferedReader(new InputStreamReader(mSocket.getInputStream(),"utf-8"))
String line="";
while ((line = in.readLine()) != null) {// 读取数据
System.out.println("*等待客户端输入*");
System.out.println("获取到客户端的信息:" + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
//通过socket来给客户端发送消息
private void serverSendMessage(String mServerSendMessage) {
new Thread() {
@Override
public void run() {
PrintWriter out;
try {
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mSocket.getOutputStream())), true);
out.println(mServerSendMessage);
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
}
三、客户端实现,跟服务器端流程类似
1、与服务器建立Socket连接
2、获取到输入输出流
3、收/发送消息
//与服务器建立连接
Socket mClientSocket = new Socket(mClientIp, mClientPort);
//从socket获取输入输出流
BufferedReader mClientIn = new BufferedReader(new InputStreamReader(mClientSocket.getInputStream()));
PrintWriter mClientOut = new PrintWriter(new BufferedWriter(new OutputStreamWriter(mClientSocket.getOutputStream())), true);
//监听服务端下发的消息
String clientReceiverMessage;
if ((clientReceiverMessage = mClientIn.readLine()) != null) {
if (mOnConnectListener != null) {
mOnConnectListener.onMessage(clientReceiverMessage);
}
}
//给服务端发消息
mClientOut.println(mClientSendMessage);
代码封装Listener接口
在实际开发过程中,我们一般会把Socket这块封装起来,然后对外设置Listener接口,在其它模块里处理业务逻辑,也有的需要处理多个socket连接和通讯,
服务器端代码实现示例
public class TcpServer {
private static final String TAG = "TcpServer";
private static TcpServer mTcpServer = new TcpServer();
private OnListener mOnListener;
private ServerSocketThread mServerSocketThread;
public static TcpServer getInstance() {
return mTcpServer;
}
public void setOnListener(OnListener onListener) {
mOnListener = onListener;
}
public interface OnListener {
void onStart();
void onNewClient(String serverIp, String clientIp, int count);
void onError(Throwable e, String message);
void onMessage(String ip, String message);
void onAutoReplyMessage(String ip, String message);
void onClientDisConnect(String ip);
void onConnectTimeOut(String ip);
}
public void setPort(int port) {
mServerPort = port;
}
………………………………
………………………………
………………………………
}
十六进制收发处理
对接硬件的项目通讯协议有可能是用十六进制的,所以数据要在字符串\byte\int 十六进制转换,不过通讯部分本质一样,代码如下
//十六进制 发送
try {
//byte[] msg = {(byte) 0xaa,(byte) 0xbb,(byte) 0x0d,(byte) 0x0a};
byte[] msg = IntByteStringHexUtil.hexStrToByteArray("AABB0D0A");
OutputStream socketWriter = socket.getOutputStream();
socketWriter.write(msg);
} catch (IOException e) {
e.printStackTrace();
}
//十六进制 接收
InputStream socketReader = socket.getInputStream();
byte buf[] = new byte[1024];
int len = socketReader.read(buf);
byte data[] = new byte[len];
System.arraycopy(buf, 0, data, 0, len);
logClient(len+","+IntByteStringHexUtil.byteArrayToHexStr(data));
Demo源码下载地址
源码也都放在CSDN上了,有需要的可以借鉴