Linux 网络编程的5种IO模型:异步IO模型

资料已经整理好,但是还有未竟之业:复习多路复用epoll 阅读例程, 异步IO 函数实现

背景

上一讲《 Linux 网络编程的5种IO模型:信号驱动IO模型 》我们已经介绍了信号驱动模型,以及带有BUG的例程。

前面四种IO模型实际上都属于同步IO,只有最后一种是真正的异步IO,因为无论是多路复用IO还是信号驱动模型,IO操作的第2个阶段都会引起用户线程阻塞,也就是内核进行数据拷贝的过程都会让用户线程阻塞。

这一讲我们来介绍最后一种IO模型。

导言

两种高性能IO设计模式

在传统的网络服务设计模式中,有两种比较经典的模式:多线程,与 线程池。

多线程

对于多线程模式,也就说来了client,服务器就会新建一个线程来处理该client的读写事件,如下图所示:

这种模式虽然处理起来简单方便,但是由于服务器为每个client的连接都采用一个线程去处理,使得资源占用非常大。因此,当连接数量达到上限时,再有用户请求连接,直接会导致资源瓶颈,严重的可能会直接导致服务器崩溃。

线程池

因此,为了解决这种一个线程对应一个客户端模式带来的问题,提出了采用线程池的方式,也就说创建一个固定大小的线程池,来一个客户端,就从线程池取一个空闲线程来处理,当客户端处理完读写操作之后,就交出对线程的占用。因此这样就避免为每一个客户端都要创建线程带来的资源浪费,使得线程可以重用。

但是线程池也有它的弊端,如果连接大多是长连接,因此可能会导致在一段时间内,线程池中的线程都被占用,那么当再有用户请求连接时,由于没有可用的空闲线程来处理,就会导致客户端连接失败,从而影响用户体验。因此,线程池比较适合大量的短连接应用。

高性能IO模型

因此便出现了下面的两种高性能IO设计模式:Reactor和Proactor。

Reactor

在Reactor模式中,会先对每个client注册感兴趣的事件,然后有一个线程专门去轮询每个client是否有事件发生,当有事件发生时,便顺序处理每个事件,当所有事件处理完之后,便再转去继续轮询,如下图所示:

从这里可以看出,多路复用IO就是采用Reactor模式。

注意,上面的图中展示的 是顺序处理每个事件,当然为了提高事件处理速度,可以通过多线程或者线程池的方式来处理事件。

Proactor

在Proactor模式中:当检测到有事件发生时,会新起一个异步操作,然后交由内核线程去处理,当内核线程完成IO操作之后,发送一个通知告知操作已完成;可以得知,异步IO模型采用的就是Proactor模式。

Linux异步IO模型与有关函数

异步IO模型是比较理想的IO模型,在异步IO模型中,当用户线程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronous read之后,它会立刻返回,说明read请求已经成功发起了,因此不会对用户线程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户线程,当这一切都完成之后,内核会给用户线程发送一个信号,告诉它read操作完成了。也就说用户线程完全不需要关心实际的整个IO操作是如何进行的,只需要先发起一个请求,当接收内核返回的成功信号时表示IO操作已经完成,可以直接去使用数据了。

也就说在异步IO模型中,IO操作的两个阶段都不会阻塞用户线程,这两个阶段都是由内核自动完成,然后发送一个信号告知用户线程操作已完成。用户线程中不需要再次调用IO函数进行具体的读写。

这点是和信号驱动模型有所不同的,在信号驱动模型中,当用户线程接收到信号表示数据已经就绪,然后需要用户线程调用IO函数进行实际的读写操作;而在异步IO模型中,收到信号表示IO操作已经完成,不需要再在用户线程中调用iO函数进行实际的读写操作。

%% 时序图
sequenceDiagram
title : 异步IO模型
participant application
participant kernel

Note right of application: 应用程序调用系统调用

application ->> kernel: aio_read
kernel ->> application: 返回

kernel ->> kernel: 准备好数据,拷贝到用户空间

