TCP和Socket通信原理详解

Web:

TCP/IP

DARPA项目研发的以太网

TCP/IP划分的层次

clip_image002

把互联网通信分层的原因:互联网通信是很复杂的事情,他们彼此之间数据、包括涉及到在物理层次上如何控制这种电气序列的传输、电平信号的传输以及包含我们如何将数据报文从源端到达目的端、还有如果我们将一个数据分隔成好几个部分来进行传输,那么数据谁是第一个、谁是第二个等?在接收方接收到以后如何进行整合?等等,这是一件非常复杂的事情,计算机通信也和人类通信一样,其也需要一种语言,因此计算机通信所用的语言也就是我们常接触到的协议,指的是双方通信时共同遵循的规范,也即发送方说的接收都能理解,按照发送方的原生目的将数据接收下来并实现存储,由于此过程非常复杂所以被分割成多个阶段的不同层次来进行完成,用于实现不同的功能。

在TCP/IP模型中,其中设备驱动程序和硬件这个层次,主要用来负责物理设备在实现通信时,如何传送电气信号?如何传送高低电平?如何尽可能的让二者实现同步或者异步的方式实现无差错的数据传输等等?使用大端方式还是小端方式?

所谓大端方式:就是数据存储时,我们要存储一个整数,整数要转换为二进制之后,他有以十进制为例有十位、百位、万位等,但是在网络上数据传输或者在计算机上数据存储其一定是一位一位存的,而不是把所有数据都挤到内存中或者网络上,因此要存储一个十进制数据或者存储一个整数时,到底是先存最高位还是先存最低位?

先存最低位的是叫做小端、先存最高位的叫做大端。

互联网上不同的架构主机其存储方式是不同的,X86架构存储的可能是小端的,其他的服务器存储时可能用到的是大端的,所以为了保证这些不同主机在互联网上交互时也能够彼此之间互相传输数据,在网络上实现的协议就要规定你传数据时,是先传大端的还是先传小端的?这些规定就是协议中应该要完成的事情,设备驱动程序和硬件就要解决诸如此类的问题。

再向上一层,网络层上有IPv4和IPv6,我们称为ip报文,ip报文层次上主要是如何传输的呢?设备驱动程序和硬件主要用来实现平面化的同一个物理网络中的硬件之间的通信,所以在面向特别大的网络时他们就无能为力了,用IPv4和IPv6实现逻辑地址,将主机上地址分成了三维空间,所以有了所谓的网络地址、所谓的主机地址。主机地址到了以后,再转换成对应MAC地址彼此之间进行通信。但是ip报文本身是不可靠的,他仅仅是一个报文,从发送方到接收方而已。如果说某一个ip报文发送到中途的时候由于网络故障原因丢失了怎么办?我们是否进行重传、以及我们如何知道ip报文处在通信过程当中?我们知道如果说传输数据有20M,ip报文所能够承载的数据量是很小的,那如何把这个数据分隔成了多个报文?而且那些报文应该合并起来重组成同一个文件呢?所以ip协议在这个层次上是无法完成此类功能的,它仅仅是实现逻辑地址空间的鉴定。如何实现所谓的数据发送报文发送的序列序列化定制?则需要借助更高的或者其他额外的功能,这个额外的功能没有在ip报文实现而是在另外一个层次上实现了,这些诸多功能的实现他们分别使用了不同的层次来完成,这样每一个层次只需要将他所需要提供功能向上提供一个接口,每一个层次用接口的方式向上层输出,同时每一个层次又调用下一层的接口所提供的功能,将来其中任何一个层次发生了改变,只要提供的接口和向下层的调用没有发生改变,那么任何一个层次的改变都不会牵一发而动全身,比如IPv4的版本升级为IPv6,只要IPv6继续要上层提供服务,向下层调用对应的功能,那么仍然不会受到影响,下层不用做修改,tcp、udp也不用做修改,使得一个大问题被分解成了n个小问题,每个小问题都可以独立维护和解决。这也是模块化的一种表现。ip层次主要提供的功能就是封装。

tcp层次提供的功能:

序列化的报文传输;

如果两个通信主机节点的性能不同,一端发送速度很快,而另一端接收速度非常慢,会发生什么问题呢?发送方的快速传输会瞬间把接收方给淹没,所以会导致大量的报文丢失,因此为避免此种情况,应该如何做?要做传输控制,tcp【是传输控制协议】

