PHP利用Socket发送HTTP协议和SMTP协议

学习背景

最近需要写一个发送邮件的功能,之前都是直接利用好成熟的扩展包来实现这个功能。所以一直不太清楚邮件功能具体是怎么实现的。
所以,这两天花时间尝试了解了一下如果使用php 发送邮件。

邮件发送原理

在了解如何使用PHP发送原理之前,先了解邮件发送和接受的原理。

1.1 邮件服务器和邮件传输协议

要在Internet上发送邮件,那么必须使用专门的电子邮件服务器(mail server)。每一个用户在邮件服务器上有一个邮箱。邮件服务器是电子邮件体系的核心。

邮件服务器之间通过SMTP协议(Sample Mail Transport Protocol)进行通信。

SMTP协议和HTTP协议有很大的不同,SMTP协议基本可以看作是一个推协议(push protocol),这个协议的TCP链接需要有发送文件的机器发起,当目标服务器不可达是,发送邮件方的用户用户代理没有办法将电子邮件报文发送到目标服务器,需要发送邮件方进行重试,这一切都会交给邮件服务器去重试。同样,接受邮件方也不总是在线,所以接受邮件方也需要代理在线的时候主动过去。

所以,现在电子邮件系统都是以存储和转发的模型为基础的。邮件服务器接受、转发、提交及存储邮件。
寄信人、收信人及他们的电脑都不用同时在线。寄信人和收信人只需在寄信或收信时,简短的链接到邮件服务器即可。

实现这样的推送、接受体系仅仅有SMTP是不够的,因此需要其他协议来配合。

为了使用户能够从自己的邮件服务器中拉取邮件,需要引入一个特殊的邮件访问协议。流行的邮件访问协议有邮局协议第三版(Post Office Protocol-Version3,POP3)、因特网邮件访问协议(Internet Mail Access Protocol,IMAP)、多用途Internet邮件扩展协议。

POP3是一个非常简单、但功能有限的邮件读取协议,大多数ISP都支持POP3。当邮件用户将邮件接收软件设定为POP3阅读电子邮件时,每当使用者要阅读电子邮件时,它都会把所有信件内容下载至使用者的计算机,此外,他可选择把邮件保留在邮件服务器上或是不保留邮件在服务器上。

IMAP是另一种邮件读取协议。当邮件用户将邮件接收设定IMAP阅读电子邮件时,它并不会把所有邮件内容下载至计算机,而只下载邮件的主题等信息。

IMAP协议相比POP3的优点是,它将每个报文与一个文件夹联系。当报文第一次到达服务器,它与收件人的INBOX文件夹相关联。收件人可以移动到新的文件夹、阅读或者删除邮件。IMAP也提供了远程查询的命令,可以条件匹配查询邮件信息。与POP3不同的是,IMAP维护会话过程的用户状态信息,比如每个报文的关联。另一个重要的特性是允许只获取报文的一部分,比如常用的移动设别都有在wifi条件下下载附件的功能,在普通网络下则只会加载邮件标题和摘要,点击全文下载完整邮件。

多用途Internet邮件扩展协议(MIME,Multipose Internet Mail Extensions)是一种编码标准,它解决了SMTP只能传送ASCII文本的限制。MIME定义了各种类型数据,如声音、图像、表格、二进制数据等的编码格式,通过对这些类型的数据编码并将它们作为邮件中的附件进行处理,以保证这些部分内容完整、正确地传输。因此,MIME增强了SMTP的传输功能,统一了编码规范。

1.2 邮件发送流程

PHP利用Socket发送HTTP协议和SMTP协议_第1张图片

  1. 发信人在用户代理上编辑邮件,并写清楚收件人的邮箱地址;
  2. 用户代理根据发信人编辑的信息,生成一封符合邮件格式的邮件;
  3. 用户代理把邮件发送到发信人的的邮件服务器上,邮件服务器上面有一个缓冲队列,发送到邮件服务器上面的邮件都会加入到缓冲队列中,等待邮件服务器上的SMTP客户端进行发送;
  4. 发信人的邮件服务器使用SMTP协议把这封邮件发送到收件人的邮件服务器上(它会自动根据收件人的邮箱来分析出收件人的邮箱服务器);
  5. 收件人的邮件服务器收到邮件后,把这封邮件放到收件人在这个服务器上的信箱中;
  6. 收件人使用用户代理来收取邮件。首先用户代理使用POP3协议来连接收件人所在的邮件服务器,身份验证成功后,用户代理就可以把邮件服务器上面的收件人邮箱里面的邮件读取出来,并展示给收件人。

