既然是面向程序员的文章那固然不能只说说原理,必定要有实际动手的操做.
其实做为我我的的经从来说,对于网络编程,这是最重要的一章!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 代码,可是不能直接用在如今的安卓环境中,由于如今的安卓环境要求要放线程中,这是有缘由的,咱们后面再解释.