網(wǎng)上有很多關(guān)于pos機(jī)跳轉(zhuǎn)商戶的原理,從服務(wù)器開發(fā)底層聊一聊協(xié)程的實(shí)現(xiàn)原理的知識,也有很多人為大家解答關(guān)于pos機(jī)跳轉(zhuǎn)商戶的原理的問題,今天pos機(jī)之家(www.dprolou.com)為大家整理了關(guān)于這方面的知識,讓我們一起來看下吧!
本文目錄一覽:
pos機(jī)跳轉(zhuǎn)商戶的原理
一、先介紹一組概念進(jìn)程
進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位進(jìn)程是一個實(shí)體,每一個進(jìn)程都有自己地址空間線程
線程是程序執(zhí)行流的最小單元一個標(biāo)準(zhǔn)的線程由線程ID,當(dāng)前指令指針(PC),寄存器集合和堆棧組成線程是進(jìn)程中的一個實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)兒在運(yùn)行中必不可少的資源,但它可與同屬一個進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源子例程
子例程是某個主程序的一部分代碼子例程又被稱為子程序、過程、方法、函數(shù)等。在主程序中可以調(diào)用子例程來執(zhí)行協(xié)程
協(xié)程與子例程一樣,協(xié)程(coroutine)也是一種程序組件協(xié)程看上去也是子程序,但執(zhí)行過程中,在子程序內(nèi)部可中斷,然后轉(zhuǎn)而執(zhí)行別的子程序,在適當(dāng)?shù)臅r候再返回來接著執(zhí)行注意,在一個子程序中中斷,去執(zhí)行其他子程序,不是函數(shù)調(diào)用,有點(diǎn)類似CPU的中斷二、同步與異步在介紹協(xié)程之前,必須先要詳細(xì)的介紹一下同步和異步,因?yàn)檫@是引出協(xié)程的重要原因同步
以客戶端為例,客戶端向服務(wù)端發(fā)出請求之后,必須阻塞等待服務(wù)端給自己返回數(shù)據(jù),屬于一問一答的模式下面是服務(wù)端同步的偽代碼,核心是handle()函數(shù):服務(wù)端在接收到消息之后,必須阻塞等待處理完消息再去發(fā)送數(shù)據(jù),這個過程必須是一次性完成的,并且是在一個線程中完成的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); } // 進(jìn)行消息的處理 else { handle(sockfd); } }} // 可以看到是數(shù)據(jù)的收發(fā)在一個過程中int handle(int sockfd) { // 先接收 recv(sockfd, rbuffer, length, 0); // 消息處理 parser_proto(rbuffer, length); // 再發(fā)送 send(sockfd, sbuffer, length, 0);}
異步
異步與同步不同,在異步的情況下,客戶端向服務(wù)端發(fā)出請求之后,客戶端可以不等待服務(wù)端給自己返回數(shù)據(jù),接著繼續(xù)執(zhí)行。當(dāng)然接收回復(fù)這一過程在其他線程中執(zhí)行,當(dāng)回復(fù)達(dá)到之后再通知客戶端去接收(一般由回調(diào)函數(shù)實(shí)現(xiàn))下面是服務(wù)端同步的偽代碼,核心是handle()函數(shù):可以看到服務(wù)端在處理數(shù)據(jù)時,把任務(wù)交給了其他的線程去執(zhí)行,而不必阻塞等待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); } // 進(jìn)行消息的處理 else { handle(sockfd); } }} // 此函數(shù)是在線程池創(chuàng)建的線程中運(yùn)行int thread_cb(int sockfd) { recv(sockfd, rbuffer, length, 0); parser_proto(rbuffer, length); send(sockfd, sbuffer, length, 0);} // 此函數(shù)是在主線程中運(yùn)行的int handle(int sockfd) { // 把讀寫任務(wù)放到另外的線程中去執(zhí)行 push_thread(sockfd, thread_cb); //將 sockfd 放到其他線程中運(yùn)行。}Linuxc/c++后臺服務(wù)器開發(fā)高階視頻學(xué)習(xí)資料后臺私信【架構(gòu)】獲取
備注
"異步IO"和"IO異步"的區(qū)別:異步IO:核心點(diǎn)是"IO",例如POSXI提供的AIO接口IO異步:核心點(diǎn)是"操作",例如上面服務(wù)端代碼的異步處理,就是IO異步操作。我們平常所說的"多線程異步"指的就是這個"同步IO"和"IO同步"與上面也是相同的道理同步與異步的區(qū)別
總結(jié)起來就是:同步:編程簡單,性能差異步:編程復(fù)雜,性能高同步編程簡單是因?yàn)槠鋽?shù)據(jù)的處理是在同一個過程中進(jìn)行處理的。但是異步的處理可能在不同的線程中進(jìn)行處理,例如在異步的情況下客戶端發(fā)送2次請求,這2個請求可能在服務(wù)端的2個線程中進(jìn)行處理,2個線程共用一個客戶端的fd,可能造成數(shù)據(jù)混亂,最常見的解決方法就是進(jìn)行加鎖四、設(shè)計協(xié)程的核心通過上面的介紹,我們知道了同步與異步之間的差異,有沒有一種方式,有異步性能,同步的代碼邏輯。來方便編程人員對 IO 操作的 組件呢? 有,采用一種輕量級的協(xié)程來實(shí)現(xiàn)。在每次 send 或者 recv 之前進(jìn)行 切換,再由調(diào)度器來處理 epoll_wait 的流程而協(xié)程的設(shè)計核心目標(biāo)就是:為了擁有同步的簡單編程方式,同時又想要擁有異步的性能五、實(shí)現(xiàn)協(xié)程的核心:跳轉(zhuǎn)(協(xié)程切換)協(xié)程想要擁有同步的編程方式和異步的性能,因此我們不能對同步的代碼進(jìn)行修改,而要想辦法對異步的代碼進(jìn)行修改,使得其下面我們以https://blog.csdn.net/qq_41453285/article/details/106357786中的HTTP客戶端異步實(shí)現(xiàn)代碼為例下面且聽我細(xì)細(xì)道來如何跳轉(zhuǎn)?往哪里跳轉(zhuǎn)?
在代碼中,客戶端調(diào)用async_http_commit()函數(shù)向服務(wù)端發(fā)送一個HTTP請求,為了實(shí)現(xiàn)異步的方式,我們在調(diào)用send()發(fā)送數(shù)據(jù)之后,把這個fd添加到epoll中進(jìn)行管理(這是異步的方式),而不是在send()后面調(diào)用recv()(這是同步的方式)下面是async_http_thread_func()函數(shù)的代碼,這個函數(shù)在while(1)循環(huán)中一直調(diào)用epoll_wait()監(jiān)聽描述符是否有事件發(fā)生,例如上面的async_http_commit()函數(shù)調(diào)用send()給服務(wù)端發(fā)送數(shù)據(jù)之后,服務(wù)端給客戶端回送響應(yīng),那么epoll_wait()就會被觸發(fā),從而調(diào)用recv()接收數(shù)據(jù)那么如何用異步代碼實(shí)現(xiàn)同步的效果呢?那就需要程序進(jìn)行跳轉(zhuǎn)在關(guān)于跳轉(zhuǎn)可以詳細(xì)見下面的解釋(圖片點(diǎn)開來看):(圖左側(cè))async_http_commit()函數(shù)接收完數(shù)據(jù)之后為了實(shí)現(xiàn)與同步一樣的效果,我們跳轉(zhuǎn)到右側(cè)的async_http_thread_func()函數(shù)(圖右側(cè))跳轉(zhuǎn)到async_http_thread_func()函數(shù)之后就可以調(diào)用epoll_wait()來檢測數(shù)據(jù)了(因?yàn)樯厦娴腶sync_http_commit()函數(shù)調(diào)用了send()發(fā)送數(shù)據(jù)了),如果epoll_wait()檢測到有數(shù)據(jù)來之后就可以在下面接收數(shù)據(jù),當(dāng)接收完數(shù)據(jù)之后再跳轉(zhuǎn)回到左側(cè)的async_http_commit()函數(shù)中跳轉(zhuǎn)的方法有哪些呢?
程序跳轉(zhuǎn)的方法有3種:①setjmp()、longjmp()函數(shù):不推薦使用,編碼復(fù)雜②ucontext③匯編實(shí)現(xiàn)這3種方法,其中最常用的就是匯編,并且Go等語言的協(xié)程也是用匯編實(shí)現(xiàn)的yield、resume
yield:讓出當(dāng)前的CPU,跳轉(zhuǎn)到指定的位置進(jìn)行執(zhí)行,這個過程叫做yieldresume:上面yield讓出CPU之后跳轉(zhuǎn)到指定的位置執(zhí)行,當(dāng)指定的位置執(zhí)行完成之后,回到當(dāng)初的位置這個過程叫做resume例如,對于上面來說,就是六、如何通過匯編實(shí)現(xiàn)協(xié)程的切換呢?下載代碼https://github.com/wangbojing/NtyCo開始解析,后面要用到對于上面的跳轉(zhuǎn)來說,其實(shí)就是協(xié)程之間的切換,如何實(shí)現(xiàn)這種切換呢?在介紹協(xié)程切換之前,來說一下線程的切換,如下圖所示:CPU有很多的寄存器,這些寄存器保存了當(dāng)先在處理器上運(yùn)行線程的信息例如當(dāng)前CPU運(yùn)行的是A線程,那么寄存器保存的都是線程A的信息當(dāng)此時需要把線程A切出CPU,來讓B線程在CPU上運(yùn)行,那么就需要把當(dāng)前寄存器的內(nèi)容都保存在線程A的棧中,然后把B運(yùn)行所需要的內(nèi)容加載到寄存器中,從而使得B線程在CPU上運(yùn)行起來協(xié)程如何切換?與線程是相同的道理,還是以上面的圖片為例:sync_http_commit()函數(shù)調(diào)用send()之后,在跳轉(zhuǎn)到pos位置之前把當(dāng)前寄存器的內(nèi)容保存到一個結(jié)構(gòu)體中(例如命名為store結(jié)構(gòu)體)跳轉(zhuǎn)到async_http_thread_func()函數(shù)指定的pos位置之后,把pos位置的內(nèi)容信息加載到寄存器中開始執(zhí)行如何實(shí)現(xiàn)這些內(nèi)容的保存與切換
首先到代碼的Nty_coroutine.c文件中找到_switch()函數(shù),這個函數(shù)是實(shí)現(xiàn)切換的核心:參數(shù)1:新的上下文參數(shù)2:當(dāng)前的上下文_switch()函數(shù)就是把當(dāng)前寄存器的內(nèi)容保存在參數(shù)2中,然后加載參數(shù)1所指定的內(nèi)容加載到寄存器中例如,如果是上面的sync_http_commit()函數(shù),其在"jump-pos"的時候就調(diào)用_switch()進(jìn)行切換,然后async_http_thread_func()函數(shù)執(zhí)行完需要切換回sync_http_commit()函數(shù)的時候,會在"jump->back"的地方調(diào)用這個函數(shù),只是參數(shù)不同而已nty_cpu_ctx結(jié)構(gòu)體就是一些寄存器的指針_switch()函數(shù)可以用下面的圖來表示那么如何實(shí)現(xiàn)這些寄存器值的保存與交換呢?以下面為例,自己看圖片吧,稍微有點(diǎn)復(fù)雜_switch()函數(shù)的參數(shù)1名為rdi、參數(shù)2名為rsi在左側(cè),前一半部分:匯編指令把寄存器的內(nèi)容保存到rsi中,留下次跳轉(zhuǎn)回來使用;后一半部分:把rdi的內(nèi)容加載到寄存器中開始使用0、8、16那些是偏移,因?yàn)橐粋€指針就是8字節(jié),所以rsp對應(yīng)的是esp、rbp對應(yīng)的是ebp......依次類推X86-64有16個64位寄存器,分別是:%rax:作為函數(shù)返回值使用%rsp:棧指針寄存器,指向棧頂%rdi,%rsi,%rdx,%rcx,%r8,%r9:用作函數(shù)參數(shù),依次對應(yīng)第1參數(shù),第2參數(shù)......依次類推(例如在_switch()函數(shù)中,參數(shù)1叫做rdi、參數(shù)2叫做rsi......)%rbx,%rbp,%r12,%r13,,:用作數(shù)據(jù)存儲,遵循被調(diào)用者使用規(guī)則,簡單說就是隨便 用,調(diào)用子函數(shù)之前要備份它,以防他被修改 %r10,%r11 用作數(shù)據(jù)存儲,遵循調(diào)用者使用規(guī)則,簡單說就是使用之前要先保存原值以上就是關(guān)于pos機(jī)跳轉(zhuǎn)商戶的原理,從服務(wù)器開發(fā)底層聊一聊協(xié)程的實(shí)現(xiàn)原理的知識,后面我們會繼續(xù)為大家整理關(guān)于pos機(jī)跳轉(zhuǎn)商戶的原理的知識,希望能夠幫助到大家!
