IP协议属于网络层,TCP、UDP协议属于传输层。
IP协议是TCP/IP协议族的动力,它为上层协议提供无状态、无连接、不可靠的服务。
TCP协议是面向连接的传输层协议,提供一种面向连接的、可靠的字节流服务。
UDP协议是面向无连接的传输层协议,提供面向事务的简单不可靠信息传输服务。
数据报文
在不同层传输的数据单位名称不同,在网络层传输的叫数据报,在传输层传输的叫报文段。
IP数据报

各个字段的详细说明:
名称 | 长度 | 说明 |
---|---|---|
版本 | 4bit | IP协议的版本,目前的IP协议版本号为4,下一代IP协议版本号为6 |
首部长度 | 4bit | IP报头的长度,最大长度60字节(15*4),分为固定部分的长度(20字节)和可变部分的长度 |
服务类型 | 8bit | Type Of Service |
总长度 | 16bit | IP报文的总长度。数据报的最大长度为 65535 字节 |
标识 | 16bit | 它是一个计数器,用来产生数据报的标识,当IP报文长度超过传输网络的MTU(最大传输单元)时必须分片,此标识表示同一个数据报的分片。 |
标志 | 3bit | R、DF、MF三位,目前只有后两位有效。DF位:为1表示不分片,为0表示分片。MF:为1表示“更多的片”,为0表示这是最后一片。 |
片偏移 | 13bit | 本分片在原先数据报文中相对首位的偏移位。片偏移以8个字节为偏移单位。 |
生存时间 | 8bit | TTL (Time To Live)表示数据报在网络中的寿命,其单位为秒。在目前的实际应用中,常以“跳”为单位。 |
协议 | 8bit | 指出IP报文携带的数据使用的哪种协议,以便目的主机的IP层能知道要将数据报上交到哪个进程。TCP的协议号为6,UDP的协议号为17。ICMP的协议号为1,IGMP的协议号为2. |
首部校验和 | 16bit | 计算IP头部的校验和,检查IP报头的完整性。 |
源地址 | 32bit | 标识IP数据报的源端设备。 |
目的地址 | 32bit | 标识IP数据报的目的地址。 |
可选字段 | 长度可变 | 1~40 字节,用于增加IP数据报的控制功能。 |
填充 | 保证IP首部长度是4字节的整倍数 |
TCP报文

名称 | 长度 | 说明 |
---|---|---|
源端口 | 16bit | 数据发送方的端口号 |
目的端口 | 16bit | 数据接受方的端口号 |
序号 | 32bit | 本数据报文中的的第一个字节的序号 |
(在数据流中每个字节都对应一个序号) | ||
确认号 | 32bit | 希望收到的下一个数据报文中的第一个字节的序号 |
数据偏移 | 4bit | 表示本报文数据段距离报文段有多远 |
保留字段 | 6bit | 保留为今后使用,但目前应置为0 |
紧急比特URG | 当值为1时表示次报文段中有需要紧急处理 | |
确认比特ACK | 值为1时确认号有效,值为0时确认号无效 | |
复位比特RST | 值为1时表示TCP连接存在严重的错误,需要重新进行连接 | |
同步比特SYN | 值为1表示这是一个连接请求或连接接受报文 | |
终止比特FIN | 值为1表示要发送的数据报已经发送完毕,需要释放传送连接 | |
窗口 | 16bit | TCP连接的一端根据缓存空间的大小来确定自己接受窗口的大小 |
限制发送放的窗口上限 | ||
检验和 | 16bit | 用来检验首部和数据两部分的正确性 |
紧急指针字段 | 16bit | 紧急指针指出在本报文段中的紧急数据的最后一个字节的序号 |
选项字段 | 长度可变 | TCP 首部可以有多达40字节的可选信息, |
用于把附加信息传递给终点,或用来对齐其它选项 |
UDP报文
相对于TCP报文,UDP报文简单了很多。