udp也实现了类似tcp一样的功能,tcp和udp还有另外一个特点:他们可以实现在本机上为本机的进程提供地址,tcp和udp使用一个16位的二进制数来表示进程地址,只不过通常把这个整数称作端口号。所以本机上的真正的通信进程要想能够跟其他主机上的进程之间进行通信时,那为了标识当前主机上的哪一个进程跟另外一个主机上的哪一个进程通信,tcp就通过端口的方式为这些进程提供地址,用16位二进制表示的整数进行标记。

clip_image004

在对应右侧即为可以对等一样的黑线,表示不同的实现位置,应用层协议是真正负责实现数据传输时完成真正的通信目的的层次,底下层次主要是为了能让报文从源端发往目的端,发往目的端后,怎么应用?怎么去理解里面报文?怎么存储?对方要请求还是我们要响应?等等,其实都是在应用层进行规定的,比如WEB服务为例,我们是在浏览器上输入一个地址请求对方传输一个网页,对方即把网页传输过来,但是对方网站上的网页可能不止一个,我们请求的可能只是某一次请求当中的一个,那对方如何知道我们请求的哪一个网页呢?tcp、udp中是没有标记的,tcp/udp提供的仅仅是进程地址标识,ip仅仅是用来传输报文的。那么请求的是哪一个网页应该是由应用层来定义的,应用层实现的是:如我们要完成特定功能,二者之间如何交互?我这边是请求还是响应,而且请求是使用什么方法来进行的,web服务请求包括从网站上拉一个网页在本地显示,也包括在本地打开一个表当填写一些数据,提交到服务器端上保存下来,web服务允许我们上传一些文件,所以这些在同一种应用中,或者叫web服务当中也有很多不同的功能,这每一种功能都需要在应用层实现,所以应用层也有应用层协议。web是一种应用层协议【打开网页的】、ftp【上传和下载文件的】、SMTP【发送邮件的】、dns【让我们能发出名称解析请求,并能接受对方的响应的】等等,所以他们都需要各自有各自的不同功能,就像操作系统一样,底下用来提供公共功能,而上面用来提供具体的应用的,像操作系统也是,底层是一层操作系统,它并不提供具体的特定功能,本身不产生生产力,真正产生生产力的是应用程序。所以应用程序是为了完成某一应用的特定应用程序。网络模型中也是遵循此法则的,可以理解底层为网络操作系统,网络通用层。上一层为网络的具体应用,应用层。这些层次通用的功能就是在系统内核中实现的,而所谓应用层应用都是在用户进程实现的,他们对应了操作系统模型,这些通用功能都在内核中实现,也即知道内核中包括了网络的功能。底层的三层也称为通信子网,他主要考虑这个报文无论是dns查询请求报文、web服务的资源请求报文,它只关心请求如何从这端传递给通信的对端。也即只关心通信细节是在内核中负责实现的,因此任何用户进程期望通过tcp或udp并借助ip这种方式实现通信那么只需要向内核中的网络子功能发起系统调用接口,用户进程实现了真正负责完成需要通信时的某一种特定应用,在操作系统上所有用户进程当中有些并不需要网络通信,比如ls不需要,用户进程当中并非每一个都要用到内核当中的网络子功能,但有些需要比如ssh,scp等,需要借助网络和对等的对端上某一个进程进行通信,像ssh客户端和sshd服务器端可以通信,二者之间可以建立会话,所以有些进程要想能够实现通信要向内核中的通信子功能发起系统调用,比如请求对方建立ssh会话的报文发给内核中的tcp层,tcp层为了将报文可靠的传输给另外一个主机需要封装tcp首部、同样要封装ip报文借助ip这种机制进行传输,最终被调制为硬件上的高低电平信号在网络上传输,对方把高低电平信号接收后,还原为ip报文,再还原为tcp报文,再还原为应用层数据,最终被对方接收到。

