[单刷 APUE 系列] 第十四章——高级 I/O

非阻塞I/O

在最前面,我们讲过IO分成带缓冲的IO和不带缓冲的IO,但是实际上,这个区别并不是很大,因为缓冲区并没有影响到实际的读写。我们知道,系统调用实际上分成两种,高速的系统调用和低速的系统调用,换句话说,低速的调用会导致系统永久性阻塞,但是需要注意的是,并不是磁盘IO都是低速调用。比如open、read、write函数,如果这些操作不能完成就会立刻出错返回,并不会导致系统阻塞。在前面的时候我们也学到过,如果在open的时刻,指定O_NONBLOCK,或者在一个已打开的文件描述符上调用fcntl函数,附加上O_NONBLOCK参数。实际上虽然指定了参数,但是在某些情况下很有可能丢失信息。在大量传输信息的时候容易出现系统调用大量失败的情况。

记录锁

在很多情况下,我们需要面对多方一起操作文件的情况,这就是一个典型的资源竞争冲突,为了保证文件的正确读写,Unix系统提供了文件记录锁的机制,也就是上文中提到过的文件记录锁。为了提供这个功能,各个系统都自行实现了API,其中,POSIX1.x标准规定的是fcntl方法,而BSD系列则是规定flock方法,SystemV在fcntl方法的基础上构建了lockf函数

fcntl函数

int fcntl(int fildes, int cmd, ...);The commands available for advisory record locking are as follows:F_GETLK    Get the first lock that blocks the lock description pointed to by the third argument, arg, taken as a pointer to a struct flock (see above).  The information retrieved overwrites the information passed to fcntl in the flock structure.  If no lock is found that would prevent this lock from being created, the structure is left unchanged by this function call except for the lock type which is set to F_UNLCK.F_SETLK    Set or clear a file segment lock according to the lock description pointed to by the third argument, arg, taken as a pointer to a struct flock (see above).  F_SETLK is used to establish shared (or read) locks (F_RDLCK) or exclusive (or write) locks, (F_WRLCK), as well as remove either type of lock (F_UNLCK).  If a shared or exclusive lock cannot be set, fcntl returns immediately with EAGAIN.F_SETLKW   This command is the same as F_SETLK except that if a shared or exclusive lock is blocked by other locks, the process waits until the request can be satisfied.  If a signal that is to be caught is received while fcntl is waiting for a region, the fcntl will be interrupted if the signal han-dler has not specified the SA_RESTART (see sigaction(2)).复制代码

前面也介绍过这个函数,不过这次会讲解记录锁的内容,对于记录所来说,cmd参数是F_GETLKF_SETLK或者FSETLKW,第三个参数是一个纸箱flock结构体的指针

struct flock {off_t       l_start;    /* starting offset */off_t       l_len;      /* len = 0 means until end of file */pid_t       l_pid;      /* lock owner */short       l_type;     /* lock type: read/write, etc. */short       l_whence;   /* type of l_start */
};复制代码

基本上也不用讲解了,注释早已说明一切。这个结构体就是通过指定文件区域和锁的类型等参数锁定文件。不过需要注意的是,l_type实际上是取值SEEK_SETSEEK_CUR、或SEEK_END。并且上面提到的类型只有两种:共享读锁和独占写锁,实际上就是读写锁。

  • F_GETLK参数判断flockptr参数所描述的锁是否会被另一把锁排斥
  • F_SETLK参数设置由flockptr所描述的锁
  • F_SETLKW这是F_SETLK的阻塞版本

很容易想到,在开发中肯定是先用F_GETLK参数测试是否能建立一把锁,而后使用F_SETLK或者F_SETLKW建立锁,但是这两者并不是原子操作,前面已经讲过,非原子操作很容易导致操作冲突。
在设置释放锁的时候,内核是根据字节数维持锁的范围的,也就是说,实际上内核只是维护了一个flock结构体的链表,然后每次的锁更改都会导致链表被遍历并且合并。
对于记录锁的自动继承和释放有3条规则:

  1. 锁和进程、文件相关联,换言之,一个进程结束的时候,所有的锁全部释放,这实际上是exit函数做清理的,第二就是文件描述符关闭的时候,该文件所有的锁都会关闭
  2. fork产生的子进程不继承父进程的锁。因为锁是用于限制多个进程读写同一个文件的,如果fork能继承锁,那就起不到约束作用了
  3. 执行exec后,新程序继承原执行程序的锁,但是close_on_exec则会不一样。

其实锁对数据库这种大量读写IO的程序才是最有用的,所以基本上锁就可以直接考虑数据库的环境,如果数据库的客户端库使用的是同一套锁机制,那就能保证文件的共享访问,但是建议性锁无法保证其他有权限存取数据库文件的进程读写此文件。而强制性锁则会让进程检查每一个open、read和write函数,验证调用进程是否违背了正在访问文件的锁,这就是强制性锁和建议性锁的区别。

IO多路转接

前面谈到过,对于内核来说,IO只有两种方式:阻塞和非阻塞,阻塞IO会导致CPU等待IO从而浪费等待时间,所以系统提供了非阻塞IO,但是非阻塞IO带来的问题就是完整IO没有完成,为了获取完整的数据,应用程序需要重复调用IO操作来确认是否完成,也就是轮询。
当从一个文件描述符读,然后又写到另一个描述符时,通常会写出以下代码

while ((n = read(STDIN_FILENO, buf, BUFSIZE)) > 0)if (write(STDOUT_FILENO, buf, n) != n)err_sys("write error");复制代码

这种循环获取的形式就是轮询,非常简单,但是消耗了CPU资源,并且如果需要有更高的要求,比如必须从两个文件描述符读取。
典型的应用就是网络守护进程,例如Nginx和Telnet,这里直接拿原著中的Telnet讲解,telnet由于存在两个输入两个输出,所以不能使用阻塞式的IO函数,开发者的第一反应,应该是fork函数,使用两个进程,每个进程都负责一条读写通道,但是这就需要进程同步,而多线程编程也同样是这样的问题。
另一个方法就是使用一个进程,但是使用非阻塞IO读取数据。其基本思想很简单,两个描述符都读取,但是一直处于循环,每次循环都查询一次两个文件描述符,如果没有就立刻返回不阻塞,这种循环就是典型的轮询,这是种非常常见的技术,实际上却是非常浪费CPU资源的技术,所以目前,基本开发以及不能也不推荐了。
还有几种技术就是异步IO,这种技术实质上就是类似通知,当描述符准备完毕后,进程通知内核,但是实际上目前原生API并不能做到移植,所以,目前大部分的开发,包括Node.js等在内的网络服务,基本都是使用第三方或者自己实现线程池。不过,目前Linux系统已经有了名为AIO的原生异步IO。
现在目前大部分的使用方式就是IO多路转接,系统构造一张链表,里面存储所有的文件描述符,然后调用函数侦听,知道其中一个已经准备完毕的时候返回。poll、pselect和select三个函数就是这样执行的。

select和pselect函数

这连个函数是POSIX规定的

int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, struct timeval *restrict timeout);
int pselect(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, const struct timespec *restrict timeout, const sigset_t *restrict sigmask);复制代码

第一个参数nfds的意思就是“最大文件描述符编号值+1”,因为文件描述符都是从0开始的,从后面readfds、writefds、errorfds中找出最大描述符编号值并+1就是这个参数的值,中间三个参数是指向描述符集的指针,使用fd_set数据结构表示,实际上有下列五个函数

void FD_CLR(fd, fd_set *fdset);
void FD_COPY(fd_set *fdset_orig, fd_set *fdset_copy);
void FD_ISSET(fd, fd_set *fdset);
void FD_SET(fd, fd_set *fdset);
void FD_ZERO(fd_set *fdset);复制代码

是不是发现比原著多了一个FD_COPY函数,实际上就是复制用的,无关紧要。最后一个参数就是制定愿意等待的时间长度,使用timeval结构体,也就是可以指定秒和微妙单位。

  1. timeout == NULL,永远等待
  2. timeout->tv_sec == 0 && timeout->tv_usec == 0,不等待
  3. timeout->tv_sec != 0 || timeout->tv_usec != 0,等待指定时间

select实际上和描述符本身阻塞无关,它只是简化了我们监听一堆文件描述符的繁琐操作,除了select以外,上面还有一个select的变体pselect,pselect和select很像,但是select得超时值用timeval结构体定义,pselect使用timespec结构,pselect可使用可选信号屏蔽字,如果sigmask为null,则两者一样,但是sigmask指向屏蔽字的时候,将以原子操作形式安装屏蔽字。

poll函数

除了select以外,大家应该还见过poll函数

int poll(struct pollfd fds[], nfds_t nfds, int timeout);复制代码

看起来poll函数相对于select更加简洁易懂,select函数对三种类型都指定了参数用于构造描述符集,但是poll函数使用的则是pollfd结构体数组,pollfd结构体如下

struct pollfd {int    fd;       /* file descriptor */short  events;   /* events to look for */short  revents;  /* events returned */
};复制代码

nfds参数指定了fds数组的大小,从上面的注释中应该也看得出来结构体究竟是怎么构造的,events是我们关心fd的事件,而revents则是内核设置,返回的时候用于说明每个描述符发生了哪些事件。

     The event bitmasks in events and revents have the following bits:POLLERR        An exceptional condition has occurred on the device or socket.  This flag is output only, and ignored if present in the input events bitmask.POLLHUP        The device or socket has been disconnected.  This flag is output only, and ignored if present in the input events bitmask.  Note that POLLHUPand POLLOUT are mutually exclusive and should never be present in the revents bitmask at the same time.POLLIN         Data other than high priority data may be read without blocking.  This is equivalent to ( POLLRDNORM | POLLRDBAND ).POLLNVAL       The file descriptor is not open.  This flag is output only, and ignored if present in the input events bitmask.POLLOUT        Normal data may be written without blocking.  This is equivalent to POLLWRNORM.POLLPRI        High priority data may be read without blocking.POLLRDBAND     Priority data may be read without blocking.POLLRDNORM     Normal data may be read without blocking.POLLWRBAND     Priority data may be written without blocking.POLLWRNORM     Normal data may be written without blocking.复制代码

上面是两个参数可取的值,每个系统实现可能存在偏差,所以需要自行尝试。

异步I/O

前面讲过,非阻塞IO带来的就是轮询,前面内容包括前面的章节整合一下,可以归纳出以下主流轮询技术:

  1. read,最原始,性能最低的一种,重复检查IO状态来完成完整数据的读取,也就是前面一小节的开头代码
  2. select,在read基础上改进的方案,通过对文件描述符上的事件状态判断
  3. poll,使用链表作为文件描述符的存储方式,和select类似
  4. epoll,目前Linux下最高效的IO事件通知机制,进入轮询时候如果没有检查到IO事件就会休眠,直到事件将其唤醒
  5. queue,和epoll类似,不过是FreeBSD下的

虽然轮询满足了非阻塞IO获取完整数据的需求,但是依旧是同步的,也需要花费CPU用于便利文件描述符或者休眠等待事件发生。所以就有了异步IO,目前据笔者所知,只有Linux下有AIO技术算是真正原生提供的API。
但是,实际上,是有模拟方式的,信号机构提供了异步形式通知事件发生的方法,使用一个信号通知进程,但是,由于信号是有限的,如果使用一个信号,则进程不知道是哪个文件描述符发生的事件,如果用多个信号,文件描述符的数量可能远远超出信号的数量。
实际上,最容易想到的办法就是多线程。让部分线程进行阻塞IO或者非阻塞IO加轮询技术来完成数据获取,让另一个线程进行计算,而后通过线程间通信将IO得到的数据进行传递,就能轻松实现异步IO。

SystemV异步IO

SystemV中异步IO是归属给STREAMS系统的,他只能用于STREAMS设备和管道,异步IO信号是SIGPOLL。实际上由于这种机制本身的限制,目前已经找不到Unix环境会去采用它了,所以这里也不需要再讲解了。

BSD异步IO

对于BSD系列的系统来说,异步IO信号是SIGIO和SIGURG信号的组合,SIGIO是通用异步IO的信号,SIGURG则是通知网络连接的数据已经到达。

POSIX异步IO

POSIX标准对不同类型文件异步IO提供了可移植的模型,异步IO使用AIO控制块来描述IO操作。

struct aiocb {int             aio_fildes;             /* File descriptor */off_t           aio_offset;             /* File offset */volatile void   *aio_buf;               /* Location of buffer */size_t          aio_nbytes;             /* Length of transfer */int             aio_reqprio;            /* Request priority offset */struct sigevent aio_sigevent;           /* Signal number and value */int             aio_lio_opcode;         /* Operation to be performed */
};复制代码

上面是苹果系统下的AIO控制块实现,实际上和POSIX规定几乎一样,它是继承于FreeBSD3.0的AIO实现,
从上面可以看出,每个字段究竟的意义,aio_fildes就是文件描述符,读写操作从aio_offset指定的偏移量位置开始,对于读操作,会将数据复制到aio_buf的缓冲区内,对于写操作,会从这个缓冲区写入磁盘,aio_nbytes字段指定了读写的字节数。
除了上面4个字段以外,aio_reqprio就是异步IO请求的顺序,aio_sigevent就是IO事件完成后如何通知,而aio_lio_opcode就是执行的操作。

struct sigevent {int                             sigev_notify;                           /* Notification type */int                             sigev_signo;                            /* Signal number */union sigval    sigev_value;                            /* Signal value */void                    (*sigev_notify_function)(union sigval);   /* Notification function */pthread_attr_t  *sigev_notify_attributes;       /* Notification attributes */
};复制代码

sigevent结构体是归属于signal信号机制模型中的数据结构,其中sigev_notify字段是通知类型

  • SIGEV_NONE 不通知进程
  • SIGEV_SIGNAL 异步IO完成后,产生sigev_signo指定的信号,
  • SIGEV_THREAD 异步请求完成后,由sigev_notify_function指定的函数被调用
int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);复制代码

在异步IO之前需要先初始化AIO控制块,当函数返回成功时候,异步IO请求就已经被放在了等待处理队列中。这些返回值与实际IO擦做的结果没有任何关系,如果想要强制所有等待中的异步操作不等待直接写入存储,则调用aio_fsync函数
当然,好像aio_fsync函数并不是非常广泛,所以在使用的时候记得运行时检查。
为了获取一个异步读写的完成状态,可以调用aio_error函数

int aio_error(const struct aiocb *aiocbp);复制代码

返回如下:

  1. 返回值为0,异步操作成功,使用aio_return函数获得返回值
  2. 返回值为-1,对aio_error操作失败
  3. 返回值为EINPROGRESS,读写操作仍处于等待状态
ssize_t aio_return(struct aiocb *aiocbp);复制代码

记住在aio_error检查已经成功之前,不要调用aio_return函数,而且需要当心每个异步操作只能调用一次aio_return函数。
如果在其他操作完成之后,异步操作还未完成,那可以使用

int aio_suspend(const struct aiocb *const list[], int nent, const struct timespec *timeout);复制代码

aio_suspend函数会阻塞当前进程直到操作完成,一般情况下很少会使用。
如果我们想要取消已经处于进行中的异步操作,可以使用如下函数

int aio_cancel(int fildes, struct aiocb *aiocbp);复制代码

这个函数会返回4个返回值:

  1. AIO_ALLDONE,所有操作已经完成
  2. AIO_CANCELED,所有操作已经取消
  3. AIO_NOtCANCELED,至少有一个请求没有取消
  4. -1,函数本身失败

除了上述函数以外,还有一个函数也被包含在异步请求函数中,但是实际上很少见到,所以这里就不多做讲解。

readv和writev函数

ssize_t readv(int d, const struct iovec *iov, int iovcnt);
ssize_t writev(int fildes, const struct iovec *iov, int iovcnt);复制代码

这两个函数用于在一次读写中读写多个非连续的缓冲区,也就是说可以将传统的多个函数读写调用压缩到一个,这连个函数第二个参数就是一个指向iovec结构体的指针,实际上是一个指向数组的指针

struct iovec {char   *iov_base;  /* Base address. */size_t iov_len;    /* Length. */
};复制代码

第三个参数就是数组的长度。iov数组中的元素最大值就是IOV_MAX。

存储映射IO

存储映射IO能将一个磁盘文件映射到存储空间中的一个缓冲区上,于是,当从缓冲区中读取数据的时候,就等同于读取文件。Unix系统提供了此类函数

void *mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);复制代码

