高性能服务器开发
基础API
socket地址API IP地址和端口对 (ip,port) 唯一的表示了使用TCP通信的一端
socket基础API #<sys/socket.h>头文件中
网络信息API 实现主基名和IP地址之间的转换 服务名称和端口号的转换 #include<netdb.h>
字节序
大端字节序 高位字节储存在低地址处 低位字节在高地址处
小端字节序 高位字节储存在高地址出 高位字节在低地址处
0x1234567
大端 01 23 45 67 0x100 0x101 0x102 0x103
小端 67 45 23 01 0x100 0x101 0x102 0x103
发送端总是要把发送的字节序转化成大端之后再发,接收端按照自己的大小端模式判断是否转换(大端字节序 网络字节序)
#include<netinet/in.h>
//将主机字节序转换成网络字节序
//长换ip 短换端口
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unsigned short int hostshort)
//网络字节序转主机字节序
unsigned long int htonl(unsigned long int hostlong)
unsigned short int htons(unsigned short int hostshort)
通用socket地址
结构体 sockaddr
#include<bits/socket.h>
struct sockaddr
{
//sa_family_t 地址族类型,通常和协议族类型对应
sa_family_t sa_family;
//存放socket地址值
char sa_data[14];
}
sa_family_t
协议族 地址族 描述
PF_UNIX AF_UNIX UNIX本地域协议族
PF_INET AF_INET TCP/IPv4协议族
PF_INET6 AF_INET6 TCP/IPv6协议族
上述宏都存在#include<bits/socket.h>头文件中,值相同,可以混用
sa_data
PF_UNIX 文件的路径名 可以到108字节
PF_INET 16bit端口号和32bitIPv4地址 6字节
PF_INET6 16bit端口号 32bit流标识 128bitIPv6地址 32bit范围ID 共26字节
上述结构体不能完成任务 于是有了新的结构体
#include<bits/socket.h>
struct sockaddr_storage
{
sa_family_t sa_family;
unsigned long int __ss_align;
char __ss_padding[128-sizeof(__ss_align)];
}
通用的结构体不好用 位操作 繁琐 造了专用socket地址
#include<sys/un.h>
struct sockaddr_un
{
sa_family_t sa_family;
char sun_path[108];
}
ipv4
struct sockaddr_in
{
sa_family_t sa_family; //地址族
u_int16_t sin_port; //端口号
struct in_addr sin_addr; //IPv4地址结构体
}
struct in_addr
{
u_int32_t s_addr; //IPv4地址,需要用网络字节序表示
}
还有个IPv6
IP地址转换函数
#include<arpa/inet.h>
//点分十进制转化为网络字节序 失败返回INADDR_NONE
in_addr_t inet_addr(const char* strptr);
//点分十进制转化成网络字节序,将转化结构存储于参数inp指向地址的结构中,成功返回1,失败返回0
int inet_aton(const char* cp, struct in_addr* inp);
//用网络字节序整数表示的IPv4转换成用点分十进制
//不可重入
char* inet_ntoa(struct in_addr in)
//将用字符串表示的IP地址src(点分十进制表示的)转换为网络字节IP,存到dst中
int inet_pton(int af,const char* src,void* dst);
//最后一个参数指定目标存储单元的大小,成功返回地址,失败返回NULL
const char* inet_ntop(int af,const void* src,char* dst,socklen_t cnt);
#include<netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET_ADDRSTRLEN 46
创建socket
socket 文件描述符
可读写 可控制/可关闭
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol);
/*
domian 使用那个底层协议族
type 指定服务类型 SOCK_STREAM 流服务 TCP
SOCK_DGRAM 数据报服务 UDP
SOCK_NONBLOCK socket为非阻塞
SOCK_CLOEXEC 用fork调用创建子进程时,在子进程中关闭
protocol 前两个参数构成的集合下选择一个具体协议,0,默认协议
成功 返回问价描述符
失败 -1
*/
命名socket
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen)
/*
sockfd 文件描述符
my_addr socket地址(分配给未命名的sockfd文件描述符)
addrlen 指出socket地址的长度
成功 0
失败 -1
errno
EACCES:绑定的地址是受保护的地址,普通用户将socket绑定到知名服务端口3306
EADDRINUSE:将socket绑定到一个处于TIME——WAIT状态的socket地址
*/
监听socket
#include<sys/socket.h>
int listen(int sockfd,int backlog);
/*
sockfd 指定被监听的socket文件描述符
backlog 提示内核监听队列的最大长度,超过了就不受理新的客户链接 客户端ECONNREFUSED错误
2.2前半连接SYN_RECV,完全链接(ESTABLISHED)的socket上限
2.2后 只表示完全链接状态 半连接由/proc/sys/net/ipv4/tcp_max_syn_backlog中设置
成功:1
失败:-1
*/
接受连接
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr* addr,socklen_t* addrlen);
/*
sockfd 经历过listen系统调用的监听socket
addr 获取被接受连接的远端socket地址
addelen 指定socket地址长度
成功:返回一个新的连接socket,该socket唯一标识了被接受的这个链接
服务器可以通过读写socket来与被接受连接对应的客户端通信
失败:返回-1 设置error
*/
对网络状况毫不知情 客户端断开或者掉线 完全不知道
只是从监听队列取出连接 不论连接处于何种情况 更不关系网络变化
发起连接
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,const struct sockaddr* serv_addr,socklen_t addrlen);
/*
sockfd socket系统调用返回的
serv_addr服务器监听的sicket地址
addrlen 指定地址的长度
成功:0
一旦建立 sockfd就唯一标识了这个连接,客户端就可以通过读写sockfd来和服务器通信
失败:-1
ECONNREFUSED 目标端口不存在
ETIMEDOUT 连接超时
*/
关闭连接
#include<unistd.h>
int close(int fd);
/*
fd 待关闭的close
不是立刻关闭close 而是 引用计数-1 为0时候真正关闭连接
*/
多进程程序中,一次fork系统调用默认父进程socket+1,父子进程都close调用才能立刻将连接关闭
立刻终止
#include<sys/socket.h>
int shutdown(int sockfd,int howto)
/*
sockfd 要关闭的socket
howto: SHUT_RD 接受缓冲区丢弃 关闭读的一半
SHUT_WR 发送缓冲区在关闭前全发,关闭写的一半,连接处于半关
SHUT_RDWR 都关了
成功 返回0
失败 返回-1
*/
数据读写
TCP流传输读写
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int sockfd, void *buf,size_t len,int flags);
/*
读取 sockfd上的数据
buf 指定缓冲区位置
len 指定缓冲区大小
flag:
成功:返回实际读取到的数据长度,可能小于期望的len,要多次调用
失败:-1
返回0 意味着对方关闭了连接
*/
ssize_t send(int sockfd, const void *buf,size_t len,int flags);
/*
给sockfd上写数据
buf 缓冲区位置
len 缓冲区大小
flag
成功:返回实际写入的数据长度
失效:-1
*/
带外数据只有最后一位生效
正常数据会被带外数据截断 不能被一个recv读出
UDP数据读写
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recvfrom(int sockfd,void* buf,size_t len,int flag,struct sockaddr* src_addr,socklen_t* addrlen);
/*
sockfd recvfrom 从哪里读的数据
buf 缓冲区位置
len 缓冲区大小
src_addr 无连接 每次读取数据都需要获取发送端的socket地址
addrlen 地址长度
*/
ssize_t sendto(int sockfd,const void* buf,size_t len,int flag,const struct sockaddr* dst_addr,socklen_t* addrlen);
/*
sockfd sendto 向那里送读的数据
buf 缓冲区位置
len 缓冲区大小
dst_addr 无连接 每次读取数据都需要获取发送端的socket地址
addrlen 地址长度
*/
TCP SOCK_STREAM也能用 最后两个参数写NULL就行
通用读写
#include<sys/socket.h>
sszie_t recvmsg(int sockfd,struct msghdr* msg,int flags);
sszie_t sendmsg(int sockfd,struct msghdr* msg,int flags);
struct msghdr
{
void *msg_name; socket 地址
socklen_t msg_namelen; socket地址的长度
struct iovec *msg_iov; 分散的内存块
size_t msg_iovlen; 分散内存块的数量
void *msg_control; 指向辅助数据的起始位置
size_t msg_controllen; 辅助数据的大小
int msg_flags; 复制函数中的flags参数,并在调用过程中更新
};
struct iovec
{
void*iov_base; 内存起始地址
size_t iov_len; 这块内存的长度
};
带外标记
#include<socket.h>
int sockatmark(int sockfd);
/*
判断sockfd是否处于带外标记,下一个读取到的数据是都是带外数据,
是的话返会1
不是的话返回0
*/
地址函数信息
#include<sys/socket.h>
int getsockname(int sockfd,struct sockaddr* address,socklen_t* address_len);
/*
sockfd 获取的目标信息
address 指定存储内存
address_len指定大小 如果过大socket地址会被阶段
成功:0
失败:-1
*/
int getpeername(int sockfd,struct sockaddr* address,socklen_t* address_len);
/*
sockfd 获取的目标信息
address 指定存储内存
address_len指定大小 如果过大socket地址会被阶段
成功:0
失败:-1
*/
socket选项
好多啊~~
pipe函数
#include<unistd.h>
int pipe(int fd[2])
/*
包含两个int型整数的数组指针
成功:0
失败:-1
fd[0] fd[1] 分别构成管道两端 fd[1]只能写入 fd[0]只能读出
需要读写 就要两条管道
默认文件描述符阻塞
read系统调用读取空管道将被阻塞,直到管道内有数据可读
write系统调用向满管道写数据,也被阻塞,直到有足够多的空间。
设置成非阻塞的 就有意思了~
阻塞非阻塞问题
写端文件文件描述符fd[1]引用计数降为0 无进程要往管道中写数据 读端文件文件描述符fd[0]的read返回0
读端文件文件描述符fd[0]引用计数降为0 无进程需要从管道读取数据 write操作将失败引发SIGPIPE信号
*/
#include<sys/types.h>
#include<sys/socket.h>
int socketpair(int domian,int type,int protocol,int fd[2]);
/*
等同于socket
只不过第一个只能用AF_UNIX
成功:0
失败:-1
*/
dup/dup2
#include<unistd.h>
int dup(int file_descriptor)
int dup2(int file_descriptor_one,int file_descriptor_two)
标准输入重定向到一个文件,或者网络连接
dup创建一个新的文件描述符,和原来的指向相同的文件,管道,网络连接
返回一个最小的整数值
dup2 返回一个不小于file_descriptor_two的整数值
失败:-1
close(STDOUT_FILENO);
dup(connfd);
printf("11\n");
close(connfd);
直接printf通过网络发送给用户
readv/writev
#include<sys/uio.h>
sszie_t readv(int fd,const struct iovec* vector,int count);
sszie_t writev(int fd,const struct iovec* vector,int count);
/*
fd 被操作的目标文件描述符、
iovec 该结构体是一块内存区
count 数组长度 多少块内存区需要从fd读出/写入到fd
成功:返回字节数
失败 -1
struct iovec iv[2];
iv[0].iov_base = header_buf;
iv[0].iov[len] = strlen(header_buf);
iv[1].iov_base = file_buf;
iv[1].iov[len] = strlen(file_buf);
ret = writev(connfd,iv,2);
*/
sendfile函数
#include<sys/sendfile.h>
sszie_t sendfile(int out_fd,int in_fd,off_t* offset,size_t count);
/*
in_fd待读出内容的文件描述符, 必须支持mmap是一个真实的文件,不是socket/管道
out_fd待写入内容的文件描述符 socket
offset 从哪里开始读
count 传输的字节数
成功:返回传输字节数
失败:-1
*/
在两个文件描述符中传送数据,内核操作,避免了内核缓冲区和用户缓冲区之间的数据拷贝 0拷贝