名称 | 长度 | 说明 |
---|---|---|
源端口 | 16bit | 数据发送方的端口号 |
目的端口 | 16bit | 数据接受方的端口号 |
包长度 | 16bit | UDP首部的长度和数据的长度之和。单位为字节 |
校验和 | 16bit | 用来检验首部和数据两部分的正确性 |
TCP三次握手和四次挥手
[图片上传中...(image-f38b3b-1624411076651-0)]
三次握手
三次握手的目的是同步连接双方的序列号和确认号并交换TCP窗口大小的信息。
握手过程:
- 第一次握手:建立连接,客户端先发送连接请求报文,将SYN设置为1,Sequence Number为x。客户端进入SYN+SEND状态,等待服务器确认。
- 第二次握手:服务器收到SYN报文。服务器收到客户端的SYN报文,需要对这个SYN报文进行确认,设置Acknowledgment Number为x+1(Sequence+1);同时,自己还要送法SYN消息,将SYN位置为1,Sequence Number为y;服务器将上述所有信息放到一个报文段(即SYN+ACK报文段)中,一并发送给客户端,此时服务器进入SYN+RECV状态。
- 第三次握手:客户端收到服务器的 SYN+ACK报文段。然后将Acknowlegment Number设为y+1,向服务器发送ACK报文段,这个报文段发送完毕后,客户端端服务器都进入ESTABLISHED状态,完成TCP三次握手。
完成了三次握手,客户端和服务器就可以开始传送数据了。
四次挥手
当客户端和服务端传输数据完毕后,需要断开TCP连接。TCP断开的过程,就是四次挥手。
- 第一次挥手:客户端(也可以是服务器),设置Sequence Number和Acknowledgment Number,向服务器发送一个FIN报文段。此时客户端进入FIN_WAIT_1状态;这表示客户端没有数据发送给主机了。
- 第二次挥手:服务器收到客户端发来的FIN报文段,向客户端回一个ACK报文段,Acknowledgement Number为Sequence Number加1;客户端进入FIN_WAIT_2状态,服务器进入CLOSE_WAIT状态;服务器告诉客户端,我同意你的"关闭"请求。
- 第三次挥手:服务器向客户端发送FIN报文段,请求关闭连接,同时服务器进入LAST_ACK状态。
- 第四次挥手:客户端收到服务器发送的FIN报文段,向主机发送ACK报文段,然后客户端进入TIME_WAIT状态,服务器收到客户端的ACK报文段以后,就关闭连接,此时,客户端等待2MSL后一次没有到收到回复,则证明服务端已正常关闭,那好,客户端也可以关闭连接了。
TCP三次握手的必要性
防止服务器端因接收了早已失效的连接请求报文,从而一直等待客户端请求,最终导致形成死锁、浪费资源。
TCP四次挥手的必要性
为了保证通信双方都能通知对方,需释放、断开连接。
为什么客户端关闭连接前要等待2MSL时间
MSL: 最大报文段生存时间
四个报文发送完毕后,就可以直接进入CLOSE状态了,但是有可能网络是不可靠的,一切都可能发生,比如有可能最后一个ACK丢失。所以TIME_WAIT状态是用来重发可能丢失的ACK报文。展开具体来讲:
- 为了保证客户端发送的最后1个连接释放确认报文 能到达服务器,从而使得服务器能正常释放连接。
- 防止早已失效的连接请求报文,出现在本连接中。客户端发送了最后1个连接释放请求确认报文后,再经过2MSL时间,则可使本连接持续时间内所产生的所有报文段都从网络中消失。
TCP、UDP比较
TCP | UDP | |
---|---|---|
可靠性 | 可靠 | 不可靠 |
连接性 | 面向连接 | 无连接 |
报文 | 面向字节流 | 面向报文 |
效率 | 低效 | 高效 |
双工性 | 全双工 | 一对一,一对多,多对一,多对多支持多播和广播 |
流量控制 | 滑动窗口机制 | |
拥塞控制 | 慢开始/拥塞避免快重传/快恢复 | |
传输速度 | 慢 | 快 |
应用场景 | 效率要求相对低,准确要求相对高。要求有连接的场景 | 效率要求相对高,准确要求相对低 |
应用 | SMTP,TELNET,HTTP,FTP | DNS,RIP,NFS,SNMP,IP电话,流媒体 |
TCP使用
TCP服务器
常用API:
ServerSocket
new ServerSocket(int port) —— 创建监听端口号为port的ServerSocket
getLocalPort() —— 获取本地端口,即Socket监听的端口
setSoTimeout(int timeout) —— 设置accept()的连接超时时间
accept() —— 等待连接。返回客户端的Socket实例对象。若设置了超时,连接超时将抛异常SocketTimeoutException,否则阻塞等待
isClosed() —— 连接是否关闭
close() —— 关闭连接
Socket
setSoTimeout(int timeout) —— 设置read()读取流的超时时间
getInetAddress().getHostAddress() —— 获取客户端的主机IP地址
getPort() —— 获取客户端与本服务器连接端口
getLocalPort() —— 获取本地端口,与serverSocket.getLocalPort()获取的端口一致
getInputStream() —— 获取输入流,用于接收客户端发送过来的信息。一般使用read(byte[] data)来读取,此方法也属于阻塞式,但若设置了读取流的超时时间,超时将抛异常SocketTimeoutException
getOutputStream() —— 获取输出流,用户给客户端发送信息
这里是一个简单的服务器实例,只实现一次请求,然后响应一次即完毕:
private void startTCPServer() {
final int port = 8989;
new Thread(new Runnable() {
@Override
public void run() {
ServerSocket server = null;
try {
// 1、创建ServerSocket服务器套接字
server = new ServerSocket(port);
// 设置连接超时时间,不设置,则是一直阻塞等待
server.setSoTimeout(8000);
// 2、等待被连接。在连接超时时间内连接有效,超时则抛异常,
Socket client = server.accept();
logD("connected...");
// 设置读取流的超时时间,不设置,则是一直阻塞读取
client.setSoTimeout(5000);
// 3、获取输入流和输出流
InputStream inputStream = client.getInputStream();
OutputStream outputStream = client.getOutputStream();
// 4、读取数据
byte[] buf = new byte[1024];
int len = inputStream.read(buf);
String receData = new String(buf, 0, len, Charset.forName("UTF-8"));
logD("received data from client: " + receData);
// 5、发送响应数据
byte[] responseBuf = "Hi, I am Server".getBytes(Charset.forName("UTF-8"));
outputStream.write(responseBuf, 0, responseBuf.length);
} catch (IOException e) {
logD("Exception:" + e.toString());
e.printStackTrace();
} finally {
if (server != null) {
try {
server.close();
server = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
TCP客户端
常用API:
Socket
new Socket(String host, int port) —— 创建连接。在90秒内未连接主机,会抛异常ConnectException。可以缩短连接超时时间,使用connect方法
new Socket() —— 创建一个套接字。主要用于connect().
connect(SocketAddress socketAddress, int timeout); —— 开始连接给定的套接字地址,并设置连接超时时间。SocketAddress中封装了主机地址与端口。
setSoTimeout(int timeout) —— 设置读取流的超时时间。必须在getInputStream()与getOutputStream()之前设置才有效
isConnected() —— 是否连接上,返回布尔值
getOutputStream() —— 获取输出流
getInputStream() —— 获取输入流
shutdownOutput() —— 关闭输入流。属于半关闭,而close()属于全关闭
close() —— 关闭连接
- SocketAddress
new InetSocketAddress(host, port) —— SocketAddress是抽象类,使用SocketAddress子类来实例化。把主机IP地址与端口封装进去。
客户端代码也是最简单的实例,一发一收即结束。
private void startTCPClient() {
final String host = "192.168.1.214";
final int port = 8989;
new Thread(new Runnable() {
@Override
public void run() {
Socket socket = null;
try {
// 1、创建连接
socket = new Socket(host, port);
if (socket.isConnected()) {
logD("connect to Server success");
}
// 2、设置读流的超时时间
socket.setSoTimeout(8000);
// 3、获取输出流与输入流
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
// 4、发送信息
byte[] sendData = "Hello, I am client".getBytes(Charset.forName("UTF-8"));
outputStream.write(sendData, 0, sendData.length);
outputStream.flush();
// 5、接收信息
byte[] buf = new byte[1024];
int len = inputStream.read(buf);
String receData = new String(buf, 0, len, Charset.forName("UTF-8"));
logD(receData);
} catch (IOException e) {
e.printStackTrace();
logD(e.toString());
} finally {
if (socket != null) {
try {
socket.close();
socket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}).start();
}
TCP总结
读取流阻塞的处理
因为上面的demo中,都是假设对方发送的数据长度小于等于1024。可是现实中数据的长度一般都是未知的,很多人用读取文件的方法来读取,如:
byte[] buf = new byte[1024];
StringBuilder rece = new StringBuilder();
int len;
while ((len = inputStream.read(buf)) != -1) {
String part = new String(buf, 0, len);
rece.append(part);
}
String receData = rece.toString();
这样,看上去很好,但实际socket的流与文件流不同,文件读到末尾,有一个结束标记。而socket的流只有在关闭流的时候,才会有被告知结束。这里的结束就是read()返回的长度等于-1。
TCP通信时,一般是不会立马关闭,而read()又是阻塞式方法,所以会导致程序一直停留在这里while ((len = inputStream.read(buf)) != -1)。
首先,有两种不太靠谱的解决方法:
- 不要用-1作为判断条件,而用>0。但我这结果都一样,不管你信不信,反正我信了
- 使用BufferedReader的readLine()去读取数据。这是读行,我的数据里没有换行,怎么读?传文件很好,但绝大多数情况下不适合。
BufferedReader bufReader = new BufferedReader(new InputStreamReader(inputStream));
String receData = bufReader.readLine();
稍微靠谱点的方法:
使用while(inputStream.available() > 0),先进行判断是否有数据可读,有就在循环内部读取。这个方法不是阻塞式,如果对方已经把数据发送出来了,这时可以正常读取;如果对方慢了一点,这里就直接执行下去了,数据的读取就被跳过了。当然也可以每次都先等待一段时间再去查。
目前没有找到特别完美又通用的解决方法(如果您有,麻烦分享下),但总有适合的,以下是个人总结的几种解决方法:
- 制定协议,添加报文头:报文总数据长度 + 是否继续保持链接 + 当前报文的序号,本报文就根据此长度来判断是否接受完毕;
- 设置读入超时时间。如果后面的读取后面的程序还要执行,那就只对读取进行异常处理;
- 发送端发送完毕后关闭连接。socket.shutdownOutput()和socket.close()都可以,前者是半关闭,后者是全关闭。前者安全点;
- 读取到某些字符后,当成结束。如读到byebye就不读了;
- 让每次传输的数据长度都不等于缓冲区buffer大小的整数倍。接收到数据后,判断长度是否为buffer长度。是,则说明还有数据要读;否,则说明已结束;
- 用一个足够大的缓冲区buffer来一次性装完所有的数据。
流的处理
个人还是喜欢用byte,可能因为以前使用C的原因,再加上项目中也是字节数据+字符数据。很多情况下,我们都是字符串数据或对象实例之类的,可以用封装类进行处理,如BufferedWriter/BufferedReader、ObjectInputStream/ObjectOutputStream、DataInputStream/DataOutputStream与PrintWriter。
对象流就不写demo了,需注意的是:
- 被读写的对象必须实现Serializable;
- 读写顺序必须一致。
DataInputStream+DataOutputStream:
// 发送
DataOutputStream dataOS = new DataOutputStream(outputStream);
dataOS.writeUTF("Hi你好啊, I am Server");
dataOS.flush();
dataOS.close(); // 如果发送后不再接收或发送,就可以关闭,否则不要关闭。因为这样也会把socket关闭,导致无法再接收或发送了,并抛SocketException异常。若不再发送,只接收,可用socket.shutdownOutput();
// 接收
DataInputStream dataIS = new DataInputStream(inputStream);
String receData = dataIS.readUTF();
logD("From Client:" + receData);
PrintWriter + BufferedReader:
// 发送
// 使用带自动flush刷新的构造函数,自动刷新仅对这三个方法有效:println, printf, format
PrintWriter printWriter = new PrintWriter(outputStream, true);
printWriter.format("First info from client = %s", host);
printWriter.printf("Second info: Android");
socket.shutdownOutput();
// 接收
// 这里可以一次性把上面两次的写入流读出来。
BufferedReader bufReader = new BufferedReader(new InputStreamReader(inputStream));
char[] buffer = new char[1024];
int length;
if ((length = bufReader.read(buffer)) != -1) {
String receData = new String(buffer, 0, length);
logD("From Client:" + receData);
}
TCP心跳保活
心跳是双方约定好保活时间,在此保活时间内告诉对方自己还在线,不要关闭连接。对方在保活时间内没有收到保活信息,就会关闭连接。
正常情况下,TCP有默认的保活时间,为2小时,可在客户端开启保活功能socket.setKeepAlive(true);。
但我们并不需要那么长的保活时间,一般10分钟就够了。方法有两个:
反射去修改默认的保活时间;
(参考:http://stackoverflow.com/questions/6565667/how-to-set-the-keepalive-timeout-in-android)
在应用层面去定时发送心跳包,实现方法很多,就不多讲了。
实例:TCP服务器与客户端自由通信
这里的自由通信,指的是服务器或客户端可以任意的发送和接收数据,而不需按一发一收,想发就发,任意时刻都能接收数据那种。
原理分析
发送用的是OutputStream,接收用的是InputStream。一发一收制,发送和接收的都是顺序写到代码中的。现在要自由,无非就是分开来,发送只管发送,接收只管接收。
发送,好处理,需要发送的时候发送就行。
接收,好像没那么容易,因为对方无论何时发送数据,我们必须接收。这里开了个子线程在循环读流。又由于读流是阻塞式,使用的处理方法是上面的第2种,设置读流超时时间。
代码实现
代码包含服务器类SimpleTCPServer与客户端类SimpleTCPClient,及各自的demo。这两个类可以当做简单的工具类使用。
两个工具类对接收的数据使用了不同的处理方法,SimpleTCPServer要求传入Handler进行处理。而SimpleTCPClient是要求调用者实现其抽象方法processData(byte[] data),此方法是在子线程中处理,把更多的处理权交给了调用者。
SimpleTCPServer:
public class SimpleTCPServer {
private static final String TAG = SimpleTCPServer.class.getSimpleName();
private static final int INPUT_STREAM_READ_TIMEOUT = 300;
/**
* 服务器,连接服务器的客户端
*/
private ServerSocket mServer;
private List<Socket> mClientList = new ArrayList<>();
private Handler mHandler;
public SimpleTCPServer(Handler handler) {
this.mHandler = handler;
}
public void listen(final int port) {
if (mServer != null && !mServer.isClosed()) {
close();
}
new Thread(new Runnable() {
@Override
public void run() {
try {
mServer = new ServerSocket(port);
while (mServer != null && !mServer.isClosed()) {
logI("start to accept");
Socket client = mServer.accept();
if (client.isConnected()) {
logI(String.format("accepted from: %s[%d]", client.getInetAddress().getHostAddress(), client.getPort()));
mClientList.add(client);
new Thread(new ReceiveRunnable(mClientList.size() - 1, client)).start();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 接收客户端数据的子线程
*/
class ReceiveRunnable implements Runnable {
private int which;
private Socket mSocket;
ReceiveRunnable(int which, Socket socket) {
this.which = which;
this.mSocket = socket;
}
@Override
public void run() {
try {
// 给读取流设置超时时间,否则会一直在read()那阻塞
mSocket.setSoTimeout(INPUT_STREAM_READ_TIMEOUT);
InputStream in = mSocket.getInputStream();
while (mSocket != null && mSocket.isConnected()) {
// 读取流
byte[] data = new byte[0];
byte[] buf = new byte[1024];
int len;
try {
while ((len = in.read(buf)) != -1) {
byte[] temp = new byte[data.length + len];
System.arraycopy(data, 0, temp, 0, data.length);
System.arraycopy(buf, 0, temp, data.length, len);
data = temp;
}
} catch (SocketTimeoutException stExp) {
// 只catch,不做任何处理
// stExp.printStackTrace();
}
// 处理流
if (data.length != 0) {
pushMsgToHandler(which, data);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 推送信息给Handler
*/
private void pushMsgToHandler(int which, byte[] data) {
Message message = mHandler.obtainMessage();
message.what = which;
message.obj = data;
mHandler.sendMessage(message);
}
/**
* 发送数据
*/
public boolean sendData(int which, byte[] bytes) {
if (which < 0 || which >= mClientList.size()) {
return false;
}
Socket socket = mClientList.get(which);
if (socket != null && socket.isConnected()) {
try {
OutputStream out = socket.getOutputStream();
out.write(bytes);
out.flush();
return true;
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
public boolean sendData(int which, String data) {
return sendData(which, data.getBytes(Charset.forName("UTF-8")));
}
public void close() {
if (mServer != null) {
try {
mServer.close();
mServer = null;
} catch (IOException e) {
e.printStackTrace();
}
}
for (Socket client : mClientList) {
try {
client.close();
client = null;
} catch (IOException e) {
e.printStackTrace();
}
}
mClientList.clear();
}
public Socket getClient(int which) {
return which < 0 || which >= mClientList.size() ? null : mClientList.get(which);
}
public int getClientCount() {
return mClientList.size();
}
private void logI(String msg) {
Log.i(TAG, msg);
}
}
SimpleTCPServer demo:
public class ServerActivity extends AppCompatActivity {
private TextView tv_msg;
private Button btn_restart;
private SimpleTCPServer mSimpleTCPServer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_server);
initData();
initView();
}
private void initData() {
mSimpleTCPServer = new SimpleTCPServer(mHandler);
mSimpleTCPServer.listen(9000);
}
private void initView() {
tv_msg = (TextView) findViewById(R.id.tv_msg);
btn_restart = (Button) findViewById(R.id.btn_restart);
tv_msg.setMovementMethod(ScrollingMovementMethod.getInstance());
btn_restart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mSimpleTCPServer != null) {
for (int i = 0; i < mSimpleTCPServer.getClientCount(); i++) {
mSimpleTCPServer.sendData(i, "Push From Sever, 服务器发送数据来了".getBytes(Charset.forName("UTF-8")));
}
}
}
});
btn_restart.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
tv_msg.setText("");
return true;
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
mSimpleTCPServer.close();
}
private MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
WeakReference<ServerActivity> refActivity;
public MyHandler(ServerActivity activity) {
refActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
ServerActivity activity = refActivity.get();
int which = msg.what;
byte[] data = (byte[]) msg.obj;
String result = new String(data, Charset.forName("UTF-8"));
activity.tv_msg.append("第" + which + "位访客"
+ activity.mSimpleTCPServer.getClient(which).getInetAddress().getHostAddress() + ":"
+ result + "\n");
activity.mSimpleTCPServer.sendData(msg.what, "Hi你好,I am server".getBytes(Charset.forName("UTF-8")));
}
}
}
SimpleTCPClient:
public abstract class SimpleTCPClient {
private static final String TAG = SimpleTCPClient.class.getSimpleName();
private static final int CONNECT_TIMEOUT = 5000;
private static final int INPUT_STREAM_READ_TIMEOUT = 300;
private Socket mSocket;
private InputStream mInputStream;
private OutputStream mOutputStream;
public SimpleTCPClient() {
}
/**
* 连接主机
* @param host 主机IP地址
* @param port 主机端口
*/
public void connect(final String host, final int port) {
if (mSocket != null) {
close();
}
new Thread(new Runnable() {
@Override
public void run() {
try {
mSocket = new Socket();
SocketAddress socketAddress = new InetSocketAddress(host, port);
// 设置连接超时时间
mSocket.connect(socketAddress, CONNECT_TIMEOUT);
if (mSocket.isConnected()) {
logI("connected to host success");
// 设置读流超时时间,必须在获取流之前设置
mSocket.setSoTimeout(INPUT_STREAM_READ_TIMEOUT);
mInputStream = mSocket.getInputStream();
mOutputStream = mSocket.getOutputStream();
new ReceiveThread().start();
} else {
mSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 接收进程
*/
class ReceiveThread extends Thread {
@Override
public void run() {
super.run();
while (mSocket != null && mSocket.isConnected() && mInputStream != null) {
// 读取流
byte[] data = new byte[0];
byte[] buf = new byte[1024];
int len;
try {
while ((len = mInputStream.read(buf)) != -1) {
byte[] temp = new byte[data.length + len];
System.arraycopy(data, 0, temp, 0, data.length);
System.arraycopy(buf, 0, temp, data.length, len);
data = temp;
}
} catch (IOException e) {
// e.printStackTrace();
}
// 处理流
if (data.length != 0) {
processData(data);
}
}
}
}
/**
* process data from received,which is not run in the main thread。
*/
public abstract void processData(byte[] data);
/**
* 发送数据
*/
public void send(byte[] data) {
if (mSocket != null && mSocket.isConnected() && mOutputStream != null) {
try {
mOutputStream.write(data);
mOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void send(String data) {
send(data.getBytes(Charset.forName("UTF-8")));
}
public void close() {
if (mSocket != null) {
try {
mInputStream.close();
mOutputStream.close();
mSocket.close();
mInputStream = null;
mOutputStream = null;
mSocket = null;
} catch (IOException e) {
e.printStackTrace();
}
}
}
public Socket getSocket() {
return mSocket;
}
private void logI(String msg) {
Log.i(TAG, msg);
}
}
SimpleTCPClient demo:
public class ClientActivity extends AppCompatActivity {
private Button btn_send;
private TextView tv_msg;
private SimpleTCPClient mSimpleTCPClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_client);
initData();
initView();
}
private void initData() {
mSimpleTCPClient = new SimpleTCPClient() {
@Override
public void processData(byte[] data) {
sendMessageToActivity(new String(data, Charset.forName("UTF-8")));
}
};
mSimpleTCPClient.connect("192.168.1.214", 9000);
}
private void initView() {
tv_msg = (TextView) findViewById(R.id.tv_msg);
btn_send = (Button) findViewById(R.id.btn_send);
tv_msg.setMovementMethod(ScrollingMovementMethod.getInstance());
btn_send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mSimpleTCPClient.send("Hello,from client:测试数据来了");
}
});
btn_send.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
tv_msg.setText("");
return true;
}
});
}
private void sendMessageToActivity(String msg) {
Message message = Message.obtain();
message.obj = msg + "\n";
mHandler.sendMessage(message);
}
private MyHandler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
private WeakReference<ClientActivity> refActivity;
MyHandler(ClientActivity activity) {
refActivity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
ClientActivity activity = refActivity.get();
activity.tv_msg.append((CharSequence) msg.obj);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
mSimpleTCPClient.close();
}
}
网友评论