计网自顶向下(Web服务器+UDPping+邮件客户端)

目录

前言

Web服务器(作业1)

过程

解释

代码

UDPping程序(作业2)

过程

解释

Client

Server

整体逻辑

代码

邮件客户端(作业3)

前置

过程

解释

(3) 邮件结束符

(4)base64库,b64encode(), encode(), decode()

(6) 接收信息

(8) SMTP命令

(9)encode() 和 decode()

代码


前言

19个Wireshark + 课后实验 + TCP/UDP,不包括很多可选练习,比如作业1(Web服务器),包含2个可选练习

(1)实现多线程服务器

(2)手写HTTP客户端代替浏览器来测试服务端请求

这部分等以后有时间了再看,先提高效率学完计网,搞定WebServer先,ddl更重要

以下是作业1,2,3,4的实验,属于应用层

计网自顶向下(Web服务器+UDPping+邮件客户端)_第1张图片

源码地址

moranzcw/Computer-Networking-A-Top-Down-Approach-NOTES: 《计算机网络-自顶向下方法(原书第6版)》编程作业,Wireshark实验文档的翻译和解答。 (github.com)

Web服务器(作业1)

过程

Github代码用git bash,git clone到本地,导入vscode

打开cmd,ipconfig,找到INET1里,IPv4后的地址,我的是192.168.***.***

打开新的cmd,对应目录,运行Webserver代码

打开另一个新的cmd来模拟另一台主机,cmd里打开浏览器(注意,此时html代码和WebServer.py代码处于同一目录下)

计网自顶向下(Web服务器+UDPping+邮件客户端)_第2张图片

然后浏览器出现

计网自顶向下(Web服务器+UDPping+邮件客户端)_第3张图片

如果输入不存在的网页html

计网自顶向下(Web服务器+UDPping+邮件客户端)_第4张图片

6789端口号是随便指定的,只要在端口范围内,并且没被占用即可

如何查看是否被占用

(1)Windows,打开Powershell,输入

Test-NetConnection -ComputerName localhost -Port 6789

TcpTestSucceeded : False,表示未被占用,可以使用

(2)cmd输入

netstat -ano

查看所有使用中的端口号

解释

以下是关于代码的详细解释

from socket import *
serverSocket = socket(AF_INET, SOCK_STREAM)

导入了socket模块并创建了一个TCP套接字,使用的协议族是IPv4(AF_INET),传输方式是流(SOCK_STREAM)

serverSocket.bind(('', 6799)) 

 将TCP欢迎套接字绑定到指定的端口号6799和任何可用的IP地址。为空字符串意味着服务器将在本机所有可用的网络接口上监听进来的连接请求

connectionSocket, addr = serverSocket.accept()

(1) connectionSocket是一个代表与客户端建立的TCP连接的套接字,它用于在服务器和客户端之间进行通信

(2)addr是一个元组,包含了客户端的IP地址和端口号。可以使用addr[0]访问IP地址,使用addr[1]访问端口号。这个元组表示与连接套接字关联的客户端的网络地址

message = connectionSocket.recv(1024) 

 从连接套接字中接收报文,其长度不超过1024字节

filename = message.split()[1]

将请求报文拆分并提取出请求的文件名,具体 

 message.split()是将message字符串按空格拆分成一个字符串列表

message = "GET /hello.html HTTP/1.1\r\nHost: www.example.com\r\n\r\n"
message_list = message.split()
print(message_list)

输出

['GET', '/hello.html', 'HTTP/1.1', 'Host:', 'www.example.com']

filename = message.split()[1]提取了拆分后的第二个元素(即'/hello.html'),它是客户端请求的文件名。

[1]表示获取列表中的第二个元素(索引从0开始)。因此filename = message.split()[1]语句的作用是从拆分后的列表中提取请求的文件名

f = open(filename[1:])
outputdata = f.read()

 打开请求的文件并读取其内容。注意,这里filename[1:]是为了去掉请求报文中的"/"。

header = ' HTTP/1.1 200 OK\nConnection: close\nContent-Type: text/html\nContent-Length: %d\n\n' % (len(outputdata))
connectionSocket.send(header.encode())