kernel ->> application: 递交到aio_read指定的信号中
application ->> application : 信号处理

#include <aio.h>

int aio_read(struct aiocb *__aiocbp);
int aio_write(struct aiocb *__aiocbp); Link with -lrt. /* 有关结构体 ,能够使用的成员已经标出 */
struct aiocb
{
▲ int aio_fildes; /* 对哪个文件进行读写. */
▲ int aio_lio_opcode; /* 要执行的操作 */
int aio_reqprio; /* Request priority offset. */
▲ volatile void *aio_buf; /* 读写用的buffer */
▲ size_t aio_nbytes; /* Length of transfer. */
▲ struct sigevent aio_sigevent; /* 告诉 AIO 在 I/O 操作完成时应该执行什么操作。 */ /* Internal members. */
struct aiocb *__next_prio;
int __abs_prio;
int __policy;
int __error_code;
__ssize_t __return_value; #ifndef __USE_FILE_OFFSET64 // 针对大文件的支持
▲ __off_t aio_offset; /* 在传统的 read 调用中,偏移量是在文件描述符上下文中进行维护的, */
char __pad[sizeof (__off64_t) - sizeof (__off_t)];
#else
▲ __off64_t aio_offset; /* 对于异步 I/O 操作来说这是不可能的,因为我们可以同时执行很多读请求,因此必须为每个特定的读请求都指定偏移量。 */
#endif
char __glibc_reserved[32];
}; struct sigevent {
int sigev_notify; /* 通知方式:为SIGEV_NONE、SIGEV_SIGNAL、SIGEV_THREAD、SIGEV_THREAD_ID(只针对linux)当中的一个; */
int sigev_signo; /* 为signal的值,当sigev_notify为SIGEV_SIGNAL时,会将这个signal发送给进程; */
union sigval sigev_value; /* 信号传递的数据 */
void (*sigev_notify_function) (union sigval);/* 当sigev_notify为SIGEV_THREAD时,处理线程将调用这个处理函数 (SIGEV_THREAD) */
void *sigev_notify_attributes;/* sigev_notify_function的参数 (SIGEV_THREAD) */
pid_t sigev_notify_thread_id; /* 当sigev_notify为SIGEV_THREAD_ID时的处理线程ID (SIGEV_THREAD_ID) */
}; union sigval { /*传递的参数*/
int sival_int; /* 信号机制传递的参数 */
void *sival_ptr; /* 若是线程机制传递的参数 */
}; // 什么时候使用 AIO ?了解 AIO 机制之后,不难发现, AIO 其实是用于解决大量 IO 并发操作而出现的,牺牲一些信号处理耗时,用多线程的方式加速 IO ,提高并行效率。
函数 作用
aio_read 请求异步读操作
aio_error 检查异步请求的状态
aio_return 获得完成的异步请求的返回状态
aio_write 请求异步写操作
aio_suspend 挂起调用进程,直到一个或多个异步请求已经完成(或失败)
aio_cancel 取消异步 I/O 请求
aio_fsync 强制同步
lio_listio 发起一系列 I/O 操作

aio_read

#include <aio.h>
int aio_read( struct aiocb *aiocbp );

描述: 请求一个异步写操作。

返回值:成功返回值 0;出错返回值 -1,并设置 errno的值。

aio_read 例子

