1:linux网络API分为:socker地址API,socker基础API,网络信息API
1,socker地址API:包含IP地址和端口(ip, port)。表示TCP通信的一端。
2,socker基础API:创建/命名/监听socker,接收/发起链接,读写数据,获取地址信息,检测带外标记和读取/设置socker选项。sys/socket.h
3,网络信息API:主机名和IP地址的转换,服务名和端口号的转换。netdb.h
2:socket和API的函数 和 相关知识。
1,函数。
IP地址转换函数
<arpa/inet.h>
in_addr_t inet_addr( const char* strptr ); // 格式转换:十进制字符串IP -> 网络字节序(大端)IP
int inet_aton( const char* cp, struct in_addr* inp ); // 格式转换:如上,但将数据保存到参数inp中
char* inet_ntoa( struct in_addr in ); // 格式转换:网络字节序(大端)IP -> 十进制字符串IP
int inet_pton( int af, const char* src, void* dst ); // 格式转换:字符串IP地址 -> 网络字节序(大端)整数IP
const char* inet_ntop( int af, const void* src, char* dst, socklen_t cnt ); 格式转换:网络字节序(大端)整数IP -> 字符串IP地址
// inet_ntoa函数不可重入。
// inet_ntop返回目标储存单元的地址。
创建socket
<sys/types.h> <sys/socket.h>
int socket( int domain, int type, int protocol );
// 1 参数domain:使用的底层协议族。
// 2 参数type:指定服务类型。SOCK_STREAM(TCP) SOCK_UGRAM(UDP)
// 3 参数protocol:指定具体协议。一般默认为0.(前面参数已经确定了协议)
// 4 返回:socket文件描述符。失败-1,设置errno
命名socket
<sys/types.h> <sys/socket.h>
int bind( int sockfd, const struct sockaddr* my_addr, socklen_t addrlen );
// 1 参数addrlen:socket地址长度。
// 2 返回:成功0,失败-1,设置errno
监听socket
<sys/socket.h>
int listen( int sockfd, int backlog );
// 1 参数sockfd:指定被监听socket
// 2 参数backlog:监听队列最大长度。典型值:5
接收链接。
<sys/types.h> <sys/socket.h>
int accept( int sockfd, struct sockaddr* addr, socklen_t* addrlen );
// 1 参数sockfd:执行过listen的socket。
// 2 参数addr:被接受链接的远程socket地址。
// 3 参数addrlen:地址长度。
// 4 返回:成功新的链接socket,失败-1设置errno
// 5 accept只从监听队列取出连接,并不检测网络状态。
发起链接
<sys/types.h> <sys/socket.h>
int connect( int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen );
// 1 参数sockfd:由socket系统调用返回一个socket
// 2 参数serv_addr:服务器监听的socket地址。
// 3 返回:成功0,失败-1设置errno
关闭连接
<unistd.h>
int close( int fd );
// 1 不会立即关闭,只是将引用减一。只有引用为0时才会真正关闭。
<sys/socket.h>
int shutdown( int sockfd, int howto );
// 1 立即关闭连接。
// 2 参数howto:决定函数的行为。SHUT_RD/WR/RDWR:可以分别控制读写关闭。
// 3 返回:成功0,失败-1设errno
TCP数据读写。
<sys/types.h> <sys/socket.h>
ssize_t recv( int sockfd, void* buf, size_t len, int flags ); // 返回:实际读取到的数据长度。失败-1errno
ssize_t send( int sockfd, const void* buf, size_t len, int flags ); // 返回:实际写入的数据长度。失败-1reeno
// 1 参数buf,len:缓冲区的地址和大小。
// 2 参数flags:数据收发额外控制。通常设0.
UDP数据读写。
<sys/types.h> <sys/socket.h>
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, const struct sockaddr* dest_addr, socklen_t *addrlen );
// 1 参数src/dest_addr:确定发送/接收端地址。(因UDP没有连接概念)
// 2 这两个函数同样可以应用于TCP读写数据。
通用数据读写函数。
<sys/socket.h>
ssize_t recvmsg( int sockfd, struct msghdr* msg, int flags );
ssize_t sendmsg( int sockfd, struct msghdr* msg, int flags );
struct msghdr{
void* msg_name; // socket 地址
socklen_t msg_namelen; // 地址长度
struct iovec* msg_iov; // 分散的内存块
int msg_iovlen; // 分散内存块的数量
void* msg_control; // 辅助数据地址
socklen_t msg_controllen; // 辅助数据大小
int msg_flags; // 保存函数flag参数
}
确定下一个数据是否时带外数据。
<sys/socket.h>
int sockatmark( int sockfd ); // 返回:带外1,否则0
获取地址信息。
<sys/socket.h>
int getsockname( int sockfd, struct sockaddr* address, socklen_t* address_len ); // 本端
int getpeername( int sockfd, struct sockaddr* address, socklen_t* address_len ); // 远端
// 1 返回:成功0,失败-1errno
获取/设置socket选项
<sys/socket.h>
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* restrict option_len );
// 1 参数level:具体协议选项。
// 2 返回:成功0,失败-1errno。 // 此后为网络信息API
获取主机完整信息。
<netdb.h>
struct hostent* gethostbyname( const char* name ); // 通过主机名称获取
struct hostent* gethostbyaddr( const void* addr, size_t len, int type ); //通过IP地址获取
// 1 参数type:IP地址的类型。
// 2 返回:主机信息。
获取服务的完整信息。
<netdb.h>
struct servent* getservbyname( const char* name, const char* proto ); // 通过名字获取
struct servent* getservbyport( int port, const char* proto ); // 通过端口号获取
// 1 参数proto:确定服务类型:UDP/TCP
获取主机和服务的综合函数。
int getaddrinfo( const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result );
int getnameinfo( const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen, char* serv, socklen_t servlen, int flags );
2,88-94在完成初版IM时,需要进行测试。
3:高级IO(高编已由函数省略)
1,函数
两个文件描述符之间直接传递数据
ssize_t sendfile( int out_fd, int in_fd, off_t* offset, size_t count );
// 1 参数count:传输字节数
// 2 返回:成功返回传输字节数,失败-1errno
// 3 in_fd:必须时真实文件,out_fd:必须是socket
两个文件描述符之间移动数据。零copy操作
ssize_t splice( int fd_in, loff_t* off_in, int fd_out, loff_t* off_out, size_t len, unsigned int flags ); // 具体需要再了解和使用。
两个管道文件描述符之间复制数据。零copy操作
ssize_t tee( int fd_in, int fd_out, size_t len, unsigned int flags );
4:linux服务程序规范。
1,函数。
和日志进程通信。
<syslog.h>
void syslog( int priority, const char* message );
// 1 参数priority:设置值与日志级别按位与。
2 设置日志进程的输出方式。
void openlog( const char* ident, int logopt, int facility );
// 1 参数ident:添加到日志消息的日期和时间之后。
// 2 参数facility:用来修改syslog函数中的默认值。
3 设置日志进程的掩码。(设置屏蔽信息)
int setlogmask( int maskpri );
4 关闭日志功能。
void closelog();
5 使进程称为后台进程:
<unistd.h>
int daemon( int nochdir, int noclose );
// 1 参数nochdir:传0,则工作目录为根目录/。否则不变。
// 2 参数noclose:传0,则被重定向为/dev/null。
// 3 返回:成功0,失败-1errno。
2,基本规范。
1)一般以后台进程形式运行。后台进程又称为守护进程。
2)通常有一套日志系统。至少能输出日志到文件。
3)一般以某个专门的非root身份运行。
4)通常时可配置的。
5)启动时,会生成一个PID文件并存入/var/run目录中。
6)通常需要考虑系统资源和限制。
3,日志进程:rsyslogd。
1)能够接收 用户/内核 的日志。
4,ps命令可查看进程、进程组和会话之间的关系。
5:高性能服务器框架。
1,函数。
2,零散细节。
1)客户连接请求时随即到达的异步事件,服务器需要使用某种IO模型来监听。
2)服务器可以解构成三个主要模块:IO处理单元,逻辑单元,储存单元。
3)服务器基本模块的功能描述。
模块 | 单个服务器程序 | 服务器机群 |
IO处理单元 | 处理客户连接,读写网络数据 | 作为接入服务器,实现负载均衡 |
逻辑单元 | 业务进程或线程 | 逻辑服务器 |
网络储存单元 | 本地数据库、文件或缓存。 | 数据库服务器 |
请求队列 | 各单元之间的通信方式。 | 各服务器之间的永久TCP连接 |
4)IO服用函数本身时阻塞的,它们能提高程序效率的原因:它们具有同时监听多个IO事件的能力。
5)同步IO向应用程序通知的是IO就绪事件,异步IO向应用程序通知的是IO完成事件。
6)服务器程序通常需要处理三类事件:IO事件,信号和定时事件。
7)如果程序是计算密集型, 并发没有任何优势。但如果是IO密集型,并发就会有意义。因为IO速度比CPU缓慢。
8)并发模式中,同步指程序完全按照代码序列顺序执行(用于处理客户逻辑),异步指程序执行需要由系统事件来驱动(用于处理IO)。
9)主线程向工作线程发送scoket最简单的方式:往管道中写数据。
10)池是一组资源的集合。这组资源在服务器启动之初就被完全创建好并初始化。常见的有:内存池,线程池,进程池和连接池。
11)高性能服务器应该避免不必要的数据复制。(避免数据复制,共享内存是最块的方式,但不一定是最好的。)
12)并发程序必须考虑上下文切换的开销。
13)尽量减少锁的开销。
3,P2P模式:peer to peer。
1)P2P模式时,主机之间很难相互发现。
4,reactoer模式。
1)它要求主线程(IO处理单元)只负责监听文件描述符是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元)。
2)特点:主线程只确定有请求,工作线程针对具体事件类型针对处理。
5,proactor模式:它将所有IO操作都交给主线程和内核来处理。工作线程仅仅负责业务逻辑。。
6,半同步/半反应堆模式:主线程用来监听IO,并发送请求到请求队列。然后工作线程竞争抢夺处理权限。
1)主线程和工作线程共享请求队列。需要锁来保证读写,消耗CPU
2)每个工作线程同一时间只能处理一个客户请求。
7,leader/follewer模式:leader监听scoket。当有新请求时,leader去处理请求,并从follower中推选新leader。
1)需要包含几个组件:句柄集,线程集,事件处理器,具体事件处理器。
6:IO复用
1,函数。
epoll系列:linux特有的IO复用函数。
<sys/epoll.h>
int epoll_create( int size ); // 创建一个描述符(标识事件表)
int epoll_ctl( int epfd, int op, int fd, struct epoll_event *event ); // 对 事件表 进行操作(添加,修改,删除注册事件)。
int epoll_wait( int epfd, struct epoll_event* events, int maxevents, int timeout ); // 等待事件。
2
2,零散细节。
1)IO复用虽然能同时监听多个文件描述符,但它本身是阻塞的。
2)epoll系列有两种模式:LT和ET。ET是相对高效的模式。
3)ET模式的文件描述符都应该是非阻塞的。(如果是阻塞的,可能会一直阻塞下去)
4)EPOLLONESHOT事件:使事件只能触发一次。若需要再次触发,需要重置。可以防止重复读写。
3,select,poll,epoll的区别
系统调用 | select | poll | epoll |
事件集合 | 用户通过三个参数分别传入可读,可写和异常事件。内核通过这些参数在线修改就绪事件。每次调用都会重读 | 统一处理所有事件类型。需要一个事件集。用户通过pollfd.event传入事件。内核通过修改pollfd.event反馈就绪事件。 | 内核通过事件表直接管理所有事件。无需反复传入事件。epoll_wait仅用来反馈就绪事件(不需要多余判断) |
应用程序索引就绪文件描述符的时间复杂度 | n | n |
1 |
最大支持文件描述符数 | 一般有最大值限制 | 65535 | 65535 |
工作模式 | LT | LT | LT/ET |
内核实现和工作效率 | 轮询方式检测就绪事件 | 轮询 | 回调方式检测就绪事件。 |
7:信号。
1,函数
2,零散细节。
1)服务器程序必须处理(至少能忽略)一些常见信号。以避免异常终止。
2)信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。所以信号需要尽快完成。典型的解决方案:信号仅提供信号,具体处理尽量方在主循环中。
8:时间堆:将所有定时器超时时间最小的一个定时器的超时值作为心搏间隔。
9:高性能IO框架库。
1,常见框架:ACE,ASIO,Libevent。
2,linux服务器程序必须处理三类事件:IO,信号和定时事件。但需要考虑三个问题
1)统一事件源。需要统一管理三类事件。一般方法为:利用IO复用系统调用来管理所有事件。
2)可移植性。
3)对并发编程的支持。
3,IO框架库以库函数的形式,封装了较为底层的系统调用,给应用程序提供了一组更便于使用的接口。
4,Libevent框架库。
1)跨平台支持,拥有统一事件源,线程安全,基于Reactor模式。
2)官方网站:libevent.org
5,P240,需要执行代码确认。
10:多线程编程。
1,线程实现方式:完全在用户空间实现,完全由内核调度和双层调度。
1)完全在用户空间实现优点:创建和调度线程无需内核的干预,速度很快。创建多各线程对系统性能没有太大影响。但无法在多CPU情况下使用。
2)内核调度:和用户空间完全相反。
3)双层调度:上两种模式的混合体。
11:进程池和线程池。
1,线程池中的线程数量应该和CPU数差不多。
2,进程之间(有关联进程)传递数据最简单的方法是管道。
12:服务器调制,调试和测试。
1,linux平台的一个优秀特性是内核微调:可以通过修改文件的方式来调正内核参数。
2,调试方法:tcpdump抓包看数据,或者gdb。
3,linux对应用程序能打开的最大文件描述符数量有两个层次的限制:用户级限制和系统级限制。
4,gdb调试多进程的两个简单方法:
1)找到PID,然后使用attach添加到gdb中。
2)set follow-fork mode(parent or child)
5,gdb调试多线程的方法。
1)info threads:显示当前可调试的所有线程。
2)threadID:调试ID线程
3)set scheduler-locking(off|on|step):off所有线程执行,on只有当前线程执行,step单步调试时,只有当前线程会执行。
6,压力测试:IO复用,多线程,多进程并发编程等方法(不懂- -)。
13:常用工具。
1,tcpdump:经典的网络抓包工具
2,lsof:列出当前系统打开的文件描述符的工具。
3,nc:用来快速构建网络连接。
1)服务器方式运行时:监听某个端口并接收客户端连接。
2)客户端方式运行时:可以向服务器发起连接并收发数据。
3)所以可以用来调试客户端和服务器。
4,strace:测试服务器性能的工具。
1)跟踪程序运行过程中执行的系统调用和接收到的信号,并将系统调用名、参数、返回值及信号名数处到标准输出或指定文件。
5,netstat:网络信息统计工具。
1)可以打印本地网卡接口上的全部连接、路由表信息、网卡接口信息。
6,ifstat:interface statistics。简单的网络流量检测工具。
7,mpstat:multi-processor statistics。能实时监测多处理器系统上的每个CPU的使用情况。