【阅读笔记】Linux高性能服务器编程(1)

第二篇-深入解析高性能服务器编程

头文件汇总

fcntl()位于<fcntl.h>,ioctl()位于<sys/ioctl.h>,errno位于<errno.h>

<sys/socket.h>:

//函数:
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int shutdown(int sockfd, int how);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
//数据结构:
struct sockaddr //通用的套接字地址结构。

<sys/types.h>:

//数据类型
socklen_t
size_t
pid_t

<netinet/in.h>:

//数据结构:
struct in_addr //IPv4地址结构。
struct sockaddr_in //Internet套接字地址结构。
//函数:
uint32_t htonl(uint32_t hostlong); // 主机字节序到网络字节序的长整型转换。
uint16_t htons(uint16_t hostshort); // 主机字节序到网络字节序的短整型转换。
uint32_t ntohl(uint32_t netlong); // 网络字节序到主机字节序的长整型转换。
uint16_t ntohs(uint16_t netshort); //网络字节序到主机字节序的短整型转换。

<arpa/inet.h>:

//函数:
const char *inet_ntoa(struct in_addr in); // 将in_addr结构转换为可读的IPv4地址字符串。
int inet_aton(const char *cp, struct in_addr *inp); // 将可读的IPv4地址字符串转换为in_addr结构。
unsigned int inet_addr(const char *cp); // 将IPv4地址的点分十进制字符串转换为网络字节序的长整型。

<unistd.h>:

int close(int fd); // 关闭一个文件描述符。
ssize_t read(int fd, void *buf, size_t count); // 从文件描述符读取数据。
ssize_t write(int fd, const void *buf, size_t count); // 向文件描述符写入数据。

<sys/un.h>
(Unix域socket特有的头文件):

struct sockaddr_un // Unix域套接字地址结构。

第5章 基础API

socket

PF_xxx和AF_xxx完全一样
理论上建立socket时是指定协议,应该用PF_xxxx,设置地址时应该用AF_xxxx

//服务端:

//ipv4的sockaddr结构体
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
server.sin_port = htons(8080);

//SOCK_DGRAM是数据包,UDP协议
//失败返回-1
int sock = socket(PF_INET, SOCK_STREAM, 0);

//绑定和监听
//失败返回-1
int ret = bind(sock, (struct sockaddr *)&server, sizeof(server));

ret = listen(sock,5);//最多有5个全连接

//接收客户端连接
struct sockaddr_in client;
socklen_t client_addr_len = sizeof(client);
//失败返回值小于0,成功返回一个可使用的socket
//客户端socket地址存放在client中
//第三个参数传入的是一个指针,accept函数可能需要修改实际大小
int cnnfd = accept(sock, (struct sockaddr*)&client, &client_addr_len);

//读
memset(buffer, 0, BUF_SIZE);
ret = recv(cnnfd, buffer, BUF_SIZE-1, 0);
//写
ret = send(cnnfd, data, strlen(data),0);

//关闭同客户端的连接
close(cnnfd);
//关闭监听端口
close(sock);
//客户端

//设置要连接的服务端地址
struct sockaddr_in server;
//...设置过程

//连接
int sock = socket(PF_INET, SOCK_STRSTREAM, 0);
//连接失败返回-1
int ret = connect(sock, (struct sockaddr*)&server, sizeof(server));

//...收发数据

close(sock);

UDP通信因为没有连接,所以需要每次传入对方的地址

int ret = recvfrom(sock, buf, BUF_SIZE, flags, 
					(struct sockaddr*)&client, &client_addr_len);
int ret = sendto(sock, buf, len, flags, 
					(struct sockaddr*)&client, &client_addr_len);

通用系统调用:

ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);

//msghdr定义
struct msghdr
  {
    void *msg_name;		/* Address to send to/receive from.  */
    socklen_t msg_namelen;	/* Length of address data.  */

    struct iovec *msg_iov;	/* Vector of data to send/receive into.  */
    size_t msg_iovlen;		/* Number of elements in the vector.  */

    void *msg_control;		/* Ancillary data (eg BSD filedesc passing). */
    size_t msg_controllen;	/* Ancillary data buffer length.
				   !! The type should be socklen_t but the
				   definition of the kernel is incompatible
				   with this.  */

    int msg_flags;		/* 无需直接指定,会复制函数调用的第三个参数 */
  };

如果是TCP连接,通用系统调用的msghdr中的地址需要置为NULL

网络信息API

#include <netdb.h>

  1. gethostbyname和gethostbyaddr
//同样,addr是struct sockaddr类型,type是指PF_INET等
struct hostent* gethostbyname(const char* name);
struct hostent* gethostbyaddr(const void * addr, size_t len, int type);

//hostent定义
/* Description of data base entry for a single host.  */
struct hostent
{
  char *h_name;			/* Official name of host.  */
  char **h_aliases;		/* Alias list.  */
  int h_addrtype;		/* Host address type.  */
  int h_length;			/* Length of address.  */
  char **h_addr_list;		/* List of addresses from name server.  */
};
  1. getservbyname和getservbyport获取服务信息
//proto可以是"tcp"、"udp",NULL
struct servent* getservbyname(const char* name, const char* proto);
struct servent* getservbyname(int port, const char* proto);

