1. socket地址API
- 主机字节序和网络字节序
字节序分为大端字节序(高字节存储在低地址处)和小端字节序(高位字节存储在内存的高地址处)。小端字节序又称主机字节序,大端字节序也称网络字节序。
主机字节序和网络字节序转换函数:
#include<netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostlong);
unsigned long int ntohl(unsigned long int netlong);
unsigned long int ntohs(unsigned short int netlong);
- 通用socket地址
表示socket地址的是结构体sockaddr,定义如下:
#include<bits/socket.h>
struct sockaddr_in
{sa_family_t sin_family; /*地址族: AF_INET*/u_int16_t sin_port; /*端口号,要用网络字节序表示*/struct in_addr sin_addr; /*IPV4地址结构体,见下*/
}
struct in_addr{u_int32_t s_addr; /*IPV4地址,要用网络字节序表示*/
}
- IP地址转换函数
人们习惯可读性好的字符串来表示IP地址,比如点分十进制。但编程中我们需要把他们转化为整数方能使用。而记录日志时则相反,要把整数表示的IP地址转化为可读的字符串。
#include<arpa/inet.h>
in_addr_t inet_addr(const char*strptr); //点分十进制字符串表示的IP地址-->网络字节序整数表示的IPV4地址。
int inet_aton(const char* op, struct in_addr* inp); //和上面同样功能,但是将转化结果存储在参数INP指向的地址结构中。
char* inet_ntoa(struct in_addr in) //网络字节序整数表示 ---> 点分十进制字符串表示
2. socket基础API
- 创建socket
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int potocol);/*参数:
domain: AF_INET AF_INET6
type: SOCK_STREAM(流服务)--TCP SOCK_UGRAM(数据报)--UDP
potocol: 一般设置为0,使用默认协议
*/
- 命名socket
将一个socket与socke地址绑定称为给socket命名。在服务器程序中,通常要命名socket,因为只有命名后客户端才能知道该如何连接它。客户端则通常不用,采用匿名方式,即操作系统自动分配的socket地址。
bind将my_addr所指的socket地址分配给未命名的sockfd文件描述符,addrlen参数指出该socket地址的长度。
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t adrlen);/*返回值:成功返回0, 失败返回-1并设置erno。
常见errno:
1. EACCES: 被绑定的地址是受保护的地址
2. EADDRINUSE: 被绑定的地址正在使用
- 监听socket
socket被命名之后,还不能接受客户连接,我们需要使用如下系统调用来创建一个监听队列以存放待处理的客户连接:
int listen(int sockfd, int backlog);/*
backlog: 提示内核监听队列的最大长度
- 接受连接
若成功,则返回一个新的连接socket,该socket唯一标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信。
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen)/*
addr: 用来获取被接受连接的远端socket地址
addrlen: 指出上述地址的长度
- 发起连接
int connect(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen);
- 关闭连接
close系统调用并非总是立即关闭一个连接,而是将fd的引用计数减1。只有当fd的引用计数为0, 才真正关闭连接
#include<unistd.h>
int close(int fd);
若如果无论如何都要立即终止连接(非减1),可以使用如下:
int shutdown(int sockfd, int howto);/*
howto: SHUT_RD:关闭sockfd上读的这一半, 应用程序不能再对socket文件描述符执行读操作SHUT_WR:关闭写,sockfd的发送缓冲区中的数据会在真正关闭连接之前全部发送出去。SHUT_RDWR: 同时关闭读写
由此可见,shutdown能够分别关闭socket上的读和写,或者都关闭。而close在关闭连接时只能同时关闭读写。
3. 数据读写
- TCP数据读写
recv成功时返回实际读取到的数据的长度,他可能小于我们期望的长度len。因此我们可能要多次调用recv,才能读取到完整的数据。recv可能返回0, 意味着对方已经关闭连接了。
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);/*
buf 和 len: 分别指定 读/写缓冲区的 位置和大小
flag: 通常设置为 0
- UDP数据读写
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
ssize_t sendto(int sockfd, const void* buf, size_t len, int flags, struct sockaddr* dest_addr, socklen_t* addrlen);
- 通用数据读写
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
4.socket选项
int getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* restrict option_len);
int setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t option_len);/*
level: SOL_SOCKET(通用socket选项)
option_name:
SO_REUSEADDR:重用本地址(如处于TIME_WAIT状态的地址)
SO_RCVBUF SO_SNDBUF:表示TCP接收缓冲区和发送缓冲区的大小。用setsockopt设置大小时,系统会将其值加倍,并不得小于某个最小值。
SO_RCVLOWAT SO_SNDLOWAT: 表示TCP接收缓冲区和发送缓冲区的低水位标记,一般用来判断socket是否可读/写。如:接收缓冲区可读送数据总数>低水位标记, I/O服用系统调用通知应用程序读取,<时,同理。
SO_LINGER: 用于控制close系统调用在关闭TCP连接时的行为。