其实在两个主机上的两个进程之间为了能够完成通信,其中任何一个主机必然一个是主动方,一个是被动方。除非是点对点之间的通信,但是不是此处考虑的问题,所以必须有一个主动打开方,一个被动打开方。而事实上被动打开端被称为服务器端,因为其随时等待客户端请求,并与对方建立连接完成通信,所以被动打开端被称为监听的一方,通常要监听在某一个地址上,注意一定要有一个地址,否则对方来请求时,65535个地址不知道是哪一个,所以请求方必须要知道请求的是对方主机上的哪一个进程,那如何知道对方主机上的哪一个进程要和自己通信?进程是被动打开的等待来访问的,就要使用众所周知的端口,就意味着互联网上的IANA有规定,哪一个端口是负责ssh通信的【22号】,如果把sshd监听在非22号端口了,客户端请求就不知道是哪一个端口了,所以被动打开就是打开一个众所周知的大家约定都认识的端口上。而套接字就是建立在应用层和tcp层之间,所以某一个进程在服务器端打算要在某一端口等待客户端建立请求,应用程序可以任意使用这些端口,不论这些端口是否使用都有,任意一个进程要真正使用时要向内核发起申请,要使用哪个端口,要监听在哪个端口上。如果有人发数据给这个端口,则进程就可以接收对应的数据了。内核接收到一个报文说要请求22号端口,如果没有任何进程注册使用22号端口、没有任何进程说明了要在22号端口上打开。那么内核接收到这个数据后,并不知道交给那个进程,所以内核就会告知对方说本地无人和他通信,事实上任何一个进程真正能够被动打开模式进行通信,要向内核注册使用某一个端口,这个过程称为绑定 bind(),绑定在某个端口上。绑定是不能被重复绑定的,就像ssh的实现有两种一种是openssh,一种是dropbear,如果启动了openssh服务,他已经绑定在tcp的22号端口上了,如果用dropbear再绑定同样的22号端口,那么以后有人向内核发请求和22号通信时,就不知道和谁通信了。所以内核绝对不允许两个进程绑定在同一个端口上。

套接字在本质上是一个设备,两个主机之间为了建立通信,事实上也需要借助于类似设备的方式来进行完成,意味着这个设备能够让本地进程跟这个设备关联起来从而实现跟另外一个主机上的设备对应的进程进行通信。这个设备称为套接字,用来表明本地的哪一个进程,用来表明了远程主机上的哪一个进程。把本地设备和另外一个主机上的设备和远程设备建立起来对应的关联关系,就类似于把本地的一个进程跟tty关联起来了,一个在tty上启动的进程,如果tty终止了,那么进程也一定会终止的。如果不想让tty终止时候,进程一定被停止,则把进程送到后台执行,而且要使用nohup方式来实现,这里也是同样的道理,如果说设备关闭了,那么应用层进程也无法通信了,那事实上这个设备的表现方式就是ip+端口。

clip_image006

由此服务器端自己一个应用进程绑定在这个设备上,也即这个进程注册使用了这个ip+Port,由此其他进程就不能再使用了,由此内核接收过来一个报文如果是跟本地的某个ip上某个协议的比如tcp的22号端口来通信时,就直接关联到这个进程上来,内核从而知道这是应用程序的哪一个进程需要使用此设备或者此套接字进行通信。但是客户端如何建立通信?服务器端是被动打开的,而且绑定在这个套接字上,随时等待客户端的请求,客户端其实不请求时,他是否需要打开一个套接字?是无需打开一个套接字的。由此当某个客户端需要和服务器端通信时,他需要如何做?客户端进程是临时启动起来的,启动起来后,发现要和另外一个主机上的套接字通信,他本地也是要使用一个端口的,因为服务器端数据报文需要回应过来的,他回应过来时,有很多进程,回应过来后,客户端没有使用端口,那就不知道回应给谁了。所以一般而言客户端进程是临时启动的,端口也是临时注册使用的。

IANA:进行分配的 IANA(The Internet Assigned Numbers Authority,互联网数

字分配机构)是负责协调一些使Internet正常运作的机构。同时,由于Internet已

经成为一个全球范围的不受集权控制的全球网络,为了使网络在全球范围内协调,

存在对互联网一些关键的部分达成技术共识的需要,而这就是IANA的任务。

端口分类:

有些端口是众所周知的,是固定给某些进程使用的,只能由他们使用。

为0~1023的端口:管理员才有权限使用,永久地分配给某应用使用;这些端口普通用户是没有权限使用的,主要为了避免某些用户通过恶意的应用程序来欺骗其他主要用户来访问本机的。是IANA分配的,这里是严格分配的

注册端口:

1024~41951:也是由IANA分配的,不是永久分配的,称为注册端口。只有一部分被注册,分配原则上非特别严格;

