一步一步从原理跟我学邮件收取及发送 4.不同平台下的socket
既然是面向程序员的文章那当然不能只说说原理,一定要有实际动手的操作.
其实作为我个人的经历来说,对于网络编程,这是最重要的一章!
作为一位混迹业内近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 流程都是这样:
.初始化 socket 环境(windows 下必须有);
.将域名转换为 ip 地址(很多书里都不会介绍这个);
.连接上这个 ip 地址;
.发送一个字符串;
.接收一个字符串.
在传统中前三个步骤相当繁琐,特别是第二步很容易误导初学者,所以对于大多数环境来说,现在的底层网络开发环境实际上又封装过一点,变成了:
.连接上域名或者是 ip 地址;
.发送一个字符串;
.接收一个字符串.
我们把这三个过程用三个伪码函数表示为 connect(), send(), recv() 方便我们讨论,对于大多数情况下它们的参数都可以直接理解为字符串(string),只有某些情况下需要理解为二进制内存缓冲块(bytes),所以实际上从根本上来说一个平台只要实现这三个函数就可以完成所有的网络通讯工作了! 初学者还没什么,学过 socket 的同学一定会非常震惊,socket 函数可有很多很多的! 没错,但那些都不过是辅助或者性能更佳的替代品而已,象windows的完成端口,linux 的 epoll 是很难很有用,但它们完成的功能说到底不过是 send(), recv() 而已,我就封装过完成端口给一个公司用过. golang 的网络开发环境就是这样封装过的.
考虑再三,我决定先给出受众最广的 java 版本测试代码. C 语言版本当然最重要,后面再专门讨论.
先给出完整代码,不过大家先别急着细看.
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() 函数的内容就行了,完成前几篇文章,乃至整个邮件通讯过程用这其中的三个函数就行了!
真的!我一点都不夸张,不过真实的环境中还要设置网络超时等,不过这些都应该封装到 connect 函数或者其他地方,通讯逻辑上就是这几个函数就行了,就这几个函数就可以实现前面用 telnet 登录到发送邮件的整个过程,即以下代码:
//简单的测试一下 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 中的命令结果显示了.
需要说明的是 RecvLine 只收取一行,而 telnet 是有多少收多少,所以这里的示例其实是没有取完 "EHLO" 命令的全部结果的,要调用 RecvLine 取多少次那就要分析 smtp 协议了,这里可以先说一下,好让大家向下测试:
要接收到服务回应的行中有 "250" 但是没有 "250-" 为止,说来拗口,不过这是非常精确的表述,心急要先测试的同学们可以仔细思考.不急的同学跟我们向前走吧.
下一篇要说 C 语言的实现,会复杂很多.
--------------------------------------------------
注1:这里虽然是 java 代码,但是不能直接用在现在的安卓环境中,因为现在的安卓环境要求要放线程中,这是有原因的,我们后面再解释.
版权声明:
本系列文章已授权百家号 "clq的程序员学前班" .
一步一步从原理跟我学邮件收取及发送 4.不同平台下的socket的更多相关文章
- 一步一步从原理跟我学邮件收取及发送 9.多行结果与socket的阻塞
前几篇的文章发表后,有网友留言说没有涉及到阻塞的问题吗?在 socket 的编程当中,这确实是个很重要的问题.结合目前我们文章的内容进度,我们来看看为什么说阻塞概念很重要. 接着上篇的内容,当我们发送 ...
- 一步一步从原理跟我学邮件收取及发送 2.邮箱的登录和绕不开的base64
一步一步从原理跟我学邮件收取及发送 2.邮箱的登录和绕不开的base64 好了,经过本系列上一篇文章 "1.网络命令的发送",假设大家已经掌握了 email 电子邮件的命令发送的方 ...
- 一步一步从原理跟我学邮件收取及发送 5.C语言的socket示例
说到 C 语言版本的程序,首先要解决的问题就是兼容性. 作为 20 年开发有 10 多年是在服务端的程序员,我深刻地感受到服务端平台的两极分化之严重,linux 派对 windows 那是超级的不屑一 ...
- 一步一步从原理跟我学邮件收取及发送 3.telnet命令行发一封信
首先要感谢博客园管理员的及时回复,本系列的第二篇文章得以恢复到首页,这是对作者的莫大鼓励.说实在的本来我真的挺受打击的.好在管理员说只是排版上有些问题,要用代码块修饰下相关的信息.说来惭愧因为常年编码 ...
- 一步一步从原理跟我学邮件收取及发送 8.EHLO 命令详解
我们在上一篇中解决了接收一行命令的问题后,就可以来具体的分析邮件发送过程中涉及到的 SMTP 协议内容了. 首先来看通讯过程中的第一个内容:服务器在客户端连接上来后会主动发送一个问好的信息,所以这第一 ...
- 一步一步从原理跟我学邮件收取及发送 10.四句代码说清base64
经过前几篇的文章,大家应该都能预感到一定要讲解 base64 函数的内容了.是的,马上要到程序登录的代码,base64 是必须要实现的. base64 很早以前我就接触了,在项目中也很喜欢用.但每换一 ...
- 一步一步从原理跟我学邮件收取及发送 11.完整的发送示例与go语言
经过了这个系列的前几篇文章的学习,现在要写出一个完整的 smtp 邮件发送过程简直易如反掌. 例如我们可以轻松地写出以下的纯 C 语言代码(引用的其他C语言文件请看文末的 github 地址): ...
- 一步一步从原理跟我学邮件收取及发送 12.telnet命令行收一封信pop3
本系列上一篇文章中我们就说到了,这一次我们要说 pop3 收信了.虽然我觉得应该先说完 mime 格式,不过估计大家已经不耐烦了 -- 怎么老在说发送啊?我们要看收取! 好吧,来啦,来啦!收取邮 ...
- 一步一步从原理跟我学邮件收取及发送 13.mime格式与常见字符编码
在前面的本系列文章中我们已经学会了邮件的发送和收取.但在收取中我们看到的是一串串的乱码,回忆前面的发送过程,我们会奇怪:我们前面的邮件是明文啊.为什么明文的邮件明明也可以正常工作,还要弄乱码似的字符串 ...
随机推荐
- JavaScript及jQuery中的各种宽高属性图解
文/poetries(简书作者)原文链接:http://www.jianshu.com/p/60332df38393 著作权归作者所有,转载请联系作者获得授权, 并标注“简书作者”. 作者声明:本 ...
- windows 命令行打开浏览器
在命令行打开百度 start chrome www.baidu.com
- 【转】String Date Calendar之间的转换
1.Calendar 转化 String Calendar calendat = Calendar.getInstance(); SimpleDateFormat sdf = new SimpleDa ...
- 一起学Linux01之环境安装
先说说为什么在公司里服务器用Linux系统而非Windows系统.其实吧,我感觉主要就是前者更靠谱,一个字就是稳!不说别的,就我现在写博客用的电脑没事给我黑个屏,断个网啥的.而且总有漏洞,保不齐就被黑 ...
- ES6(一) let and const
1.let 用于变量声明,仅在块级作用域内有效. ES6新增了块级作用域,在ES5中没有块级作用域这个概念. { let a = 10; var b = 1; } console.log(b); // ...
- day9集合以及这段时间的总结 未完待续
随笔: # 可变类型:# 列表(可以存放多个值,可以按索引取值,是有序的),# 字典(字典里面是KEY:VALUE类型,key必须是不可变类型,不能按索引取值 因为它们是无序的,按KEY取值),# 集 ...
- WPF的消息机制(二)- WPF内部的5个窗口之隐藏消息窗口
目录 WPF的消息机制(一)-让应用程序动起来 WPF的消息机制(二)-WPF内部的5个窗口 (1)隐藏消息窗口 (2)处理激活和关闭的消息的窗口和系统资源通知窗口 (3)用于用户交互的可见窗口 (4 ...
- LeetCode题目总结(二)
我的代码在github上,https://github.com/WINTERFELLS/LeetCode-Answers 这里只提供个人的解题思路,不一定是最好的. Problems 21-40 合并 ...
- sql server 提示无法彻底删除_复制-而无法删除数据库或重新配置发布订阅
EXEC sp_removedbreplication 'Sys' --记着把当前执行EXEC sp_removedbreplication 'Sys'连接也关闭哦! 即使勾下面关闭连接,还会报错! ...
- angular4.0命令行汇总
查看ng命令行 ng help 创建项目 ng new projectName ng new projectName --routing[--routing表示创建带路由的项目] 配置依赖 npm i ...