1. linux Socket(2) 函数
socket()
函数创建了一个通信节点,并且返回一个指向这个阶段的文件描述符fd。调用的方法如下
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
参数的描述
- domain用来选择通信协议,参数主要有一下一些常用类型
- AF_UNIX, AF_LOCAL:用于本地通信,Unix Domain Socket
- AF_INET:IPv4协议栈
- AF_INET6:IPv6协议栈
- 还有其他一些参看官方文档
- type参数定义通信语义
- SOCK_STREAM:提供一种有序的,可靠的,双向的,基于连接的字节流
- SOCK_DGRAM:支持数据报文(非连接的,不可靠的具有固定的最大字节长度的消息)
- SOCK_SEQPACKET:提供一种有序的,可靠的,双向的基于连接的数据传输通道传输数据报文,报文的最大大小固定
在Linux 2.6.27版本以后,type参数可以按位添加新的属性(比如说使用'或'操作),支持新的一些特性
- SOCK_NONBLOCK : 给新打开的文件描述符设置O_NONBLOCK文件状态标识
- SOCK_CLOEXEC :给新打开的文件描述符设置FD_CLOEXEC标识(执行后关闭)
- protocol参数指定和socket一起使用的特殊的协议栈(比如说SCTP),
SOCK_STREAM类型的socket是一个全双工的字节流,不预留消息边界:
- 一个stream socket必须处于连接状态才能收发数据;
- 通过调用
connect()
函数来连接到另一个创建的socket; - 一旦连接建立起来了,就可以通过
read()
和write()
函数调用来进行数据传输了; - 最后调用
close()
函数关闭连接; - 实现了SOCK_STREAM的通信协议栈确保数据不会丢失或者重复;
SOCK_SEQPACKET使用的系统调用和SOCK_STREAM类型的socket一样,唯一的差异是read(2)
函数只会返回请求的大小的数据包,多余的接收数据会被丢弃。同时会预留报文的消息边界
2. Linux bind(2)函数
bind()
函数调用方法如下
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数的描述
通过socket()
函数创建完socket以后,这时候socket还是只是属于某一个命名空间(地址族),并没有分配任何的地址。bind()函数通过addr参数来指定了socket的一个地址,sockfd参数是指向这个socket的文件描述符,addrlen指定了地址结构体的字节数。通常我们把这种操作称为是给socket命名。
地址结构体的定义取决于所用的通信协议族,比如说unix domian socket的通信方式AF_UNIX,它的地址结构定义如下:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* pathname */
};
而如果socket创建的时候指定的通信协议是IP,比如说AF_INET对应的是IPV4,它的地址结构应该是由IP和Port来组成
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
原始的IP协议栈并没有port的概念,只有更高层的传输层协议比如说TCP,UDP才会有port的概念。
3. Linux listen(2)函数
函数的定义如下
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
参数的描述
listen()
函数把socket标志成一个被动的socket,这个socket可以用于接收新的连接请求。
- sockfd指向一个socket,socket的类型是SOCK_STREAM或者SOCK_SEQPACKET。
- backlog参数定义socket的等待连接的queue的最大长度。
4. Linux accept(2)函数
函数的定义如下
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数的描述
系统调用accept()
函数用于基于连接的socket(比如说SOCK_STREAM,SOCK_SEQPACKET)。
- sockfd是指向要处理的socket的文件描述符。
- 这个socket首先通过
socket(2)
函数创建; - 然后通过
bind(2)
函数绑定到一个本地地址; - 最后通过函数调用
listen(2)
处于监听状态; - 系统调用
accept(2)
从socket的等待连接的queue里面取出第一个连接请求,然后创建一个处于连接状态的新的socket,并返回这个socket的文件描述符。新的socket不处于监听状态,老的socket不受影响
- addr是指向一个sockaddr结构体的指针,这个结构体里面填写的是对端socket的地址。
- addrlen代表地址结构的大小
如果queue里面没有等待的连接请求,并且socket没有标志成nonblocking,accept
函数会阻塞等待新的请求;
如果socket标志成了nonblocking,并且queue里面没有等待的请求,accept
函数会返回失败。
如果你希望监听socket上的连接请求,并且得到通知,可以使用slelect(2)
、poll(2)
、epoll(2)
等函数。这个时候再去调用accept()函数来获取连接状态的socket
5. Linux connect(2)函数
函数定义
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
参数的描述
系统调用connect()
发起一个当前socket到远端地址的一个连接,sockfd指定要发起连接的socket;addr指定连接的目标地址;addrlen指定地址结构体大小。
- 如果socket的类型是SOCK_DGRAM,addr的地址就是数据报文发送的默认目的地址,并且是数据报文接收的唯一源地址;
- 如果socket的类型是SOCK_STREAM或者SOCK_SEQPACKET,那么这个系统调用则会建立起一个连接,addr指定绑定的目的地址;
总的来说,基于连接的协议栈可以成功连接一次;非基于连接的协议栈可以连接多次来改变连接联合(association)。
6. Linux poll(2)函数
函数定义
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
参数的描述
poll()函数等待一组文件描述符准备好进行I/O操作。
- fds参数指定这一组文件描述符,实际上就是一个结构体数组,
struct pollfd{
int fd; /*file descriptor*/
short events; /* requested events*/
short revents; /* returned events */
};
- fd代表一个文件描述符,比如说指向一个socket,或者指向一个打开的文件;
- events是个bit mask,是一个输入参数,指定当前这个文件描述符期望的操作;
- revents也是一个bitmask,是一个输出参数,内核会填写这个字段并返回,告诉application监听到的事件;
-
nfds参数代表这个结构体数组元素的个数
-
timeout设定超时长度,单位是毫秒(ms),这个函数调用会阻塞等待,直到出现以下场景:
- 有一个文件描述符可以进行I/O操作了
- 被信号量处理打断
- 超时
返回值
poll
函数可能有以下几种返回值
- 正数,成功的时候返回一个正值,代表revents非零的结构体的个数;
- 0,超时了,并且没有任何一个文件描述符ready;
- -1,发生错误
7. Linux select(2)函数
函数定义及包含的头文件
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数的描述
select()
函数允许程序监听多个文件描述符,直到一个或者多个文件描述符准备好进行I/O操作。