一.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的安卓客户端开发的更多相关文章

  1. 基于TCP的安卓服务器开发

    一.说明 前文介绍了基于安卓客户端的开发,在此基础上,进行少许改动即可开发出一款基于TCP的安卓服务器,理论知识请参见笔者上一篇博文,下面直接实践操作. 二.权限申明 <!--允许应用程序改变网 ...

  2. 基于TCP协议的客户端

    基于TCP协议的客户端 此客户端能用于下一篇博客的单线程服务器和多线程服务器 import java.io.BufferedReader; import java.io.IOException; im ...

  3. 基于Netbeans的安卓Android开发环境配置 - CSDN博客

    原文:基于Netbeans的安卓Android开发环境配置 - CSDN博客 基于Netbeans的安卓Android开发环境配置 一.准备工作 NetBeans 勾选网页中的Accept-选择对应系 ...

  4. 【TCP/IP网络编程】:04基于TCP的服务器端/客户端

    摘要:结合前面所讲述的知识,本篇文章主要介绍了简单服务器端和客户端实现的框架流程及相关函数接口. 理解TCP和UDP 根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字(本 ...

  5. C/C++网络编程4——实现基于TCP的服务器端/客户端1

    一.TCP服务器调用顺序: 调用socket函数创建套接字:声明并初始化地址信息结构体变量:调用bind函数向套接字分配地址:调用listen函数进入等待连接请求状态,只有调用了listen函数后客户 ...

  6. 基于TCP通信的客户端断线重连

    转载:http://www.cnblogs.com/networkcomms/p/4304362.html 源码下载 在CS程序中,断线重连应该是一个常见的功能. 此处的断线重连主要指的是服务器端因为 ...

  7. C/C++网络编程5——实现基于TCP的服务器端/客户端2

    三次握手过程详解: 1:客户端的协议栈向服务器端发送SYN包,并告诉服务器端当前放送序号为j,客户端进入SYNC_SEND状态. 2:服务器端的协议栈收到这个包以后,和客户端进行ACK应答,应答值为j ...

  8. 基于TCP的通信 客户端

    #include <WINSOCK2.H> #include <stdio.h> // socket 套接字 #pragma comment (lib,"Ws2_32 ...

  9. ios即时通讯客户端开发之-mac上基于XMPP的聊天客户端开发环境搭建

    1.搭建服务器  -  安装顺序 - (mysql->openfire->spark) 数据库:mysql 服务器管理工具: openfire 测试工具: spark mysql 安装 h ...

随机推荐

  1. Java内存模型学习笔记

    Java内存模型(JMM):描述了java程序中各种变量(线程共享变量)的范根规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的底层细节.共享变量就是指一个线程中的变量在其他线程中也是可见 ...

  2. java static语句的总结

    static 是静态方法,他的引用不需要对象,可以使用类名直接进行引用,当然也不需要this.      由于不需要对象,所以static方法内无法调用非static的方法或对象   至于为什么mai ...

  3. 关于ashrpt中行源的CPU + Wait for CPU事件深入解读

    该等待事件并不包含在等待事件范围,而是出现在ash的具体行源中,如下: 标注语句的每次执行大约1小时,如下awr所示: 该sql语句的最后一层Insert如下: insert into ta_tf l ...

  4. Python爬虫(四)——开封市58同城数据模型训练与检测

    前文参考: Python爬虫(一)——开封市58同城租房信息 Python爬虫(二)——对开封市58同城出租房数据进行分析 Python爬虫(三)——对豆瓣图书各模块评论数与评分图形化分析 数据的构建 ...

  5. [C++ Primer Plus] 第4章、复合类型(二)课后习题

    1.编写一个 c++ 程序,如下述输出示例所示的那样请求并显示信息 : What is your first name? Betty SueWhat is your last name? YeweWh ...

  6. vscode设置代码块

    需要注意一点是,内容主体里面带有缩进的地方不能用 Tab,只能用空格

  7. Day 10733 使用独立安装包安装.Net Framework 4.6.2时,提示『无法建立到信任根颁发机构的证书链』

    出现该问题的原因是未能更新本机的受信任证书颁发机构证书列表,导致安装包验证失败,以下几种情况可以造成此问题: 1.安装该运行库的系统未接入互联网: 2.安装该运行库的系统所在的网络环境,屏蔽了对微软受 ...

  8. 异常:unity3d ArgumentException: The Assembly System.Configuration is referenced by System.Data.

    异常:ArgumentException: The Assembly System.Configuration is referenced by System.Data. But the dll is ...

  9. PHP深入浅出之命名空间(Namespace)的使用详解

    对于命名空间,官方文档已经说得很详细[查看],我在这里做了一下实践和总结. 命名空间一个最明确的目的就是解决重名问题,PHP中不允许两个函数或者类出现相同的名字,否则会产生一个致命的错误.这种情况下只 ...

  10. java servlet练习测试

    步骤: 0.首先创建web project,工程名:test_servlet 1.编写Servlet,TestServlet.java文件内容: package com.ouyang.servlet; ...