动态端口或私有端口:

41952+: -65535 是本地的客户端程序当作客户端时他们临时使用跟其他主机进行通信的端口。

对于有写非常繁忙的服务器,为了实现建立连接套接字,仅有这么多端口是不够用的,如果不够用时,可以调整本地的这个范围,41952并是不固定的,从20000开始后都作为私有端口也是可以的,需要修改本地的内核参数来实现

/proc/sys/net/ipv4/ip_local_port_range: 定义了那个范围内的端口可以作为临时使用的,定义两个数字,表示可以做为临时端口的起始数字和结束数字,在非常繁忙的服务器需要大量连接套接字的情况下可能需要调整

应用层应用并非只使用tcp或者是udp,传输层协议实现的协议并非只有TCP、UDP。

传输层协议:TCP【传输控制协议】、UDP【用户数据报协议】、SCTP(流控制传输协议 对tcp的改进)、DCCP(数据报传输控制协议 对udp的改进)

互联网上95%都是用的tcp协议,因为tcp可靠,stcp可能比tcp有着强大的控制功能,有着更好的消息边界功能,dccp同样比udp有着更好的功能。

有些应用层协议可以完全不使用tcp也不使用udp或者不使用传输层协议,而是直接使用ip报文,这些应用层协议自己直接到达ip报文,这些套接字称为 裸套接字【raw socket】

根据套接字关联到哪一种协议上,可以将套接字分为三种、

套接字类型:

tcp socket tcp套接字,借助于tcp进行传输

udp socket udp套接字,借助于udp进行传输

raw socket 原始套接字,不使用任何传输层协议,而是自己直接封装所有的控制机制,由IPv4或者IPv6封装成ip报文后就进行传输了。

但是udp socket、raw socket这些套接字并不常见,经常接触的是tcp socket。

tcp socket如何实现完成数据传输的?

互联网上两个节点之间进行通信时,他们要基于tcp完成众多的功能,

TCP协议的功能包括:

1.连接建立

tcp需要三次握手建立连接的,需要在传输的两个端点或者两个主机的进程之间建立通信会话,称为连接建立

2.将数据打包成段

打包成段以后,才能借助ip功能,一段一段进行传输,ip报文最多传输大小是65535,ip报文最大长度是65535【16位的】,但是ip报文真正在网络上进行传输时,要借助于设备的传输机制,而以太网传输的MTU最大协议传输单元为1500字节,所以ip报文是不可能传输65535这么大的报文的,通常都是1500以下。每一段都包含一个校验和,从而能够实现将报文传输到目的端后,目的端可以检查这个报文中是否发生了错误,从而可以检测出数据报文在发送过程中是否出现错误的机制,如果出现了错误,让发送端重传此报文。

3.确认、重传以及超时

一般而言tcp是有连接的协议,所以发送端发一个报文,接收端就会确认一个报文,从而使得发送端可以继续发送后续的报文,如果报文出错了,可以重传,如果发送方发出报文后,接收方总是没有数据接收到,也需要重传,需要建立超时机制,从而使得某些超时的报文还可以重传。将一个大的数据分隔成段以后,到底哪一段是第一段?哪一段是第二段呢?

4.排序

报文传输中需要排序,这些排序中,每一个报文都有一个序列号,序列号并非从0开始的,而且是32位的,其中间是通过某一种算法来生成哪一个序列号当作起始序列号的,如果序列号用满了,一直到达32位最大计数数值了,那么他还会绕回来,从0开始重新计数,需要注意的是:序列号绕回来后,如果起始值过大的话,可能会导致tcp序列号卷绕,如果传输时间过长的话。加序列号的作用:使得tcp分段能够以正确的顺序在目的端,在接收方可以重新组装起来;接收方可以确认到底是哪一个报文接收到了,哪一个报文没有接收到,要重传哪些,接收方也可以通过序列号来移除重复的段,比如接收一份由于超时了发送方没有收到接收方的确认,发送方没有收到接收方确认,它会再一次的发送过去,自己执行重传,那么最终网络可靠恢复了,即接收方可以收到两份,接收到两份后,会放弃一份重复的报文。

5.流量控制