设置HTTP响应头和内容,其中包括状态代码,内容类型,内容长度等信息。在此之后,它通过套接字将响应头发送给客户端。具体 

  • HTTP/1.1:这是HTTP协议的版本号,表示使用的是HTTP 1.1版本。
  • 200:这是状态代码,表示请求成功。这里使用的是200,表示服务器成功处理了客户端的请求并返回相应的内容。
  • OK:这是状态消息,与状态代码相对应,表示请求成功。
  • Connection: close:这是指定连接选项的字段,告诉客户端在响应之后关闭连接。这里使用close选项,表示服务器在发送完响应后关闭与客户端的连接。
  • Content-Type: text/html:这是指定响应内容类型的字段,告诉客户端接收到的是HTML类型的内容。在这里,服务器假设响应的内容为HTML文档。
  • Content-Length: %d:这是指定响应内容长度的字段,告诉客户端响应的实体主体内容的长度。%d是一个占位符,后面的(len(outputdata))将会填充实际的内容长度。
  • \n\n:这是两个换行符,表示头部信息结束,后面是实体主体内容
for i in range(0, len(outputdata)):
    connectionSocket.send(outputdata[i].encode())

 将请求的文件内容发送到套接字,具体

此处的 outputdata 对应字符串 HelloWorld.html

encode()是将这个字符转换为字节流的方法。encode()方法将Unicode字符串编码为字节序列,以便可以在网络上进行传输。

connectionSocket.close()

 关闭套接字来结束与客户端的通信

except IOError:
    header = ' HTTP/1.1 404 Not Found'
    connectionSocket.send(header.encode())
    connectionSocket.close()

代码

#import socket module
from socket import *
serverSocket = socket(AF_INET, SOCK_STREAM)
# Prepare a server socket
# bind()的参数只有一个, 是ip和端口结合的套接字
serverSocket.bind(('', 6799)) # 将TCP欢迎套接字绑定到指定端口
serverSocket.listen(1) # 最大连接数1

while True:
    # Establish the connection
    print('Ready to serve')
    connectionSocket, addr = serverSocket.accept() # 接受客户端请求后,建立新的TCP连接套接字
    try:
        message = connectionSocket.recv(1024) # 获取客户发送的报文
        filename = message.split()[1]
        f = open(filename[1:])
        outputdata = f.read();
        # Send the content of the requested file to the client

        header = ' HTTP/1.1 200 OK\nConnection: close\nContent-Type: text/html\nContent-Length: %d\n\n' % (len(outputdata))
        connectionSocket.send(header.encode())

        for i in range(0, len(outputdata)):
            connectionSocket.send(outputdata[i].encode())
        connectionSocket.close()
    except IOError:
        # Send response message for file not found
        header = ' HTTP/1.1 404 Not Found'
        connectionSocket.send(header.encode())

        # Close client socket
        connectionSocket.close()
serverSocket.close()

UDPping程序(作业2)

过程

计网自顶向下(Web服务器+UDPping+邮件客户端)_第5张图片

两个cmd模拟两台主机

server

计网自顶向下(Web服务器+UDPping+邮件客户端)_第6张图片

另一台主机

计网自顶向下(Web服务器+UDPping+邮件客户端)_第7张图片

解释

Client

  1. from socket import *:导入socket模块,这个模块提供了网络通信所需的函数和类。
  2. import time:导入time模块,用于获取当前时间。
  3. serverName = '192.168.15.1':定义服务器地址,这里使用一个远程主机的IP地址。
  4. serverPort = 12000:定义服务器指定的端口号。
  5. clientSocket = socket(AF_INET, SOCK_DGRAM):创建UDP套接字,使用IPv4协议。
    • AF_INET表示使用IPv4地址族。
    • SOCK_DGRAM表示使用UDP协议。
  6. clientSocket.settimeout(1):设置套接字超时时间为1秒。
  7. for i in range(0, 10)::循环10次,发送10个ping消息。
  8. sendTime = time.time():获取当前时间作为发送时间。
  9. message = ('Ping %d %s' % (i+1, sendTime)).encode():生成包含序列号和发送时间的消息,并将其编码为字节串以便发送

