Basic Network I/O Techniques
经常能在各种地方看到这么一句话:
Everything in Unix is a file
当Unix程序做任何的I/O时,本质上他们也是通过读/写相应的文件的描述符(file descriptor)来进行操作。
在Unix系统中,文件描述符是一个泛化的概念,它是一个代表了某个打开的‘文件’的一个整型值(integer)。此处的‘文件’既可以是一个字面意义上存储在磁盘上的一个数据文件,也可以是一个网络通信,一个FIFO,一个Pipe…等。
本文中,我将研究一下基本的C语言下的Network I/O。
Socket是系统中一种使用文件描述符来与其它程序进行通信的方式。 通常来说,我们会使用Socket来进行网络中传输层的数据传输,即使用TCP或者UDP协议,来进行网络的通信。
要通过网络进行数据传输,不管是否需要进行连接,我们都至少需要知晓对应的数据接收方的地址。网络环境下,我们需要使用对应的IP来路由到对应接受程序所在的主机(Host)地址,而后再使用端口(Port)来定位到对应的程序。当下的环境中,我们存在IPv4与IPv6等不同的IP协议版本,或者我们可能使用不同的协议(protocol)进行通信,因此,我们需要通过一些通信元数据的配置,来获得我们需要的Socket,从而使用Socket来进行网络通信。
总体说来,使用Socket进行数据通信有着如下的基本流程:
- 获得对应的用来告诉Socket地址信息的元数据 struct addrinfo
- 使用上一步获得的地址信息,创建一个可用的Socket
- 如果是一个服务端应用
- 当前的Socket,通常是使用本机的地址信息进行创建的
- 使用bind将该程序绑定到系统可用的某个端口,使其它网络程序可以访问
- 使用listen来声明我们将在当前主机的对应端口来进行监听
- 我们可以调用一个阻塞的accept操作,一直到有其他方connect到我们监听的端口,此时会产生一个用来描述这个新连接的socket file descriptor
- 通过这个新连接得到的socketfd,使用send/recv进行通信(如果使用无连接UDP,则使用对应的sendto/recvfrom操作)
- 如果是一个客户端应用
- 当前的Socket,通常是使用目标服务地址信息进行创建的
- 使用connect进行到服务端的连接
- 通过这个新连接得到的socketfd,使用send/recv进行通信(如果使用无连接UDP,则使用对应的sendto/recvfrom操作)
- 使用close或者shutdown关闭创立的Socket连接
struct addrinfo
网络通信,需要知晓通信方对应的地址,再通过创建对应的Socket来建立连接,进行通信。struct addrinfo就是对应的用来告诉Socket地址信息的元数据。
1 | struct addrinfo { |
这个结构体里,我们可以指定对应的IP协议(IPv4 or IPv6),socket的类型(TPC or UDP),以及其它地址相关的信息。我们需要通过填入对应的元信息,再使用这个addrinfo来进行Socket的创建。当然,我们不需要手动的进行填写,我们可以使用getaddrinfo函数来进行数据的填充:
1 | int getaddrinfo(const char *node, // e.g. "www.example.com" or IP |
例如,我们可以用如下方式来获得一个绑定本机IP以及特定PORT的addrinfo:
1 | int status; |
或者,获得对应一个网络端的addrinfo:
1 | int status; |
通过以上的操作,我们将获得res,即上例的servinfo, 这是一个addrinfo的链表。我们需要遍历这个链表来找到一个可用的信息来创建Socket。需要注意的是,对于锁获得的res,即上述的servinfo,我们需要调用 freeaddrinfo(servinfo) 来清理对应的数据。
在获得对应的信息后,我们就可以依据上述流程,创建对应的Socket,我们将得到一个socketfd。之后,我们就可以对socketfd这个文件描述符进行相应的网络I/O操作。需要注意的是,对于每一步操作,都需要检验其返回值,来检查操作是否成功的执行或者资源是否可用。
Demo TCP Server
1 | /* |
Demo TCP Client
1 | /* |