为了避免网络上两台主机之间处理速率不同的主机之间实现通信时,发送过快的一方把过慢的一方给堵塞淹没,那么就需要实现流量控制,要想实现流量控制,tcp协议必须要维护一个缓冲区,由于tcp通信时双向的,那么其缓存区在任何一端都分为两类,一个是发送缓冲,一个是接收缓冲。tcp采用了称为滑动窗口的算法来实现向对方通告自己的接收窗口有多大?你可以一次发多少数据等,所以这个滑动窗口允许包含了总共多少个字节的未确认段同时能够在发送方和接收方之间进行传输。如果说按tcp的传输机制,发一个报文确认一个报文,那么tcp效率是很差的,为了避免此情况,发送方可以发送一批过去,确认的时候可以确认一批。那么这一批是多少呢?肯定不能是以发送方为准、或者以接收方为准。所以双方需要通过一个所谓的通知信息来告诉对方互相进行协调一次可以发多少,从而可以实现批量发送和传输,可以借助缓存区和滑动窗口来完成。如果说双方通信中的某一方,如果说接收缓冲区满了,那么就不能再接收发送缓存区发来的数据了。通知对方说自己的滑动窗口大小为0,所以不能传任何数据,如果过了一些时间后,处理数据了,滑动窗口就不为0了,通知对方滑动窗口有大小了,则对方可以发送数据过来了。窗口不断发生变化,所以称为滑动窗口。之所以称为滑动的另一个原因,接收缓冲区在真正接收数据报文时,假如说其每一个数据报文被称为一个窗口的话、或者是一个单位的窗口。那么这个时候当缓存区中接收进来报文后,本地进程需要取出一个处理一个。

clip_image008

假如本地窗口满了的话,则告诉对方没有任何空间可接收数据了,但是本地处理过后,就留有空间了,本地窗口向左移动一下,所以左侧就留有空间了,这样依次处理依次往左移,

clip_image010

这就是滑动窗口,借助于滑动窗口的方式来实现流量控制。

6.拥塞控制

虽然双方的窗口没问题,但是当前网络上面所能够实现通信的可能不止本进程一个,我们一个主机上有n个进程都需要于其他主机进行通信,两个主机之间所通信的进程也不止一个,但是tcp的拥塞控制或者是拥塞避免机制的主要功能就是用来设计防止快速的发送压垮整个网络,其跟流量控制是同一个意义,但是从额外来讲,他实现了从连接建立之后避免网络被塞满导致其他进程通信无法进行,而有意设计的一个算法,比如双方第一次通信时,发送方并不知道接收方有多少个滑动窗口可以发送数据,也不知道对方一次可以处理多少数据,那么就需要通过试探方式,通过慢启动的方式,第一次发送时只发送比较小的报文,发送过后很快就处理了,那么下一次就可以发送大一些的报文,这样依次提高。这种机制叫慢启动的方式,当达到一个量很大时,就需要通过拥塞避免算法来尽可能的保证下一次慢速发或者发送的报文小一些。

RFC: 请求注解文档(Request For Comments) 是一个起草制定协议标准的组织,来定义其实现的细节。

为了能够实现在两个主机之间基于tcp的方式建立通信,在两个主机的内核之间,其tcp中必须建立可靠的连接并进行通信的,以tcp协议为例,为了能够完成通信双方需要基于网络进程通信,通信过程是全双工的

两个方向各自是独立的连接, 发 收

发送方有一个发送缓冲区,网卡是拿一个发送一个,内核上层应用程序它产生数据的速度比较快,而网卡发送时只能一个一个往外发,所以就通过先把数据放到缓冲区中,有发送缓冲区和接收缓冲区

缓冲区是内核维护的内存空间,一般内核会为每一个进程维护一段缓冲区,准确说应该是两段 发送缓冲、接收缓冲,其大小可以定义为不同值。本地的某一个进程要想实现借助内核通信,进程需要注册使用套接字,进程在内核的边缘注册使用套接字,进程就通过套接字设备和内核交互,如果内核了解到进程是要发送数据,就把数据放到发送缓冲区中,如果是要接收数据,就到接收缓冲区来取数据。同样对方也是一样的机制,一个进程进行通信,要实现双方通信,服务器上是使用的监听套接字,客户端使用的是临时套接字。tcp协议就是通过这种方式来进行通信的。

clip_image012