'Ping %d %s' % (i+1, sendTime)表示将i+1和sendTime插入到字符串'Ping %d %s'中的%d和%s处,%d表示整数类型,%s表示字符类型(包括字符串)。

例如,当i=0和sendTime=1635558427.123456时,上述代码的结果是'Ping 1 1635558427.123456'

  1. try::尝试执行以下代码块。
  2. clientSocket.sendto(message, (serverName, serverPort)):将消息发送到服务器。
    • sendto()函数用于向特定地址发送UDP数据报。
  3. modifiedMessage, serverAddress = clientSocket.recvfrom(1024):接收服务器的响应消息,并同时获取服务器地址。
    • recvfrom()函数用于接收UDP数据报,返回接收到的数据和发送方的地址

(1)recvfrom()函数用于接收UDP数据报,并返回两个值:接收到的数据和发送方的地址。发送方的地址由IP地址和端口号的组合表示

(2)IP地址用于标识网络中的主机,而端口号则用于标识主机上运行的应用程序或服务

  1. rtt = time.time() - sendTime:计算往返时间(RTT)

rtt = round trip time  往返时间

  1. print('Sequence %d: Reply from %s RTT = %.3fs' % (i+1, serverName, rtt)):显示收到响应的信息,包括序列号、服务器地址和RTT

(1)"%.3fs" 是一个格式化字符串,用于将浮点数值插入到字符串中,并指定小数点后保留三位小数

(2)例如,一个浮点数值rtt为2.34567,则"%.3fs" % rtt 的结果将是"2.346s",保留了三位小数并转换为字符串类型

  1. except Exception as e::如果出现异常,则执行以下代码块。
  2. print('Sequence %d: Request timed out' % (i+1)):显示请求超时的信息。
  3. clientSocket.close():关闭套接字。

Server

  1. from socket import *:导入socket模块。
  2. import random:导入random模块,用于生成随机数。
  3. serverSocket = socket(AF_INET, SOCK_DGRAM):创建UDP套接字。
  4. serverSocket.bind(('', 12000)):将IP地址和端口号绑定到套接字上。
    • ''表示使用任意可用的IP地址。
  5. while True::无限循环,不断处理客户端的请求。
  6. rand = random.randint(0, 10):生成一个0到10之间的随机数。
  7. message, address = serverSocket.recvfrom(1024):接收客户端的请求消息,同时获取客户端的地址。
  8. message = message.upper():将接收到的消息转换为大写形式。
  9. if rand < 4::如果随机数小于4,模拟丢包,不回复客户端。
  10. continue:继续下一次循环。
  11. serverSocket.sendto(message, address):向客户端发送响应消息。
    • sendto()函数用于向特定地址发送UDP数据报

整体逻辑

  1. 客户端通过UDP套接字向服务器发送ping消息。
  2. 服务器接收到客户端的消息后,根据随机数决定是否丢弃该消息。
  3. 如果服务器不丢弃消息,则将消息转换为大写形式并回复客户端。
  4. 客户端收到服务器的响应后,计算往返时间(RTT)并显示结果。
  5. 如果在超时时间内未收到服务器的响应,则显示请求超时的消息。
  6. 循环10次,完成10个ping请求。
  7. 最后关闭客户端的套接字

代码

Client

from socket import *
import time

serverName = '192.168.15.1' # 服务器地址,本例中使用一台远程主机
serverPort = 12000 # 服务器指定的端口
clientSocket = socket(AF_INET, SOCK_DGRAM) # 创建UDP套接字,使用IPv4协议
clientSocket.settimeout(1) # 设置套接字超时1秒

for i in range(0, 10):
    sendTime = time.time()
    message = ('Ping %d %s' % (i+1, sendTime)).encode() # 生成数据报,编码为tytes以便发送
    try:
        clientSocket.sendto(message, (serverName, serverPort)) # 将信息发送到服务器
        modifiedMessage, serverAddress = clientSocket.recvfrom(1024) # 从服务器接受信息,同时得到服务器地址
        rtt = time.time() - sendTime # 计算往返时间
        print('Sequence %d: Reply from %s    RTT = %.3fs' % (i+1, serverName, rtt)) # 显示信息
    except Exception as e:
        print('Sequence %d: Request timed out' % (i+1))
    
