TCP长连接、短连接
一、TCP连接:
当网络通信时采用TCP协议时,在真正的读写操作之前,server与client之间必须建立一个连接,当读写操作完成后,双方不再需要这个连接时它们可以释放这个连接,连接的建立是需要三次握手的,而释放则需要4次握手,所以说每个连接的建立都是需要资源消耗和时间消耗的。
三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:
(1)第一次握手:Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
(2)第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
(3)第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
SYN攻击:
在三次握手过程中,Server发送SYN-ACK之后,收到Client的ACK之前的TCP连接称为半连接(half-open connect),此时Server处于SYN_RCVD状态,当收到ACK后,Server转入ESTABLISHED状态。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server回复确认包,并等待Client的确认,由于源地址是不存在的,因此,Server需要不断重发直至超时,这些伪造的SYN包将产时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。SYN攻击时一种典型的DDOS攻击,检测SYN攻击的方式非常简单,即当Server上有大量半连接状态且源IP地址是随机的,则可以断定遭到SYN攻击了,使用如下命令可以让之现形:#netstat -nap | grep SYN_RECV;
四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:
由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭,上图描述的即是如此。
(1)第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
上面是一方主动关闭,另一方被动关闭的情况,实际中还会出现同时发起主动关闭的情况,具体流程如下图:
二、长连接与短链接
长连接: 指在一个TCP连接上可以连续发送多个数据包,
在TCP连接保持期间,如果没有数据包发送,需要双方发检测包以维持此连接;
4000
一般需要自己做在线维持。
短连接: 指通信双方有数据交互时,就建立一个TCP连接,数据发送完成后,则断开此TCP连接;
一般银行都使用短连接。
它的优点是:管理起来比较简单,存在的连接都是有用的连接,不需要额外的控制手段
比如http的,只是连接、请求、关闭,过程时间较短,服务器若是一段时间内没有收到请求即可关闭连接。
其实长连接是相对于通常的短连接而说的,也就是长时间保持客户端与服务端的连接状态。
长连接与短连接的操作过程
通常的短连接操作步骤是:
连接→数据传输→关闭连接;
而长连接通常就是:
连接→数据传输→保持连接(心跳)→数据传输→保持连接(心跳)→……→关闭连接;
这就要求长连接在没有数据通信时,定时发送数据包(心跳),以维持连接状态,
短连接在没有数据传输时直接关闭就行了
什么时候用长连接,短连接?
长连接多用于操作频繁,点对点的通讯,而且连接数不能太多情况。
每个TCP连接都需要三步握手,这需要时间,如果每个操作都是先连接,再操作的话那么处理速度会降低很多,
所以每个操作完后都不断开,下次次处理时直接发送数据包就OK了,不用建立TCP连接。
例如:数据库的连接用长连接,
如果用短连接频繁的通信会造成socket错误,而且频繁的socket 创建也是对资源的浪费。
三、发送接收方式
1、异步
报文发送和接收是分开的,相互独立的,互不影响。这种方式又分两种情况:
(1)异步双工:接收和发送在同一个程序中,由两个不同的子进程分别负责发送和接收
(2)异步单工:接收和发送是用两个不同的程序来完成。
2、同步
报文发送和接收是同步进行,既报文发送后等待接收返回报文。
同步方式一般需要考虑超时问题,即报文发出去后不能无限等待,需要设定超时时间,
超过该时间发送方不再等待读返回报文,直接通知超时返回。
在长连接中一般是没有条件能够判断读写什么时候结束,所以必须要加长度报文头。
读函数先是读取报文头的长度,再根据这个长度去读相应长度的报文。
四、单工、半双工和全双工
根据通信双方的分工和信号传输方向可将通信分为三种方式:
单工、
半双工、
全双工。
在计算机网络中主要采用双工方式,其中:
局域网采用半双工方式,
城域网和广域网采用全双年方式。
1. 单工(Simplex)方式:
通信双方设备中发送器与接收器分工明确,只能在由发送器向接收器的单一固定方向上传送数据。
采用单工通信的典型发送设备如早期计算机的读卡器,典型的接收设备如打印机。
2. 半双工(Half Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以相互传送数据,但某一时刻则只能向一个方向传送数据。
例如,步话机是半双工设备,因为在一个时刻只能有一方说话。
3. 全双工(Full Duplex)方式:
通信双方设备既是发送器,也是接收器,两台设备可以同时在两个方向上传送数据。
例如,电话是全双工设备,因为双方可同时说话。
而像WEB网站的http服务一般都用短链接,因为长连接对于服务端来说会耗费一定的资源,
而像WEB网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源,
如果用长连接,而且同时有成千上万的用户,如果每个用户都占用一个连接的话,那可想而知吧。
所以并发量大,但每个用户无需频繁操作情况下需用短连好。
总之,长连接和短连接的选择要视情况而定。
五、长连接demo
package cn.cloudBy.tcp; import java.io.Serializable; import java.text.SimpleDateFormat; import java.util.Date; public class KeepAlive implements Serializable{ private static final long serialVersionUID = -2813120366138988480L; /* 覆盖该方法,仅用于测试使用。 * @see java.lang.Object#toString() */ @Override public String toString() { return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+"\t维持连接包"; } }
package cn.cloudBy.tcp; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.net.UnknownHostException; import java.util.concurrent.ConcurrentHashMap; public class Client { /** * 处理服务端发回的对象,可实现该接口。 */ public static interface ObjectAction{ void doAction(Object obj,Client client); } public static final class DefaultObjectAction implements ObjectAction{ public void doAction(Object obj,Client client) { System.out.println("处理:\t"+obj.toString()); } } public static void main(String[] args) throws UnknownHostException, IOException { String serverIp = "127.0.0.1"; int port = 65432; Client client = new Client(serverIp,port); client.start(); } private String serverIp; private int port; private Socket socket; private boolean running=false; private long lastSendTime; private ConcurrentHashMap<Class, ObjectAction> actionMapping = new ConcurrentHashMap<Class,ObjectAction>(); public Client(String serverIp, int port) { this.serverIp=serverIp;this.port=port; } public void start() throws UnknownHostException, IOException { if(running)return; socket = new Socket(serverIp,port); System.out.println("本地端口:"+socket.getLocalPort()); lastSendTime=System.currentTimeMillis(); running=true; new Thread(new KeepAliveWatchDog()).start(); new Thread(new ReceiveWatchDog()).start(); } public void stop(){ if(running)running=false; } public void addActionMap(Class<Object> cls,ObjectAction action){ actionMapping.put(cls, action); } public void sendObject(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); oos.writeObject(obj); System.out.println("发送:\t"+obj); oos.flush(); } class KeepAliveWatchDog implements Runnable{ long checkDelay = 10; long keepAliveDelay = 2000; public void run() { while(running){ if(System.currentTimeMillis()-lastSendTime>keepAliveDelay){ try { Client.this.sendObject(new KeepAlive()); } catch (IOException e) { e.printStackTrace(); Client.this.stop(); } lastSendTime = System.currentTimeMillis(); }else{ try { Thread.sleep(checkDelay); } catch (InterruptedException e) { e.printStackTrace(); Client.this.stop(); } } } } } class ReceiveWatchDog implements Runnable{ public void run() { while(running){ try { InputStream in = socket.getInputStream(); if(in.available()>0){ ObjectInputStream ois = new ObjectInputStream(in); Object obj = ois.readObject(); System.out.println("接收:\t"+obj); ObjectAction oa = actionMapping.get(obj.getClass()); oa = oa==null?new DefaultObjectAction():o 1fa47 a; oa.doAction(obj, Client.this); }else{ Thread.sleep(10); } } catch (Exception e) { e.printStackTrace(); Client.this.stop(); } } } } }
package cn.cloudBy.tcp; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ConcurrentHashMap; public class Server { /** * 要处理客户端发来的对象,并返回一个对象,可实现该接口。 */ public interface ObjectAction { Object doAction(Object rev); } public static final class DefaultObjectAction implements ObjectAction { public Object doAction(Object rev) { System.out.println("处理并返回:" + rev); return rev; } } public static void main(String[] args) { int port = 65432; Server server = new Server(port); server.start(); } private int port; private volatile boolean running = false; private long receiveTimeDelay = 3000; private ConcurrentHashMap<Class, ObjectAction> actionMapping = new ConcurrentHashMap<Class, ObjectAction>(); private Thread connWatchDog; public Server(int port) { this.port = port; } public void start() { if (running) return; running = true; connWatchDog = new Thread(new ConnWatchDog()); connWatchDog.start(); } @SuppressWarnings("deprecation") public void stop() { if (running) running = false; if (connWatchDog != null) connWatchDog.stop(); } public void addActionMap(Class<Object> cls, ObjectAction action) { actionMapping.put(cls, action); } class ConnWatchDog implements Runnable { public void run() { try { ServerSocket ss = new ServerSocket(port, 5); while (running) { Socket s = ss.accept(); new Thread(new SocketAction(s)).start(); } } catch (IOException e) { e.printStackTrace(); Server.this.stop(); } } } class SocketAction implements Runnable { Socket s; boolean run = true; long lastReceiveTime = System.currentTimeMillis(); public SocketAction(Socket s) { this.s = s; } public void run() { while (running && run) { if (System.currentTimeMillis() - lastReceiveTime > receiveTimeDelay) { overThis(); } else { try { InputStream in = s.getInputStream(); if (in.available() > 0) { ObjectInputStream ois = new ObjectInputStream(in); Object obj = ois.readObject(); lastReceiveTime = System.currentTimeMillis(); System.out.println("接收:\t" + obj); ObjectAction oa = actionMapping.get(obj.getClass()); oa = oa == null ? new DefaultObjectAction() : oa; Object out = oa.doAction(obj); if (out != null) { ObjectOutputStream oos = new ObjectOutputStream( s.getOutputStream()); oos.writeObject(out); oos.flush(); } } else { Thread.sleep(10); } } catch (Exception e) { e.printStackTrace(); overThis(); } } } } private void overThis() { if (run) run = false; if (s != null) { try { s.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("关闭:" + s.getRemoteSocketAddress()); } } }六、附
关于三次握手与四次挥手通常都会有典型的面试题,在此提出供有需求的XDJM们参考:
(1)三次握手是什么或者流程?四次握手呢?答案前面分析就是。
(2)为什么建立连接是三次握手,而关闭连接却是四次挥手呢?
- TIME-WAIT的TCP连接参数
- Qt实现TcpClient和TcpServer连接收发数据
- 查看Apache并发请求数及其TCP连接状态(转)
- HttpClient出现TCP连接异常关闭发送RST包
- SQL Server 连接问题-TCP/IP
- Libevent参考手册第八章:连接侦听器: 接受 TCP 连接
- 在建立与服务器的连接时出错。provider: TCP 提供程序, error: 0 - 由于目标机器积极拒绝,无法连接。)
- 网络基本功(二十三):Wireshark抓包实例诊断TCP连接问题
- java用ServerSocket类结合io流实现一对一聊天(java实现简单的TCP聊天程序只能用telnet连接后方可使用)
- TCP/IP连接建立与断开
- TCP建立连接的三次握手
- TCP连接四次挥手
- 浏览器与apache通讯中的TCP连接状态迁移
- 【TCP连接状态详解】
- tcpkill清除异常tcp连接
- TCP四次挥手断开连接详解
- 第二章 传输层:TCP、UDP和SCTP(2)—TCP连接的建立和终止
- TCP连接的建立与终止
- TCP连接状态详解
- UDP连接和TCP连接的异同