从socket编程走向IO多路复用的并发编程

importance
date
Mar 28, 2024
slug
从socket编程走向IO多路复用
status
Published
tags
network
summary
对于socket编程我其实一直是一知半解的,这篇文章主要是一个梳理的性质,从socket编程到我们如何使用IO多路复用的并发编程。
type
Post
对于socket编程我其实一直是一知半解的,这篇文章主要是一个梳理的性质,从socket编程到我们如何使用IO多路复用的并发编程。

socket编程

首先要搞清楚,socket编程是基于一个client-sever模型的。
notion image
而 {host, port} 唯一地定义了socket的标识,这是我们进行socket编程的前提。对于客户端来说,它需要服务器的 {host, port} 来进行连接,而对于服务器端来说,它同样需要客户端的 {host, post} 来确定它到底需要监听来自哪些客户端的请求。
而 {host, port} 在socket编程中被整合进入了 sockaddr_in 或者 sockaddr_in6中,他们用于表示 TCP/IPv4 或者 TCP/IPv6 当然也有可能是UDP,但是我们主要关注TCP。
notion image

客户端向服务器端发起连接

客户端如果要发起连接,首先需要服务器端的sockaddr_in,通常我们使用 getaddrinfo 来帮我生成 sockaddr
notion image
 
首先必须设置好 hints,表示我们需要什么类型的socket,这里我们需要一个TCP类型的socket,并且可以是IPv4或者v6
接着我们调用getaddrinfo 获得对应的addr,res返回的是一个链表可能有多个sockaddr,这是因为我们解析的host有可能是一个域名,而域名可以绑定多个IP地址。
通过getaddrinfo返回的res,我们可以很便捷的创建一个socket
我们就可以拿着这个socket去和服务器进行连接:
至此客户端的连接到服务器端的过程已经完成,connect会自动生成一个 {客户端host, 随机port} 的客户端sockaddr表示客户端的 socket地址。
 

服务器端监听并接受连接

服务器端的关键步骤有三个。
1)bind:绑定服务器socket到服务器的sockaddr。
notion image
2) listen:这个很简单,就是将主动套接字转换为监听套接字,表示服务器端可以进行连接了。
notion image
3)accept:当客户端发送连接请求,监听套接字如果接受请求,那么监听套接字的状态就会发生改变,这时候我们可以创建一个已连接套接字。(已连接套接字,客户端套接字)这一对套接字之间进行数据传输。
notion image
为什么需要监听套接字和已连接套接字两个概念,因为监听套接字实际上仅仅用于创建连接,它的状态改变只是代表着新的连接请求。
 

IO 多路复用

想象有这样一个场景,我们需要使用read函数,从命令行标准输入读取数据,同时我们也需要从远端服务器读取数据,如果远端服务器没有发送了数据,那么客户端的socket处于一个阻塞状态。
从命令行标准输入读取数据,或者从socket中读取数据,都有可能造成阻塞。我们如果 read(stdin) 那么毫无疑问即使socket中有新的数据,我们也会等待知道,命令行中有输入才能把进程从read的阻塞状态中拯救过来。
于是,有一些聪明人开始思考,有没有办法我们可以同时监听stdin和socket,如果哪个有数据,我们就处理哪个。于是诞生了select为代表的IO多路复用。
 
select的函数签名,它需要一个bitset用于指示需要监听哪些文件描述符,以及最大监听描述符。
notion image
 
首先我们需要设置bitset,将我们需要监听的文件描述符对应bit位置为1
notion image
notion image
select调用后会被阻塞,如果有任意的文件描述符指向文件的状态改变,那么select会将对应bit位置为1并返回。这里listenfd状态改变,那么select会把listenfd的bit位置为1
notion image
我们可以使用FD_ISSET这个宏来确定到底是哪一个fd状态发生了改变。
简单示例代码:
 

基于IO多路复用的并发编程

想象一下这样一个场景,我们的服务器端可以接受多个连接,但是开始的时候我们肯定是没有连接的,也就是只有listenfd。我们需要同时处理当新的客户端请求连接时候,创建已连接套接字。同时如果有连接,也就是已连接套接字得到数据后,我们需要读取这些数据。
这就需要一个pool来维护所有的client。
此代码首先使用 getaddrinfo 准备套接字并绑定到端口 9090 上。之后,它使用 select 进入主循环,监听连接请求并读取任何来自已连接客户端的数据。当客户端断开连接或读取发生错误时,该客户端的文件描述符会被关闭并从客户端列表中移除。
 

© songlinLife 2023 - 2025