clientSocket.close() # 关闭套接字

Server

from socket import *
import random

# create a UDP socket
# notice the use of SOCK_DGRAM for UDP packets
serverSocket = socket(AF_INET, SOCK_DGRAM)
# Assign(分配) IP address and port number to socket
serverSocket.bind(('', 12000))

while True:
    # Generate random number(生成随机数) in the range of 0 to 10
    rand = random.randint(0, 10)
    # Receive the client packet along with the address it is coming from
    message, address = serverSocket.recvfrom(1024)
    # Capitalize(使大写) the message from the client
    message = message.upper()
    # If rand is less is than 4, we consider the packet lost and do not respond
    if rand < 4:
        continue
    # Otherwise, the server responds
    serverSocket.sendto(message, address)

邮件客户端(作业3)

前置

关于SMTP

介绍

当我们发送一封电子邮件时,SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)起着至关重要的作用。SMTP 是用于在网络上传输电子邮件的标准协议,它定义了电子邮件是如何被发送和接收的

重点

  1. 寄信流程: 当你发送一封电子邮件时,你的邮件客户端(如Gmail、Outlook等)会将这封邮件发送给你所使用的邮件服务器。这个过程就是通过 SMTP 完成的

  2. 转发邮件: SMTP 也用于将邮件从一个邮件服务器传输到另一个邮件服务器。例如,当你发送一封邮件给另一个域名的邮箱时,你的邮件服务器就会使用 SMTP 将邮件传输到对方的邮件服务器

  3. 端口: SMTP 默认使用端口25进行通信,但为了安全起见,常常也会使用加密端口如587或465。这些端口提供了加密和身份验证功能,以保护邮件传输过程中的数据安全

  4. 简单性: SMTP 的"Simple"部分意味着它的设计相对简单,易于理解和实现。这使得它成为电子邮件传输的基本协议    

关于MX记录

(1)邮件交换记录(Mail Exchange record)的缩写

在域名系统(DNS)中,MX记录指定了负责接收该域名电子邮件的邮件服务器。当某人发送电子邮件到一个特定的域名时,发件人的邮件服务器会查询目标域名的MX记录,以确定应该将邮件传递到哪个邮件服务器 

(2)包括两个部分:优先级邮件服务器地址

优先级指定了邮件服务器的优先顺序,当一个域名有多个MX记录时,优先级较高的邮件服务器会被优先选择。邮件服务器地址则是指定了接收该域名邮件的邮件服务器的地址

(3)举例

一个域名可能具有多个MX记录,每条记录对应一个邮件服务器,而这些邮件服务器可能是按照优先级排序的,这样就可以确保即使主要的邮件服务器不可用,电子邮件仍然可以被正确地发送到备用的邮件服务器上

补充理解

如何验证 Email 地址:SMTP 协议入门教程 - 阮一峰的网络日志 (ruanyifeng.com)

SMTP协议详解及工作过程_51CTO博客_smtp协议

过程

(1)首先你需要一个163和一个qq邮箱

(2)登录163邮箱,可以先看看这个

计网自顶向下(Web服务器+UDPping+邮件客户端)_第8张图片

计网自顶向下(Web服务器+UDPping+邮件客户端)_第9张图片

计网自顶向下(Web服务器+UDPping+邮件客户端)_第10张图片

(3)开启SMTP (Simple Mail Transfer Protocol)

计网自顶向下(Web服务器+UDPping+邮件客户端)_第11张图片

具体操作

计网自顶向下(Web服务器+UDPping+邮件客户端)_第12张图片

首页这里有个设置,点击

计网自顶向下(Web服务器+UDPping+邮件客户端)_第13张图片

然后手机扫码发送短信授权

计网自顶向下(Web服务器+UDPping+邮件客户端)_第14张图片

把这串字符作为代码中的密码即可 

(4)将代码中的发送 / 接受邮箱,用户名和授权码改为自己的

第一次出现以下BUG

计网自顶向下(Web服务器+UDPping+邮件客户端)_第15张图片

正确应该是 235 Authentication successfully

而不是535 Error: authentication failed

计网自顶向下(Web服务器+UDPping+邮件客户端)_第16张图片然后一看,发现改错代码了