Socket 编程

2.1 Socket基本释义

Socket是一种操作系统提供的进程间通信机制。
·
在操作系统中,通常会为应用提供一组应用程序接口,称为套接字接口。应用程序可以通过套接字,来使用套接字,以进行数据交换。最早的套接字接口来自于4.2BSD,因此现代常见的套接字接口大多源于Berkeley套接字(Berkeley sockets)标准。在套接字接口中,以IP地址及通信端口组成的套接字地址(socket address)。 远程的套接字地址,以及本地的套接字地址完成连接后,在加上使用的协议(protocol),这五元组(five-elemnet tuple),作为套接字对(socket pairs),之后就可以彼此交换数据。
·
例如,在同一台计算机上,TCP协议与UDP协议可以同时使用相同的port而不相互干扰。操作系统根据套接字地址,可以决定应将数据送达特定的进程或线程。这就像电话系统中,以电话号码上加上分机号码,来决定通话对象一样。

2.2 Socket定义

系统内部接口(内部网络),接口描述(抽象接口描述符)和接口地址之间的差别其实很细微,日常编程用的时候几乎不做区别。并且详细的网络接口有以下几个特征:
1. 本地接口地址,由本地ip地址和(包括Tcp,UDP)端口号
2. 传输协议,例如TCP、UDP、raw IP 协议,如果只是指定IP地址,那么TCP 53 与 UDP 53 不是同一个接口
·
一个已经创建连接的接口双方都有整数形式的接口描述,用来唯一表示该接口。操作系统根据对方接口发送过来的IP以及传输协议投信息来提取接口的地址信息,并且将应用数据去除投信息之后,提交给相应的应用程序。在很多网络协议、教科书已经文本中,接口指的是有一个独一无二的接口号实体。
·
Socket用于描述IP地址和端口,是一个通信的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet的主机上一般运行了多个服务软件,同时提供了几种服务。每种服务都打开一个socket,并绑定到一个端口上,不同过的端口对应于不同的服务。
·
Socket实质上提供了进程通信的端点。进程通信之前,双方首先必须各自创建一个端点,否则没有办法建立联系并相互通信。
·
一个完整的Socket有一个本地唯一的Socket号,有操作系统分配。
最重要的是,Socket是面向客户/服务器(C/S)模型而设计的,针对客户和服务器提供不同的Socket系统调用。客户随机申请一个Socket,系统为之分配一个Socket号,服务器拥有全局公认的Socket,任何客户都可以向它发出连接请求和信息请求。

2.3 Socket连接过程

根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
(1). 服务器监听: 是服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网路状态。
(2). 客户端请求:是指客户端的套接字提出连接请求,要连接的目标是服务器的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就像服务器端套接字提出连接请求。
(3). 连接确认:是指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,它就响应客户端套接字请求,建立一个新的进程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,连接就建立好了。而服务器套接字继续处于监听状态,继续接受其他客户端套接字的连接请求。

2.4 Socket示例代码


$host = "192.168.1.99";
$prot = 1234; // port

// set max time 
set_time_limit(0);

// create new Socket
$socket = socket_create(AF_INET,SOCK_STREAM,0) or die("Could not create socket\n");
$result = socket_bind($socket,$host,$port) or die("Could not bind socket\n");
$resutl = socket_listen($socket,3) or die("Could not setup socket listener\n");

// 另一个Socket来处理通信
$spawn=socket_accept($socket)ordie("Couldnotacceptincomingconnection\n");//获得客户端的输入
$input=socket_read($spawn,1024)ordie("Couldnotreadinput\n");//清空输入字符串
$input=trim($input);//处理客户端输入并返回结果
$output=strrev($input)."\n";
socket_write($spawn,$output,strlen($output))ordie("Couldnotwriteoutput\n");//关闭
socket_close($spawn);
socket_close($socket);