#include <unistd.h>
#include <stdio.h>
#include <aio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/timeb.h> #define BUFFER_SIZE 1024*1024 void ptime(const char* tip){
struct timeb tb;
ftime(&tb);
fprintf(stdout, "%s %u : %u\n", tip, tb.time, tb.millitm);
} int main(){
/* 句柄,返回码 */
int fd = -1, ret = -1; fd = open("./file.txt", O_RDONLY);
if(fd <= 0){
fprintf(stderr, "open file errro: %s\n", strerror(errno));
return -1;
} /* aio控制结构 */
aiocb my_aiocb;
memset(&my_aiocb, 0, sizeof(my_aiocb)); /* 初始化 */
my_aiocb.aio_fildes = fd;
my_aiocb.aio_reqprio = 0;
my_aiocb.aio_nbytes = BUFFER_SIZE;
char buf[BUFFER_SIZE + 1] = {0};
my_aiocb.aio_buf = buf; ptime("start read"); /* aio 读 */
ret = aio_read(&my_aiocb);
if(ret < 0){
fprintf(stderr, "aio read error: %s\n", strerror(errno));
return -2;
} ptime("reading"); /* 检查状态 */
while(aio_error(&my_aiocb) == EINPROGRESS);
/* (这种做法不是最有效的,只是为了演示aio_error如何使用)可以调用 aio_error 来确定 aio_read 的状态。只要状态是 EINPROGRESS,就一直忙碌等待,直到状态发生变化为止。请求可能成功,也可能失败。*/ ptime("after read"); if ((ret = aio_return( &my_iocb )) > 0) {
/* got ret bytes on the read */
fprintf(stdout, "read: %10.10s\n", my_aiocb.aio_buf);
} else {
/* read failed, consult errno */
fprintf(stderr, "return: %d\n", ret);
} close(fd); return 0;
}

aio_error

int aio_error( struct aiocb *aiocbp );

描述:用来确定请求的状态。

返回值

  • EINPROGRESS,说明请求尚未完成
  • ECANCELLED,说明请求被应用程序取消了
  • -1,说明发生了错误,具体错误原因可以查阅 errno

aio_return

ssize_t aio_return( struct aiocb *aiocbp );

描述:获得完成的异步请求的返回状态。

异步 I/O 和标准 I/O 之间的另外一个区别是我们不能立即访问这个函数的返回状态,因为我们并没有阻塞在 read 调用上。在标准的 read 调用中,返回状态是在该函数返回时提供的。但是在异步 I/O 中, 我们要使用 aio_return 函数。

只有在 aio_error 调用确定请求已经完成(可能成功,也可能发生了错误)之后,才会调用这个函数。

返回值:所传输的字节数,如果出错,返回 -1(等价于 readwrite 系统调用的返回值)。

aio_write

int aio_write( struct aiocb *aiocbp );

描述: 请求一个异步写操作。

aio_write 函数会立即返回,说明请求已经进行排队(成功时返回值为 0,失败时返回值为 -1, 并相应地设置 errno)。

这与 aio_read 类似,但是在偏移量上有一点不一样:对于write 来说,这个偏移量只有在没有设置 O_APPEND 选项的文件上下文中才会非常重要。

如果设置了 O_APPEND,那么这个偏移量就会被忽略,数据都会被附加到文件的末尾。否则,aio_offset 域就确定了数据在要写入的文件中的偏移量。

aio_suspend

int aio_suspend( const struct aiocb *const aiocb_list[],
int nitems, const struct timespec *timeout );

描述:挂起(或阻塞)调用进程,直到以下情况发生:

  • 一个或多个处于 aiocb_list中的异步请求完成
  • 有信号递达
  • 调用时指定的时间已到,发生超时

调用者提供了一个 aiocb 引用列表,其中任何一个完成都会导致 aio_suspend 返回。

参数解析:

cblist:一组异步IO请求 (aiocb_list中任何 NULL 元素都会被忽略)

nitems:该组的成员数量

timeout:超时时间,NULL代表永远阻塞

返回值:成功返回0;失败返回-1,设置errno:

EAGAIN :超时,希望程序重试。

EINTR : 被信号中断(也有可能是等待的某个操作的完成信号)

ENOSYS :这个功能未被当前系统支持(未实现)

aio_suspend 例程

使用非常简单。我们要提供一个 aiocb 引用列表。

...

struct aioct *cblist[MAX_LIST];

/* Clear the list. */
bzero( (char *)cblist, sizeof(cblist) ); /* Load one or more references into the list */
cblist[0] = &my_aiocb;
...
for(i = 0; i < ..; i++)
{ }
ret = aio_read( &my_aiocb );
... ret = aio_suspend(cblist, MAX_LIST, NULL ); ...

