前言: 最近又看到了关于TCP长连接和短连接的概念,以前也看过Http长连接和短连接的概念,因为Http是建立在TCP协议之上的,所以它其实是依赖TCP的长连接和短连接。所以,我就萌生了一个想法,看看这两种方式的传输效率上到底有多大差别——实践出真知!或者 Takl is cheap, show me your code!

TCP长连接和短连接

长连接是指可以实现服务端和客户端之间连续的传输数据,在传输的过程中,连接保持开启,并不中断,数据传输完成后连接不关闭。
短连接是指当服务器端和客户端连接成功后开始传输数据,传输完成即关闭连接。如果需要再次传输数据,则需要在创建新的连接进行数据传输。

如果你没有系统的学习过计算机网络的话,可能感觉这样似乎没有什么区别,这里我简单说明一下:
传输时间 = 建立连接的时间 + 传输数据的时间

TCP 建立连接需要进行三次握手,如果频繁的传输数据并且采用短连接的形式,则会频繁的建立连接,降低了传输效率。但是如果采用长连接的形式,则只需要建立一次连接,就可以传输多次数据了。当然了,好处有,坏处也是少不了的。短连接传输完成即关闭,服务器不需要保存连接对象,对于服务器的压力较小;长连接则需要保持多个连接对象(很多客户一起使用,需要保持他们的连接),对于服务器的压力较大,并且长连接如何处理数据也是一个问题了——传输数据本质上就是字节流,如果连续的传输字节流,你如何知道哪一部分是属于哪一个文件的呢?

如果你对这个问题,感兴趣的可以参考我这篇博客,应该会对你有一些启发,这是我个人的一种解决方案。
单个TCP(Socket)连接,发送多个文件

测试用例

我这里使用六个图片文件作为测试,对于短连接就是连续发送六次数据;对于长连接是发送一次数据。

测试图片的大小

TCP 短连接测试

TCP短连接客户端

