赞
踩
各占2个字节,端口是传输层和应用层的服务接口,用于寻找发送端和接受端的进程,一般来讲,通过端口号和IP地址,可以唯一确定一个TCP连接,在网络编程中,通常被称为一个socket接口。
占四个字节,用来标识从TCP发送端向TCP接收端发送的数据字节流。 因此,如果数据量过大,超过2^32 那么TCP报文就不再发送。
占四个字节,包含发送确认的一端所期望收到的下一个序号。因此,确认信号应该是上次已经成功收到数据字节 序号+1 。 该字段只在ACK标志被设置时才有效。
占4bit ,用于指出TCP首部的长度,若不存在此项,则默认为20字节 ,数据偏移的最大值为60字节((2^4-1)*4)。首部长度实际上也指示了数据区在报文段中的起始偏移值。
占6bit,暂时忽略,值全为0。
占两个字节,用于流量控制和拥塞控制,表示当前接收缓冲区的大小。在计算机网络中,通常是用接收方的接收能力的大小来控制发送方的数据发送量。TCP连接的一端根据缓冲区的大小来确定自己的接收窗口值,并且将其告诉对方,使对方可以确定发送数据的字节数。
占两个字节,范围包括首部和数据两部分。 用于错误检查。源主机基于部分IP头信息,TCP头信息和数据内容计算一个校验和,目的主机也要进行相同的计算,如果收到的内容没有错误过,两个计算应该是完全一样,从而证明数据的有效性。
占两个字节,指向数据段内的最后一个字节位置,这个字段只在UDP被设置时才有效。
是可选的,默认情况不选。
这个字段中加入额外的0,以确保TCP头部是32的整数倍。
客户端TCP向服务端TCP发送一个特殊的TCP报文段,不包含应用层数据。但是报文段首部的SYN标志 会被设置为1,表示此报文段为建立连接的报文段,因此为称为SYN报文段 。另外,客户端会选择一个初始序号,记录此报文段的序列号seq=x 。该报文段会封装在一个IP数据报中被发送到服务器端。这个报文段表达的就是希望建立连接的信息。此时,客户端处于SYN-SENT 阶段。
一旦包含SYN报文段的IP数据报到达服务器主机,服务器从IP数据报中提取TCP-SYN报文段,为该TCP连接分配需要的缓存和变量,并向客户端发送表示允许连接的报文段。这个报文段也不包含任何应用层数据,但是包含 三个 重要信息:首先,SYN=1 ,其次该报文段确认序号标志ACK=1,确认序号ack=x+1 最后服务器选择自己的初始序号seq=y , 这个报文表达的就是允许建立该连接,自己的初始序号为y,有时也被称为SYNACK报文段。 此时服务器处于SYN-RCVD (RCVD=received=收到)状态。
在收到SYNACK 报文段之后,客户端也要给该连接分配缓存和变量,客户端向服务器再发送一个报文段,对允许连接的报文段进行确认。ACK=1,ack=y+1,seq=x+1 。 并且由于连接已经建立,SYN=0 ,并且已经可以携带被传送到服务器的应用层数据 。 服务器接收到第三次握手消息后,处于ESTAB-LISHED状态,处于ESTAB-LISHED状态就可以进行双向通信。
为了这种情况:B向A发送FIN=1的释放连接请求,但是这个报文丢失了,A没有接到不会发送确认信息,那么B就会超时重传,这时A在WAIT-TIME 还能够接收到这个请求,这时在回复一个确认就行了。(A收到FIN=1的请求后WAIT-TIME会重新计时)
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个FIN只意味着这个方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
TCP协议的连接是全双工连接 ,一个TCP连接存在双向的读写通道。
简单来说就是先关读,后关写 ,一共需要四个阶段。以客户机发起关闭连接为例:
从上面可以看到,主动发起关闭连接操作的一方将达到TIME-WAIT 状态,而且这个状态要保持Maximum Segment Lifetime(生存时间)的两倍时间。为什么要这样做,而不是直接进入CLOSED状态?
上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况。
流量控制针对的是点对点之间的(发送方和接受方)之间的速度匹配服务,因为接收方的应用程序读取的速度不一定很迅速,而接受方的缓存是有限的,就需要避免发送的速度过快而导致的问题。
拥塞控制是由于网络中的路由和链路传输速度限制,防止过多的数据注入网络中,要避免网络的过载而进行的控制。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程 ,涉及到所有的主机,路由器,以及与降低网络传输性能有关的所有因素。
拥塞控制是使用拥塞窗口进行控制的,运行在发送方的拥塞控制机制通过跟踪一个额外的变量,即拥塞窗口(cwnd) ,它对于发送方能够快速向网络中发送流量的速率进行了限制,对于发送方: LastByteSent-LastByteAcked<=cwnd
需要获得网络内部流量分布的信息。在实施拥塞控制之前,还需要在结点之间交换信息和各种命令,以便选择控制的策略和实施控制。这样就产生了额外的开销。拥塞控制还需要将一些资源分配给各个用户单独使用,使得网络资源不能更好地实现共享。
滑动窗口实现了TCP流控制首先明确滑动窗口的范畴:TCP是全双工的协议,会话的双方都可以同时接收和发送数据。TCP会话的双方都各自维护一个发送窗口 和一个 接收窗口 。各自的接收窗口大小取决于应用,系统,硬件的限制 (TCP传输速率不能大于应用的数据处理速率)。各自的发送窗口则要求取决于接收窗口大小。
滑动窗口解决的是流量控制的问题,就是如果接收端和发送端对数据包的处理速度不同,如何让双方达成一致。接收端的缓存传输数据给应用层,但这个过程不一定是即时的,如果发送速度太快,会出现接收数据overflow,流量数据是用来解决这个问题的。
发送方的发送缓存内的数据可以被分为四类:
接收方的缓存数据分为三类:
窗口大小代表了设备一次能处理多少数据,之后再传给应用层。缓存传给应用层的数据不是乱序的,窗口机制保证了这一点。现实中,应用层可能无法立刻从缓存中读取数据。
TCP滑动窗口技术通过动态改变窗口大小来调节两台主机间的数据传输。每个TCP/IP主机支持全双工数据传输,因此TCP有两个滑动窗口:一个用来接收数据,另一个用来发送数据。TCP使用ACK确认技术,其确认号指的是下一个所期待的字节。假定发送方设备以每一次三个数据包的方式发送数据,也就是说,窗口大小为3。发送方发送序列号为1,2,3的三个数据包,接受方设备成功接收数据包,用序列号4确认。发送方设备收到确认,继续以窗口大小3发送数据。当接受方设备要求降低或者增大网络流量时,可以对窗口大小进行减小或者增加,本例降低窗口大小为2,每一次发送两个数据包。当接受方设备要求窗口为0,表明接收方已经接收了全部数据,或者接收方应用程序没有时间读取数据,要求暂停发送。发送方接收到携带窗口号为0的确认,停止这一方向的数据传输。
这里我们可以看到假设窗口的大小是1,也是就每次只能发送一个数据只有接受方对这个数据进行确认了以后才能发送第2个数据。我们可以看到发送方每发送一个数据接受方就要给发送方一个ACK对这个数据进行确认。只有接受到了这个确认数据以后发送方才能传输下个数据。 这样我们考虑一下如果说窗口过小,那么当传输比较大的数据的时候需要不停的对数据进行确认,这个时候就会造成很大的延迟。如果说窗口的大小定义的过大。我们假设发送方一次发送100个数据。但是接收方只能处理50个数据。这样每次都会只对这50个数据进行确认。发送方下一次还是发送100个数据,但是接受方还是只能处理50个数据。这样就避免了不必要的数据来拥塞我们的链路。所以我们就引入了滑动窗口机制,窗口的大小并不是固定的而是根据我们之间的链路的带宽的大小,这个时候链路是否拥护塞。接受方是否能处理这么多数据了。
首先第一次发送数据时,窗口大小是根据链路带宽大小来确定的。 假设此时窗口为3。这时接收方收到数据以后会对数据进行确认(ACK)告诉发送方我下次希望收到的数据是多少。这里我们看到接收方发送的ACK=3(这是对发送方发送序列2的回答确认,下一次接收方期望接收到的是3序列的信号)。这时,发送方收到这个数据以后就知道我第一次发送的三个数据对方只收到了两个。因此下次从第三个数据开始发。这个时候窗口大小就变成2 。
发送方发送了两个数据。
看到接收方发送的ACK=5,表示他下一次希望收到的数据是5,发送方就知道刚才发送的两个数据被接收,现在开始发送第五个数据。
这就是滑动窗口的工作机制,当链路变好了或者变差了这个窗口还会发生变化,并不是第一次协商好了以后就永远不变。
滑动窗口协议是TCP使用的一种流量控制方法。该协议允许发送方在停止并等待确认前可以发送多个分组。由于发送方不必每发一个分组就停下来等待确认,因此该协议可以加速数据传输。
只有在接收窗口向前滑动时(此时也发送了确认),发送窗口才有可能向前滑动。
收发两端的窗口按照以上规律不断地向前滑动,因此协议称为滑动窗口协议。
当发送窗口和接收窗口的大小都等于1时,就是停止等待协议。
数据是一条数据源,数据是没有界限的,发送的次数和接收的次数无直接关系。也就write的次数和read的次数无直接联系。
接收方将数据放在缓冲区,数据一次不能接收完,可以放在缓冲区进行二次读取。
发送的次数和接收的次数是相等的。
如果接受端一次未能读取完传输层的数据,剩余的数据将直接丢弃掉。
package Internet.TCPDemo; import java.io.*; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; public class TCPServer { public static void main(String[] args) throws IOException { //创建一个服务器端口实例 ServerSocket serverSocket=new ServerSocket(); //绑定端口 serverSocket.bind(new InetSocketAddress(8888)); //监听并获得socket实例,该方法会阻塞 Socket socket=serverSocket.accept(); System.out.println(socket.getRemoteSocketAddress()+" 客户端连接上"); //字符缓冲读取流,用来接收客户端消息 BufferedReader buffer=new BufferedReader(new InputStreamReader(socket.getInputStream())); //输出流,用来向客户端发送 OutputStream outputStream=socket.getOutputStream(); while(true){ String s=null; while((s=buffer.readLine())!=null){ //因为服务器端调用的是readLine,他看到换行符才会读取结束。 System.out.println("客户端信息________ " +s); //将接收到的消息返回给客户端 outputStream.write((s+"\n").getBytes()); outputStream.flush(); } } //关闭资源:后开的先关,因为异常抛出,所以下面几个方法报错 //outputStream.close(); //buffer.close(); //socket.close(); //serverSocket.close(); } }
package Internet.TCPDemo; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Scanner; public class TCPClient { public static void main(String[] args) throws IOException { //ipconfig dos命令 查找本机ip //创建Socket实例 Socket socket=new Socket(); //连接服务器 socket.connect(new InetSocketAddress("127.0.0.1",8888)); Scanner scanner=new Scanner(System.in); //得到输出流实例 OutputStream outputStream=socket.getOutputStream(); //得到输入流实例 BufferedReader reader=new BufferedReader(new InputStreamReader(socket.getInputStream())); while(true){ String s=scanner.nextLine();//读取标准输入 //因为服务端是行读,这边写需要加\n outputStream.write((s+"\n").getBytes()); //向服务器发送消息 outputStream.flush(); String s1=reader.readLine(); //读取服务器返回的消息 System.out.println("服务器返回消息---》"+s1); if(s.equals(s1)){ //比对服务器返回的消息 System.out.println("服务端成功接收"); continue; } System.out.println("数据丢失请重新发送"); } //关闭资源 // outputStream.close(); // reader.close(); // socket.close(); } }
package Internet.UDPDemo; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.SocketException; public class UDPServer { public static void main(String[] args) { try { //创建数据报服务端口 DatagramSocket socket = new DatagramSocket(); String msg="hello"; //创建数据报实例 DatagramPacket packet=new DatagramPacket(msg.getBytes(),msg.length(),new InetSocketAddress("127.0.0.1",666)); //发送数据报 socket.send(packet); }catch (SocketException e){ } catch (IOException e) { e.printStackTrace(); } } } /* * UDP:用户数据包协议: * 不可靠的数据传输 * 数据传输时不会进行事前连接和事后释放。 */
package Internet.UDPDemo; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.SocketException; public class UDPClient { public static void main(String[] args) { try { //创建socket实例,创建数据报服务端口。 DatagramSocket socket=new DatagramSocket(666); byte [] b=new byte[100]; //创建数据报实例 DatagramPacket packet=new DatagramPacket(b,100); //接收数据报 socket.receive(packet); System.out.println(new String(b)); socket.close(); } catch (SocketException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。