aio_cancel

int aio_cancel( int fd, struct aiocb *aiocbp);

描述:允许我们取消对某个文件描述符执行的一个或所有 I/O 请求。

参数解析:

fd : 与读写请求有关的文件描述符

aiocbp:读写请求(为NULL时,取消所有请求)

返回值:成功取消返回AIO_CANCELED,请求被完成时返回AIO_NOTCANCELED

要取消对某个给定文件描述符的所有请求,我们需要提供这个文件的描述符,以及一个对 aiocbpNULL 引用。

  • 如果所有的请求都取消了,这个函数就会返回 AIO_CANCELED

  • 如果至少有一个请求没有被取消,那么这个函数就会返回 AIO_NOT_CANCELED

  • 如果没有一个请求可以被取消,那么这个函数就会返回 AIO_ALLDONE

可以使用 aio_error 来验证每个 AIO 请求。如果这个请求已经被取消了,那么 aio_error就会返回 -1,并且 errno 会被设置为 ECANCELED

aio_fsync

int aio_fsync(int op, struct aiocb *aiocbp);

描述: 在AIO是交给其他线程来完成的,如果需要手动执行同步,则需要调用这个函数 。 函数执行时,将强制完成该AIO上的所有操作。 与一般文件IO的fsync用法基本一致。

如果想要所有等待的异步操作不等待而写入持久化的存储中,可以设立一个AIO控制板并调用该函数。

aio_read、aio_write函数会进行数据的缓冲。使用了aio_fsync就不必再去使用aio_read和aio_write了

参数解析:

op: operation为操作码

  • O_SYNC : 同步异步IO数据,当前所有IO操作均将完成
  • O_DSYNC:同步一个IO请求,并不等待所有的IO完成 (相当于调用fdatasync函数 )

aiocbp:异步请求

lio_listio

int lio_listio( int mode, struct aiocb *aiocb_list[], int nitems,
struct sigevent *sevp); #include <signal.h> union sigval { /*传递的参数*/
int sival_int; /* 信号机制传递的参数 */
void *sival_ptr; /* 若是线程机制传递的参数 */
}; struct sigevent {
int sigev_notify; /* 设置通知机制方法,线程为SIGEV_THREAD,信号为SIGEV_SIGNAL*/
int sigev_signo; /* 若是信号机制,该参数设置为触发的信号 */
union sigval sigev_value;/* 传递的参数*/
void (*sigev_notify_function)(union sigval);
/* 若是线程机制,该参数为线程函数*/
void *sigev_notify_attributes;
/* 线程函数的属性 */
};

描述:同时发起多个传输。

意味着我们可以在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操作。从性能的角度来看,大大提高了效率。

参数解析:

mode:

  • LIO_WAIT:阻塞这个调用,直到所有的 I/O 都完成为止。
  • LIO_NOWAIT:操作进行排队之后,立即返回。

list:一组异步IO请求 (aiocb_list中任何 NULL 元素都会被忽略)

nitems:请求的个数

sigevent:在所有 I/O 操作都完成时产生信号的方法。

注意:

对于 lio_listio 的请求与传统的 readwrite 请求在必须指定的操作方面稍有不同。

  • 对于读操作来说,aio_lio_opcode 域的值为 LIO_READ

  • 对于写操作来说,我们要使用 LIO_WRITE

  • 允许 LIO_NOP (不执行)

lio_listio 例程

struct aiocb aiocb1, aiocb2;
struct aiocb *list[MAX_LIST]; ... /* Prepare the first aiocb */
aiocb1.aio_fildes = fd;
aiocb1.aio_buf = malloc( BUFSIZE+1 );
aiocb1.aio_nbytes = BUFSIZE;
aiocb1.aio_offset = next_offset;
aiocb1.aio_lio_opcode = LIO_READ; ... bzero( (char *)list, sizeof(list) );
list[0] = &aiocb1;
list[1] = &aiocb2; ret = lio_listio( LIO_WAIT, list, MAX_LIST, NULL );

