Android Socket

学向勤中得,萤窗万卷书。这篇文章主要讲述Android Socket相关的知识,希望能为你提供帮助。
android Socket
参考资料【Android Socket】菜鸟教程
怎么理解TCP的面向连接和UDP的无连接
https://www.cnblogs.com/xiaomayizoe/p/5258754.html
https://www.cnblogs.com/qifengshi/p/6602881.html
OverviewSocket 一词起源于UNIX 操作系统,UNIX 系统中将IP + 端口号 的通信的方式称之为Socket 中文译为套接字 ,在学习Socket的时候不要太过于纠结它的名字,毕竟只是个代号。
TCP和UDP首先要想比较容易的理解TCP和UDP协议,首先得对整体的网络协议框架有一个整体的了解,并不需要多清楚,只要把握整体就好了,这里我推荐 阮一峰 阮老师的博客 网络协议入门1 , 网络协议入门2 。
这里的TCP 协议不等于TCP/IP协议族 ,这一点不要弄混。
首先呢UDP 是先与TCP 出现的,但是UDP协议是存在着一些“问题”的,所有后来就有了更为复杂的TCP协议。
UDP(User Datagram Protocol)协议如果已经知道对方的IP端口号 ,通过UDP协议我们是已经可以实现,不同的计算机的程序之间的通信了,但是UDP协议还存在着一个“问题”。

  • UDP协议是一种无连接的协议(发送数据之前无需建立连接),不能确保服务器是否收到了发送的数据,这就不能保证数据的完成性,当服务器高负载的情况下很容易出现。
  • UDP协议无法保证发送数据的顺序
比如说,通过UDP协议向服务器发送了如下的数据
11
22
33
但是服务器可能接收到的是
33
11
22
但是这种,情况一般是在网络拥堵的情况下才会出现。
TCP(Transmission Control Protocol) 协议因为UDP的一些“弊端”,所以TCP协议出现了,TCP协议,面向连接,发送数据有顺序,不会造成丢包,但是一些代价也随之而来。TCP与UDP相比消耗的资源较多。
TCP 三次握手https://www.cnblogs.com/qifengshi/p/6602881.html
总结当对数据的完整性非常高的的时候,需要采用TCP协议传输,比如传输文件 ,发送邮件
当对数据完成行要求不高的时候,可以采用UDP协议,比如视频通话
UDP 和 TCP 没有绝对的好与坏,之后谁更适用。
InetAddressThis class represents an Internet Protocol (IP) address.
该类代表着一个IP地址.
/** * 获取本机信息 * 主机名 * IP地址 */ private void getLocalHostInfo() { try { InetAddress inetAddress = InetAddress.getLocalHost(); String hostName = inetAddress.getHostName(); String ipAddress = inetAddress.getHostAddress(); System.out.printf("HostName:%s IP Address:%s", hostName, ipAddress); } catch (UnknownHostException e) { e.printStackTrace(); } }

基于TCP的Socket
Android Socket

文章图片

一个DEMO通过Socket实现文件上传到服务器的功能,上传功能具有断点续传机制。
效果图如下
Android Socket

文章图片

实现思路如下首先,需要在Android 的 Assets 目录下,放入一个名为tmep.flv 的视频文件,
客户端实现思路
  1. 建立与服务端的连接
  2. 选择要上传的文件
  3. 在写入文件的时候,要先想服务器发送头信息(如: 要上传的文件的名称)
  4. 当暂停的时候释放Socket占用的资源,并且记录已经写入了的长度
  5. 当再次开始的时候,获取已经写入了的长度,在写入的时候通过流的skip 方法,跳过这些流。
  6. 建立新的Socket连接,继续向服务器发送数据
PS: 在写入的时候会更新进度条,来显示写入进度
服务端实现思路
  1. 开启服务等待客户端了连接
  2. 当客户端连接后,首先获取客户端传递过来的头信息
  3. 获取到头信息的文件名后,在本地建立文件并写入
  4. 当客户端暂停后,清空资源,等待客户端再次连接
  5. 当客户端再次连接后,向本地已经存在的文件中追加数据
注意事项
  • 在没有完全地交互完的时候,不要关闭流,如果关闭了流,那么Socket连接也会关闭,会抛出SocketClosed 异常。
