对I/O传输、I/O模型以及socket的困惑由来已久,这些天查阅了大量的资料,来写下对I/O模型以及socket的一些理解与思考,希望能帮到大家,因为还是一名大三学生,难免会有错误的地方,还请大家斧正。
1. 端口
端口在百度百科的定义是:
在网络技术中,端口(Port)大致有两种意思:一是物理意义上的端口,比如,ADSL Modem、集线器、交换机、路由器用于连接其他网络设备的接口,如RJ-45端口、SC端口等等;二是逻辑意义上的端口,一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等。
我们所需要理解的是第二者,是逻辑意义上的端口。在这里切记的是,端口只是个逻辑意义上的,用来区分不同socket之间的一个标识符而已,同样它是在不同socket通信的“门牌号”,不同的socket建立连接后,在相同ip下就需要用端口号来找到对应的socket来进行操作。之所以在这里介绍端口的定义,是因为笔者在之前将端口理解成水管入口那样的东西,认为数据是像水一样从端口进去的,这个理解是错误的,数据的复制,读取等操作最终都是OS内核来完成的,端口只是个标识符而已。
2. Socket
2.1 socket的概念
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
Socket俗称套接字,是TCP/UDP连接的端点,用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口。一般来说一个套接字由目标ip、目标端口、源ip、源端口加上协议(一般为TCP与UDP)总共五个元属性组成,只要任意一个元属性不同,那么就不是同一个socket。
socket是面向C/S架构的,它在应用层上提供了几个常用的接口,用来连接(connect),应答(accept),读取(read),写入(write)。可以认为它是一个接口,这个接口用来处理服务器与客户端之间的通信的,以一个形象的比喻,TCP socket就好比一个电话号码,你要打电话给对方就需要知道对方的电话号码(对方的socket),从而建立起连接。这里需注意的是,TCP连接需要两个socket连接起来,而UDP连接则只需要自身的socket,自身的socket有对端的ip与端口就行了,因为TCP需要三次握手之类的确定可靠性,而UDP是无连接,不可靠的,所以UDP socket连接打比方来说,像写信,只需要知道收件人就行了,对方不需要知道自身的情况。
以前在网上看到有人把socket丢在应用层与传输层之间的位置,将其视做应用层与传输层的连接的接口,确实可以这样认为,它联通了应用层与传输层,以TCP/UDP为传输协议在两端传输数据。
从socket的定义来看,其实可以得到一个socket只能建立起一条TCP连接的结论,因为每个socket在每台终端上是唯一的,每个socket在建立起的时候就就决定了只能连接到它的目标ip的某个端口。这时候,会有同学问,那服务器不是可以监听一个端口而实现多个客户端向它传输数据吗?不就是服务端的socket对应了多个TCP连接吗?
2.2 socket的通信过程
这就需要了解到服务端的TCP socket的特殊情况以及TCP socket如何连接通信的情况了。对于服务端来说,绑定某个端口,并开始对这个端口开始监听的时候就建立起了服务端的TCP socket(暂时叫做server socket),但是这个server socket并没有满足socket的定义,因为它没有目标端口以及目标ip,因为server socket只是用来监听端口有无其他socket连接的到来。下面是TCP socket如何连接并通信的过程:
- 服务端绑定某个端口建立起监听,这个socket开始监听有无其它socket请求连接的到来;
- 客户端设定服务端的ip以及端口,随机一个本地端口(可指定),加上自身的ip组成一个socket,客户端通过此socket对服务端发起请求(在程序中可调用connect函数);
- 服务端监听到端口有数据的到来,随即server socket作出回应,接受应答(调用accept函数),在服务端产生一个新的socket,这个socket有来自请求客户端的ip以及端口号,新产生的socket与客户端的socket建立起一个TCP 连接(需要三次握手);
- 新产生的连接可以开始写入和读取对方发来的数据。
还有一点注意的是,server socket自持有一个请求队列,如果有多个客户端请求服务端,那么这多个客户端的请求会按照到达的先后时间入队,在之后按照队列顺序被一个个处理,所以每个socket(除了server socket外)都只能建立一个TCP连接,不管对于服务端还是客户端来说。
而UDP socket连接则是没有客户端连接(connect)与服务端应答(accept)与监听,直接朝着本端口socket指定的ip与端口发送数据(没有三次握手确认对端状态,不保证传输的可靠性,但是提升了效率):
另外,需要大家强烈注意的是,本端socket连接上对端另一个socket,那么这种连接便是网络传输,而本端socket连接自身的情况,便是本地传输了,所以socket不仅可以连接其它的socket,还可以连接自身。
2.3 socket的缓冲区
再来谈及socket的缓冲区,每个TCP socket(不太确定server socket有木有)的缓冲区有两个,发送缓冲区与接收缓冲区。socket是位于操作系统内核的,可能大家在其它地方看到过I/O的流程分为两步,一步是等待数据的到达,另一步就是将数据从内核缓冲区拷贝到用户进程。等待数据的到达其实是判断socket接受缓冲区是否到达一个程度,如果达到了就将数据转移到用户进程里,这是I/O输入的本质,而输出则相反,将数据拷贝到发送缓冲区,等到发送缓冲区到了一定的空间或者到了一定的时间才发送出去,所以发送数据时不是立马发送出去的。而UDP socket连接则没有发送缓冲区,只有接受缓冲区,不管对端是否可以正确的接受都会把数据发送出去。
接受缓冲区里的数据如果一直没有调用read操作读取得话,会一直待在接受缓冲区里,如果接收缓冲区空间满了后,会发送给对端关闭连接的通知,从而保证数据的不丢失,这也保证了TCP是可靠性传输,而如果对端无视这个通知时,那么会拒绝接受对端传来的数据。
对于TCP连接来说,假如socket发送缓冲区的容量超过了用户进程内缓冲区内所待发数据,那么把用户进程缓冲区的数据拷贝到发送缓冲区。而假如容量不够,那么能拷贝多少到发送缓冲区就拷贝多少,到发送缓冲区发送了n个字节的数据后,本端会通知内核继续拷贝n个字节到发送缓冲区,而对端同时会持续通知本端对端接收缓冲区的状态,从而调节本端发送数据的大小,保证TCP传输的高可靠性。