Java高手速成 | 使用TCP进行手机文件传输
由于TCP是面向流的,这意味着接收端有可能会在一次接收动作中接收两个或者多个数据包,那么当发送方需要把一个大文件分批连续发送时,如何保证接收方能够正确地接收并重修组会成一个完整的文件显得十分重要,本节通过一个端到端的手机文件传输程序,详细阐述了如何使用多线程进行任意大小文件的TCP分批发送和接收。
TCP是面向流的。面向流是指无保护消息边界的,如果发送端连续发送数据,接收端有可能会在一次接收动作中接收两个或者更多的数据包。
举个例子来说,如果发送端连续发送三个数据包,大小分别是1KB、2KB、4KB,这三个数据包都已经到达接收端缓冲区中,如果使用UDP协议,无论接收缓冲区多大,都必须有三次接收动作才能把所有数据包接收完。而使用TCP协议,只要把接收缓冲区大小设置为7KB以上,就能够一次将所有数据包接收下来,即只需进行一次接收动作。
这是由于TCP协议把数据当作一串数据流,所以并不知道消息的边界,即独立的消息之间是如何分隔开的。这便会造成消息的混乱,也就是说不能保证一个Send方法发出的数据被一个Receive方法读取。例如,客户端发送的消息是:第一次发送abcde,第二次发送12345,服务器方接收的可能是abcde12345,即一次性接收完;也可能是第一次接收abc,第二次接收de123,第三次接收45。
针对这个问题,一般有3种解决方案:①发送和接收定长的消息;②把消息的尺寸与消息一块发送;③使用特殊的标记来区分消息间隔。
下面通过一个具体的例子——手机文件传输,来说明如何使用上面方法解决接收方接收发送方连续发送的数据。
【例1】编写基于TCP协议的手机文件传输程序。
本程序分为发送端程序FileSend_TCP与接收端程序FileAccept_TCP。接收端使用后台服务来监听与接收文件,并将接收到的文件存储在SD卡中。发送端程序调用Android系统内置的相册,让用户选取图片文件,程序运行效果如图1所示。
先来看发送端程序FileSend_TCP。发送端启用一个线程(SendThread)发送图片,为了帮助接收端对接收数据定界,数据组织为“文件名:文件长度:文件内容”。这样,接收方收到数据后可以根据“:”对各部分数据进行划分,通过文件长度对接收文件的内容定界。
FileSend_TCP的MainActivity.java:
程序中使用sendRunnable对象构造发送图像的线程,并在该线程中用Toast显示发送情况。由于Toast.show()方法是将文本显示在界面上,故它需要通过消息队列来完成。Android中使用Looper类封装消息队列,并为线程开启消息循环,默认情况下主线程会自动为其创建Looper对象,开启消息循环,但子线程是没有开启消息循环的。因此,在调用Toast的show()方法前,需要使用Looper.prepare()方法启用Looper,在调用Toast.show()方法后使用Looper.loop(),使得上述操作能够在消息队列中被处理。
服务器端接收数据采用Service组件,Service程序写在ListenService.java文件中,该服务启用一个监听线程(ListenThread)来监听客户端的连接,每当有连接进来则启用另一个接收线程(receiveThread)接收文件数据。
FileAccept_TCP程序的ListenService.java:
本示例同样要添加权限,两个程序还需添加对SD卡操作的权限。
最后,使分别运行FileSend_TCP和FileAccept_TCP程序的两个实体手机处于同一局域网中,在发送端FileSend_TCP程序界面的编辑框中输入接收端FileAccept_TCP的IP地址,从相册中选择图像后,就可以将图像发送到接收端的手机中了。
此外,本程序也可以在两个Android虚拟机中进行演示,如图1所示。首先开启两个虚拟机,让其中一个虚拟机,如emulator-5554运行接收端程序(服务器端),另一个虚拟机,emulator-5556运行发送端程序(客户端)。然后,打开Windows的控制台程序,在其中输入如下命令。
■ 图1 TCP传输文件在两个Android 9虚拟机上的运行效果
其中,adb是android debug bridge,即android调试桥的缩写,它是Android SDK中的工具,监视Socket TCP 5554和其他端口,以允许集成开发环境和虚拟机进行通信,通过它可以直接操作和管理Android模拟器或实体机。上述命令会将发送到TCP端口4567上的数据重定向到接收端设备emulator-5554的TCP端口4567上,其格式为:
在Android虚拟机中,127.0.0.1为虚拟机的地址,本地主机的地址映射为10.0.2.2,因此,最后我们在emulator-5556虚拟机中输入10.0.2.2,就可以通过上述重定向操作将发往本地主机TCP端口4567上的数据发送到emulator-5554设备的TCP端口4567上了。
- 点赞
- 收藏
- 关注作者
评论(0)