目录:
- socket
- bind
- listen
- connect
- accept
1. socket:
(1)函数原型:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
(2)参数:
socket函数用于创建一个套接字,这是在应用层调用的,所以应用程序需要在调用socket创建套接字时根据应用层的业务类型,指定它所希望采用的网络层、传输层所使用的的协议。
内核中套接字是一层一层进行抽象展示的,把共性的东西抽取出来,这样对外提供的接口可以尽量统一。
内核中把套接字的定义会抽象出来展示,如:struct sock -> struct inet_sock -> struct tcp_sock 从抽象到具体.
socket函数的这三个参数其实就是把抽象的socket具体化的条件,domain
参数决定图中所示的第二层通信域
,type
决定了第三层的通信模式
,protocol
决定了第四层真正的通信协议
。
domain
参数的几个常用值:
AF_UNIX / AF_LOCAL : local communication, 本地通信
AF_INET : IPv4 网络通信
AF_INET6 : IPv6 网络通信
type
参数的几个常用值:
SOCK_STREAM : 流套接字:提供序列化的、可靠的、双工的、基于连接的字节流式服务
SOCK_DGRAM : 数据报套接字:提供数据报式的服务(无连接、不可靠、有最大长度限制)
SOCK_RAW : 原始套接字:提供原始套接字服务
此外,type
还可以设置以下几种类型:
SOCK_NONBLOCK : 设置为非阻塞式IO,functl(O_NONBLOCK) 是一样的效果
SOCK_CLOEXEC : 与open(O_CLOEXEC)一样,open打开时关闭文件描述符,防止父进程泄漏打开的文件给子进程
protocol
参数
确定socket到底支持哪个协议,例如如果domain=AF_INET, type = SOCK_STREAM,那么内核就会自动设置Protocol = IPPROTO_TCP;
如果domain = AF_INET, type = SOCK_DGRAM, 则 protocol = IPPROTO_UDP

socket(AF_INET, SOCK_STREAM, 0); //等价于 :
socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
socket(AF_INET, SOCK_DGRAM, 0); //等价于:
socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
(3)返回值:
成功时返回sockfd套接字描述符,失败返回-1,并设置errno。
常见的errno:
EACCES: 没有权限创建sockfd套接字
EINVAL: 定的协议错误
2. bind:
(1)bind 函数原型:
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
使用举例:
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//inet_pton(AF_INET, "192.254.1.16", &(servaddr.sin_addr.s_addr));
servaddr.sin_port = htons(65535);
bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
(2)bind 参数:
socket函数创建了一个sockfd套接字描述符,想要进行网络通信还要将套接字与端口号、IP地址绑定起来(端口号+IP地址用来描述TCP连接的一端),Unix中用套接字地址结构
这样的一个结构体在内核与应用程序之间传递IP地址和端口号:
服务端(客户端)应用进程将自己想要绑定(连接)的IP地址和端口后写进套接字地址结构这样的结构体中,然后传递给内核;服务端内核在接收到新的客户端连接后将客户端的IP地址、端口号通过此结构体传递给应用进程。
另外,由于历史原因,因为套接字地址结构是在ANSI C之前定义的,当时函数还没有 (void *)这样的通配参数类型,所以类似于bind的函数如果想要接受 IPv4、IPv6等多种类型的地址结构结构体,就要有一个通配的结构体类型:因此而产生了通用套接字地址结构
。
IPv4 套接字地址结构:
#include <netinet/in.h>
struct sockadrr_in {
uint8_t sin_len; //length of structure(16)
sa_family_t sin_family; //AF_INET
in_port_t sin_prot; //`16` bit TCP or UDP port number
struct in_addr sin_addr; //`32` bit IPv4 Address
char sin_zero[8]; //unused
};
struct in_addr {
in_addr_t s_addr;
};
通用套接字地址结构:
#include <sys/socket.h>
struct sockaddr {
uint8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
};
字节序转换函数:
主机字节序可能与网络字节序不相同,因此在传入地址、端口号等时需要先进行字节序转换:
#include <netinet/in.h>
unit16_t htons(uint16_t val); //将16 bit位的整数从主机序转换为网络序,返回值为网络序
uint32_t htonl(uint32_t val);
uint16_t ntohs(uint16_t val);
uint32_t ntohl(uint32_t val);
地址转换函数:
由于网络IP地址的表达方式有多种(ASCII字符串、网络字节序的二进制),因此需要转换。
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);
//成功则返回1,格式无效返回0,失败返回-1
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
//成功则返回指向结果的指针,失败返回NULL
(3)bind 返回值:
成功返回0,失败返回-1,并设置errno
常见的errno:
EADDRINUSE: Address already in use,所以要绑定的地址已使用
3. listen:
(1)listen 函数原型:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
(2)listen 参数:
backlog:
内核为一个监听套接字维护两个队列:(注意一定是监听套接字,服务端等待连接的套接字)
- SYN队列:
在三次握手中,收到了客户端的第一个SYN,此时连接放入SYN队列中(未完成连接队列); - ACCEPT队列:
在三次握手中,收到了客户端的第三个ACK,此时连接从SYN队列中取出,放入到ACCEPT队列中。服务端调用accept,就是从ACCEPT队列中取出一个已完成连接。
backlog到底指的是哪个队列的大小?
在《TCP/IP详解(卷一)》中这样描述:
SYN队列的大小由内核参数net.ipv4.tcp_max_syn_backlog
决定(默认1000);
ACCEPT队列取 min(backlog, net.core.someconn=128)
的最小值来决定。
也就是说listen函数的backlog参数是用来配置ACCEPT全连接队列的,但是ACCEPT队列同时要受另一个内核参数的限制。
(3)listen 返回值:
成功返回0,失败返回-1,并设置errno
常见的errno:
EADDRINUSE: Another socket is already listening on the same port. 另一个套接字已经在监听这个端口
ENOTSOCK: The file descriptor sockfd does not refer to a socket. 传入的fd文件描述符不是套接字
4. connect:
(1)connect 函数原型:
#include <sys/socket.h>
#include <sys/types.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
(2)connect 参数:
略
(3)connect 返回值:
成功返回0,失败返回-1,并设置errno
常见的errno:
EINPROGRESS: sockfd为非阻塞,且数据未就绪时connect返回(待补充)
ECONNREFUSED: No-one listening on the remote address. 所连接的IP地址上没有套接字监听
ETIMEOUT: Timeout while attempting connection. The server may be too busy to accept new connections. 连接超时(TCP三次握手超时,可能是服务器繁忙没有响应)
5. accept:
(1)accept 函数原型:
#include <sys/socket.h>
int accept(int listenfd, struct sockaddr *cliaddr, socklen_t *addrlen);
(2)accept 参数:
略
(3)accept 返回值:
成功返回非负描述符(connfd),失败返回-1,并设置errno
常见的errno:
(待补充)
参考内容: