网上有很多关于pos机跳转商户的原理,从服务器开发底层聊一聊协程的实现原理的知识,也有很多人为大家解答关于pos机跳转商户的原理的问题,今天pos机之家(www.poszjia.com)为大家整理了关于这方面的知识,让我们一起来看下吧!
本文目录一览:
pos机跳转商户的原理
一、先介绍一组概念进程
进程是系统进行资源分配和调度的基本单位进程是一个实体,每一个进程都有自己地址空间线程
线程是程序执行流的最小单元一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源子例程
子例程是某个主程序的一部分代码子例程又被称为子程序、过程、方法、函数等。在主程序中可以调用子例程来执行协程
协程与子例程一样,协程(coroutine)也是一种程序组件协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断二、同步与异步在介绍协程之前,必须先要详细的介绍一下同步和异步,因为这是引出协程的重要原因同步
以客户端为例,客户端向服务端发出请求之后,必须阻塞等待服务端给自己返回数据,属于一问一答的模式下面是服务端同步的伪代码,核心是handle()函数:服务端在接收到消息之后,必须阻塞等待处理完消息再去发送数据,这个过程必须是一次性完成的,并且是在一个线程中完成的while (1) { int nready = epoll_wait(epfd, events, EVENT_SIZE, -1); for (i = 0;i < nready;i ++) { int sockfd = events[i].data.fd; if (sockfd == listenfd) { int connfd = accept(listenfd, xxx, xxxx); setnonblock(connfd); ev.events = EPOLLIN | EPOLLET; ev.data.fd = connfd; epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev); } // 进行消息的处理 else { handle(sockfd); } }} // 可以看到是数据的收发在一个过程中int handle(int sockfd) { // 先接收 recv(sockfd, rbuffer, length, 0); // 消息处理 parser_proto(rbuffer, length); // 再发送 send(sockfd, sbuffer, length, 0);}
异步
异步与同步不同,在异步的情况下,客户端向服务端发出请求之后,客户端可以不等待服务端给自己返回数据,接着继续执行。当然接收回复这一过程在其他线程中执行,当回复达到之后再通知客户端去接收(一般由回调函数实现)下面是服务端同步的伪代码,核心是handle()函数:可以看到服务端在处理数据时,把任务交给了其他的线程去执行,而不必阻塞等待while (1) { int nready = epoll_wait(epfd, events, EVENT_SIZE, -1); for (i = 0;i < nready;i ++) { int sockfd = events[i].data.fd; if (sockfd == listenfd) { int connfd = accept(listenfd, xxx, xxxx); setnonblock(connfd); ev.events = EPOLLIN | EPOLLET; ev.data.fd = connfd; epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev); } // 进行消息的处理 else { handle(sockfd); } }} // 此函数是在线程池创建的线程中运行int thread_cb(int sockfd) { recv(sockfd, rbuffer, length, 0); parser_proto(rbuffer, length); send(sockfd, sbuffer, length, 0);} // 此函数是在主线程中运行的int handle(int sockfd) { // 把读写任务放到另外的线程中去执行 push_thread(sockfd, thread_cb); //将 sockfd 放到其他线程中运行。}Linuxc/c++后台服务器开发高阶视频学习资料后台私信【架构】获取
备注
"异步IO"和"IO异步"的区别:异步IO:核心点是"IO",例如POSXI提供的AIO接口IO异步:核心点是"操作",例如上面服务端代码的异步处理,就是IO异步操作。我们平常所说的"多线程异步"指的就是这个"同步IO"和"IO同步"与上面也是相同的道理同步与异步的区别
总结起来就是:同步:编程简单,性能差异步:编程复杂,性能高同步编程简单是因为其数据的处理是在同一个过程中进行处理的。但是异步的处理可能在不同的线程中进行处理,例如在异步的情况下客户端发送2次请求,这2个请求可能在服务端的2个线程中进行处理,2个线程共用一个客户端的fd,可能造成数据混乱,最常见的解决方法就是进行加锁四、设计协程的核心通过上面的介绍,我们知道了同步与异步之间的差异,有没有一种方式,有异步性能,同步的代码逻辑。来方便编程人员对 IO 操作的 组件呢? 有,采用一种轻量级的协程来实现。在每次 send 或者 recv 之前进行 切换,再由调度器来处理 epoll_wait 的流程而协程的设计核心目标就是:为了拥有同步的简单编程方式,同时又想要拥有异步的性能五、实现协程的核心:跳转(协程切换)协程想要拥有同步的编程方式和异步的性能,因此我们不能对同步的代码进行修改,而要想办法对异步的代码进行修改,使得其下面我们以https://blog.csdn.net/qq_41453285/article/details/106357786中的HTTP客户端异步实现代码为例下面且听我细细道来如何跳转?往哪里跳转?
在代码中,客户端调用async_http_commit()函数向服务端发送一个HTTP请求,为了实现异步的方式,我们在调用send()发送数据之后,把这个fd添加到epoll中进行管理(这是异步的方式),而不是在send()后面调用recv()(这是同步的方式)下面是async_http_thread_func()函数的代码,这个函数在while(1)循环中一直调用epoll_wait()监听描述符是否有事件发生,例如上面的async_http_commit()函数调用send()给服务端发送数据之后,服务端给客户端回送响应,那么epoll_wait()就会被触发,从而调用recv()接收数据那么如何用异步代码实现同步的效果呢?那就需要程序进行跳转在关于跳转可以详细见下面的解释(图片点开来看):(图左侧)async_http_commit()函数接收完数据之后为了实现与同步一样的效果,我们跳转到右侧的async_http_thread_func()函数(图右侧)跳转到async_http_thread_func()函数之后就可以调用epoll_wait()来检测数据了(因为上面的async_http_commit()函数调用了send()发送数据了),如果epoll_wait()检测到有数据来之后就可以在下面接收数据,当接收完数据之后再跳转回到左侧的async_http_commit()函数中跳转的方法有哪些呢?
程序跳转的方法有3种:①setjmp()、longjmp()函数:不推荐使用,编码复杂②ucontext③汇编实现这3种方法,其中最常用的就是汇编,并且Go等语言的协程也是用汇编实现的yield、resume
yield:让出当前的CPU,跳转到指定的位置进行执行,这个过程叫做yieldresume:上面yield让出CPU之后跳转到指定的位置执行,当指定的位置执行完成之后,回到当初的位置这个过程叫做resume例如,对于上面来说,就是六、如何通过汇编实现协程的切换呢?下载代码https://github.com/wangbojing/NtyCo开始解析,后面要用到对于上面的跳转来说,其实就是协程之间的切换,如何实现这种切换呢?在介绍协程切换之前,来说一下线程的切换,如下图所示:CPU有很多的寄存器,这些寄存器保存了当先在处理器上运行线程的信息例如当前CPU运行的是A线程,那么寄存器保存的都是线程A的信息当此时需要把线程A切出CPU,来让B线程在CPU上运行,那么就需要把当前寄存器的内容都保存在线程A的栈中,然后把B运行所需要的内容加载到寄存器中,从而使得B线程在CPU上运行起来协程如何切换?与线程是相同的道理,还是以上面的图片为例:sync_http_commit()函数调用send()之后,在跳转到pos位置之前把当前寄存器的内容保存到一个结构体中(例如命名为store结构体)跳转到async_http_thread_func()函数指定的pos位置之后,把pos位置的内容信息加载到寄存器中开始执行如何实现这些内容的保存与切换
首先到代码的Nty_coroutine.c文件中找到_switch()函数,这个函数是实现切换的核心:参数1:新的上下文参数2:当前的上下文_switch()函数就是把当前寄存器的内容保存在参数2中,然后加载参数1所指定的内容加载到寄存器中例如,如果是上面的sync_http_commit()函数,其在"jump-pos"的时候就调用_switch()进行切换,然后async_http_thread_func()函数执行完需要切换回sync_http_commit()函数的时候,会在"jump->back"的地方调用这个函数,只是参数不同而已nty_cpu_ctx结构体就是一些寄存器的指针_switch()函数可以用下面的图来表示那么如何实现这些寄存器值的保存与交换呢?以下面为例,自己看图片吧,稍微有点复杂_switch()函数的参数1名为rdi、参数2名为rsi在左侧,前一半部分:汇编指令把寄存器的内容保存到rsi中,留下次跳转回来使用;后一半部分:把rdi的内容加载到寄存器中开始使用0、8、16那些是偏移,因为一个指针就是8字节,所以rsp对应的是esp、rbp对应的是ebp......依次类推X86-64有16个64位寄存器,分别是:%rax:作为函数返回值使用%rsp:栈指针寄存器,指向栈顶%rdi,%rsi,%rdx,%rcx,%r8,%r9:用作函数参数,依次对应第1参数,第2参数......依次类推(例如在_switch()函数中,参数1叫做rdi、参数2叫做rsi......)%rbx,%rbp,%r12,%r13,,:用作数据存储,遵循被调用者使用规则,简单说就是随便 用,调用子函数之前要备份它,以防他被修改 %r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值以上就是关于pos机跳转商户的原理,从服务器开发底层聊一聊协程的实现原理的知识,后面我们会继续为大家整理关于pos机跳转商户的原理的知识,希望能够帮助到大家!
