Exp7:基于TCP的Socket编程
⼀、实验⽬的
学习使⽤Stream Socket(包括ServerSocket和Socket)
了解粘包概念
⼆、实验任务
使⽤Socket和ServerSocket编写代码
解决粘包问题
三、实验过程
3.1 基础知识
3.1.1 Socket和ServerSocket交互过程
3.1.2 建⽴服务器端
- 服务器建⽴通信ServerSocket
- 服务器建⽴Socket接收客户端连接
- 建⽴IO输⼊流读取客户端发送的数据
- 建⽴IO输出流向客户端发送数据消息
3.1.3 建⽴客户端
创建Socket通信,设置通信服务器的IP和Port
建⽴IO输出流向服务器发送数据消息
建⽴IO输⼊流读取服务器发送来的数据消息
3.2 ⼩试⽜⼑: TCP传输案例
在src⽂件夹下新建⼀个Package,名为exp7
- 编写TCPServer类
1 | package com.company; |
- 编写TCPClient类
1 | package com.company; |
task1:
分别运⾏Server端和2个Client端,请将运⾏结果附在实验报告中。
当我们不使用while(true)时,tcpserver只接受一个客户端传来的信息,如下图所示:
那么,当sever一直开启时:我们在main函数中创建多个client线程,向服务器发送连接请求:
1 | package com.company; |
task2:
⽤下段代码修改TCPServer中// todo task2:replace 后的三⾏,试运⾏Server端和Client端,观察运⾏结果,并将实验结果及产⽣的原因附在实验报告中
1 | int len; |
我们发现TCPServer和TCPClient已经建立了连接,但是在处理客户端信息时出现了错误,导致一直卡在哪儿不能继续运行下去。
1 | int len = inputStream.read(bytes); |
经过 测试,发现当 去掉While循环,便可以正常执行。
但这并不是一种好方法,因为当输入的 inputStream 信息很庞大,但是我只能读取前1024个字节。
其实,这里while循环不结束的原因是在client里面,当送出 outputStream之后,并没有关闭socket。因此server就会误以为后面还会有信息进来,因此一直在等待。虽然在client最后关闭了outputStream,但是那是在收到了server回传的信息之后了,这就形成了一个死锁。要解决这个问题,就要将这个socket关闭。 :
1 | try { |
关闭后,恢复正常:
task3:
使⽤Scanner修改TCPClient类,达成如下效果,请将实验结果附在实验报告中:
1 | client reads a line of characters from its keyboard and sends data to server |
只需要修改client即可;
1 | Scanner scanner = new Scanner(System.in); |
task4:
在task3的基础上继续TCPServer类和TCPClient类,Client端能够读取多⾏从控制台输⼊的数据分别发送,Server端收到后分别回写(请测试数据为中⽂时的情况),请将实验结果附在实验报告中。
- Server
1 | package com.company; |
- Client
1 | package com.company; |
3.3 什么是粘包
- TCP本质上并不会发⽣数据层⾯的粘包
- TCP的发送⽅和接收⽅⼀定会确保数据是以⼀种有序的⽅式到达的
- 所谓的粘包是数据处理逻辑层⾯上的粘包,即应⽤层上的
task5:
查阅资料,分析粘包可能产⽣的原因并搜索若⼲种解决⽅法(>=3),并附在实验报告中。
在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小、数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。
粘包出现原因
- 发送端需要等缓冲区满才发送出去,造成粘包
发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
- 接收方不及时接收缓冲区的包,造成多个包接收
接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
为了避免粘包现象,可采取以下几种措施:
(1)对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;但是但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。
(2)对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;但是第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包
(3)由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。但是第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。比如说认为确定一个消息边界。