上述四个函数都是不可重入的,可重入版本后面加上_r
通过这些函数可以方便地获取本地地址、服务地址等

第6章 高级IO函数

pipe

#include <unistd.h>

int pipe(int fd[2]);

fd[0]读,fd[1]写

//双向管道
int fd[2];
int res = socketpair(PF_UNIX, SOCK_STRSTREAM, 0, fd);

dup和dup2

用于复制文件描述符
#include <unistd.h>

readv和writev

#include <sys/uio.h>
从文件描述符读到分散的内存块中,从多个分散的内存块中合并写入文件描述符
分散读,集中写
相当于简化版的recvmsg和sendmsg

例如HTTP服务返回内容,头部一块,内容一块

//header_buf, file_buf
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 = file_len;
ret = writev(connfd, iv, 2);

读也类似

sendfile

#include<sys/sendfile.h>
两个文件描述符之间直接传递数据,不用拷贝到用户态

ssize_t sendfile(int out_fd, int in_fd, off_t* offset, size_t count);

专门用于通过网络传输文件,out_fd必须是socket,in_fd必须是真实文件,不能是socket或管道

mmap和mummap

#include <sys/mman.h>

splice和tee

include <fcntl.h>
splice
两个文件描述符移动数据,也是不用拷贝到用户态
零拷贝
tee
两个管道文件描述符间复制数据,零拷贝操作

fcntl

include <fcntl.h>
file control,提供了对文件描述符的各种控制操作(ioctl也可以)
例如将一个文件描述符设置为非阻塞

//第二个参数标明执行什么功能,第三个参数作为这个功能的参数
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);

第7章 Linux服务器程序规范

第8章 高性能服务器程序框架

IO处理单元负责接收客户连接和数据,然后交由一个逻辑单元处理。
请求队列是个单元之间通信方式的抽象(池)

第9章 IO复用

epoll

#include <sys/epoll.h>
两种触发模式LT(Level Trigger)和ET(Edge Trigger)
LT只要有数据就触发,所以允许程序这次不处理,下次处理;ET要求程序必须处理,因为下次不会再通知。

LT和ET这么叫源于数字信号,水平触发是只要高于某个阈值就触发,边沿触发是发生变化时触发。

// 创建
//返回一个文件描述符,表示内核事件表,size是内核事件表大小
int epoll_create(int size);

//操作内核事件表
//op如EPOLL_CTL_ADD,EPOLL_CTL_MOD,EPOLL_CTL_DEL等等
//成功返回0,失败返回-1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
struct epoll_event
{
  uint32_t events;	/* Epoll events */
  epoll_data_t data;	/* User data variable */
} __EPOLL_PACKED;
//epoll_data_t是一个联合体,可以是fd,可以是void *ptr

//检查是否就绪
//返回就绪文件描述符数量,失败返回-1
//将所有就绪事件从内核事件表复制到events指向的数组
int epoll_wait(int epfd, struct epoll_event* events, int maxevents,
				int timeout);

事件:

  • EPOLLIN
    • 有数据可读
  • EPOLLET
    • 表明使用ET模式
  • EPOLLONSHOT
    • 只触发一次,可保证同一时间只有一个工作线程处理该socket
    • 用完重新设置oneshot
  • EPOLLHUP
    • 对方挂断

第10章 信号

#include <signal.h>
//两种方式设置信号
extern __sighandler_t signal (int __sig, __sighandler_t __handler)
     __THROW;
/* Get and/or set the action for signal SIG.  */
extern int sigaction (int __sig, const struct sigaction *__restrict __act,
		      struct sigaction *__restrict __oact) __THROW;

//sighandler处理函数指针
typedef void (*__sighandler_t) (int);

/* Structure describing the action to be taken when a signal arrives.  */
struct sigaction
  {
    /* Signal handler.  */
#if defined __USE_POSIX199309 || defined __USE_XOPEN_EXTENDED
    union
      {
	/* Used if SA_SIGINFO is not set.  */
	__sighandler_t sa_handler;
	/* Used if SA_SIGINFO is set.  */
	void (*sa_sigaction) (int, siginfo_t *, void *);
      }
    __sigaction_handler;
# define sa_handler	__sigaction_handler.sa_handler
# define sa_sigaction	__sigaction_handler.sa_sigaction
#else
    __sighandler_t sa_handler;
#endif

    /* Additional set of signals to be blocked.  */
    __sigset_t sa_mask;

    /* Special flags.  */
    int sa_flags;

    /* Restore handler.  */
    void (*sa_restorer) (void);
  };

一个例子:

void addsig(int sig){//设置信号及其处理函数
    struct sigaction sa;
    memset(&sa, 0, sizeof(sa));
    sa.sa_handler = sig_handler;
    sa.sa_flags |= SA_RESTART;
    sigfillset(&sa.sa_mask);
    int ret = sigaction(sig,&sa,NULL);
    assert(ret != -1);
}

在以上这段代码中,设置SA_RESTARTflag的作用是当一些系统调用阻塞期间出现信号,系统调用将中断(并设置errno为EINTR错误),该flag可恢复这些系统调用。
sa_mask是屏蔽信号集,信号处理期间屏蔽其他信号,避免重复触发
可在sig_handler中将信号通过PF_UNIX的socket发送,并通过epoll统一处理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值