一步一步從原理跟我學郵件收取及發送 4.不一樣平臺下的socket

    既然是面向程序員的文章那固然不能只說說原理,必定要有實際動手的操做.
    其實做爲我我的的經從來說,對於網絡編程,這是最重要的一章!php


    做爲一位混跡業內近20年的快退休的程序員,我學習過不少的開發語言和程序類型,好比:pascal,c,c++,delphi,vc,java,kjava,symbian .... objectc,ios ..直到最近還由於工做的關係還得研究前端用的 js (雖然早就學過).列這麼多我不是炫耀,也是不要反映老程序員的悲哀歷史,我是想經過這些學習到應用的經歷告訴你們,其實寫什麼程序都並不難,難的都是入門. 好比 js/css 我就屢次過其門而不入,由於我長期從過後端和 pc 端的工做,web 前端以前並無真正的應用過,一直就沒真正入門.要真正的入門,每種語言或工做環境的也是不一樣,C 語言是易學難精,能作出一個 hello world 輸出就能夠算入門了,若是是 ios 開發,若是不會申請帳號,不會搭建虛擬機,不會與蘋果審覈人員鬥智鬥勇,光學會幾個 objectc 語法那是沒法獨立完成工做的. 而網絡編程的入門標誌是你能用程序向服務器發送命令了而且能收到迴應.在咱們的"那個年代",delphi 很是流行,有種叫控件的東西,能夠直接完成不少工做,而不用太關心具體實現,因此當年我還沒學會網絡編程的時候就會寫 ftp 文件傳輸程序了 ... 可是這是沒有用的,由於有一天同窗拿着個人軟件向我反映說在學校機房用不了,我百思不得其解,把代碼檢查了一遍又一遍,直到不少年後我才明白真正的緣由(是 port 命令的問題,具體說就太遠了).

    固然了我並非否認控件,咱們如今寫 php,java 通常狀況下也不須要本身去實現 smtp 這樣的過程,我只是說那樣的話你算不得是入了網絡編程的門(好比如今的 golang 默認的 smtp 模塊,你不明白 smtp 原理的話就是用不起來的).

    真正的網絡編程入門應該這樣思考,我用的這個控件或者是 php 模塊是怎樣實現發送郵件的呢? 固然看了前面的幾篇文章你們知道是發送一條條命令來實現的,那麼怎麼在個人語言環境中發送一條命令呢? 解決了這個問題就算是入門了.

    說來慚愧我是工做了近一年後才知道網絡命令的發送最終是要使用到操做系統的 socket 函數的. windows 下,linux 下都是如此,包括如今的手機安卓,ios 也是如此,還有些你們不知道的環境下也是如此. 寫網絡應用程序是必定要學會 socket 編程的, "socket" 就是網絡編程的關鍵字.因此要學會某個平臺下的網絡編程,例如 ios 的,那麼只要在 baidu 上輸入 "ios socket" 就能夠了.不過這裏還要先提醒一下大夥,手機平臺下反而是不推薦直接使用原始的 socket 進行編程,要使用系統二次封裝後的函數,緣由咱們後面再說,不過原理都是同樣的,因此仍是得先學會基礎的 socket 編程.

    說了這麼多廢話快讓咱們開始吧! 說到開始還真犯難,用什麼環境來作示例呢,曾經有本 O'Reilly 的 email 編程書讀者評論好的說它很好,差的說它不好,說它差的人主要就是批評它都是理論根本不能上手.我我的以爲它主要是用 perl 和 java 的已有模塊來作示例,根本就不能從原理層次去進入嘗試. 手機開發環境是要用地次封裝的確定不適合,java 很流行,但也是封裝過的,並且我我的並不喜歡 java,foxmail 和我寫的 eemail 都是 delphi 的,但我要用 delphi 的話估計如今的程序員沒幾個能看得懂的,那就只好走傳統路線用 C 語言了. 但 C 語言的環境並很差處理,象 vc 的話就要加入 lib 等等,這些操做其實也是要脫一層皮的. 想來想去,我決定給出多種語言的一個最基本示例,你們下載後就能夠直接用,而我後面用來說解的則會使用我一個專門用來測試的 C 語言小環境,你們能夠當僞碼來看.

不管是哪一個環境,大概的 socket 流程都是這樣:css

1.初始化 socket 環境(windows 下必須有);
2.將域名轉換爲 ip 地址(不少書裏都不會介紹這個);
3.鏈接上這個 ip 地址;
4.發送一個字符串;
5.接收一個字符串.


在傳統中前三個步驟至關繁瑣,特別是第二步很容易誤導初學者,因此對於大多數環境來講,如今的底層網絡開發環境實際上又封裝過一點,變成了:前端