AIO例程

现在我们已经了解了有关的 AIO 函数。

接下来,我们将通过信号(signal)函数回调(callback)来探索异步函数的通知机制。

使用信号进行异步通知

使用信号进行进程间通信(IPC)是 UNIX 中的一种传统机制,AIO 也可以支持这种机制。在这种范例中, 应用程序需要定义信号处理程序,在产生指定的信号时就会调用这个处理程序。应用程序然后配置一个异步请求将在请求完成时产生一个信号。作为信号上下文的一部分,特定的aiocb 请求被提供用来记录多个可能会出现的请求。

/*
我们在 aio_completion_handler 函数中设置信号处理程序来捕获 SIGIO 信号。 然后
- 初始化 aio_sigevent 结构产生 SIGIO 信号来进行通知,
- 指定aio_sigevent.sigev_notify使用信号方式,
- 指定aio_sigevent.sigev_signo使用的信号。 当读操作完成时,信号处理程序就从该信号的 si_value 结构中提取出 aiocb,并检查错误状态和返回状态来确定 I/O 操作是否完成。 对于性能来说,这个处理程序也是通过请求下一次异步传输而继续进行 I/O 操作的理想地方。采用这种方式,在一次数据传输完成时,我们就可以立即开始下一次数据传输操作。
*/
void setup_io( ... )
{
int fd;
struct sigaction sig_act;
struct aiocb my_aiocb; ... /* Set up the signal handler */
sigemptyset(&sig_act.sa_mask);
sig_act.sa_flags = SA_SIGINFO;
sig_act.sa_sigaction = aio_completion_handler; /* Set up the AIO request */
bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
my_aiocb.aio_fildes = fd;
my_aiocb.aio_buf = malloc(BUF_SIZE+1);
my_aiocb.aio_nbytes = BUF_SIZE;
my_aiocb.aio_offset = next_offset; /* Link the AIO request with the Signal Handler */
my_aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
my_aiocb.aio_sigevent.sigev_signo = SIGIO;
my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb; /* Map the Signal to the Signal Handler */
ret = sigaction( SIGIO, &sig_act, NULL ); ... ret = aio_read( &my_aiocb ); } void aio_completion_handler( int signo, siginfo_t *info, void *context )
{
struct aiocb *req; /* Ensure it's our signal */
if (info->si_signo == SIGIO) { req = (struct aiocb *)info->si_value.sival_ptr; /* Did the request complete? */
if (aio_error( req ) == 0) { /* Request completed successfully, get the return status */
ret = aio_return( req ); } } return;
}

使用回调函数进行异步通知

另外一种通知方式是系统回调函数。这种机制不会为通知而产生一个信号,而是会调用用户空间的一个函数

来实现通知功能。我们在 sigevent结构中设置了对 aiocb 的引用,从而可以惟一标识正在完成的特定请求。

