偶爾學習一種新的編程語言是件好事,但不能僅止步於 “Hello World"。
時常學習一種新的編程語言對你有好處,即使這種語言不會流行起來或者已經過時。使用新的語言處理舊的問題會促使你重新思考你當前處理問題的視角、方法和習慣。
我喜歡嘗試新鮮的事物,特別是編程語言。但是,當你用新的語言實現了“你好,世界!”或者斐波那契序列之後,通常你會感到基本上再沒什麼可做的,沒有任何新奇的地方。你可以試著實現埃拉托斯特尼篩法,借此探索一點數據結構和算法性能。但是我想要一些實際的東西,可能以後還會被復用。因此,不久前我自己創造了一個問題,這個問題可以幫助我僅用幾百行代碼去熟悉一種語言。
問題涉及了一種語言幾個非常重要的方面:字符串,文件和網絡輸入輸出,當然還有並行。稱這個問題為 TCP/IP 代理(或者你可以稱它 網絡調試器)。問題的想法:你有一個TCP/IP偵聽器(單線程或多線程)在給定的端口接受連接,當它接受到連接(調用者)時,它必須連接另外一台主機(遠程主機),並且在調用者和遠程主機間全雙工傳輸數據。另外,代理還可以使用各種格式的日志記錄通信內容,用來幫助分析數據。
我不再計算需要使用這種工具的場合。任何時候,只要涉及到網絡編程,這種工具必不可少。我已經用不同的語言實現了這種工具很多次:C,C++,Perl,PHP。最近的兩個實現用的是Python和Erlang。這種工具代表了我正在尋找的那種實際問題。
我可以指定更具體的需求。應用必須能夠同時服務多個連接。對於每個連接,它需要以三種方式記錄數據:一個以十六進制格式導出的表示雙向順序數據的導出日志文件;兩個二進制日志文件分別記錄輸入和輸出數據流。
這篇文章我將用Go語言來實現這個程序。Go語言的作者聲稱Go語言血脈中就支持並行和多線程。我打算讓它們名符其實。
如果借助boost庫使用C++開發這個程序的話,我很可能選擇主偵聽器線程加上用於服務每個連接的線程。由此,一個單獨的連接將完全占用一個線程。
下面是用Go語言實現的程序中用來服務每個連接的線程:
1. 一個雙向十六進制導出器線程
2. 兩個線程以二進制格式記錄輸入和輸出數據流
3. 兩個線程用來在本地和遠端主機間雙向傳輸數據
總共5個線程。
再強調一遍,五個線程用來服務每一個單獨的連接。我實現所有這些線程不是因為多線程本身的緣故,而是因為Go語言鼓勵多線程,C++恰恰相反。Go語言本身就支持多線程,使用起來非常簡單。我用Go語言實現TCP/IP代理時沒有使用互斥信號量和條件變量。使用Go語言的管道(channels)可以優雅地實現線程同步。
好吧,下面是源代碼,帶有解釋。如果你不熟悉Go編程語言,注釋應該會有幫助。我的目的不只關注程序功能,同時還關注Go語言本身。