如果A主机和B主机双方第一次建立通信,则先3次握手,握手完成后,发送方开始发送数据,发送方数据放到本地的发送缓冲区中,于是本地的网卡设备从本地的发送缓冲区中取一个数据调制成信号后,发送过去了。对方接收到了,接收到后放到接收缓冲区上,而后内核发现这个是本地套接字通信的,进而将接收缓冲区中的数据提交给对应的进程。双方通信速度随着网络带宽的增加会比较快,而进程处理速度会比较慢,比如:用户请求的web服务器,访问一个网页文件,此网页文件是在硬盘上放的,需要先到硬盘通过io操作【磁盘io比较慢】上把网页文件取出,所以一旦发生这种请求时,如接收过来3个请求,而接收缓冲总共能接收10个请求,也即还可以接收7个请求,此时就要借助滑动窗口机制来实现。

web通信就是通过这种模型来实现的,web使用的HTTP协议,而HTTP协议是基于tcp工作的,在大规模的场景中,通过调整或调优主要是调整发送缓冲区大小和接收缓冲区调大等

socket:socket是IPC【进程间通信】的一种实现,socket主要是跨主机的进程间通信的方式,它同一或不同主机上的进程间进行的通信,主要实现不同主机间的进程进行通信的。

socket是一种规范,其也有定义的,1983年在4.2版本第一个BSD上第一个完整意义的socket出现,而被接纳为整个Linux或者UNIX上的使用标准。

这一个典型的C/S架构场景中,socket应用程序的使用socket通信模型如下:

应用程序创建一个socket,socket是一个允许通信的设备,双方都要用到,只不过服务器需要将自己的socket绑定在一个众所周知的端口上,从而能够接收客户端请求的,另外需要注意的是在通信中,基于socket通信的领域,还有另外一个概念称为domain。不是dns下的domain。

每一个socket通信在domain中实现,domain是识别一个socket的方法(是识别socket地址方法),socket有地址格式。

domain在tcp通信中,domain有3种

1.Unix Domain: 主要是用于基于socket机制实现同一主机不同进程间通信的一种方式;称为AF_UNIX, AF称为地址家族,也称为地址组,主要用来标记接下来通信是用于ip地址的IPv4还是IPv6还是UNIX domain的方式进行通信的。所以其地址组名称也可以称为 AF_UNIX

有些地方posix被称为AF_LOCAL,本地的 其地址是一个路径名(是一个文件)

2.IPv4 Domain: AF_INET, 基于socket机制借助于ipv4协议实现不同主机(也可以是同一主机)上的进程间通信的机制; 地址是32位的ipv4地址+16位的端口号(都是通过网络协议栈来实现的)

MySQL中两种方式都实现了

3.IPv6 Domain: AF_INET6,基于socket机制借助于ipv6协议实现不同主机(也可以是同一主机)上的进程间通信的机制;地址是128位的Ipv6地址+16位的端口号

AF_INET既能实现同一主机上的通信也能实现不同主机上的通信,那为什么需要用到UNIX domain?UNIX domain和IPv4 domain不同之处在于IPv4 domain即便在同一个主机上,他也需要借助网络功能来实现,而UNIX domain完全不通过网络协议栈,不用封装网络ip报文,了解到是本机就直接进行通信。所以本机上两个进程进行通信,

如果借助于IPv4 domain,客户端发送请求先到内核中,内核需要封装tcp首部,封装IP首部,而后路由,路由了解到是本机,所以此报文又回来再拆ip首部,再拆tcp首部,再交到本地的另外一个进程,那么此过程浪费了很多效率。

而借助UNIX domain,他直接在内核中借助一个文件来实现,在内核中直接创建一个文件,第一个程序发送方往文件中写数据,接收方往文件中读数据即可,完全绕过了网络协议栈,通信效率高很多,其可用带宽Linux内部达几十G。

因此在同一个主机上,本地既是服务器端又是客户端时,通过UNIX domain方式进行通信,效率是高很多的。尤其像MySQL需要传输大量数据的服务器通常都会通过此方式来实现。

不同的domain所使用的地址格式是个不相同的,socket既可以使用tcp也可以使用udp,使用tcp时,表示是面向流的,面向数据流的。而使用udp时,使用的是面向数据报的socket。所以面向流的就有了tcp的特性,可以实现可靠的双向的字节流通信

socket的类型:取决于socket是绑定在tcp还是udp上,socket也可以绑定在stcp或者是dccp上。

