套接字地址结构
大多数套接字函数都需要一个指向套接字地址结构的指针作为参数。每个协议族都定义它自己的套接字地址结构。
![[uTools_1693906445487.png]]
通用套接字地址结构
这些通用套接字地址结构的唯一通途就是,对指向特定于协议的套接字地址结构的指针执行类型强制转换。
基本TCP套接字编程
socket函数
为了执行网络I/O,一个进程必须要做的第一件事就是调用socket函数,指定期望的通信协议类型(IPV4的TCP,IPV6的UDP、Unix域字节流协议等)。
AF_前缀表示地址族,PF_前缀代表协议族。
connect函数
TCP客户用connect函数建立与TCP服务器的连接。
sockfd是由socket函数返回的套接字描述符。
如果是TCP套接字,调用connect函数将激发三次握手过程。
bind函数
此函数是把一个本地协议地址赋予一个套接字。 对于网际网协议,协议地址是32位的IPv4地址或128位的16位的TCP或UDP端口号的组合。
服务器在启动时捆绑它们的接口。如果一个TCP客户或服务器未曾调用bind绑定的一个端口,当调用connect或listen时,内核会为相应的套接字选择一个临时端口。
listen函数
此函数仅由TCP服务器调用,它做两件事。
- socket创建一个套接字时,是主动地,也就是它是一个将调用connect发起连接的客户套接字。此函数把它变成被动套接字,指示内核应接受指向该套接字的连接请求。
- 规定了内核应该为相应套接字排队的最大连接数。
在socket和bind函数后,在accept函数前。
内核给任何一个给定的监听套接字维护两个队列,未完成连接队列,已完成连接队列。
![[uTools_1693907496628.png]]
![[uTools_1693907530063.png]]
accept函数
此函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成队列为空,则进程进入睡眠。
fork和exec函数
fork是调用一次,但返回两次。
它在调用进程(父进程)中返回一次,返回值是新派生进程(子进程)的进程ID;在子进程中又返回一次,返回值为0。因此,返回值本身告诉当前进程是子进程还是父进程。
任何子进程只有一个父进程,子进程可以通过调用getppid
获取父进程ID。如果父进程想跟踪它所有的子进程的ID,必须记录每次调用fork的返回值。
fork典型用法:
一个进程创建一个自身的副本,这样每个副本都可以在另一个副本执行其他任务的同时处理各自的操作。这是网络服务器的典型用法。
exec把当前进程映像替换成新的程序文件,而且该程序通常从main函数开始执行。进程ID并不改变。我们称调用exec的进程为调用进程,新执行的程序为新程序。
并发服务器
Unix中编写并发服务器的最简单方法就是fork一个子进程来服务每个客户。
close函数
close一个TCP套接字的默认行为是把该套接字标记为关闭,然后立即返回调用程序。
该套接字描述符不能再由调用进程使用,也就是它不能再作为read和write的第一个参数。
[[IO五种模型]]
select函数
该函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒它,无需阻塞等待。
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds
:监视的文件描述符的最大值加1。readfds
:可读文件描述符的集合。writefds
:可写文件描述符的集合。exceptfds
:异常条件文件描述符的集合。timeout
:超时时间,如果为NULL,则select
将一直阻塞,直到有文件描述符就绪。
select
函数将会修改传入的fd_set
集合,标记出就绪的文件描述符。你可以使用FD_ISSET
宏来检查文件描述符是否已经就绪。
poll函数
功能与select类似。不过在处理流设备时,它能提供额外的信息。
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds
:一个指向pollfd
结构体数组的指针,每个结构体包含了一个文件描述符和要监视的事件。nfds
:数组中的元素数目。timeout
:超时时间,以毫秒为单位,如果为-1,则poll
将一直阻塞,直到有文件描述符就绪;如果为0,则poll
将立即返回,不阻塞;如果大于0,则表示超时时间。
poll
函数会检查指定文件描述符的状态,并标记就绪的文件描述符。与select
不同,poll
不受文件描述符数量的限制,并且不需要修改全局的文件描述符集合。它还提供了更多的事件类型和更精细的控制,使得编程更加灵活。