Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。
本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。
send() 遵循 POSIX.1 - 2008
MSG_CONFIRM 是 Linux 扩展
标准 c 库,libc, -lc
sockfd = socket(int socket_family, int socket_type, int protocol);
本文主要描述 Linux 网络套接字层的用户编程接口。 BSD 兼容的套接字是用户进程和内核网络协议栈的统一接口。各协议模块被分配不同的协议家族(AF_INET、AF_IPX、AF_PACKET 等)以及不同的套接字类型(如 SOCK_STREAM、SOCK_DGRAM),参考 socket(2) 获取更多关于协议家族和类型的信息。
套接字层函数
这些套接字层函数是用户进程用来发送和接收数据包以及其他套接字操作。
socket(2) 创建一个套接字,connect(2) 连接一个套接字到一个远程套接字地址,bind(2) 将一个套接字绑定到一个本地套接字地址上,listen(2) 告知套接字有新连接需要被接受,accept(2) 用来获取新来连接的新的套接字,socketpair(2) 返回两个连接的匿名套接字(只有类似 AF_UNIX 的本地套接字才有这个实现)。
send(2)、sendto() 和 sendmsg(2) 在套接字上发送数据,recv(2)、recvfrom、recvmsg(2) 从套接字上接收数据。poll(2) 和 select(2) 等待数据来临或者是否发送数据就绪。此外,标准的 I/O 操作类似 write(2)/writev(2)/sendfile(2)/read(2)/readv(2) 可以用来读写套接字上的数据。
getsockname(2) 返回本地套接字地址,getpeername(2) 返回远程套接字地址。getsockopt(2) 和 setsockopt(2) 用来设置/获取套接字层或者协议层选项。ioctl(2) 可以用来设置或者读取一些其他选项。
close(2) 用来关闭一个套接字。shutdown(2) 关闭全双工套接字双方。
Seeking 或者 pread(2)、pwrite(2) 这种从非 0 位置读写操作套接字是不支持的。
可以通过 fcntl(2) 来设置一个套接字文件描述符的非阻塞标记,实现套接字的非阻塞 I/O 操作。一旦设置,套接字上所有可能导致阻塞状态通常都会返回 EAGAIN(表示操作稍后需要重试),connect(2) 会返回 EINPROGRESS 错误。用户可以通过 poll(2) 或者 select(2) 来等待各种事件。
I/O 事件 | ||
事件 | 轮询标记 | 发生时机 |
读 | POLLIN | 新数据到达 |
读 | POLLIN | 一个连接配置已完成(对于面向连接的套接字) |
读 | POLLHUP | 对端发起了断开连接请求 |
读 | POLLHUP | 连接断开了(对于面向连接的套接字),当写套接字时,会发送 SIGPIPE 信号 |
写 | POLLOUT | 套接字具有足够的空间来写入新数据 |
读写 | POLLIN/POLLOUT | 向外连接的 connect(2) 完成 |
读写 | POLLERR | 发生了异步错误 |
读写 | POLLHUP | 对端关闭了一个方向的连接 |
异常 | POLLPRI | 紧急数据到达,随后会发送 SIGURG。 |
另一个代替 poll(2)/select(2) 的方式是内核通过 SIGIO 信号通知应用程序,对于这种方式,必须通过 fcntl(2) 来设置套接字文件描述符的 O_ASYNC 标记,然后通过 sigaction(2) 安装 SIGIO 的信号处理函数,可以参考后面关于信号的讨论。
套接字地址结构
每个套接字域(domain)都有自己的套接字地址格式。每个结构以一个整型的“家族”字段(sa_family_t) 来指示地址结构的类型。
Linux 假定发送/接受缓冲区的一半用于内部内核结构,因此对应的 /proc 文件大小是线上可观测大小的两倍
下面是一个 getsockopt 函数的使用代码:
int rc;
int s;
int option_value;
int option_len;
struct linger l;
int getsockopt(int s, int level, int option_name,
char *option_value,
int *option_len);
⋮
/* Is out-of-band data in the normal input queue? */
option_len = sizeof(int);
rc = getsockopt(
s, SOL_SOCKET, SO_OOBINLINE, (
char *) &option_value, &option_len);
if (rc == 0)
{
if (option_len == sizeof(int))
{
if (option_value)
/* yes it is in the normal queue */
else
/* no it is not
*/
}
}
⋮
/* Do I linger on close? */
option_len = sizeof(l);
rc = getsockopt(
s, SOL_SOCKET, SO_LINGER, (char *) &l, &option_len);
if (rc == 0)
{
if (option_len == sizeof(l))
{
if (l.l_onoff)
/* yes I linger */
else
/* no I do not */
}
}