绑定在TCP上:流式socket,SOCK_STREAM

面向流可实现:可靠、双向、面向字节流的通信,没有消息边界

绑定在UDP上:数据报式socket, SOCK_DGRAM

是不可靠、有消息边界

内核将套接字的接口向外提供了,相关的系统调用:

socket(): 创建一个新的socket,被动一方需要将套接字绑定在众所周知的端口上,意味着其在内核中打开了一个套接字文件

bind():绑定于一个套接字地址上;是上面三种格式中的某一种,绑定只说明了需要注册使用此端口。需要真正接收客户端连接还需要listen。

listen(): 监听套接字; 通常被动打开那方才需要listen

accept(): 接收连接请求;从内核中接收一个连接

客户端发连接请求

connect(): 发起连接请求; 主动打开端

连接正常后需要发送报文了,

close(): 关闭连接

接收和发送连接数据 read()和write(): recv(), send(), recvfrom(), sendto()

接收和发送就是向此套接字文件中写数据和读数据

真正基于tcp方式进行连接通信时,tcp是一种有状态的通信方式

基于TCP客户端/服务器程序的套接字函数

clip_image014

服务器在进程启动之前是处于closed状态的,使用service启动进程,就打开一个端口等待客户端请求了,进程就处于监听状态了listen,listen之后客户端就可以发请求了,客户端开始时是closed,当其发起connect连接请求后,就开始建立tcp的三次握手了,tcp的三次握手当中,首先发送syn,服务器端收到syn后要确认ack和发送syn,而后客户端要确认ack,而后就发送真正的请求报文了,接着是数据传输过程,当传输结束后,就调用closed来关闭连接,关闭连接要4次断开,而这整个过程大体上tcp协议,他用一种tcp状态的方式来标记。

tcp协议通过tcp状态来标记当前处于通信过程的哪个阶段:

CLOSED, LISTEN, SYN_SENT, SYN_RECV, ESTABLISHED, FIN_WAIT1, CLOSE_WAIT,

FIN_WAIT2, LAST_ACK, TIME_WAIT, CLOSED

从closed到listen称为被动打开,listen不监听了,又转换成为closed状态,客户端使用connect建立连接时,状态要发生改变了,服务器端接收客户端请求,服务器是发送客户端请求,客户端发送请求称为主动打开SYN_SENT,发送syn请求,由closed变为syn_sent为主动打开方,所以客户端从syn_sent到closed,从客户端发送请求了,对方没有响应,客户端发送syn_sent后,服务器端就收到syn_sent请求了,当服务器端收到syn_sent请求后,服务器端就从listen转为syn_recv,此过程叫接收syn并发送syn+ack,自己就成了syn_recv了,客户端收到服务器端发来的syn+ack就变为established状态了,而后就可以交互了established状态。要先走的那方需要关闭,就发送fin_wait1,

clip_image016

四次断开的过程是一方请求,另外一方确认,而另外一方也需要请求而后确认

开始都处于established状态,其中一方发送fin,其就处于fin_wait1状态,等待对方确认,确认后,等待对方发送fin,就处于fin_wait2状态,一旦收到fin后,确认即可,在确认之前需要等待一会,使其进入此通道,此阶段叫time_wait。另外一端,在接收到fin并执行确认后,而后自己再发送fin,此阶段称为closed_wait,等待关闭,发送fin后收到对方确认处于last_ack阶段,而后转换成closed状态。

clip_image018

如果说有很多处于fin_wait1或者是处于fin_wait2状态的连接,则别的连接就进入不了,因为所有能够建立的连接数是有限的,此时就需要分析产生此种状况的原因。

常用netstat, ss工具查看此时有多少连接,每一个连接处于整个tcp连接状态的那个阶段,从而可以判断服务器为什么繁忙,甚至可以检测服务器上是否有一些非正常连接处于在线状态的。

tcp协议通过tcp状态来标记当前处于通信过程的哪个阶段:

CLOSED, LISTEN, SYN_SENT, SYN_RECV, ESTABLISHED, FIN_WAIT1, CLOSE_WAIT,

FIN_WAIT2, LAST_ACK, TIME_WAIT, CLOSED

爱编程-编程爱好者经验分享平台

文章评论

  

版权所有 爱编程 © Copyright 2012. w2bc.com. All Rights Reserved.
闽ICP备12017094号-3