linux 高性能服务器开发

高性能服务器开发

基础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拷贝
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值