日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

从简单到高并发服务器(一)

發布時間:2025/7/14 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 从简单到高并发服务器(一) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一個單線程的回聲服務器 (Echo Server)

我們從一個簡單的服務器開始說起。
它可以接受一個客戶的連接,接收消息,然后把這個消息發送回去,關閉連接——完工。我們用 Linux 和 iOS / OSX 上都通用的 BSD Socket 來編寫這個服務器的代碼。主體部分大概是這樣的:(C++ 語法)

#include <stdio.h> #include <stdlib.h>#include <netdb.h> #include <netinet/in.h>#include <unistd.h> #include <string.h>void fuck_you(void) {exit(EXIT_FAILURE); };int main(int argc, char *argv[]) {int server_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);struct sockaddr_in server_addr;struct sockaddr_in client_addr;int portno = 5432;bzero((char *) &server_addr, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(portno);// 注1: 將 socket 綁定到具體的 IP 地址int bind_succ = bind( server_sock, (struct sockaddr *) &server_addr, sizeof(server_addr));if (bind_succ < 0) fuck_you();// 注2: 開始監聽int listen_succ = listen(server_sock, 1024);if (listen_succ < 0) fuck_you();// 注3: 這是一個永遠不會停止的循環while(true){int client_addr_size;printf("before accept.\n");// 注4: 調用 accept 等待傳入連接的時候會阻塞 (the thread is blocked here!)int client_sock = accept( server_sock // 從這個服務器 socket 上面接受連接, (struct sockaddr *) &client_addr // 這里獲得發起連接的客戶端的地址, (socklen_t*)& client_addr_size);printf("handle client sock: %d", client_sock);// 準備用于接收消息的緩沖區const int buffer_size = 1024;char * recv_buffer = (char*)malloc(buffer_size);printf("before recv, buffer ready, address: %p\n", recv_buffer);// 注5: 調用 recv 來接收客戶端發送的信息,這個過程會阻塞int msg_size = recv( client_sock // 注意,是用 client_sock 來接收, recv_buffer // 將接收到的內容放到緩沖區, buffer_size // 告訴系統我們設置的緩沖區有多大, 0);fwrite(recv_buffer, sizeof(char), msg_size, stdout);// 注6: 把接收到的信息原樣發送出去int byte_sent = send( client_sock, recv_buffer, msg_size, 0);free(recv_buffer);// 注7: 主動斷開客戶端的連接close(client_sock);} }

這段代碼當然是很粗糙(誤:粗口),可能會有內存泄漏,如果客戶發送的消息過長會接收不完全……各種各樣的問題,但是它基本上呈現出了一個服務器程序到底是怎樣運作的。

以下是代碼中提到的,要實現一個TCP服務器幾個重要的工作:

  • 綁定監聽地址,并開始監聽(注1和注2)

  • 等待客戶端連接(注4)

  • 接收客戶端發送的數據(注5)

  • 發送回復(注6)

  • 實際上以上這四點也是任何服務器都要完成的事情。
    如果是使用 Udp 的話,則不需要等待客戶端連接這個步驟,這是因為 Udp 是面向數據包而不是面向連接的傳輸協議;而使用 Tcp 則需要等待客戶端連接,實際上還會涉及到“三路握手” (3-way handshake) 這個建立 Tcp 連接的過程。
    但是這個握手過程,由于是屬于 TCP 協議的標準部分,因此實際上是由操作系統來幫助我們完成的(所有支持 TCP/IP 協議棧的操作系統都會替程序員完成這個過程)。我們只需要通過調用 accept 這個API,就相當于告訴系統“現在開始幫我處理握手這個事情,有人找你握手了再來告訴我吧”。

    線程與阻塞

    握手過程調用 accept 會阻塞整個程序的執行,阻塞是什么意思呢?
    如果我們寫代碼的時候,寫一個死循環,就如代碼中 注3 那樣:

    while(true) {printf("I just can't stop speaking!\n"); }

    即使不運行這個程序,你也應該可以預料到,在屏幕上會不斷打出一行行的內容。這說明,程序沒有被阻塞的情況下,就會一直執行下去。嚴格來說,printf 也會阻塞,只不過阻塞的時間非常短,并且可以自動解除阻塞狀態,具體的解釋以后再說。

    而調用 accept 就不可以自動解除阻塞狀態了——如果你成功運行剛才的代碼,你會看到,屏幕輸出了 before accept. 之后,并沒有馬上接著輸出 handle client sock: ——程序一直停留在 accept 被調用的地方,也可以認為是 accept 一直沒有返回結果。

    阻塞的本質是,操作系統把執行你的代碼的線程暫停了,而線程則是操作系統安排CPU調度的基本單位,這通常意味著操作系統把 CPU 拿去干其他事情了,而你的程序不能使用 CPU進行計算,只能暫停。直到有一個客戶成功連接到你的服務器為止。

    為了模擬這個事情,我們可以使用 python + gevent 來模擬很多(300)個客戶端并發地不停發起TCP連接:

    from __future__ import print_function from gevent.socket import socket as gsocket import gevent import socketdef do_connect(addr, index):if 0: client_sock = socket.socket()while True:client_sock = gsocket(socket.AF_INET,socket.SOCK_STREAM,socket.IPPROTO_TCP)print(addr)client_sock.connect(addr)print('client {0} connected.'.format(index))gevent.sleep(10)client_sock.send('Hello World')data = client_sock.recv(1024)print('recv data: {0}'.format(data))if __name__ == '__main__':server_addr = ('127.0.0.1', 5432)greenlets = list()for i in xrange(300):g = gevent.spawn(do_connect, server_addr, i)greenlets.append(g)gevent.joinall(greenlets)

    然后,如無意外,你就可以看到程序繼續得到執行,非常有規律地重復,并且總是按順序地一個連接一個連接地處理。如果注意到客戶端的輸出話你可能會看到,在后面的發起的連接都超時了,會看到很多 Traceback。

    這肯定不是我們日常訪問網站所能得到的體驗:很快就可以連接上并且看到網頁的內容(當然,在天朝,例外有很多)。所以這不是理想的高并發服務器。

    為什么比較早發起連接的客戶端不會超時,而后面發起的會超時呢?原因就是服務器端在阻塞等待 IO 的時候,單線程無法響應其他請求。

    為了驗證這個結論,你可以把客戶端代碼中,發送數據前 gevent.sleep 的時間加長,例如改為20 秒,你會發現更多的連接會超時—— 因為服務器花費在等待客戶端發送數據的時間更多了,那么在相同超時時間前服務窗口內能夠 accept 的連接數量就更少了。

    (未完待續)

    總結

    以上是生活随笔為你收集整理的从简单到高并发服务器(一)的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。