基于TCP的安卓客户端开发
一.Socket通信简介
Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信。两者的最大差异在于,http连接使用的是“请求—响应方式”,即在请求时建立连接通道,当客户端向服务器发送请求后,服务器端才能向客户端返回数据。而Socket通信则是在双方建立起连接后就可以直接进行数据的传输,在连接时可实现信息的主动推送,而不需要每次由客户端向服务器发送请求。 那么,什么是socket?Socket又称套接字,在程序内部提供了与外界通信的端口,即端口通信。通过建立socket连接,可为通信双方的数据传输传提供通道。socket的主要特点有数据丢失率低,使用简单且易于移植。
- 什么是Socket?
是一种抽象层,应用程序通过它来发送和接收数据,使用Socket可以将应用程序添加到网络中,与处于同一网络中的其他应用程序进行通信。简单来说,Socket提供了程序内部与外界通信的端口并为通信双方的提供了数据传输通道。
- Socket的分类
根据不同的的底层协议,Socket的实现是多样化的。本指南中只介绍TCP/IP协议族的内容,在这个协议族当中主要的Socket类型为流套接字(streamsocket)和数据报套接字(datagramsocket)。流套接字将TCP作为其端对端协议,提供了一个可信赖的字节流服务。数据报套接字使用UDP协议,提供数据打包发送服务。 下面,我们来认识一下这两种Socket类型的基本实现模型。
二.Socket基本通信模型
- TCP通信模型
- UDP通信模型
三.Socket基本实现原理
- 基于TCP协议的Socket
服务器端首先声明一个ServerSocket对象并且指定端口号,然后调用Serversocket的accept()方法接收客户端的数据。accept()方法在没有数据进行接收的处于堵塞状态。(Socketsocket=serversocket.accept()),一旦接收到数据,通过inputstream读取接收的数据。
客户端创建一个Socket对象,指定服务器端的ip地址和端口号(Socketsocket=newSocket("172.168.10.108",8080);),通过inputstream读取数据,获取服务器发出的数据(OutputStreamoutputstream=socket.getOutputStream()),最后将要发送的数据写入到outputstream即可进行TCP协议的socket数据传输。
- 基于UDP协议的数据传输
服务器端首先创建一个DatagramSocket对象,并且指点监听的端口。接下来创建一个空的DatagramSocket对象用于接收数据(bytedata[]=newbyte[1024;]DatagramSocketpacket=newDatagramSocket(data,data.length)),使用DatagramSocket的receive方法接收客户端发送的数据,receive()与serversocket的accepet()类似,在没有数据进行接收的处于堵塞状态。
客户端也创建个DatagramSocket对象,并且指点监听的端口。接下来创建一个InetAddress对象,这个对象类似与一个网络的发送地址(InetAddressserveraddress=InetAddress.getByName("172.168.1.120")).定义要发送的一个字符串,创建一个DatagramPacket对象,并制定要讲这个数据报包发送到网络的那个地址以及端口号,最后使用DatagramSocket的对象的send()发送数据。*(Stringstr="hello";bytedata[]=str.getByte();DatagramPacketpacket=new DatagramPacket(data,data.length,serveraddress,4567);socket.send(packet);)
注:以上通信原理内容非笔者总结,引于此目的在于“实践在即,理论先行”
四.权限申明
1 <!--允许应用程序改变网络状态-->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/> <!--允许应用程序改变WIFI连接状态-->
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/> <!--允许应用程序访问有关的网络信息-->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <!--允许应用程序访问WIFI网卡的网络信息-->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> <!--允许应用程序完全使用网络-->
<uses-permission android:name="android.permission.INTERNET"/>
五.布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
>
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginTop="12dp"
android:layout_gravity="center"
android:text="IP:"
android:textSize="20dp"/>
<EditText
android:id="@+id/IPEditText"
android:layout_width="0dp"
android:layout_height="match_parent"
android:gravity="center"
android:layout_weight="4"
android:text="223.3.200.4"
android:textSize="20dp"
/>
<TextView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:layout_marginTop="12dp"
android:layout_gravity="center"
android:text="Port:"
android:textSize="20dp"/>
<EditText
android:id="@+id/PortEditText"
android:layout_width="0dp"
android:layout_height="match_parent"
android:gravity="center"
android:layout_weight="2"
android:text="8086"
android:textSize="20dp"
/>
<Button
android:id="@+id/ConnectButton"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="连接"/> </LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp" >
<EditText
android:id="@+id/MessagetoSendEditText"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="3"
android:text="Data from Client"/>
<Button
android:id="@+id/SendButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="发送"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
>
<TextView
android:id="@+id/DisplayTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="Display area:"
android:textSize="20dp"/>
</LinearLayout> </LinearLayout>
六.具体代码实现
package com.example.john.androidsockettest_client; import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast; import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException; public class MainActivity extends AppCompatActivity { //Handler中的消息类型
public static final int DEBUG = 0x00;
public static final int RECEIVEDATAFROMSERVER = 0x01;
public static final int SENDDATATOSERVER = 0x02;
//线程
Socket socket = null; //成功建立一次连接后获得的套接字
ConnectThread connectThread; //当run方法执行完后,线程就会退出,故不需要主动关闭
SendThread sendThread; //发送线程,由send按键触发
ReceiveThread receiveThread; //接收线程,连接成功后一直运行
//待发送的消息
public String messagetoSend = "";
//控件
TextView displayTextView; //显示接收、发送的数据及Debug信息
Button sendButton; //发送按钮,点击触发发送线程
Button connectButton; //连接按钮,点击触发连接线程
EditText messagetoSendEditText;
EditText iPEditText;
EditText portEditText; public Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.what == RECEIVEDATAFROMSERVER) {
Bundle bundle = msg.getData();
displayTextView.append("Server:"+bundle.getString("string1")+"\n");
}
else if (msg.what == DEBUG) {
Bundle bundle = msg.getData();
displayTextView.append("Debug:"+bundle.getString("string1")+"\n");
}
else if (msg.what == SENDDATATOSERVER) {
Bundle bundle = msg.getData();
displayTextView.append("Client:"+bundle.getString("string1")+"\n");
}
} };
//子线程更新UI
public void SendMessagetoHandler(final int messageType , String string1toHandler){
Message msg = new Message();
msg.what = messageType; //消息类型
Bundle bundle = new Bundle();
bundle.clear();
bundle.putString("string1", string1toHandler); //向bundle中添加字符串
msg.setData(bundle);
myHandler.sendMessage(msg);
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
displayTextView = (TextView) findViewById(R.id.DisplayTextView);
sendButton = (Button) findViewById(R.id.SendButton);
messagetoSendEditText = (EditText) findViewById(R.id.MessagetoSendEditText);
iPEditText = (EditText)findViewById(R.id.IPEditText);
portEditText = (EditText)findViewById(R.id.PortEditText);
connectButton = (Button)findViewById(R.id.ConnectButton); connectButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
connectThread= new ConnectThread();
connectThread.start();
}
});
sendButton.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
messagetoSend = messagetoSendEditText.getText().toString();
//使用连接成功后得到的socket构造发送线程,每点击一次send按钮触发一次发送线程
sendThread = new SendThread(socket);
sendThread.start();
}
});
} //******** 连接线程 **********
class ConnectThread extends Thread{
@Override
public void run() {
try{
//连接服务器 并设置连接超时为1秒
socket = new Socket();
socket.connect(new InetSocketAddress(iPEditText.getText().toString(),
Integer.parseInt(portEditText.getText().toString())), 1000);
}catch (SocketTimeoutException aa) {
//更新UI:连接失败
SendMessagetoHandler(DEBUG,"服务器连接失败!");
return; //直接返回
} catch (IOException e) {
e.printStackTrace(); }
//更新UI:连接成功
SendMessagetoHandler(DEBUG,"服务器连接成功!"); //打开接收线程
receiveThread = new ReceiveThread(socket);
receiveThread.start();
}
}
//******** 发送线程 **********
class SendThread extends Thread{
private Socket mSocket;
//发送线程的构造函数,由连接线程传入套接字
public SendThread(Socket socket) {mSocket = socket;} @Override
public void run() {
try{
OutputStream outputStream = mSocket.getOutputStream();
//向服务器发送信息
outputStream.write(messagetoSend.getBytes("gbk"));
outputStream.flush();
//更新UI:显示发送出的数据
SendMessagetoHandler(SENDDATATOSERVER,messagetoSend);
}catch (IOException e) {
e.printStackTrace();
//更新UI:显示发送错误信息
SendMessagetoHandler(DEBUG,"发送失败!");
return;
}
}
} //******** 接收线程 **********
class ReceiveThread extends Thread{ private Socket mSocket;
//接收线程的构造函数,由连接线程传入套接字
public ReceiveThread(Socket socket){mSocket = socket;} @Override
public void run() {
while(true){ //连接成功后将一直运行
try {
BufferedReader bufferedReader;
String line = null;
String readBuffer="";
bufferedReader = new BufferedReader(new InputStreamReader(mSocket.getInputStream()));
while ((line = bufferedReader.readLine()) != null) {
readBuffer = line + readBuffer;
SendMessagetoHandler(RECEIVEDATAFROMSERVER,readBuffer);
readBuffer = "";
}
bufferedReader.close();
}catch (IOException e) {
e.printStackTrace();
//更新UI:显示发送错误信息
SendMessagetoHandler(DEBUG,"接收失败!");
return;
}
}
}
}
}
采用多线程编程,输入IP地址和端口号后点击“连接”按钮开启ConnectThread,在该线程中执行对服务器的连接,由连接成功后的socket构造ReceiveThread并启动,在该线程中执行while(true){}循环持续监听输入流,并通过Handler异步消息处理机制更新主UI(Server:)。点击“发送”按钮,由socket构造SendThread并启动,向服务器端发送数据并更新主UI(Client:)。在子线程运行过程中,出现错误会向主UI呈递调试信息(Debug:)。以上即是该TCP客户端主要的编程思想。
七.Debug
- 关于readLine()
在写ReceiveThread线程代码块时,笔者最初读取输入流的写法如下:
while ((line = bufferedReader.readLine()) != null) {
readBuffer = line + readBuffer;
readBuffer = "";
}
SendMessagetoHandler(RECEIVEDATAFROMSERVER,readBuffer);
结果在主UI一直没有看到消息更新(直到把服务器端口关闭,服务器之前发的数据才更新到UI),这就说明第5行代码根本没有实现,循环没有出来。后来将第5行代码放入while循环内部运行正常。
原因如下:
误以为readLine()是读取到没有数据时就返回null(因为其它read方法当读到没有数据时返回-1),而实际上readLine()是一个阻塞函数,当没有数据读取时,就一直会阻塞在那,而不是返回null;因为readLine()阻塞后,第5行代码根本就不会执行到,所以在UI不会显示接收到的数据。要想执行到第5行,一个办法是发送完数据后就关掉流,这样readLine()结束阻塞状态,而能够得到正确的结果,但显然不能传一行就关一次数据流;另外一个办法是把第5行代码放到while循环体内。
readLine()只有在数据流发生异常或者另一端被close()掉时,才会返回null值。
如果不指定buffer大小,则readLine()使用的buffer有8192个字符。在达到buffer大小之前,只有遇到"/r"、"/n"、"/r/n"才会返回。
- 使用InputStream类中的read(b:byte[]):int函数
//另一种读取方式 :+read(b:byte[]):int 从输入流中读取b.length个字节到
// 数组b中,并且返回实际读取的字节数。到流的最后返回-1
byte[] b = new byte[1024]; //每个数据包最大1024个字节
int length = 0;
while((length = mSocket.getInputStream().read(b)) != -1){ //在流产生时执行里面的代码
String r_msg = new String(b,0,length,ISO_ENCODE);
SendMessagetoHandler(RECEIVEDATAFROMSERVER,r_msg);
//SendMessagetoHandler(DEBUG,"length="+length);
}
这种方式不要求发送端在数据包后添加回车换行标志,所写即所发。
八.实际运行效果
如果这篇blog对您有所帮助,请留下您的一个赞,也欢迎留言讨论,笔者将在第一时间回复!
基于TCP的安卓客户端开发的更多相关文章
- 基于TCP的安卓服务器开发
一.说明 前文介绍了基于安卓客户端的开发,在此基础上,进行少许改动即可开发出一款基于TCP的安卓服务器,理论知识请参见笔者上一篇博文,下面直接实践操作. 二.权限申明 <!--允许应用程序改变网 ...
- 基于TCP协议的客户端
基于TCP协议的客户端 此客户端能用于下一篇博客的单线程服务器和多线程服务器 import java.io.BufferedReader; import java.io.IOException; im ...
- 基于Netbeans的安卓Android开发环境配置 - CSDN博客
原文:基于Netbeans的安卓Android开发环境配置 - CSDN博客 基于Netbeans的安卓Android开发环境配置 一.准备工作 NetBeans 勾选网页中的Accept-选择对应系 ...
- 【TCP/IP网络编程】:04基于TCP的服务器端/客户端
摘要:结合前面所讲述的知识,本篇文章主要介绍了简单服务器端和客户端实现的框架流程及相关函数接口. 理解TCP和UDP 根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字(本 ...
- C/C++网络编程4——实现基于TCP的服务器端/客户端1
一.TCP服务器调用顺序: 调用socket函数创建套接字:声明并初始化地址信息结构体变量:调用bind函数向套接字分配地址:调用listen函数进入等待连接请求状态,只有调用了listen函数后客户 ...
- 基于TCP通信的客户端断线重连
转载:http://www.cnblogs.com/networkcomms/p/4304362.html 源码下载 在CS程序中,断线重连应该是一个常见的功能. 此处的断线重连主要指的是服务器端因为 ...
- C/C++网络编程5——实现基于TCP的服务器端/客户端2
三次握手过程详解: 1:客户端的协议栈向服务器端发送SYN包,并告诉服务器端当前放送序号为j,客户端进入SYNC_SEND状态. 2:服务器端的协议栈收到这个包以后,和客户端进行ACK应答,应答值为j ...
- 基于TCP的通信 客户端
#include <WINSOCK2.H> #include <stdio.h> // socket 套接字 #pragma comment (lib,"Ws2_32 ...
- ios即时通讯客户端开发之-mac上基于XMPP的聊天客户端开发环境搭建
1.搭建服务器 - 安装顺序 - (mysql->openfire->spark) 数据库:mysql 服务器管理工具: openfire 测试工具: spark mysql 安装 h ...
随机推荐
- .net core webapi+vue 跨域访问
最近在做一个前后端分离的示例,以下代码完美解决跨域的问题 一.后端服务 1.首先我们建一个.net core webapi的项目 2.项目引用Microsoft.AspNetCore.Cors 包 3 ...
- C#简单的九九乘法表
for(int i=1;i<10;i++) { for(int j=1;j<=i;j++) { Console.Write("{0}*{1}={2}",j,i,i*j) ...
- FastClick用法
https://majing.io/posts/10000007721218 为什么要使用FastClick 移动设备上的浏览器默认会在用户点击屏幕大约延迟300毫秒后才会触发点击事件,这是为了检查用 ...
- js 数组的拷贝
在js中,数组Array是引用类型,直接将数组赋值给一个变量名,二者所指向的地址是一样的. 所以直接复制数组会产生意想不到的结构. 要想解决拷贝一个数组但是对副本的修改不影响原来的数组,有以下方式: ...
- 浅谈JS中的typeof和instanceof的区别
JS中的typeof和instanceof常用来判断一个变量是否为空,或者是什么类型. typeof typeof运算符返回一个用来表示表达式的数据类型的字符串. typeof一般返回以下几个字符串: ...
- 《WAP团队》作业四——基于原型的团队项目需求调研与分析
基于原型的团队项目需求调研与分析 本项目是一个家教系统的实现,随着时代的进步,现今已经进入信息技术时代,越来越多的人注意到了教育的重要性.家长对于孩子的学习提高注意力,大家都不想自己的孩子输在起跑线上 ...
- 路由表flags的U值引起的能ping通网关,ping不通其它网段的案例
故障的: 正常的: 初步分析: 看路由表的flags ,之前故障时是U.现在正常的是UG查了下说明,应该是这个原因.U — 路由是活动的G — 路由指向网关
- ThinkPHP5.0完全开发手册 --技术文档
1.ThinkPHP5.0完全开发手册.chm 链接:https://pan.baidu.com/s/1199wK6q6O9IyOf5RU_-Xow 提取码:hnek 2.ThinkPHP5.0完全开 ...
- spoj1433 KPSUM
题意:略: 首先知道10,20,......100,200,1000的前面的符号都是负号. 举具体例子:221时,计算过程为 000-009, 010-019, 020-029...... ...
- Http头:Expires,Cache-Control,Last-Modified,ETag
Expires:过期时间 el:Expirse:Fri,30 Oct 1998 14:19:41 Cache-Control:缓存控制 el:Cache-Contro ...