addr指定映射存储区的起始地址。通常为0,也就是系统自动分配区域。fd参数指定被映射文件的文件描述符,也就代表必须先打开这个文件。prot参数指定了映射存储区的保护要求如下:
|prot|说明|
|----|---|
|PROT_READ|存储区可读|
|PROT_WRITE|存储区可写|
|PROT_EXEC|存储区可执行|
|PROT_NONE|存储区不可访问|
当然,这个参数的指定必然是基于文件描述符的打开方式的,很容易明白,因为存储映射IO技术本质上还是基于文件描述符的,所以不可能绕过文件描述符的限制读写。
flag参数影响映射存储区的多种属性,如下就是可选值:

     MAP_ANONYMOUS     Synonym for MAP_ANON.MAP_ANON          Map anonymous memory not associated with any specific file.  The offset argument is ignored.  Mac OS X specific: the file descriptor usedfor creating MAP_ANON regions can be used to pass some Mach VM flags, and can be specified as -1 if no such flags are associated with theregion.  Mach VM flags are defined in <mach/vm_statistics.h> and the ones that currently apply to mmap are:VM_FLAGS_PURGABLE   to create Mach purgable (i.e. volatile) memoryVM_MAKE_TAG(tag)    to associate an 8-bit tag with the region<mach/vm_statistics.h> defines some preset tags (with a VM_MEMORY_ prefix).  Users are encouraged to use tags between 240 and 255.  Tagsare used by tools such as vmmap(1) to help identify specific memory regions.VM_FLAGS_SUPERPAGE_SIZE_*     to use superpages for the allocation.  See <mach/vm_statistics.h> for supported architectures and sizes (oruse VM_FLAGS_SUPERPAGE_SIZE_ANY to have the kernel choose a size).  The specified size must be divisible by the superpage size (except forVM_FLAGS_SUPERPAGE_SIZE_ANY), and if you use MAP_FIXED, the specified address must be properly aligned. If the system cannot satisfy therequest with superpages, the call will fail. Note that currently, superpages are always wired and not inherited by children of the process.MAP_FILE          Mapped from a regular file.  (This is the default mapping type, and need not be specified.)MAP_FIXED         Do not permit the system to select a different address than the one specified.  If the specified address cannot be used, mmap() will fail.If MAP_FIXED is specified, addr must be a multiple of the pagesize.  If a MAP_FIXED request is successful, the mapping established bymmap() replaces any previous mappings for the process' pages in the range from addr to addr + len.  Use of this option is discouraged.MAP_HASSEMAPHORE  Notify the kernel that the region may contain semaphores and that special handling may be necessary.MAP_PRIVATE       Modifications are private (copy-on-write).MAP_SHARED        Modifications are shared.MAP_NOCACHE       Pages in this mapping are not retained in the kernel's memory cache.  If the system runs low on memory, pages in MAP_NOCACHE mappings willbe among the first to be reclaimed.  This flag is intended for mappings that have little locality and provides a hint to the kernel thatpages in this mapping are unlikely to be needed again in the near future.复制代码

这就不讲解了,原著上已经讲解的足够清楚了。
调用mprotect可以更改现有映射的权限

int mprotect(void *addr, size_t len, int prot);复制代码

也就是一个修改映射区域权限的函数,当页已经修改完毕,可以调用msync函数冲洗到被映射的文件中。

int msync(void *addr, size_t len, int flags);复制代码

基本就和fsync函数差不多,也不多说了,基本上都在Unix手册上
当进程终止的之后,自然会自动解除存储区的映射,或者可以调用munmap函数解除

int munmap(void *addr, size_t len);复制代码

munmap函数删除了指定地址的映射,如果继续对其进行读写会导致无效内存引用。并且这个函数不会冲洗缓冲区内容到文件,所以需要小心使用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/292707.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

正则表达式的简单应用

1. 正则表达式(1) 什么是正则表达式&#xff1f;正则表达式是一种功能强大而灵活的文本处理工具。(2) 正则表达式的主要作用正则表达式主要用于&#xff1a;验证字符串&#xff0c;即检验输入的字符串是否符合给定的模式。例如&#xff0c;验证输入的电话号码、手机号码、号码等…

Prism区域异常问题分析(导航失效?)

前文本篇文章主要讨论在WPF当中使用Prism区域导航的失效的问题, 在其它的博客当中也出现了多次讨论这个问题以及对应的解决方法,例如重写OnInitialized方法等等。我认为这都不是解决问题的根源, 既然如此, 下面我们将来分析Prism的IRegionManager的具体流程。Prism初始化过程首…

HOOK学习笔记与心得

一、 Hook介绍钩子(Hook)&#xff0c;是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息&#xff0c;而且所监视的窗口可以是其他进程所创建的。当消息到达后&#xff0c;在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理wind…

使用SQLServer2005插入一条数据时返回当前插入数据的ID

使用SQLServer2005插入一条数据时返回当前插入数据的ID在执行完插入后 再执行 select identity from users 就OK 就是刚才插入的那行的 ID了 补充&#xff1a; identity 表示当前新增的主键ID这个是在一个session中查找的&#xff0c;SELECT MAX(id) FROM Users 执行这个查询,就…

access函数_ACCESS中的DLookUp函数是如何运算的?

​一、DLookUp函数介绍1. DLookUp函数的用途&#xff1a;可以用于从指定集合(一个域)中获取符合条件的特定字段的值。2. DLookUp函数的格式为&#xff1a;DLookUp( expr , domain , [criteria] )其中&#xff1a;expr 为字段名&#xff0c;或以字段名为基础的表达式字符串domai…