对应修改后,成功!

计网自顶向下(Web服务器+UDPping+邮件客户端)_第17张图片

计网自顶向下(Web服务器+UDPping+邮件客户端)_第18张图片

163邮箱(发)

计网自顶向下(Web服务器+UDPping+邮件客户端)_第19张图片

qq邮箱(收)

计网自顶向下(Web服务器+UDPping+邮件客户端)_第20张图片

解释

(1)整体思路

  1. 导入模块:从Python标准库中导入了socketbase64模块,用于网络通信和Base64编码。

  2. 设置邮件内容相关信息:包括邮件主题、类型和内容,以及邮件结束符号。

  3. 选择邮件服务器:设置邮件服务器的地址为"smtp.163.com"。

  4. 设置发件人和收件人的邮箱地址。

  5. 对发件人的认证信息进行Base64编码,包括用户名和密码。

  6. 创建一个套接字(socket)clientSocket,并与邮件服务器建立TCP连接​​​​​​

  7. 通过套接字与邮件服务器进行交互:

    • 发送HELO命令并打印服务器响应。
    • 进行身份验证,包括发送用户名和密码,并接收服务器的响应进行验证。
  8. 发送邮件相关命令:

    • 发送MAIL FROMRCPT TO命令,用于指定发件人和收件人。
    • 发送DATA命令表示即将发送邮件内容。
    • 发送邮件内容,并以单个点作为结束标识。
    • 发送QUIT命令表示退出连接。
  9. 关闭套接字,断开连接

(2) 邮箱服务器SMTP地址

计网自顶向下(Web服务器+UDPping+邮件客户端)_第21张图片

(3) 邮件结束符

endmsg = "\r\n.\r\n"  # 邮件结束符

在SMTP协议中,当发送完整的邮件内容后,需要使用"\r\n.\r\n"来表示邮件内容的结束。这个字符串告诉邮件服务器已经发送完所有邮件数据,服务器可以开始处理这封邮件了

"回车"(\r)指示打印头或光标返回到当前行的开头

"换行"(\n)指示将光标移动到下一行,并且可以包括回车操作以定位到下一行的开头

  • 在 Unix、Linux、macOS 等系统中,使用的是换行符(\n),即表示为一个字符。
  • 在 Windows 系统中,通常使用回车加换行的组合(\r\n)表示换行

实际的文本文件中,换行通常表示为回车加换行的组合(\r\n),这样可以确保在不同操作系统上都能正确地显示换行效果 

所以,"\r\n.\r\n" 用于结束邮件的发送,它包含两个换行和一个句点,用于标记邮件的结束 

(4)base64库,b64encode(), encode(), decode()

# Auth information (Encode with base64) (认证信息,使用base64编码)
username = base64.b64encode(fromaddress.encode()).decode()
password = base64.b64encode("*************".encode()).decode()

(一) 

fromaddress,邮箱地址的字符串,它包含用户名和域名

比如"[email protected]",其中"user"是邮箱的用户名,"example.com"是邮箱的域名

  • base64是一个Python标准库,提供了对Base64编码和解码的支持。
  • b64encodebase64库中的一个函数,用于对数据进行Base64编码

encode(): 这个方法用于将字符串编码为指定的编码格式,返回一个 bytes 对象

decode(): 这个方法用于将 bytes 对象解码为指定的字符串格式,返回一个字符串 

(二) 

a. encode()将字符串编码为字节对象

(因为字符串是由Unicode字符组成的,而计算机处理和传输数据时一般使用字节数据。因此,需要将字符串转换为字节对象以进行后续的处理和编码)

b.  b64encode()对字节对象进行base64编码

(Base64编码是将数据转换为只包含可打印ASCII字符的编码形式。它通常用于在文本协议中传输二进制数据)

c. decode()将编码后的结果解码为字符串

(将字节对象转换为Base64编码后,得到的是一个表示编码形式的字节序列。如果我们要将其作为字符串使用或展示,需要使用decode()方法将其解码为字符串形式)

(三) 

字符串通过字节编码、Base64编码和解码操作,获得的结果是一个与原来字符串内容相同的新字符串。但是它们的数据类型和编码形式是不同的 

