转载:https://blog.youkuaiyun.com/oyhy_/article/details/70201855
1、摘要
tcp是面向连接的,可靠的字节流协议。因此,在传输数据之前通信双方必须建立一个tcp连接,建立tcp连接需要在服务器和客户端之间进行三次握手。通信双方数据传输完毕之后连接释放,释放连接需要在通信双方之间进行四次挥手。
2、tcp协议端口的连接状态
1)、LISTENING,提供某种服务,侦听远方tcp端口的连接请求,当提供的服务没有被连接时,处于LISTENING状态,端口是开放的,等待被连接
2)、SYN_SENT(客户端状态)
客户端调用connect,发送SYN请求简历一个连接,在发送连接请求后等待匹配连接请求,此时状态为SYN_SENT.
3)、SYN_RECEIVED(服务端状态)
在收到和发送一个连接请求后,等待对方对连接请求的确认,当服务器收到客户端发送的同步信号时,将标志位ACK和SYN置为1发送给客户端,此时服务器端处于SYN_RECEIVED状态,如果连接成功了就变为ESTABLISHED,正常情况下SYN_RECEIVED非常短暂.
4)、ESTABLISHED
ESTABLISHED状态表示两台机器正在传输数据。
5)、FIN-WAIT-1
等待远程TCP连接中断请求,或先前的连接中断请求的确认,主动关闭端应用程序调用close,TCP发出FIN请求主动关闭连接,之后进入FIN-WAIT-1状态。
6)、FIN-WAIT-2
从远程tcp等待连接中断请求,主动关闭端接到ack后,就进入了FIN-WAIT-2,这是在关闭连接时,客户端和服务端两次握手之后的状态,是著名的半关闭状态了,在这个状态下,应用程序还有接收数据的能力,但是已经无法发送数据,但是也有一种可能是,客户端一直处于FIN_WAIT_2状态,而服务器一直处于WAIT_CLOSE状态,而直到应用层来决定关闭这个状态。
7)、CLOSE-WAIT
等待从本地用户发来的连接中断请求,被动关闭端TCP接到FIN后,就发出ACK以回应FIN请求
8)、CLOSING
等待远程TCP对连接中断的确认,处于此种状态比较少见。
9)、LAST_ACK
等待原来的发向远程TCP的链接中断请求的确认。被动关闭端一段时间后,接收到文件结束符的应用程序将调用CLOSE关闭连接,TCP也发送一个 FIN,等待对方的ACK.进入LAST-ACK。
10)TIME-WAIT
在主动关闭端接收到FIN后,TCP就发送ACK包,并进入TIME-WAIT状态,等待足够的时间以确保远程TCP接收到连接中断请求的确认,很大程度上保证了双方都可以正常结束,但是也存在问题,须等待2MSL时间的过去才能进行下一次连接。
11)、CLOSED
被动关闭端在接受到ACK包后,就进入了closed的状态,连接结束,没有任何连接状态。
2、为什么连接要三次握手
不管三次握手还是四次握手,这是保证信息来回两个链路可达(信息能从A到B,也能从B到A)的最低要求。
举例:
A:你好我是A,你能听到我讲话吗?
B:听到了,我是B,你能听到我讲话吗?
A:恩,听到了。
建立连接,开始聊天!
3、为什么终止连接要四次挥手
a、当主机A确认发送完数据且知道B已经接收完了,想要关闭发送数据接口,就会发FIN给主机B
b、主机B收到A发送的FIN,表示收到了,就会发送ACK回复。
c、但是B可能还在发送数据,没有想要关闭数据接口的意思,所以FIN与ACK不是同时发送的,而是等到B数据发送完了,才会发送FIN给主机A。
d、A收到B发来的FIN,知道B的数据也发送完了,回复ACK,A等2msl以后,没有收到B传来的任何消息,知道B已经收到自己的ACK了,A就关闭连接,B也关闭连接了。
A为什么等待2msl?
在client发送出最后的ack回复,但该ack可能会丢失,server如果没有收到ack,将不断的重复发送FIN给client.所以client不能立即关闭,它必须确认server接收到了该ack,client会在发送出ack之后进入到TIME_WAIT状态。client会设置一个计时器,等待2msl的时间。如果再该时间内再次收到FIN,那么clienti会重发ack并再次等待2msl,client都没有再次收到fin,那么client推断ack已经被成功接收,则结束tcp连接。
举例:
A:喂,我不说了 A->FIN_WAIT1
B:我知道了,等下,上一句还没说完,balabala B->ClOSE_WAIT | a->ESTABLISHED
B:好了,我也说完了,我不说了 B->LAST_ACK
A;我知道了 A->TIME_WAIT | B->CLOSED
A等待2msl后,保证B收到了消息,否则重说一次“我知道了”,A->CLOSED
TCP和UDP的区别和优缺点
1、区别总结
1)、tcp是面向连接的(如打电话要先建立连接);udp是无连接的,即发送数据前不需要建立连接。
2)、tcp提供可靠的服务,也就是说,通过tcp连接传送的数据,无差错,不丢失,不重复,且按序到达;udp尽最大努力交付,即不保证可靠交付,tcp通过校验和重传控制,序号标识,滑动窗口、确认应该实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
3)、udp具有较好的实时性,工作效率比tcp高,适用于对高速传输和实时性较高的通信或广播通信。
4)、每一条TCP链接只能是点到点的;udp支持一对一,一对多,多对一和多对多的交互通信。
5)、tcp对系统资源要求较多,udp对系统资源要求较少。
6)、TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。
2、为什么udp有时比tcp更有优势
udp以其简单、传输快的优势,在越来越多的场景下取代了tcp,如实时游戏。
1)、网速的提升给UDP的稳定性提供可靠网络保障,丢包率很低,如果使用应用层重传,能够确保传输的可靠性。
2)、TCP为了实现网络通信的可靠性,使用了复杂的拥塞控制算法,建立了繁琐的握手过程,由于tcp内置的系统协议栈中,极难对其进行改进。
采用tcp一旦发生丢包,tcp会将后续的包缓存起来,等前面的包重传并接收到后再继续发送,延时会越来越大,基于udp对实时性要求较为严格的情况下,采用自定义重传机制,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成影响。
3、udp与tcp的变成步骤不同
tcp编程的服务器端一般步骤是:
1)、创建一个socket,用函数socket();
2)、设置socket属性
3)、绑定ip地址,端口等信息到socket上
4)、开启监听
5)、接收客户端上来的连接
6)、收发数据
7)、关闭网络连接
8)、关闭监听
package com.hsx.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServerDemo2{
public static void main( String[] args ){
try{
ServerSocket serverSocket = new ServerSocket( 10002 );
while( true ){
Socket socket = null;
BufferedReader reader = null;
PrintWriter writer = null;
try{
socket = serverSocket.accept();
reader = new BufferedReader( new InputStreamReader( socket.getInputStream(), "UTF-8" ) );
String line = reader.readLine();
while( line != null ){
System.out.println( line );
writer = new PrintWriter( socket.getOutputStream(), true );
writer.println( "i received your second msg" );
if( "second".equals( line ) ){
break;
}
line = reader.readLine();
}
// System.out.println( line );
// writer = new PrintWriter( socket.getOutputStream(), true );
// writer.println( "i received your msg" );
}
catch( IOException e ){
e.printStackTrace();
}
finally{
try{
if( socket != null ){
socket.close();
}
if( reader != null ){
reader.close();
}
if( writer != null ){
writer.close();
}
}
catch( IOException e ){
e.printStackTrace();
}
}
}
}
catch( IOException e ){
e.printStackTrace();
}
}
}
tcp变成的客户端
1)、创建一个socket
2)、设置socket属性
3)、绑定ip地址,端口等信息
4)、设置要连接的对方的ip地址和端口等属性
5)、连接服务器
6)、收发数据
7)、关闭网络连接
package com.hsx.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* Created by 58 on 2018/9/21.
*/
public class SocketClientDemo implements Runnable{
private Socket socket;
BufferedReader reader;
private PrintWriter writer;
private String msg;
public SocketClientDemo( String msg ){
try{
this.msg = msg;
socket = new Socket( "10.9.194.47", 10002 );
}
catch( IOException e ){
e.printStackTrace();
}
}
@Override
public void run(){
try{
String line, buffer = "";
writer = new PrintWriter( socket.getOutputStream(), true );
writer.println( msg );
writer.println( "first" );
reader = new BufferedReader( new InputStreamReader( socket.getInputStream(), "UTF-8" ) );
int num = 0;
while( ( line = reader.readLine() ) != null ){
buffer += line;
System.out.println( line );
if( num++ == 1 ){
writer.println( "second" );
}
}
}
catch( Exception e ){
e.printStackTrace();
}
finally{
try{
if( socket != null ){
socket.close();
}
if( reader != null ){
reader.close();
}
if( writer != null ){
writer.close();
}
}
catch( IOException e ){
e.printStackTrace();
}
}
}
public static void main( String[] args ){
new Thread( new SocketClientDemo( "client msg 1" ) ).start();
/* new Thread( new SocketClientDemo("client msg 2") ).start();
new Thread( new SocketClientDemo("client msg 3") ).start();*/
/* for( int i = 0; i < 100; i++ ){
new Thread( new SocketClientDemo( "client msg " + i ) ).start();
}*/
}
}
UDP编程服务端的步骤:
1)、创建一个socket
2)、设置socket属性
3)、绑定ip地址、端口信息等到socket上
4)、循环接收数据
5)、关闭网络连接
package com.hsx.socket;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
* 基于udp的socket server
*/
public class UDPServer{
public static void main( String[] args ){
try{
//1、创城奖datagramsocket 指定端口
DatagramSocket datagramSocket = new DatagramSocket( 10002 );
//2、创建数据报,用户接收客户端发送的数据
//2.1、创建字节数据,指定接收的数据报的大小
byte[] data = new byte[ 1024 ];
DatagramPacket datagramPacket = new DatagramPacket( data, data.length );
//3、接收客户端发送的数据
System.out.println( "服务端已经开启,等待客户端的连接" );
//此方法在接收到数据之前会一直阻塞
datagramSocket.receive( datagramPacket );
//4、读取客户端发送的数据
//参数:data要转换的数组0从数组的下边0开始 datagrampacket.getlength()长度为接收到的长度
String info = new String( data, 0, datagramPacket.getLength() );
System.out.println( "这是服务器,客户端发来的消息是:" + info );
/**
* 向客户端进行响应
*/
//1、定义客户端的地址、端口、数据
//1.1、获取客户端的ip地址
InetAddress inetAddress = datagramPacket.getAddress();
//获取客户端端口号
int port = datagramPacket.getPort();
//将要响应的内容保存到byte数组中
byte[] data2 = "welcome".getBytes();
//2、创建数据报,包含响应的数据信息
DatagramPacket datagramPacket1 = new DatagramPacket( data2, data2.length, inetAddress, port );
//3、响应客户端
datagramSocket.send( datagramPacket1 );
//4、关闭资源
datagramSocket.close();
System.out.println( "服务已关闭。" );
}
catch( SocketException e ){
e.printStackTrace();
}
catch( IOException e ){
e.printStackTrace();
}
}
}
UDP编程客户端的步骤是:
1)、创建一个socket
2)、设置socket属性
3)、绑定ip地址,端口等新到到socket上
4)、设置对方的ip地址和端口等属性
5)、发送数据
6)、关闭网络连接
package com.hsx.socket;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* udp client
*/
public class UDPClient{
public static void main( String[] args ){
//1、定义服务器地址、端口号、数据
try{
InetAddress inetAddress = InetAddress.getByName( "10.9.194.47" );
int port = 10002;
byte[] data = "username:shaoxing;password:123".getBytes();
//2、创建数据报,包含发送的信息
DatagramPacket datagramPacket = new DatagramPacket( data, data.length, inetAddress, port );
//3、创建DatagramSocket对象
DatagramSocket datagramSocket = new DatagramSocket();
//4、向服务器发送数据报
datagramSocket.send( datagramPacket );
/**
* 客户端接收服务端响应信息
*/
//1、创建数据报,用于接收服务器端响应的数据,数据保存到字节数组中
byte[] data2 = new byte[ 1024 ];
DatagramPacket datagramPacket1 = new DatagramPacket( data2, data2.length );
//2、接收服务器响应的数据
datagramSocket.receive( datagramPacket1 );
//3、读取数据
String reply = new String( data2, datagramPacket1.getLength() );
System.out.println( "这是客户端,服务端发来的消息是:" + reply );
//4、关闭资源
datagramSocket.close();
}
catch( Exception e ){
e.printStackTrace();
}
}
}
参考资料:https://blog.youkuaiyun.com/xiaobangkuaipao/article/details/76793702