目录
1. socket套接字的属性、地址和创建
2. 如何使用socket编写简单的同步阻塞的服务器/客户端
3. 理解Linux五种I/O模型
1.socket套接字
套接字是Linux系统提供的一种进程间通信的机制,通过socket套接字既可以满足同一计算机内部进程的通信,也可以满足不同计算机进程之间的通信。socket的使用与文件的操作使用文件描述符非常类似,操作套接字使用套接字描述符。套接字描述符使用文件描述符实现的,许多文件描述符的函数(如read和write)都可以处理套接字描述符。
1.1 套接字的创建
创建文件描述符使用open函数,创建套接字描述符使用socket函数,创建套接字的时候指定套接字的域、类型和协议三个参数。
socket函数原型:int socket(int domain,int type,int protocol)
1. 套接字的域指定通信中使用的介质。目前使用较多的是AF_INET(IPv4因特网域)、AF_INET6(IPv6因特网域)、AF_UNIX(Unix域,文件系统套接字)
2. 套接字的类型进一步确认通信的特征,在计算机网络中经常提及网络层的IP协议、传输层TCP/UDP协议。socket中通过指定套接字的类型指定面向连接和面向非链接的两种通信机制。
l SOCK_STREAM(流套接字)。流套接字提供的是有序、可靠、双向字节流的连接,整个通信过程维持一个连接,直到通信双方关闭连接。发送的数据可以确保不会丢失、复制、乱序到达。大的消息将被分片、传输、重组。这很像一个文件流,它接收大量的数据。然后以小数据块的形式写入底层磁盘。通过TCP/IP连接实现。
l SOCK_DGRAM(数据报套接字)。与流套接字相反,数据报套接字不建立和维持一个连接,它对可以发送的数据报的长度有限制。数据报作为一个单独的网络消息被传输,可能会丢失、复制或乱序到达、通过UDP/IP协议实现。
3. 套接字的协议protocol通常是0,指按照指定的域和套接字的类型选择默认的协议。
1.2 套接字地址
要想让通过socket调用创建的套接字可以被其它的进程调用,服务器程序必须给该套接字命名,也就是绑定一个固定的地址。例如AF_UNIX文件套接字会关联到一个文件系统的路径名、AF_INET域的套接字会关联到某个端口。不同域的套接字有自己对应的地址格式,客户端根据服务器端套接字的地址访问该套接字。
2. 使用socket编写简单的同步阻塞服务器/客户端
2.1简单socket服务器
1. 创建监听套接字,使用socket系统调用创建套接字,返回套接字描述符
2. 命名监听套接字,使用bind系统调用将套接字描述符绑定到某个固定的套接字地址
3. 使用listen系统调用创建监听套接字队列。为了能够在监听套接字上接收进入的连接,服务器程序必须创建一个队列来保存未处理的连接请求,Linux系统会对队列中可以容纳的未处理连接做出最大数目的限制,listen函数使用backlog参数限制。在套接字队列中,等待处理的连接不能超过backlog的指,再往后的连接将被拒绝,导致客户的连接请求失败。
int listen(int socket,int backlog)
4. 使用accept系统调用等待客户建立对监听套接字的连接。当客户端与服务端成功建立连接后,accept函数将创建一个新的套接字与该客户进行通信,accept函数返回的套接字类型与监听套接字的类型是完全一样的;如果服务器的套接字队列中没有未处理的连接时,accept将阻塞直到有客户连接为止。可以通过对套接字描述符设置O_NOBLOCK标志来改变这一行为。
5. 在新创建的套接字上进行读写操作
6. 关闭套接字
2.2 简单socket客户端
1. 根据服务器套接字的域、类型、协议,创建相应套接字
2. 如果处理的是面向连接的网络服务,在开始数据交换之前,需要在客户端和服务端之间通过connect函数建立一个连接。在connect中所指定的地址是想与之通信的服务器的地址。成功时,connect函数调用返回0,失败时返回-1。如果连接不能马上建立,connect调用将阻塞一段时间,一段时间后任然无法成功建立连接,连接将放弃,connect调用失败。和accept函数一样,connect函数也可以设置为非阻塞模式,那么在连接不能马上建立连接时,connect将会返回-1。
2.3 数据传输
前面讲过套接字描述符和文件描述符非常相似,并且套接字描述符是在文件描述符的基础上建立的。因此可以使用write和read函数来用作socket的数据的输出和输入。除此之外,针对socket的数据传输,linux提供了一些新的函数。
l write/read
l send/recv,在上面增加flag标志
l sendmsg/recvmsg
l sendto/recvfrom,数据报服务
三、Linux I/O模型
3.1 如何理解阻塞/非阻塞,同步/异步两组概念?
A: 阻塞与非阻塞指的是调用发起者者(线程)等待结果时的状态。阻塞调用是指调用结果返回之前,当前线程会被挂起,直到结果返回。非阻塞调用指不能立刻得到结果之前,线程不会阻塞等待,而是返回某种信息标示结果是否产生结果。
B: 同步和异步指的是通信双方的消息通信机制。同步指的是发起者(甲方)主动去获取乙方结果,在等待乙方产生结果的过程中,甲方可以处于阻塞状态,也可以是非阻塞状态。异步则相反,是由乙方通过状态、通知来通知甲方,或者通过回调函数处理这个调用。
举个例子:
我去书店买一本书,可以采用同步或者异步方式。在程序设计时也要先确定是采用同步还是异步方式。
1. 同步方式
A: 我去书店买一本书,立即买到了,或者没有就走,这就是非阻塞的(非阻塞方式)
B: 如果恰好书店没有,我就一直等到书店有了这本书买到了再走,这就是阻塞的,而排在我后面的人只有我买到了书后才能买书了(阻塞方式)
C: 如果恰好书店没有书,我就告诉书店老板,书来了告诉我一声我来取。然后我就去做别的事情(信号驱动)
2. 异步方式
我去书店买书,不管有没有书,告诉老板一声书来了,直接送到我家里。
反映在程序设计时,当用户进程调用系统调用。用户进程对应了我,内核对应书店老板,书对应资源,买书就是一个系统调用,其中内核拷贝数据到进程这个过程近似于老板送书到我家里。
3.2 Linux 五种I/O模型
1. 同步阻塞I/O
最常用的一个模型是同步阻塞I/O。在这个模型中,用户进程调用一个I/O系统调用send/recv函数,这会导致应用程序阻塞直到系统调用完成为止。
2. 同步非阻塞I/O
同步非阻塞I/O中,设备是以非阻塞形式打开的,这意味在这种模型中,设备是以非阻塞的形式打开的。这意味着 I/O 操作不会立即完成,read
操作可能会返回一个错误代码,说明这个命令不能立即满足(EAGAIN
或 EWOULDBLOCK
)。默认创建的socket是阻塞模式,可以参考“”将socket设置为非阻塞模式,因为效率低很少使用非阻塞I/O。
3. 同步I/O多路复用
I/O多路复用就是通过一种机制,可以监听多个描述符,一个某个描述符就绪,一般就是读就绪或者写就绪,能够通知程序进行相应的操作,参考下面5个链接。
select:
poll:
epoll:
4. 同步信号驱动I/O
首先允许套接字接口进行信号驱动I/O,并且安装信号处理函数,进程继续运行并不阻塞,当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据。
5. 异步I/O
以上四种I/O模型都是同步I/O,在Linux中只用使用特殊的AIO系统调用才是异步I/O模型。当一个异步系统调用发出后,调用者不能立即得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者的输入和输出操作。异步I/O的使用参考“”使用异步I/O大大提升应用程序的性能。下面有一副图比较5种I/O模型,将一个系统调用分为两部分,系统调用发起,数据从内核空间拷贝到用户空间。
参考资料:
1.
2.
3.
4. select:
5. poll:
6. epoll:
7.
8. https://mmoaay.gitbooks.io/boost-asio-cpp-network-programming-chinese/content/Chapter1.html