package short_tcp;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;public class ShortClient {private static final String host = "127.0.0.1";private static final int port = 10000;public static void main(String[] args) {// 存放需要传输文件的目录Path path = Paths.get("D:", "DragonFile", "tcp", "targetFile");// Files.find() 和 walk() 方法似乎使用起来更加简单,但是它传递的参数,// 我暂时还不清楚,那就不用了,免得引入额外的麻烦。File dir = path.toFile();// 获取该目录下的所有文件File[] files = dir.listFiles();// 创建短连接客户端对象ShortClient shortClient = new ShortClient();// 连续发送6个文件System.out.println("Start data transfer...");long start = System.currentTimeMillis();for (File file : files) {shortClient.send(file);}System.out.println("6个文件使用tcp短连接的方式发送结束,总耗时:" + (System.currentTimeMillis()-start) + " ms。");System.out.println("End of data transmission!");}public void send(File file) {// 创建一个 Socket 客户端try (Socket client = new Socket(host, port)) {// 获取输出流,用于发送数据OutputStream output = new BufferedOutputStream(client.getOutputStream());// 获取输入流,用于接收服务器的响应数据InputStream input = new BufferedInputStream(client.getInputStream());// 每次实际读取到的字节数int len = 0;// 每次读取1024字节的数据byte[] b = new byte[1024];// 创建一个文件输入流,用于获取文件数据,并使用输出流发送到服务器try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {while ((len = in.read(b)) != -1) {output.write(b, 0, len);}// 强制刷新输出流,防止阻塞output.flush();}// 关闭输出流,注意可不能使用 output.close()!它会把整个套接字关闭的!// 关于它会不会刷新输出流,我没有找到资料,当我感觉应该是会的,但是感觉不靠谱,// 上面我仍然手动刷新输出流吧!client.shutdownOutput(); // 读取服务器的响应信息len = input.read(b);// 注意如果 len 为 -1,会导致 RunTimeExceptionSystem.out.println(new String(b, 0, len, StandardCharsets.UTF_8));} catch (IOException e) {e.printStackTrace();}}
}

TCP短连接服务端

package short_tcp;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.UUID;public class ShortServer {private static final int port = 10000;public static void main(String[] args) {new ShortServer().start();}public void start() {try (ServerSocket server = new ServerSocket(port)) {// 这里不使用多线程,因为我主要关注点在于创建socket连接的开销while (true) {System.out.println("Waitting for client to connect...");try (Socket client = server.accept()) {InputStream input = new BufferedInputStream(client.getInputStream());OutputStream output = new BufferedOutputStream(client.getOutputStream());/* 统一使用 .jpg 扩展名了,这里要指明一点,由于传输的只是文件的数据部分,* 所以我是无法知道该使用什么扩展名的,当然了,是可以把其它信息传输过来,* 但是那样处理回麻烦一点。* */// 使用 UUID 生成随机的文件名String filename = UUID.randomUUID().toString() + ".jpg";// 实际读取到的字节数int len = 0;// 每次读取1024字节byte[] b = new byte[1024];// 创建一个输出流,保存接收到的文件数据try (OutputStream out = new BufferedOutputStream(new FileOutputStream(new File("D:/DragonFile/tcp/short_tcp", filename)))) {// 我在客户端使用了关闭了输出流,这里是可以使用 -1 来判断的!while ((len = input.read(b)) != -1) {out.write(b, 0, len);}// 这里不需要手动刷新,因为 try-with-resource // 语句会自动关闭流,而关闭流的时候,会调用flush方法的。// 注意这些细节问题。}output.write("The data has been received successfully".getBytes(StandardCharsets.UTF_8));output.flush();     // 如果这里不进行手动刷新,会导致客户端读取到 -1 的异常!} catch (IOException e) {e.printStackTrace();}}} catch (IOException e) {e.printStackTrace();}}
}

说明:关于这部分的代码,就是很普通的socket传输文件,只是调用多次,传输多个文件而已。只要是学习过Socket网络编程的,基本上都没有问题了。

测试结果

服务器端

客户端

说明
第一次建立Socket连接的过程不算,因为它耗时太多了,但主要并不是传输的时间。我们看后面三次,分别是25ms、25ms、25ms,平均时间也就是25ms了我这里不是精确计算,只能做一个大致的估计。

传输文件结果

传输文件的大小
这里只看总的大小了(虽然应该看每一个的大小或者md5值,但是应该还是没有问题的!)

TCP 长连接测试

这里长连接,就是一次性把六个文件的数据,通过一次连接传输过去,多个文件一起传输,如果不携带额外的控制信息的话,那么文件传输是没有意义的,因为谁也无法从字节流里面把文件分出来,这是一个数学上的问题了。不过它和我以前这篇博客倒是有些相似了,如果只有你自己知道,就可以去做一些有趣的事情了。文件合并(图片+视频),修改md5值,隐藏文件

所以,需要除了文件本身的字节数据以外的控制信息,这里我定义一个简单的协议。协议包含一个头部和数据部分,头部是8个字节的长度信息,数据部分就是相应长度的数据信息了。它很简单,不过在这里也足够用了。 具体可以参考我最上面推荐的博客,并且我的注释也写得很详细了。

TCP长连接客户端

package long_tcp;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;public class LongClient {private static final String host = "127.0.0.1";private static final int port = 10000;public static void main(String[] args) {// 存放需要传输文件的目录Path path = Paths.get("D:", "DragonFile", "tcp", "targetFile");File dir = path.toFile();// 获取该目录下的所有文件File[] files = dir.listFiles();// 创建长连接客户端对象LongClient longClient = new LongClient();System.out.println("Start data transfer...");long start = System.currentTimeMillis();longClient.send(files);System.out.println("6个文件使用tcp长连接的方式发送结束,总耗时:" + (System.currentTimeMillis()-start) + " ms。");System.out.println("End of data transmission!");}public void send(File[] files) {try (Socket client = new Socket(host, port)) {// 获取输出流,用于发送数据OutputStream output = new BufferedOutputStream(client.getOutputStream());// 获取输入流,用于接收服务器的响应数据InputStream input = new BufferedInputStream(client.getInputStream());// 每次实际读取到的字节数int len = 0;// 每次读取1024字节的数据byte[] b = new byte[1024];for (File file : files) {// 获取文件的长度long length = file.length();// 先将文件的长度写入socketoutput.write(this.long2Byte(length));try (InputStream in = new BufferedInputStream(new FileInputStream(file))) {// 然后写入文件数据while ((len = in.read(b)) != -1) {output.write(b, 0, len);}}// 手动刷新输出流output.flush();// 读取服务器响应len = input.read(b);// 打印服务器响应数据System.out.println(new String(b, 0, len, StandardCharsets.UTF_8));}// 关闭输出流client.shutdownOutput();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}// 使用长度为8个字节的数组保存long的值public byte[] long2Byte(long length) {byte[] b = new byte[8];b[0] = (byte) (length >>> 56);b[1] = (byte) (length >>> 48);b[2] = (byte) (length >>> 40);b[3] = (byte) (length >>> 32);b[4] = (byte) (length >>> 24);b[5] = (byte) (length >>> 16);b[6] = (byte) (length >>> 8);b[7] = (byte) (length >>> 0);return b;}
}

TCP长连接服务端

package long_tcp;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.UUID;public class LongServer {private static final int port = 10000;public static void main(String[] args) {new LongServer().start();}public void start() {try (ServerSocket server = new ServerSocket(port)) {// 这里因为只有一次连接,我其实是不需要使用死循环的,但是为了保证统一,还是使用吧while (true) {System.out.println("Waitting for client to connect...");try (Socket client = server.accept()) {InputStream input = new BufferedInputStream(client.getInputStream());OutputStream output = new BufferedOutputStream(client.getOutputStream());// 读取8个字节的长度,这里因为情况比较特殊,它是一定能读取到8个头的,// 至于其它情况,暂时不考虑,就认为它一定能读取完整的8个字节,但是下面的1024就不一定了。byte[] head = new byte[8];// 读取数据部分byte[] data = new byte[1024];// 这里使用一个死循环来读取,记得要正确退出!while (true) {// 我这里认为它一定可以读取满8个字节的,因为我是本机测试,基本上没有问题的,// 对于特殊的情况,我们不去考虑它,注意我们的关注点是传输的效率问题!if (input.read(head) == -1) {break;  //如果为 -1,说明流已经关闭了}// 我这里转成int,因为我测试使用的图片不是太大,这样处理也方便一些int length = (int) byte2Long(head);System.out.println("length:" + length);String filename = UUID.randomUUID().toString() + ".jpg";try (OutputStream out = new BufferedOutputStream(new FileOutputStream(new File("D:/DragonFile/tcp/long_tcp", filename)))) {int len = 0;int hasRead = 0;while ((len = input.read(data)) != -1) {out.write(data, 0, len);hasRead += len;// 当文件最后一部分不足1024时,直接读取此部分,然后结束,文件已经读取完成了。int remind = length - hasRead;if (remind < 1024 && remind > 0) {byte[] b = new byte[remind];len = input.read(b);  // 这里没有特殊原因,它也是可以读取remind长度的。out.write(data, 0, len);System.out.println("remind:" + remind + " len:" + len);break;   // 一个文件的数据已经读取完成了,及时退出该次读取} else if (remind == 0) {// 这里我有点疏忽了,没有考虑,文件小于1024字节时的读取break;}}// 响应客户端接收文件成功的数据output.write("The data has been received successfully".getBytes(StandardCharsets.UTF_8));output.flush(); // 刷新输出流}}}}  } catch (IOException e) {e.printStackTrace();}}public long byte2Long(byte[] b) {        return (((long)b[0] << 56) +((long)(b[1] & 255) << 48) +((long)(b[2] & 255) << 40) +((long)(b[3] & 255) << 32) +((long)(b[4] & 255) << 24) +((b[5] & 255) << 16) +((b[6] & 255) <<  8) +((b[7] & 255) <<  0));}
}

测试结果

客户端

服务端
注:这里我额外打印了获取到的文件的长度,因为长连接的处理比较麻烦,稍微有些问题没考虑到就不行了。

传输的文件
注意:不要看到文件就认为成功了,文件的大小也必须是一样的,即传输前和传输后的大小是一样的,更为严格的要求是文件的md5值是一样的,我这里为了简单期间就只看大小了。
传输的文件

传输文件的大小

说明
第一次建立Socket连接的过程不算,因为它耗时太多了,但主要并不是传输的时间。我们看后面三次,分别是23ms、22ms、25ms,平均时间也就是23.33ms了我这里不是精确计算,只能做一个大致的估计。

结果分析

对于第一次建立连接发送数据,时间都是非常长的。但是对于这里到底是哪里耗时这么多,我暂时也不太清楚,它可能属于底层连接的问题了。我只比较后面三次的时间,但是感觉似乎差距也不是很大,顶多也就快了1ms多,但是它实在是太小了,让人觉得似乎长连接的效率也就是那样了!按照我一开始的理解,它应该会有一定的效率提升的。 所以,一定是哪里设计的不够合理,让我们来分析一下,对于整个传输过程来说,耗时主要是建立连接+传输数据了。但是如果数据量特别大,那它所占据的耗时百分比就越大,所以我这里传输的文件似乎太大了。反而掩盖了长连接所带来的减少建立连接的时间。比如传输10m文件,无论是长连接还是短连接效率上应该都是没有什么区别了,特别是网络特别慢的情况下,整个耗时都可以看做是传输耗时了,对于建立连接的耗时可以忽略不计了! 而且长连接其实还是比短连接多传输了一些控制信息,不然它是无法处理一个数据流为多个文件的。

新的测试文件

文件内容:
看我引经据典来写几句话吧,哈哈。

文件大小:
这次文件是很小了,保证了一次即可传输完成,这样应该就可以突出建立连接的时间了。

复制一下,还是传输六个文件

TCP短连接测试结果

这次我除了第一次外,再测试五次,然后统计这五次的平均时间。


总时间:17ms + 18ms + 19ms + 20ms + 18ms = 92ms
平均时间:92ms/5 = 18.4ms

TCP长连接测试结果



总时间:14ms + 16ms + 17ms + 17ms + 16ms = 80ms
平均时间:80ms/5 = 16ms

总结

这里虽然使用了小文件,但是似乎效果也不是很理想,因为我可能没有考虑到在本机进行环回测试,其实网速是很快的,所以其实我上面的图片也不算是大文件了。不过,我使用大文件效果是更不理想的,这样也算是可以了。
通过上面的比较,可以发现:平均每次传输可以减少2ms的时延,大概提高了 2ms/18ms = 11%。当然了,随着文件的增大,整个效率是会逐渐减少的,甚至于没什么效果了。但是,我们这里是应该可以认为,长连接确实是可以提高一定的效率的,特别是在含有大量的连接,但是传输的数据量特别少的情况下,它是很有效的,特别是某些应用广泛的协议——HTTP协议。 通常使用HTTP的客户端,都是属于数据量很小的传输,我想效果应该是有的,虽然长连接的处理也比较麻烦了。

PS:
比如,当我传输小文件时,代码无法正常工作了,直接卡死了。所以我就把服务器关闭了,最后传输花费了20357ms。问题主要出在在字节流中取出一个文件时发生的,我的注释里面写了详细情况,也可以看我推荐的博客。长连接的处理比较麻烦,我这里是一种简化的模拟,但是也要面对一些不好处理的问题!

TCP长连接和短连接代码及其比较相关推荐

  1. [转]TCP(HTTP)长连接和短连接区别和怎样维护长连接

    原文链接 一.HTTP协议和TCP协议 HTTP的长连接和短连接本质上是TCP长连接和短连接.HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议.IP协议主要解决网络路由和寻址问题, ...

  2. 分析 HTTP,TCP 的长连接和短连接以及 sock

    作者:zhoulujun 链接:https://www.zhoulujun.cn/html/webfront/SGML/web/2015_1016_317.html 1 HTTP 协议与 TCP/IP ...

  3. tcp、http协议的长连接和短连接

    转载:http://www.cnblogs.com/onlysun/p/4520553.html 当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当 ...

  4. http,tcp的长连接和短连接

    http长连接和短连接 短连接:客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接. 长连接:从HTTP/1.1起,默认使用长连接,用以保持连接特性.使用长连接的HTTP协议,会 ...

  5. HTTP、TCP、Socket、长连接、短连接的概念和原理及其区别

    文章目录 HTTP Scoket HTTP协议和TCP/IP的关系 Socket与TCP/IP的关系 Socket与HTTP的关系 TCP/IP协议分层 长连接和短连接 长连接和短连接的优点和缺点 短 ...

  6. TCP协议的长连接和短连接详解

    一.前言 TCP在真正开始进行数据传输之前,Server 和 Client 之间必须建立一个连接.当数据传输完成后,双方不再需要这个连接时,就可以释放这个连接. TCP连接的建立是通过三次握手,而连接 ...

  7. TCP协议-长连接和短连接

    一 前言 TCP在真正开始进行数据传输之前,Server 和 Client 之间必须建立一个连接.当数据传输完成后,双方不再需要这个连接时,就可以释放这个连接. TCP连接的建立是通过三次握手,而连接 ...

  8. TCP(HTTP)长连接和短连接区别和怎样维护长连接

    一.HTTP协议和TCP协议 HTTP的长连接和短连接本质上是TCP长连接和短连接.HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议.IP协议主要解决网络路由和寻址问题,TCP协议 ...

  9. python 长连接_TCP长连接和短连接 Python代码

    TCP在真正的读写操作之前,server与client之间必须建立一个连接, 当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接, 连接的建立通过三次握手,释放则需要四次握手, 所以说每个连 ...

最新文章

  1. 十五天精通WCF——第十四天 一起聊聊FaultException
  2. Android manifest属性总结
  3. Django开发准则与最佳实践
  4. 全站 HTTPS 就一定安全了吗?
  5. [蓝桥杯][算法提高VIP]贪吃的大嘴(多重背包)
  6. pandas dataframe根据筛选结果修改值
  7. Java序列化后isXXX变量没有了
  8. win10下安装Ubuntu18.4双系统(适合小白)
  9. 怎样在百度地图上画圈_知识地图分享:你不是记忆差,你只是没找对方法
  10. linux卸载apk命令,apk的安装和卸载 - Jenly的个人空间 - OSCHINA - 中文开源技术交流社区...
  11. macbook快捷键_MacBook 键盘的「fn」键有什么用
  12. JavaScript:typeof的用法以及undefined和null的区别
  13. 阿里Sentinel支持Spring Cloud Gateway的实现
  14. 中国纺织行业前景动态分析与投资战略研究报告2022-2028年
  15. c++项目开发——吃豆子游戏
  16. android studio安装SDK时无法勾选
  17. 分析项目-AQI分析与预测
  18. 6、T5L DGUS ll 应用开发指南摘录(一)
  19. xmanager linux 远程桌面,Xmanager连接CentOS的远程桌面
  20. 股票波段操作的技术缺陷在哪里?

热门文章

  1. 互联网早报:哈啰科技发布新款助力车“白鸥”、小哈能量站等多款新产品
  2. 计算机组成fc fz是什么,计算机组成原理实验二
  3. 开源了个人开发的一个微信小程序,提供共享场景服务,支持图片和小视频
  4. VOS客户端上服务器和软交换状态显示红灯的原因分析
  5. react-native打包图片资源的大坑啊
  6. [转载]受 Windows XP SP2 影响的软件列表
  7. 测试分析之从用户价值角度设计测试点
  8. android编程xml动画,Android中xml设置Animation动画效果详解
  9. 腾讯云运维工程师认证TCA原题(含解析)
  10. Python 办公效率化学习(自学)三.Excel文件读取