(5)创建套接字,并与服务器建立TCP连接

# 创建套接字并与邮件服务器建立TCP连接
clientSocket = socket(AF_INET, SOCK_STREAM)
clientSocket.connect((mailserver, 25)) # 邮箱地址和端口号

socket() 创建一个套接字对象 clientSocket

AF_INET 参数表示使用 IPv4 地址族,SOCK_STREAM 参数表示这是一个面向连接的 TCP 套接字

(mailserver, 25) 是一个元组,包含了要连接的目标服务器的地址和端口号

一旦调用了 connect 方法,客户端套接字就会尝试连接到指定的邮件服务器

(6) 接收信息

recv = clientSocket.recv(1024).decode()

使用 recv 方法从服务器端接收最多 1024 字节的数据,并将其解码为字符串

这个方法会阻塞程序,直到有数据到达或者连接关闭,如果没有数据到达,传输失败,print(recv)将不会输出任何结果 

换种解释就是

.recv 方法是在 Python 的 socket 编程中用于接收数据的方法。它用于从连接的另一端接收数据,其语法通常为 socket.recv(buffer_size)。其中,buffer_size 参数指定了一次性可以接收的最大数据量。调用 .recv 方法将会阻塞程序直到有数据到达,或者直到连接被关闭

(7) Python语法

if recv1[:3] != '250':
    print('250 reply not received from server.')

[:3] 表示前3个字符

(8) SMTP命令

"HELO" 等命令是 SMTP 协议规定的命令之一,用于客户端向服务器打招呼并标识自己。这个命令是固定的,不能随意更改

  1. MAIL FROM: [email protected]\r\n 这是用于指定邮件的发件人地址的命令。在这个例子中,使用了字符串拼接来构造这个命令,并通过 clientSocket 发送到邮件服务器。

  2. RCPT TO: [email protected]\r\n 这是用于指定邮件的收件人地址的命令。类似地,这个命令也是用字符串拼接构造的,并发送到邮件服务器。

  3. DATA\r\n 这个命令告诉邮件服务器即将发送邮件数据。一旦收到这个命令,服务器会准备接收邮件内容。

  4. message = 'from:' + fromaddress + '\r\n' ... 这段代码构造了邮件的内容,包括发件人、收件人、主题、内容类型等信息,并将其转换成符合 SMTP 协议格式的字符串。

  5. clientSocket.sendall(message.encode()) 这里使用 sendall() 方法发送邮件内容的字符串到服务器。

  6. clientSocket.sendall(endmsg.encode()) endmsg 可能是表示邮件内容结束的标识,通过 sendall() 发送到服务器。

  7. recv = clientSocket.recv(1024).decode() 这里是接收服务器返回的响应消息,然后根据响应消息进行相应的处理,比如判断是否成功发送邮件

(9)encode() 和 decode()

发送数据 

send()方法是用来发送TCP数据的,而sendall()方法则会在必要时将所有数据发送完毕 

  • 每次调用send或者sendall发送数据之前都需要通过encode方法将字符串编码为字节对象,这是因为网络传输的数据必须是字节类型。
  • 调用encode方法后,会将字符串转换为特定的字节编码形式(如UTF-8),得到一个字节对象作为发送的数据

接受数据

  • 每次从套接字接收到数据后,获得的是字节对象,需要通过decode方法将字节对象解码成字符串,以便我们能够处理和理解这些数据。
  • 调用decode方法后,会根据指定的编码方式将字节对象解码为字符串

(10)前半部分多次 send(), sendall(), recv() 的作用

(一)客户端需要将邮件按照 SMTP 协议要求的格式逐步发送给服务器,以确保服务器能够正确地接收并处理邮件内容

(二)调用了 recv 方法来接收服务器的响应

客户端可以了解到是否有任何错误发生,以及服务器是否成功接收并处理了客户端发送的邮件内容

最后的几行message,作用是发送邮件信息,要求遵循SMTP协议规范 

(11)MAIL FROM 和 RCPT TO 命令

clientSocket.sendall(('MAIL FROM: <' + fromaddress + '>\r\n').encode())