atitit。wondows 右键菜单的管理与位置存储

atitit。wondows 右键菜单的管理与位置存储 原理 。这样的功能称为Windows外壳扩展(Shell Extensions) 1 常用右键菜单 atiContentMenu1 通用tool1 文件夹的右键菜单位置3 所有的文件的右键菜单位置3 右键菜文件夹模式3 原理 。这样的功能称为Windows外壳扩展(Shell Extensions…

汇编语言之基础知识

1、机器语言 说到汇编语言的产生&#xff0c;首先要讲一下机器语言。机器语言是机器指令的集合。什么是机器指令&#xff1f;我们在使用CE时&#xff0c;常常见到。 请看下图&#xff1a; 图中所示的就是机器指令&#xff08;或称机器码&#xff09;&#xff0c;这是十六进制的…

Entity Framework 简单增删改操作

前言 在 Entity Framework 简单查询操作 中主要是学习了在Entity Framework中的几种不同模式的查询操作&#xff0c;现在主要来学习一下简单的增加、删除、修改操作。 增加 在EF中添加操作一般有两种方式&#xff1a;一是直接创建对象&#xff0c;然后调用“DbSet”的”Add()”…

华为云服务器初探二(完结)

在上一篇《华为云服务器初探》 中介绍了在使用华为云服务器部署时的一些关键点&#xff0c;本篇继续&#xff0c;内容涉及如下:中间件的部署问题解决NAT 网关使用数据库服务的访问dotNET Core 程序的构建Redis首先更正上一篇中的一个错误&#xff0c;在运行参数中进行密码设置&…

C# 对Datatable排序

一&#xff0c;在C#中要对Datatable排序&#xff0c;可使用DefaultView的Sort方法。先获取Datatable的DefaultView&#xff0c;然后设置 得到的Dataview的sort属性&#xff0c;最后用视图的ToTable方法将排好序的dataview导出为Datatable。 代码如下&#xff1a; …

for in for of区别_Python 第5课:for…in循环黄金搭档之列表

乐学趣学Py● 05&#xff1a;for…in循环黄金搭档之列表●Python趣味小百科Python程序中有一个有彩蛋&#xff0c;在IDLE Pythton模式下输入import this会出现一首(The Zen of Python, by Tim Peters)‘Pyton之禅’的小诗。这首小诗表明了用Python编写代码时遵循的原则&#xf…

Android官方开发文档Training系列课程中文版:Android的安全建议 .

转载:http://blog.csdn.net/sahadev_/article/details/52949855 原文地址:http://android.xsoftlab.net/training/articles/security-tips.html Android系统内置的安全策略可以有效的降低应用程序的安全问题。所以默认创建的应用程序已经包含了一定程度的安全保护措施。 Andr…

PHP提取字符串中的数字

function number($str) {return preg_replace(/\D/s, , $str); } // echo 123456 echo number(Hello 123 world 456 !!); //支持小数 function number($str) { return preg_replace(/[^\.0123456789]/s, , $str); } 转载于:https://www.cnblogs.com/shcolo/p/5945192.html

开源虎墩同名电影《小虎墩大英雄》定档大年初一

文末有福利&#xff0c;记得看到最后哦~| 作者&#xff1a;虎虎生风的开源虎墩组| 编辑&#xff1a;刘雪洁| 责编&#xff1a;王玥敏开源虎墩诞生记&#xff1a;小源机器人2.0大家还记得 2020 疫情肆虐的时候开源社与来自全国各地的开源爱好者隔空合作&#xff0c;共同打造的疫…

HYDRAstor:可扩展的二级存储

为什么80%的码农都做不了架构师&#xff1f;>>> 原文&#xff1a;HYDRAstor: a Scalable Secondary Storage. HYDRAstor&#xff08;官网&#xff09;是NEC推出的二级存储系统,先后有多篇关于HYDRAstor的论文发表在FAST&#xff08;包括后来9livesdata发表的论文&a…

测试环境搭建及维护

搭建良好的测试环境是执行测试用例的前提&#xff0c;也是完成测试任务顺利完成的保证。测试环境大体可分为硬件环境和软件环境&#xff0c;硬件环境包括测试必须的PC机&#xff0c;服务器&#xff0c;设备&#xff0c;网线&#xff0c;分配器等硬件设备&#xff1b;软件环境包…