/*
在创建自己的 aiocb 请求之后,我们使用 SIGEV_THREAD 请求了一个线程回调函数来作为通知方法(aio_sigevent.sigev_notify指定)。 然后我们将指定特定的通知处理程序,并将要传输的上下文加载到处理程序中(aio_sigevent.notify_function指定,在这种情况中,是个对 aiocb 请求自己的引用)。 在这个处理程序中,我们简单地引用到达的 sigval 指针并使用 AIO 函数来验证请求已经完成。
*/
void setup_io( ... )
{
int fd;
struct aiocb my_aiocb; ... /* Set up the AIO request */
bzero( (char *)&my_aiocb, sizeof(struct aiocb) );
my_aiocb.aio_fildes = fd;
my_aiocb.aio_buf = malloc(BUF_SIZE+1);
my_aiocb.aio_nbytes = BUF_SIZE;
my_aiocb.aio_offset = next_offset; /* Link the AIO request with a thread callback */
my_aiocb.aio_sigevent.sigev_notify = SIGEV_THREAD;
my_aiocb.aio_sigevent.notify_function = aio_completion_handler;
my_aiocb.aio_sigevent.notify_attributes = NULL;
my_aiocb.aio_sigevent.sigev_value.sival_ptr = &my_aiocb; ... ret = aio_read( &my_aiocb ); } void aio_completion_handler( sigval_t sigval )
{
struct aiocb *req; req = (struct aiocb *)sigval.sival_ptr; /* Did the request complete? */
if (aio_error( req ) == 0) { /* Request completed successfully, get the return status */
ret = aio_return( req ); } return;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #include <aio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/stat.h>
#include <fcntl.h> static char *memBuffer;
static int sFileDesc;
static struct sigaction sOldSigAction; static void MySigQuitHandler(int sig)
{
printf("Signal Quit! The number is: %d\n", sig);
} static void MyFileReadCompleteProcedure(int sig, siginfo_t *si, void *ucontext)
{
printf("The file length is: %zu, and the content is: %s\n", strlen(memBuffer), memBuffer);
int status = close(sFileDesc);
if(status == 0)
puts("File closed successfully!");
else
printf("The error code is: %d\n", status); free(memBuffer); // 还原原来的SIGUSR1信号行为
if(sigaction(SIGUSR1, &sOldSigAction, NULL) == -1)
puts("SIGUSR1 signal restore failed!");
} int main(void)
{
struct sigaction sigAction = { .sa_flags = SA_RESTART, .sa_handler = &MySigQuitHandler }; sigemptyset(&sigAction.sa_mask); if (sigaction(SIGQUIT, &sigAction, NULL) == -1)
{
puts("Signal failed!");
return -1;
} sigAction.sa_sigaction = &MyFileReadCompleteProcedure;
if(sigaction(SIGUSR1, &sigAction, &sOldSigAction) == -1)
{
puts("Signal failed!");
return -1;
} const char *filePath = "myfile.txt"; const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
sFileDesc = open(filePath, O_RDONLY, mode);
if(sFileDesc == -1)
{
printf("The file: %s cannot be opened!\n", filePath);
return -1;
} const long fileLength = lseek(sFileDesc, 0, SEEK_END);
lseek(sFileDesc, 0, SEEK_SET); memBuffer = malloc(fileLength + 1);
memBuffer[fileLength] = '\0'; struct aiocb aioBuffer;
aioBuffer.aio_fildes = sFileDesc;
aioBuffer.aio_offset = 0;
aioBuffer.aio_buf = memBuffer;
aioBuffer.aio_nbytes = fileLength;
aioBuffer.aio_reqprio = 0;
aioBuffer.aio_sigevent = (struct sigevent){.sigev_notify = SIGEV_SIGNAL, .sigev_signo = SIGUSR1, .sigev_value.sival_ptr = memBuffer }; aio_read(&aioBuffer); getchar(); return 0;
}

附录 :对 AIO 进行系统优化

proc 文件系统包含了两个虚拟文件,它们可以用来对异步 I/O 的性能进行优化( 这对于大部分应用程序来说都已经足够了):

  • /proc/sys/fs/aio-nr 文件提供了系统范围异步 I/O 请求现在的数目。
  • /proc/sys/fs/aio-max-nr 文件是所允许的并发请求的最大个数。最大个数通常是 64KB

参考资料

Linux 网络编程的5种IO模型:异步IO模型的更多相关文章

  1. Linux 网络编程的5种IO模型:多路复用(select/poll/epoll)

    Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 背景 我们在上一讲 Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO中,对于其中的 阻塞/非阻塞IO 进行了 ...

  2. Linux 网络编程的5种IO模型:信号驱动IO模型

    Linux 网络编程的5种IO模型:信号驱动IO模型 背景 上一讲 Linux 网络编程的5种IO模型:多路复用(select/poll/epoll) 我们讲解了多路复用等方面的知识,以及有关例程. ...

  3. Linux 网络编程的5种IO模型:阻塞IO与非阻塞IO

    背景 整理之前学习socket编程的时候复习到了多路复用,搜索了有关资料,了解到多路复用也有局限性,本着打破砂锅问到底的精神,最终找到了关于IO模型的知识点. 在<Unix网络编程>一书中 ...

  4. Linux网络编程(六)

    网络编程中,使用多路IO复用的典型场合: 1.当客户处理多个描述字时(交互式输入以及网络接口),必须使用IO复用. 2.一个客户同时处理多个套接口. 3.一个tcp服务程序既要处理监听套接口,又要处理 ...

  5. Linux网络编程一步一步学【转】

    转自:http://blog.chinaunix.net/uid-10747583-id-297982.html Linux网络编程一步一步学+基础  原文地址:http://blogold.chin ...

  6. Linux 网络编程(IO模型)

    针对linux 操作系统的5类IO模型,阻塞式.非阻塞式.多路复用.信号驱动和异步IO进行整理,参考<linux网络编程>及相关网络资料. 阻塞模式 在socket编程(如下图)中调用如下 ...

  7. Java网络编程和NIO详解3:IO模型与Java网络编程模型

    Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...

  8. Python学习笔记整理总结【网络编程】【线程/进程/协程/IO多路模型/select/poll/epoll/selector】

    一.socket(单链接) 1.socket:应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socke ...

  9. linux网络编程模型

    1.编程模型 Linux网络编程模型是基于socket的编程模型

随机推荐

  1. 过万 star 高星项目的秘密——GitHub 热点速览 Vol.39

    作者:HelloGitHub-小鱼干 虽然国外十一并不过国庆,但是本周的 GitHub 也稍显疲软,GitHub 周榜的获 star 超过 1k 的项目寥寥无几,本周新开源的项目更是屈指可数.用 C ...

  2. Python-如何拆分含有多种分隔符的字符串?

    案例: 把某个字符串依据分隔符拆分,该字符包含不同的多种分隔符,如下 s = '12;;7.osjd;.jshdjdknx+' 其中 ; . + 是分隔符 有哪些解决方案? 方法1:通过str.spl ...

  3. 093 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 02 static关键字 03 static关键字(下)

    093 01 Android 零基础入门 02 Java面向对象 02 Java封装 01 封装的实现 03 # 088 01 Android 零基础入门 02 Java面向对象 02 Java封装 ...

  4. Java知识系统回顾整理01基础01第一个程序06Eclipse使用技巧

    一.批量修改 ALT+SHIFT+R 二.快速输入主方法 1. 敲入main 2. alt+/ 三.快速输入System.out.println 1. 敲入syso 2. alt+/ 四.快速输入fo ...

  5. 浅谈Prufer序列

    \(\text{Prufer}\)序列,是树与序列的一种双射. 构建过程: 每次找到一个编号最小的叶子节点\(Leaf\),将它删掉,并将它所连接的点的度数\(-1\),且加入\(\text{Pruf ...

  6. 【原创】经验分享:一个小小emoji尽然牵扯出来这么多东西?

    前言 之前也分享过很多工作中踩坑的经验: 一个线上问题的思考:Eureka注册中心集群如何实现客户端请求负载及故障转移? [原创]经验分享:一个Content-Length引发的血案(almost.. ...

  7. 初学者的Android移植:在Debian上建立一个稳定的构建环境

    介绍 通过在chrooted环境中设置开发环境,避免依赖冲突和沙箱您的Android开发从您的Debian GNU/Linux系统.这是为通配符类别准备的,因为从源代码构建Android似乎没有在其他 ...

  8. 极简 Node.js 入门 - 4.5 双工流

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  9. Java bean 链式获取成员变量无需判空的工具设计

    Java bean 链式获取成员变量无需判空的工具设计 本篇文章已发布至公众号 Hollis 对于Java程序员来说,null是令人头痛的东西.时常会受到空指针异常(NPE)的骚扰.连Java的发明者 ...

  10. 推荐算法之: DeepFM及使用DeepCTR测试

    算法介绍 左边deep network,右边FM,所以叫deepFM 包含两个部分: Part1: FM(Factorization machines),因子分解机部分 在传统的一阶线性回归之上,加了 ...