clientSocket.sendall(('RCPT TO: <' + toaddress + '>\r\n').encode())

 < 和    在这个代码中是为了表示电子邮件地址的开始和结束符号。在SMTP协议中,使用<>来标识一个完整的电子邮件地址。在这段代码中,   和    用于包围 fromaddress 和 toaddress 变量,在电子邮件中指定发送人和收信人

代码

当我尝试去掉代码中的部分send, sendall(), recv时,程序报错502,因为邮件服务端对认证方式有限制,需要按特定格式发送(也就是说,只保留开头部分和结尾message部分,去掉中间的多次认证,是不行的,任何一个命令,都不能少)

# 导入模块
from socket import *
import base64

# Mail content
subject = "宝贝"  # 标题
contenttype = "text/plain"  # 类型
msg = "想我了没"  # 内容
endmsg = "\r\n.\r\n"  # 邮件结束符

# Choose a mail server (SMTP服务器地址)
mailserver = "smtp.163.com"

# Sender and reciever (发件人和收件人)
fromaddress = "***********@163.com"
toaddress = "************@qq.com"

# Auth information (Encode with base64) (认证信息,使用base64编码)
# base64编码, 以便进行SMTP身份认证
username = base64.b64encode(fromaddress.encode()).decode()
password = base64.b64encode("*************".encode()).decode()

# Create socket called clientSocket and establish a TCP connection with mailserver
# 创建套接字并与邮件服务器建立TCP连接
clientSocket = socket(AF_INET, SOCK_STREAM)
clientSocket.connect((mailserver, 25)) # 邮箱地址和端口号

recv = clientSocket.recv(1024).decode()
print(recv)
if recv[:3] != '220':
    print('220 reply not received from server.')

# Send HELO command and print server response.
# 发送HELO命令
heloCommand = 'HELO Alice\r\n'
clientSocket.send(heloCommand.encode()) # 发送命令
recv1 = clientSocket.recv(1024).decode() # 接收响应
print(recv1)
if recv1[:3] != '250':
    print('250 reply not received from server.')

# Auth (认证)
# 身份验证的头部信息
# 发送'AUTH LOGIN\r\n'到服务器
clientSocket.sendall('AUTH LOGIN\r\n'.encode())

# 从客户端socket接受消息,长度为1024字节,解码为字符串
recv = clientSocket.recv(1024).decode()
print(recv)
# 检查前3个字符
if (recv[:3] != '334'):
    print('334 reply not received from server')

# 用户名添加到消息末尾并发送给服务器
clientSocket.sendall((username + '\r\n').encode())

# 从客户端socket接收消息
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '334'):
    print('334 reply not received from server')

# 密码添加到消息末尾并发送给服务器
clientSocket.sendall((password + '\r\n').encode())

# 从客户端socket接收消息, 并解码为字符串
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '235'):
    print('235 reply not received from server')

# Send MAIL FROM command and print server response.
# 发送MAIL FROM命令
clientSocket.sendall(('MAIL FROM: <' + fromaddress + '>\r\n').encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '250'):
    print('250 reply not received from server')

# Send RCPT TO command and print server response.
# 发送RCPT TO命令
clientSocket.sendall(('RCPT TO: <' + toaddress + '>\r\n').encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '250'):
    print('250 reply not received from server')

# Send DATA command
# 发送DATA命令
clientSocket.send('DATA\r\n'.encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '354'):
    print('354 reply not received from server')

# Send message data
# 发送邮件内容
message = 'from:' + fromaddress + '\r\n'
message += 'to:' + toaddress + '\r\n'
message += 'subject:' + subject + '\r\n'
message += 'Content-Type:' + contenttype + '\r\n'
message += '\r\n' + msg
clientSocket.sendall(message.encode())

# Message ends with a single period (邮件以单个点结束)
clientSocket.sendall(endmsg.encode())
recv = clientSocket.recv(1024).decode()
print(recv)
if (recv[:3] != '250'):
    print('250 reply not received from server')

# Send QUIT command and get server response
# 发送QUIT命令
clientSocket.sendall('QUIT\r\n'.encode())

# 关闭套接字 close connection
clientSocket.close()

你可能感兴趣的:(网络编程,计算机网络,udp,tcp)