零蚀
前言
-
前言
- 经过前一章的“教训”,我觉得后面再这么学下去就废了,因为只是API,我也不是来背单词的,看了等于白看,怎么能就这?所以从原理入手了,当然不是源码。源码也不能反映什么,学网络当然还是从网络基础开始,这章会从TCP-IP的学习层面打开。
-
步骤
- step 1 :了解TCP-IP原理
- step 2 :Socket基于TCP的操作和选择,以及原因。
-
回顾
- 首先我们要知一些非常基础的事情,比如,IP是传输的协议,它就像我们搬砖的程序员,他只是将一些零零碎碎的功能拼凑成一个完成的APP,至于这个功能有多烂,有多好,他不知道,他只是负责传输数据,他就是一个勤劳的搬运工,其次是我们的TCP和UDP,他们是一个架构师,他们在网络层,他们把设计好的,经过优化的功能组件交给IP,IP就开始了搬砖。这就是TCP,UDP,IP的简单关系。
UDP & TCP
-
UDP
-
UDP是一种不可靠的,面向无连接的,这个解释就是应用将UDP下的数据传由IP层发送出去,但是不保证到目的地,这就是面向无连接(数据没有明确的接盘侠)。它的数据格式主要是由下面这样构成。这里的检验和是不必要的(可选项),因为UDP是不安全的,如果检验和不对,数据说明有问题,直接就丢数据了(而且是没有人知道的丢弃,不会产生任何的错误报文),应用层也只会接收到完整的正确的数据才会进行下一步处理。
-
不是说UDP是无连接的,不安全吗,为什么要检测数据的正确性呢,其实在传输的过程中,路由器的软硬件,这会对UDP的数据报产生一些偏差,导致大量的丢包,导致传输失败。但是如果没有这种检测机制,会导致数据发生差错但是检测不出来(这种检验和我们可以通过socket检测出来)。
-
-
TCP
- TCP提供了全双工的通讯模式,所以为了保障两个方向上的独立传输,所以每一端要保证每个方向上的序号,这里SYN为标志,新建时SYN =1 ,发送第一个字节 SYN+1,连接建立后ACK =1 ,TCP的报文如下,序号表示这段报文中第一个数据的字节,
-
简单介绍一下6个标志
- URG : 紧急指针有效
- ACK : 确认序号有效
- PSH : 接收方应该尽快将报文交给应用层。
- RST : 重新建立连接
- SYN : 同步序号用来发器一个连接
- FIN : 发端完成发送任务
-
TCP的检验和覆盖整个TCP报文,这是一个强制字段,由发端计算和存储,由收端验收,计算方式这里不谈。
-
三次握手
- 三次握手应该是一种比较容易被问到的常识,第一次是发送链接请求,后面两次分别是服务器和客户端对对方的一种确认机制。它的简单版流程如下:
-
四次挥手
- 四次挥手,客户端请求关闭连接也是同样的逻辑,第一次是客户端发送关闭连接请求,然后服务端表示确认,然后随后发送一个文件结束符,表示没有文件传输了,然后客户端返回一个确认(防止数据传输有误,而断连),服务器关闭应用,然后这里除了这个情况还有半关闭,这主要针对shutdown命令,半关闭指可读不可发。
Socket
-
基于TCP的Socket
-
现需要了解什么是长连接,短连接。创建连接的内容之前已经说了,这里回介绍一下长/短连接,短连接就是传输数据结束后就会关闭连接,每次传输数据都要重新创建连接。长连接是复用当前连接,达到多次通信的目的。当然这存在于有连接情况的TCP中,而连握手都没有的UDP是没有连接这一概念。
-
长连接的优缺点:
-
优点:长连接只需要创建连接一次,然后就可以不断的通信,这样减少握手时间,提高了运行程序的效率。
-
缺点:服务端的连接一直存在所以回存在多个socket对象,所以回大量的占用资源。
-
-
短连接的优缺点:
-
优点:短连接服务端不需要保存多个socket对象,减少内存占用率
-
缺点:每次传输数据都需要握手,比较耗时间。
-
-
如此来构建一个简单的Socket数据传输的代码案例(短连接)。
- 服务端,服务端的代码这里我获取了一下本地的IP,并设计好数据几首好的端口,accept会一直阻塞着,直到有客户端搭建连接。并把获取到的数据进行打印。
public class Server { public static void main(String[] args) throws Exception { InetAddress inetAddress=InetAddress.getLocalHost(); System.out.println("本地地址:"+inetAddress.getHostAddress()); ServerSocket server=new ServerSocket(8765); Socket accept = server.accept(); InputStream inStream = accept.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inStream)); String getString=""; while (!"".equals(getString=reader.readLine())&&getString!=null){ System.out.println(getString); } reader.close(); accept.close(); server.close(); } }
- 客户端,客户端这里用了两种连接方式,代码里用的方式比注释中只是多了一个超时时间设置。
public class Client { public static void main(String[] args) throws IOException { // Socket client=new Socket("192.168.157.89",8765); Socket client=new Socket(); InetSocketAddress inetSocketAddress = new InetSocketAddress("192.168.157.89", 8765); client.connect(inetSocketAddress,3000); // 设置3s延迟 OutputStream outputStream = client.getOutputStream(); outputStream.write("客户端发了一句话".getBytes()); outputStream.close(); client.close(); } }
-
当然这里如果客户端只是和服务器构建连接,但是Server并没有接收到Client端发送来的消息,依旧会在read方法处阻塞,等到客户端发来数据后,服务端才会接收到流中的数据。如果客户端没有发送消息,会一直阻塞,所以这里的read方法也有阻塞效果。这里可以在发送前加
Thread.sleep()
进行验证。(我们可以以此设计长连接) -
有一点我们需要注意的是,当调用 java.net.SocketinputStream类的 close()方法时, 顺便也将 Socket (套接字) close()关闭。
-
ServerSocket(int port,int blocklog)
这里的blocklog是限制client的数量,默认值为50。
-
我们可以通过bind将一个socket绑定到一个地址上(isBound测试),只有绑定后我们才可以通过socket进行本地IP地址信息的获取(getlnetAddress)。
-
setReceiveBufferSize (int sizeKB)
设置缓冲区的大小,和接受的窗口大小,如果我们的数据缓冲区需要大于64KB的话,必须通过这个方法进行设置, -
socket.shutdownInput()
半读设置,这种设置会在输入流的末尾加上EOF,文件结束符的标志,那么传过来了的数据都会被丢弃,这就导致了别人可以读自己的out,但是自己不能读另一方发送的数据。 -
socket.shutdownOutput()
半写设置,与半读设置功能类似,但是半写设置,在设置后调用写操作(write),会引发异常。
-
-
Socket选项
-
当我们关闭了socket其实我们的操作系统是不会马上释放掉这个端口,这个时候这个连接会处于超时状态(TIME_WAIT),这个时候我们的socket是不可以将套接字绑定到这个socketAddress上的,所以我们可以使用
getReuseAddress
和setReuseAddress(boolean)
进行对端口的复用。这样可以提高端口的利用率。 -
setTcpNoDelay(boolean)
是否屏蔽Nagle算法,这个方法主要是应对数据包小而量大的情况,当数据包小的时候他会进行缓存,等到最大报文长度(MSS)才发送,(ACK服务器延时机制,服务器获取数据后,回等待一段时间,确认ACK后和数据一起发送给客户端)。这个方法的应用场景:如果是比较依赖实时性的数据传输,就可以设置为true,如果不依赖实时性,可以将其设置为false,这样Nagle算法可以减少开销(过于频繁的发送小包也会导致网络拥堵)。
-
setSendBufferSize(int)
设置发送的缓冲区大小。 -
setSoLinger(boolean,int)
设置逗留时间,之前我们说过socket在close之后不会立即关闭输出,所以有端口复用,但是这里不会立即关闭的原因是因为缓冲区中可能还有数据需要发送,带发送完才会进行关闭。所以就有了逗留时间,具体如下:-
如果boolean为false(默认),发送完在关闭连接。
-
如果boolean为true,int =0 ,如果调用close回立刻放弃缓存区内所有的数据,关闭连接
-
如果boolean为true,int <= 65535s,close()会发生塞,然后等待缓冲区数据发送完毕不再阻塞关闭连接,如果数据没有结束,会发送 RST标记,然后算开链接。
-
-
accept.setSoTimeout(ms)
,在上面代码里,客户端我们是使用的client.connect进行连接超时的设置,而setSoTimeout(ms)
是输入流read的超时时间,如果超时回抛出异常。 -
setKeepAlive(boolean)
socket保活机制(测试),这种机制如果双方在一定的时间没有任何数据包,那么双方会发送一个ACK检测包,来测试对方的连接是否还有效,如果失效就断开连接,但是如果客户端没有设置,一旦发生断网等事件,服务器是无法知道客户端的连接是否失效。 -
setTrafficClass(int tc)
设置数据传输质量- 0x10:(ip中IPTOS_LOWCOST类型),最小延迟,传输数据的速度快,把数据快速送达目的地
- 0x02:(ip中IPTOS_LOWCOST类型),发送成本低,
- 0x04:(ip中IPTOS_RELIABILITY类型),高可靠性,保证把数据可靠地送到目的地
- 0x08:(ip中IPTOS_THROUGHPUT类型),最高吞吐量, 一次可以接收或者发送大批量的数据
-