socket编程之socket()函数

769 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情


现在来详细介绍一下socket()这个系统调用。

先看一些用法和参数:

#include <sys/types.h>
#include <sys/socket.h>int socket(int domain, int type, int protocol); 

可是这个函数的参数是干什么的呢?刚开始用这个函数可能会一脸懵,别怕,我慢慢介绍。

函数参数是用来指定你想要的socket类型(IPv4还是IPv6,数据流(stream)还是数据报(datagram),TCP还是UDP)。

domain参数指的是协议族,你可以设置为PF_INET或者PF_INET6,也可以是下表(只列举了一部分)中的某个常值。

family说明
AF_INETIPv4协议
AF_INET6IPv6协议
AF_LOCALUnix域协议
AF_ROUTE路由套接字
AF_KEY密钥套接字

type表示socket的类型,可以是下表中的某个常值。

type说明
SOCK_STREAM字节流socket
SOCK_DGRAM数据报socket
SOCK_SEQPACKET有序分组socket
SOCK_RAW原始socket

至于protocol,你可以简单地将protocol设置为0,会自动选择domaintype字段组合的系统默认值。当然喽,如果你想亲力亲为,你也可以调用 getprotobyname() 来查找你想要的协议,参数可以是“tcp”、“udp”等,还可以直接使用下表中的常值。

protocol说明
IPPROTO_TCPTCP传输协议
IPPROTO_UDPUDP传输协议
IPPROTO_SCTPSCTP传输协议

可能你已经注意到了,domain字段的候选值中既有AF_INET又有PF_INETAF_XXXPF_XXX有什么区别呢?

AF_前缀表示地址族(AF表示Address Family),PF_ 前缀表示协议族(PF表示Protocol Family)。你可能发现有些资料的socket()第一个参数使用AF_XXX,有些资料却使用PF_XXX,这确实有点让人头疼。

追根溯源,这其实是有历史原因的。很久很久之前,人们设想单个协议族可以支持多个地址族,但是这个想法就从来没有实现过。而且<sys/socket.h>这个头文件中为某个给定协议定义的PF_值总是和此协议的AF_值相等,这就直接造成了AF_PF_滥用的乱象。

相比PF_XXX,很多程序员更喜欢将AF_XXX作为第一个参数传入socket,甚至包括《Unix网络编程》的作者Stevens也在书中直接用AF_XXX作为参数(这其实只是作者想与大部分代码保持一致罢了,算是一种妥协)。但大多数人做的未必就是对的。

我们最推崇的一种做法是遵守POSIX规范,将socket()函数的第一个参数设置为PF_值,而在struct sockaddr_in结构中使用AF_

说这么多已经够用了。结合之前讲过的getaddrinfo(),我们需要做的就是将getaddrinfo()调用得到结果直接喂给socket(),像这样:

int s;
struct addrinfo hints, *res;
​
// do the lookup
// [pretend we already filled out the "hints" struct]
getaddrinfo("www.example.com", "http", &hints, &res);
​
// again, you should do error-checking on getaddrinfo(), and walk
// the "res" linked list looking for valid entries instead of just
// assuming the first one is good (like many of these examples do).
// See the section on client/server for real examples.
​
s = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

socket()函数在返回成功时会返回一个套接字描述符(socket descriptor),它是一个非负整数,在后续的其他函数调用中,我们将使用它来表示这个socket。

如果发生错误,socket()会返回-1,此时errno这个全局变量会被设置为该错误的值。

只要一个Unix函数发生错误,Unix的全局变量errno就会被设置为一个指明该错误类型的某个正数,而函数本身通常返回-1

下面展示了<sys/errno.h>头文件中定义的errno的部分候选值:

...
/*
 * Error codes
 */#define EPERM           1               /* Operation not permitted */
#define ENOENT          2               /* No such file or directory */
#define ESRCH           3               /* No such process */
#define EINTR           4               /* Interrupted system call */
#define EIO             5               /* Input/output error */
#define ENXIO           6               /* Device not configured */
#define E2BIG           7               /* Argument list too long */
#define ENOEXEC         8               /* Exec format error */
#define EBADF           9               /* Bad file descriptor */
#define ECHILD          10              /* No child processes */
#define EDEADLK         11              /* Resource deadlock avoided */
                                        /* 11 was EAGAIN */
#define ENOMEM          12              /* Cannot allocate memory */
#define EACCES          13              /* Permission denied */
#define EFAULT          14              /* Bad address */
...