代码实现服务端代码
/** * 通过Socket来实现上传操作 */ public class SocketUploadClass implements ICallAble {private ServerSocket serverSocket; //线程池 ExecutorService executorService; @Override public void call() { try { startServer(); } catch (IOException e) { e.printStackTrace(); } }/** * 初始化服务器 */ private void startServer() throws IOException { serverSocket = new ServerSocket(9988); //创建线程池 线程数 = 可用cpu 数量 * 50 this.executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 50); //等待客户端接入 while (true) { Socket socket = serverSocket.accept(); executorService.execute(new SocketTask(socket)); } }/** * 用来处理用户写入的县城类 */ class SocketTask implements Runnable { //连接到服务器的线程 Socket socket; public SocketTask(Socket socket) { this.socket = socket; }@Override public void run() { String ipAddress = socket.getInetAddress().toString(); int port = socket.getPort(); System.out.printf("Client 【%s】 Port:%d 已连接", ipAddress, port); //接收数据 try { InputStream inputStream = socket.getInputStream(); BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream); //读取第一行的头信息 String header = ""; int i; while ((i = inputStream.read()) != -1) { if (i == \'\\r\') break; header += (char) i; } System.out.println(header); //要存储的文件名 String fileName = header.split("=")[1]; //将传递过来的内容写入到本地文件 byte[] buffer = new byte[1024]; FileOutputStream fileOutputStream = new FileOutputStream(fileName, true); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream); int len; while ((len = bufferedInputStream.read(buffer)) != -1) { bufferedOutputStream.write(buffer, 0, len); } //释放资源 bufferedOutputStream.flush(); bufferedInputStream.close(); bufferedOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } }

客户端代码
布局文件
< ?xml version="1.0" encoding="utf-8"?> < LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> < Button android:id="@+id/uploadBtn" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="upload" android:text="Update"/> < Button android:id="@+id/pauseBtn" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="pause" android:text="Pause"/> < ProgressBar android:id="@+id/processPB" style="@style/Widget.AppCompat.ProgressBar.Horizontal" android:layout_width="match_parent" android:layout_height="wrap_content"/> < /LinearLayout>

Activity代码
public class SocketUploadActivity extends CommonActivity {private ProgressBar mProgressBar; private Button mPauseBtn; private Button mUploadBtn; private Socket mSocket; //要上传的文件名称 private String fileName = "temp.flv"; //标识是否开始写入 private boolean start = true; private int mCurrentLen; @Override public int layoutId() { return R.layout.activity_socket_upload; }@Override public void init() { mUploadBtn = (Button) this.findViewById(R.id.uploadBtn); mPauseBtn = (Button) this.findViewById(R.id.pauseBtn); mProgressBar = (ProgressBar) this.findViewById(R.id.processPB); }/** * 上传文件 */ public void upload(View view) { AssetManager manager = this.getAssets(); new Thread(() -> { try { start = true; if (mSocket == null || mSocket.isClosed()) mSocket = new Socket("10.0.2.2", 9988); InputStream inputStream = manager.open(fileName); //当前已经读取到的总长度 mCurrentLen = getSharedPreferences("socketConfig", MODE_PRIVATE).getInt(fileName, 0); int totalLen = inputStream.available(); //总长度 int currentRate = 0; byte[] buffer = new byte[1024]; int len; //读取文件 OutputStream outputStream = mSocket.getOutputStream(); BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream); //跳过已经传输了的宽度 inputStream.skip(mCurrentLen); //写入一行头信息,告知服务器一些关于文件的信息,这里仅仅写入了文件的名称,也可以写入其他的 bufferedOutputStream.write(("file=" + fileName + "\\r").getBytes("UTF-8")); //当用户没有暂停上传,并且还有数据的时候,写入数据 while (start & & (len = inputStream.read(buffer)) != -1) { bufferedOutputStream.write(buffer, 0, len); //计算已经写入了的长度 mCurrentLen += len; //计算已经上传了的比例 int rate = (int) Math.floor(mCurrentLen * 1.0 / totalLen * 100); //更新进度,更新进度条不需要返回UI线程,直接更新就行 if (rate > currentRate) { currentRate = rate; mProgressBar.setProgress(rate); L.e("Total Len=: " + totalLen + " Rate:" + rate); } } //释放资源 bufferedOutputStream.flush(); inputStream.close(); bufferedOutputStream.close(); outputStream.close(); } catch (IOException e) { e.printStackTrace(); } }).start(); }/** * 暂停写入,关闭Socket连接,将已经写入的长度进行存储,实现断点续传 */ public void pause(View view) throws IOException { start = false; //为了进行断点续传,将已经写入的数据的长度存储起来 SharedPreferences.Editor editor = this.getSharedPreferences("socketConfig", MODE_PRIVATE).edit(); editor.putInt(fileName, mCurrentLen); editor.apply(); mSocket.close(); } }

检验是否上传成功看一看我们长传过来的视频是否能够正常播放,如果能够就证明我们上传成功了。

    推荐阅读