//http协议请求
function get_http(){

    $url = 'bdzd/socket_receive.php';
    $host = '127.0.0.1';
    $port = 80;

    $param = ['name'=>'道路很漫长','time'=> 20180620];
    $url .= '?'.http_build_query($param);
    // fsockopen 打开一个网络连接或者unix套接字连接
    // fsockopen()将返回一个文件句柄,之后可以被其他文件类函数调用(例如: fgets(),fgetss(),fwrite(),fclose(),feof())
    $socket = fsockopen($host, $port, $errno, $errstr, 10);
    print_r(stream_get_transports());
    echo '
'
; $request = "GET /$url HTTP/1.1\r\n"; $request .= "Host:$host:$port\r\n"; $request .= "Connection:close\r\n\r\n"; fwrite($socket,$request); echo "Request:
"
.str_replace("\r\n","
"
,$request); echo "
Response:
"
; $response = ""; while($out = fread($socket, 2048)){ $response .= $out; } echo str_replace("\r\n","
"
,$response); fclose($socket); } /*sock模拟post 请求*/ function post_http(){ $url = 'bdzd/socket_receive.php'; $host = 'literature.test'; $port = 80; /* 利用socket打开一个套接字连接 */ $socket = fsockopen($host,$port,$errno,$errstr,$timeout = 10); if($errno) { echo '请求发生错误: '.$errstr; die; } $param = ['name'=>'套接字POST请求','date'=>20180620]; $data = http_build_query($param); $length = strlen($data); $request = "POST /{$url} HTTP/1.1\r\n"; $request .= "Host:{$host}:{$port}\r\n"; $request .= "Content-type:application/x-www-form-urlencoded\r\n"; $request .= "Content-length:".strlen($data)."\r\n"; $request .= "Connection:close\r\n\r\n"; $request .= "{$data}"; fwrite($socket,$request); echo '

Request:


'
.str_replace("\r\n",'
'
,$request); echo "

Response:


"
; $response = ''; while (! feof($socket)){ $response .=fread($socket,2048); } echo str_replace("\r\n","
"
,$response); fclose($socket); // $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); // socket_connect($socket, $host,$port); // $request = "POST /${url} HTTP/1.1\r\n"; // $request .= "Host:${host}:{$port}\r\n"; // $request .= "Content-type:application/x-www-form-urlencoded\r\n"; // $request .= "Content-length:".strlen($data)."\r\n"; // $request .= "Connection:close\r\n\r\n"; // $request .= "${data}"; // socket_write($socket,$request); // echo "Request:
".str_replace("\r\n","
",$request);
// echo "
Response:
";
// $response = ""; // while ($out = socket_read($socket, 2048)) { // $response .= $out; // } // echo str_replace("\r\n","
",$response);
// socket_close($socket); } /* 利用socket发送smtp协议 * 并且发送邮件 */ function smtp_test(){ $user = '[email protected]'; $password = 'king'; $mailto = $user; $subject = '测试 socket SMTP协议 '; $body = "现在我在测试http协议的发送功能"; $mail_host = 'smtp.qiye.163.com'; $mail_port = 25; $socket = fsockopen($mail_host, $mail_port, $error_no, $error_msg); if (! $socket) return "错误信息".$error_no.'
'
.$error_msg; fputs($socket,"HELO localhost\r\n"); echo "Say Hello :".fgets($socket,512)."
"
; fputs($socket, "AUTH LOGIN"."\r\n"); echo "登录提示:".fgets($socket,512)."
"
; fputs($socket,base64_encode($user)."\r\n"); echo "登录输入邮箱用户名:".fgets($socket,512)."
"
; fputs($socket,base64_encode($password)."\r\n"); echo "使用密码:".fgets($socket, 512)."
"
; fputs($socket,"MAIL FROM:<${user}>\r\n"); echo fgets($socket, 512)."
"
; fputs($socket,"RCPT TO:<${mailto}>\r\n"); echo fgets($socket, 512)."
"
; fputs($socket,"DATA\r\n"); echo fgets($socket, 512)."
"
; $header = "MIME-Version:1.0\r\n"; $header .= "To: ${mailto}\r\n"; $header .= "From: ${user}\r\n"; $header .= "Subject: ${subject}\r\n"; fputs($socket, $header . "\r\n" . $body); echo fgets($socket, 512)."
"
; fputs($socket, "\r\n.\r\n"); echo fgets($socket, 512)."
"
; fputs($socket,"QUIT\r\n"); fclose($socket); }

以上算是我学习的socket和邮件发送过程中了解的一些东西。
同样还有几个问题。
邮件协议为什么要使用SMTP和HTTP协议比较起来有什么优势吗?
Socket的通信是基于进程还是什么,线程之间又是如何通信的。socket服务器是否只是监听传递过来socket请求,一旦请求确认之后,会额外新建一个进程用户请求信息的传输。
Socket本机通信和网络Socket通信有什么区别?

你可能感兴趣的:(PHP)