socket()函数的原型如下,这个函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1。
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- domain
函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。
名称 | 含义 | 名称 | 含义 |
---|---|---|---|
PF_UNIX PF_LOCAL | 本地通信 | PF_X25 | ITU-T X25 / ISO-8208协议 |
AF_INET,PF_INET | IPv4 Internet协议 | PF_AX25 | Amateur radio AX.25 |
PF_INET6 | IPv6 Internet协议 | PF_ATMPVC | 原始ATM PVC访问 |
PF_IPX | IPX-Novell协议 | PF_APPLETALK | Appletalk |
PF_NETLINK | 内核用户界面设备 | PF_PACKET | 底层包访问 |
- type 函数socket()的参数type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。
名称 | 含义 |
---|---|
SOCK_STREAM | Tcp连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输 |
SOCK_DGRAM | 支持UDP连接(无连接状态的消息) |
SOCK_SEQPACKET | 序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出 |
SOCK_RAW | RAW类型,提供原始网络协议访问 |
SOCK_RDM | 提供可靠的数据报文,不过可能数据会有乱序 |
并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。
-
protocol 函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
-
errno 函数socket()并不总是执行成功,有可能会出现错误,错误的产生有多种原因,可以通过errno获得:
值 | 含义 |
---|---|
EACCES | 没有权限建立制定的domain的type的socket |
EAFNOSUPPORT | 不支持所给的地址类型 |
EINVAL | 不支持此协议或者协议不可用 |
EMFILE | 进程文件表溢出 |
ENFILE | 已经达到系统允许打开的文件数量,打开文件过多 |
ENOBUFS/ENOMEM | 内存不足。socket只有到资源足够或者有进程释放内存 |
EPROTONOSUPPORT | 制定的协议type在domain中不存在 |
比如我们建立一个流式套接字可以这样:
int sock = socket(AF_INET, SOCK_STREAM, 0);
在套接口中,一个套接字只是用户程序与内核交互信息的枢纽,它自身没有太多的信息,也没有网络协议地址和 端口号等信息,在进行网络通信的时候,必须把一个套接字与一个地址相关联,这个过程就是地址绑定的过程。许多时候内核会我们自动绑定一个地址,然而有时用 户可能需要自己来完成这个绑定的过程,以满足实际应用的需要,最典型的情况是一个服务器进程需要绑定一个众所周知的地址或端口以等待客户来连接。这个事由 bind的函数完成。
int bind( int sockfd, struct sockaddr* addr, socklen_t addrlen)
- sockfd 就是我们调用socket函数后创建的socket 句柄或者称文件描述符号。
- addr
addr是指向一个结构为sockaddr参数的指针,sockaddr中包含了地址、端口和IP地址的信息。在进行地址绑定的时候,需要弦将地址结构中的IP地址、端口、类型等结构struct sockaddr中的域进行设置之后才能进行绑定,这样进行绑定后才能将套接字文件描述符与地址等接合在一起。
由于历史原因,我们前后有两个地址结构: struct sockaddr 该结构定义如下:
struct sockaddr {
uint8_t sa_len;
unsigned short sa_family; /* 地址家族, AF_xxx */
char sa_data[14]; /*14字节协议地址*/
};
其实这个结构逐渐被舍弃,但是也还是因为历史原因,在很多的函数,比如connect、bind等还是用这个作为声明,实际上现在用的是第二个结构,我们需要把第二个结构强转成sockaddr。 struct sockaddr_in 其定义如下:
struct sockaddr_in {
uint8_t sa_len; /* 结构体长度*/
short int sin_family; /* 通信类型 */
unsigned short int sin_port; /* 端口 */
struct in_addr sin_addr; /* Internet 地址 */
unsigned char sin_zero[8]; /* 未使用的*/
};
struct in_addr { //sin_addr的结构体类型in_addr 原型
unsigned long s_addr; /*存4字节的 IP 地址(使用网络字节顺序)。*/
};
在使用的时候我们必须指定通信类型,也必须把端口号和地址转换成网络序的字节序(这是什么意思呢?)。
- addrlen addr结构的长度,可以设置成sizeof(struct sockaddr)。使用sizeof(struct sockaddr)来设置套接字的类型和其对已ing的结构。
bind()函数的返回值为0时表示绑定成功,-1表示绑定失败,errno的错误值如表1所示。
值 | 含义 | 备注 |
---|---|---|
EADDRINUSE | 给定地址已经使用 | |
EBADF | sockfd不合法 | |
EINVAL | sockfd已经绑定到其他地址 | |
ENOTSOCK | sockfd是一个文件描述符,不是socket描述符 | |
EACCES | 地址被保护,用户的权限不足 | |
EADDRNOTAVAIL | 接口不存在或者绑定地址不是本地 | UNIX协议族,AF_UNIX |
EFAULT | my_addr指针超出用户空间 | UNIX协议族,AF_UNIX |
EINVAL | 地址长度错误,或者socket不是AF_UNIX族 | UNIX协议族,AF_UNIX |
ELOOP | 解析my_addr时符号链接过多 | UNIX协议族,AF_UNIX |
ENAMETOOLONG | my_addr过长 | UNIX协议族,AF_UNIX |
ENOENT | 文件不存在 | UNIX协议族,AF_UNIX |
ENOMEN | 内存内核不足 | UNIX协议族,AF_UNIX |
ENOTDIR | 不是目录 | UNIX协议族,AF_UNIX |
比如这样:
struct sockaddr_in addr;
memset(&addr, 0, sizeof(struct sockaddr_in));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)
{
perror("bind");
exit(1);
}
int listen(int sockfd, int backlog);
listen()函数将sockfd标记为被动打开的套接字,并作为accept的参数用来接收到达的连接请求。
-
sockfd是一个套接字类型的文件描述符,具体类型为SOCK_STREAM或者SOCK_SEQPACKET。
-
backlog参数用来描述sockfd的等待连接队列能够达到的最大值。当一个请求到达并且该队列为满时,客户端可能会收到一个表示连接失败的错误,或者如果底层协议支持重传(比如tcp协议),本次请求会被丢弃不作处理,在下次重试时期望能连接成功(下次重传的时候队列可能已经腾出空间)。
说起这个backlog就有一点儿历史了,等下文描述。
- errno
值 | 含义 |
---|---|
EADDRINUSE | 另一个套接字已经绑定在相同的端口上。 |
EBADF | 参数sockfd不是有效的文件描述符。 |
ENOTSOCK | 参数sockfd不是套接字。 |
EOPNOTSUPP | 参数sockfd不是支持listen操作的套接字类型。 |
声明如下
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数说明如下
- sockfd 是系统调用 socket() 返回的套接字文件描述符。
- serv_addr 是 保存着目的地端口和 IP 地址的数据结构 struct sockaddr_in。
- addrlen 设置 为 sizeof(struct sockaddr_in)
errno connect函数在调用失败的时候返回值-1,并会设置全局错误变量 errno。
值 | 含义 |
---|---|
EBADF | 参数sockfd 非合法socket处理代码 |
EFAULT | 参数serv_addr指针指向无法存取的内存空间 |
ENOTSOCK | 参数sockfd为一文件描述词,非socket。 |
EISCONN | 参数sockfd的socket已是连线状态 |
ECONNREFUSED | 连线要求被server端拒绝。 |
ETIMEDOUT | 企图连线的操作超过限定时间仍未有响应。 |
ENETUNREACH | 无法传送数据包至指定的主机。 |
EAFNOSUPPORT | sockaddr结构的sa_family不正确。 |
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明 sockfd是由socket函数返回的套接字描述符,参数addr和addrlen用来返回已连接的对端进程(客户端)的协议地址。如果我们对客户端的协议地址不感兴趣,可以把arrd和addrlen均置为空指针。
返回值
成功时,返回非负整数,该整数是接收到套接字的描述符;出错时,返回-1,相应地设定全局变量errno。
值 | 含义 |
---|---|
EBADF | 非法的socket |
EFAULT | 参数addr指针指向无法存取的内存空间 |
ENOTSOCK | 参数s为一文件描述词,非socket |
EOPNOTSUPP | 指定的socket并非SOCK_STREAM |
EPERM | 防火墙拒绝此连线 |
ENOBUFS | 系统的缓冲内存不足 |
ENOMEM | 核心内存不足 |
特别需要说明下的是,这个accept是一个阻塞式的函数,对于一个阻塞的套套接字,一直阻塞,或者返回一个错误值,对于非阻塞套接字。accept有可能返回-1,但是如果errno的值为,EAGAIN或者EWOULDBLOCK,此时需要重新调用一次accept函数。
函数申明如下
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- sockfd :套接字
- buf : 待发送或者接收的缓存
- len : 如果是recv指期望接收的长度,如果是send指要发送的长度。
- flags : 标志位,取值如下表:
flags | 说明 | recv | send |
---|---|---|---|
MSG_DONTROUTE | 绕过路由表查找 | • | |
MSG_DONTWAIT | 仅本操作非阻塞 | • | • |
MSG_OOB | 发送或接收带外数据 | • | • |
MSG_PEEK | 窥看外来消息 | • | |
MSG_WAITALL | 等待所有数据 | • |
errno
值 | 含义 |
---|---|
EAGAIN | 套接字已标记为非阻塞,而接收操作被阻塞或者接收超时 |
EBADF | sock不是有效的描述词 |
ECONNREFUSE | 远程主机阻绝网络连接 |
EFAULT | 内存空间访问出错 |
EINTR | 操作被信号中断 |
EINVAL | 参数无效 |
ENOMEM | 内存不足 |
ENOTCONN | 与面向连接关联的套接字尚未被连接上 |
ENOTSOCK | sock索引的不是套接字 当返回值是0时,为正常关闭连接; |
当返回值为-1时是不是一定就错误了,当返回值为0时该怎么做呢? 如何正确判断一个对端已经关闭了连接?