1.鏈接上域名或者是 ip 地址;
2.發送一個字符串;
3.接收一個字符串.


    咱們把這三個過程用三個僞碼函數表示爲 connect(), send(), recv() 方便咱們討論,對於大多數狀況下它們的參數均可以直接理解爲字符串(string),只有某些狀況下須要理解爲二進制內存緩衝塊(bytes),因此實際上從根本上來講一個平臺只要實現這三個函數就能夠完成全部的網絡通信工做了! 初學者還沒什麼,學過 socket 的同窗必定會很是震驚,socket 函數可有不少不少的! 沒錯,但那些都不過是輔助或者性能更佳的替代品而已,象windows的完成端口,linux 的 epoll 是很難頗有用,但它們完成的功能說到底不過是 send(), recv() 而已,我就封裝過完成端口給一個公司用過. golang 的網絡開發環境就是這樣封裝過的.

    考慮再三,我決定先給出受衆最廣的 java 版本測試代碼. C 語言版本固然最重要,後面再專門討論.

    先給出完整代碼,不過你們先別急着細看.java

package st;

import java.io.*;
import java.net.*;

public class SocketTest1 {
    
    public static BufferedReader br = null;
    public static PrintWriter pw = null;
    public static Socket socket = null;
    public static OutputStream os = null;
    public static InputStream is = null;

    public static void main(String[] args) {

        try{

            System.out.println("start");

            //簡單的測試一下 smtp
            test_smtp();

            br.close();
            is.close();
            pw.close();
            os.close();
            socket.close();

        }catch(Exception e) {
            System.out.println("Error."+e);

        }
        

    }//
    
    //簡單的測試一下 smtp
    public static void test_smtp()
    {
        //鏈接
        Connect("newbt.net", 25);
        
        //收取一行
        String line = RecvLine();
        System.out.println("recv:" + line);
        
        //發送一個命令
        SendLine("EHLO");
        
        //收取一行
        line = RecvLine();
        System.out.println("recv:" + line);
    }
    
    public static void Connect(String host, int port)
    {
        try{
            //socket = new Socket("newbt.net", 25);
            socket = new Socket(host, port);
            
            //--------------------------------------------------
            os = socket.getOutputStream();//字節輸出流
            pw = new PrintWriter(os);//將輸出流包裝成打印流
            
            is = socket.getInputStream();
            br = new BufferedReader(new InputStreamReader(is));
            
        }catch(Exception e) {
            System.out.println("Error."+e);

        }    
    }

    //發送一個命令行
    public static void SendLine(String s)
    {
        //pw.write("EHLO\r\n");
        pw.write(s + "\r\n");
        pw.flush();
    }
    
    //收取一行服務器發來的信息
    public static String RecvLine()
    {
        try{
            String s = br.readLine();
            return s;
        }catch(Exception e) {
            System.out.println("Error."+e);

        }
        
        return null;
    }
    
}//

代碼也不算長,不過我仍然以爲囉嗦,既然是用來完成郵件發送收取工做的,其實只須要關心 test_smtp() 函數的內容就好了,完成前幾篇文章,乃至整個郵件通信過程用這其中的三個函數就好了!linux

真的!我一點都不誇張,不過真實的環境中還要設置網絡超時等,不過這些都應該封裝到 connect 函數或者其餘地方,通信邏輯上就是這幾個函數就好了,就這幾個函數就能夠實現前面用 telnet 登陸到發送郵件的整個過程,即如下代碼:ios

    //簡單的測試一下 smtp
    public static void test_smtp()
    {
        //鏈接
        Connect("newbt.net", 25);
        
        //收取一行
        String line = RecvLine();
        System.out.println("recv:" + line);
        
        //發送一個命令
        SendLine("EHLO");
        
        //收取一行
        line = RecvLine();
        System.out.println("recv:" + line);
    }

一個 SendLine 就至關於您在 telnet 中的輸入一行命令而後按下回車鍵這個動做,而 RecvLine 函數作的工做就是把命令的結果從服務器下取下來,取下來的結果再輸出就是至關於 telnet 中的命令結果顯示了.c++

須要說明的是 RecvLine 只收取一行,而 telnet 是有多少收多少,因此這裏的示例實際上是沒有取完 "EHLO" 命令的所有結果的,要調用 RecvLine 取多少次那就要分析 smtp 協議了,這裏能夠先說一下,好讓你們向下測試:程序員

要接收到服務迴應的行中有 "250" 可是沒有 "250-" 爲止,說來拗口,不過這是很是精確的表述,心急要先測試的同窗們能夠仔細思考.不急的同窗跟咱們向前走吧.golang

下一篇要說 C 語言的實現,會複雜不少.web

--------------------------------------------------

注1:這裏雖然是 java 代碼,可是不能直接用在如今的安卓環境中,由於如今的安卓環境要求要放線程中,這是有緣由的,咱們後面再解釋.

相關文章
相關標籤/搜索