其实也没什么特别的缘由,主要是本身想了解一下网络通讯这块的东西,而后一直以为socket高深难懂,因此决定挑战一下本身,作一个简单的学习了解。web
之前由于项目缘由接触过socket,可是当时是使用的socket.io的这个框架,对于socket的底层是如何实现的,以及框架是如何封装的并无进行深刻的研究。编程
因此此次就抛砖引玉简单的讲一下我对socket的理解,很是欢迎各位大佬多多指教。数组
字面上socket又成为“套接字”安全
实际上:网络上 的 两个程序 经过一个双向的通讯链接 实现数据的交换,这个链接的一端称为一个socket服务器
客户端和服务端的socket创建一个链接,经过两端创建的这个通讯管道进行网络的请求和响应。socket能够理解成通讯管道(隧道)的两个端口,一个入口,一个出口网络
IP地址(网络上主机设备的惟一标识)框架
端口号(定位程序)socket
传输协议(使用什么样的方式进行交互,通讯规则)函数
套接字的类型有不少种,好比 DARPA Internet 地址(Internet 套接字)、本地节点的路径名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。涉及到网络请求方面的主要指的都是Internet套接字学习
根据数据的传输方式,能够将Internet套接字分红两种类型
SOCK_STREAM 是一种可靠的、双向的通讯数据流,数据能够准确无误地到达另外一台计算机,若是损坏或丢失,能够从新发送。
流格式套接字有本身的纠错机制
SOCK_STREAM 有如下几个特征:
能够将 SOCK_STREAM 比喻成一条传送带,只要传送带自己没有问题(也就是不断网),就能够保证数据不会丢失;同时,比较晚传送的数据不会先到达,比较早传送的数据不会晚到达,保证了数据的传递是按照顺序传递的。
流式套接字使用的是传输控制协议(TCP),进而保证了数据能够 准确无误的顺序到达,而且接收者不须要和发送者保持相同的节奏接收数据。
应用场景:HTTP协议就是基于面向链接的套接字(TCP服务),数据信息必需要准确无误的进行传输
计算机只管传输数据,不做数据校验,若是数据在传输中损坏,或者没有到达另外一台计算机,是没有办法补救的。也就是说,数据错了就错了,没法重传。
SOCK_DGRAM 有如下几个特征:
能够将SOCK_DGRAM理解为快递行业,用货车发往同一地点的两件包裹无需保证顺序,只要以最快的速度送到客户手中便可。这种方式存在损坏或者丢失的风险,并且对快递包裹的大小也是有必定限制的。因此当传递大量包裹的时候就须要分批发送。
用两辆车分别发送的两件包裹,接收者也须要分两次接收,因此数据的发送和接收必须是同步的。(也能够理解为:接收次数和发送次数是相同的)
总之,数据报套接字是一种不可靠的、不按顺序传递的、以追求速度为目的的套接字,使用的是UDP协议。
应用场景:QQ视频聊天 & QQ语音 都是使用SOCK_DGRAM来进行数据传输的。
面向链接的套接字
无链接的套接字
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,均可以用“打开open -> 读写write/read -> 关闭close”模式来操做。也能够理解为socket就是该模式的一个实现,socket便是一种特殊的文件
socket(): 建立socket套接字
bind(): 将套接字和IP、端口绑定,一般由服务端调用
listen(): TCP专用,开启监听状态,等待用户发起请求,套接字一直处于“睡眠”中,直到客户端发起请求才会被“唤醒”
accept(): TCP专用,服务器等待客户端链接请求,通常是阻塞态(暂停运行),直到客户端发起请求
connect(): TCP专用,客户端向服务器主动发起链接请求,直到服务器传回数据后,connect() 才运行结束
send(): TCP专用,发送数据
recv(): TCP专用,接收数据
sendto(): UDP专用,发送数据到指定的IP地址和端口,数据信息包含目标地址
recvfrom(): UDP专用,接收数据,返回数据远端的IP地址和端口,接收数据请求的时候将目标地址信息保存
shutdown(): TCP专用,优雅的断开流链接,断开输入流 断开输出流 同时断开I/O流,会等缓冲区的数据发送完毕以后再断开。用来关闭链接,而不是套接字,因此须要在调用以后再调一次close(),才能将套接字从内存中清除
closesocket(): 关闭socket套接字,不只会关闭服务端的socket,还会通知客户端链接已断开,客户端也会清理 socket 相关资源,该操做会丢失输出缓冲区中的数据
基于TCP的socket通讯
TCP的服务端与客户端必须一对一的创建链接才能够进行数据的通讯
在TCP中,套接字是一对一的关系,若是要向10个客户端提供服务的话,那么除了负责监听的套接字以外,还须要建立10个套接字
建立好TCP套接字以后,传输数据时无需添加地址信息,由于TCP的套接字与对方套接字相链接,知道数据的目标地址信息
基于UDP的socket通讯
UDP中的服务器端和客户端没有链接,无需在链接状态下交换数据
UDP 套接字不会保持链接状态,每次传输数据都要添加目标地址信息,这至关于在邮寄包裹前填写收件人地址
在UDP中,无论是服务端仍是客户端都只须要1个套接字便可
UDP不存在严格的服务端和客户端的区分,只是由于其提供服务而称为服务端
首先须要强调一点:HTTP协议是基于请求/响应模式的,所以只要服务端给了响应,本次HTTP链接就结束了。 也能够理解为是本次HTTP请求就结束了,根本没有长链接 短链接这一说
对于网络上说的HTTP分为长链接和短链接,实际上是指TCP链接。TCP链接是一个双向的通道,它是能够保持一段时间不关闭的,所以TCP链接才是真正有长链接和短链接之分的
对于HTTP链接的说法 实际上是HTTP请求和HTTP响应更为准确,因此 长链接是指的TCP链接,而不是HTTP链接
首先要明白 长链接意味着链接会被重复使用,是指TCP的链接通道被重复使用,并非HTTP请求的复用,是多个HTTP请求能够复用同一个TCP链接,这样节省了不少TCP链接创建和断开的握手消耗。
HTTP1.0协议不支持长链接,从HTTP1.1协议之后默认是长链接,咱们能够看到平时的Web应用的HTTP头部Connection也确实是keep-alive,可是须要服务端和客户端都设置才算是长链接。
长链接也并非永久链接的,若是一段时间内(具体的时间长短,是能够在header当中进行设置的,也就是所谓的超时时间),这个链接没有HTTP请求发出的话,那么这个长链接就会被断掉。
长链接优点: 假设打开一个网页,里面确定包含不少CSS、JS等一系列资源,若是是短链接的话(每次都要从新创建TCP链接),因此一个网页的打开须要耗费几个甚至几十个的TCP链接。若是是长链接的话,那么这么屡次HTTP请求(这些请求包括请求网页内容,CSS文件,JS文件,图片等等),其实使用的都是同一个TCP链接,很显然这样能够节省不少的消耗。
假设一个场景:淘宝界面显示库存的剩余个数,客户端写一个死循环,不停的去请求服务端的数据
短轮询
长轮询
对于客户端来讲,长轮询和短轮询是同样的,就是不停的去请求;对于服务端来讲,短轮询的状况下服务端每次请求无论有没有变化都会当即返回结果信息,而长轮询的状况下是有变化才返回结果信息,没变化的话则不会返回信息,直到超时为止
1.决定方式
2.实现方式
Socket的长短链接的差异:整个客户端和服务端的请求是经过一个socket仍是多个socket进行的
Socket链接中 断开流 其实就是断开链接了,由于Socket原本就是依靠流进行通讯的
在关闭链接的时候 必定要显示关闭socket,而不是经过调用shutDown方法关闭某个流,这样容易形成内存泄漏
顺序是先关流再关socket
首先要知道:要实现长链接,通常须要发送结束标记符号来告诉客户端 - 服务端的某段消息已经发送完毕,不然客户端会一直阻塞在read方法
因此 客户端须要本身主动退出读取的动做,为了防止客户端一直阻塞在read()方法处
咱们能够在服务端每发送完一段消息而且刷新前就进行一个写入结束符号的标志,当客户端解析到结束符号时,就可直接退出read的循环读取操做,避免一直阻塞。
节点(防火墙)会自动把必定时间以内没有数据交互的链接给断掉,因此要保持长链接,须要保活,须要发送心跳包,防止长时间没有数据交互,致使链接被断掉
客户端和服务端制定一个通讯协议,每隔必定时间(通常15秒左右),由一方发起,向对方发送协议包;对方收到这个包后,按指定好的通讯协议回一个。若没收到回复,则判断网络出现问题,服务器可及时的断开链接,客户端也能够及时重连。
总的来讲:心跳包主要也就是用于长链接的保活和断线处理。通常的应用下,断定时间在30-40秒比较不错。若是实在要求高,那就在6-9秒
经过TCP协议层发送KeepAlive包。这个方法只需设置好你使用的TCP的KeepAlive项就好,其余的操做系统会帮你完成。操做系统会按时发送KeepAlive包,一发现网络异常,立刻断开。
开启KeepAlive功能须要消耗额外的宽带和流量,因此TCP协议层默认并不开启KeepAlive功能,另外一方面,KeepAlive设置不合理时可能会由于短暂的网络波动而断开如今的TCP链接。而且,默认的KeepAlive超时须要7,200,000 MilliSeconds, 即2小时,探测次数为5次。对于不少服务端应用程序来讲,2小时的空闲时间太长。所以,咱们须要手工开启KeepAlive功能并设置合理的KeepAlive参数。
TCP的 SO_KEEPALIVE。系统默认是设置的2小时的心跳频率。可是它检查不到机器断电、网线拔出、防火墙这些断线。并且逻辑层处理断线可能也不是那么好处理。通常,若是只是用于保活仍是能够的。
客户端同一时间发送几条数据,而服务端接收的是这几条数据合在一块儿的一条数据,通过TCP的传输,三条数据被合并成一条了,这就是数据的粘包问题。 也称数据的无边界性,read()/recv()函数不知道数据包的开始或结束标志(实际上也没有任何开始或结束标志),只把它们当作连续的数据流来处理。
假设咱们但愿客户端每次发送一位学生的学号,让服务器端返回该学生的姓名、住址、成绩等信息,这时候可能就会出现问题,服务器端不能区分学生的学号。例如第一次发送 1,第二次发送 3,服务器可能当成 13 来处理,返回的信息显然是错误的。
解决办法
封包的时候给每一个包的数据加一个标记,来标明数据的长度和类型(类型显然是须要的,咱们须要知道它是文本、图片、仍是录音等等,来用正确的方式处理这个数据)。
拆包的时候,先获取到咱们给每一个包的标记,而后根据标记的数据长度,去获取数据。最后再根据标记的类型去处理数据。(文字输出、图片展现、录音播放等等)