网络编程:
自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了。
计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。
网络编程就是如何在程序中实现两台计算机的通信。
举个例子,当你使用浏览器访问新浪网时,你的计算机就和新浪的某台服务器通过互联网连接起来了,然后,新浪的服务器把网页内容作为数据通过互联网传输到你的电脑上。
网络通信是两台计算机上的两个进程之间的通信。
由于你的电脑上可能不止浏览器,还有QQ、Skype、Dropbox、邮件客户端等,不同的程序连接的别的计算机也会不同,所以,更确切地说,网络通信是两台计算机上的两个进程之间的通信。比如,浏览器进程和新浪服务器上的某个Web服务进程在通信,而QQ进程是和腾讯的某个服务器上的某个进程在通信。
网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
1 什么是协议
计算机为了联网,就必须规定通信协议,早期的计算机网络,都是由各厂商自己规定一套协议,IBM、Apple和Microsoft都有各自的网络协议,互不兼容,这就好比一群人有的说英语,有的说中文,有的说德语,说同一种语言的人可以交流,不同的语言之间就不行了。
为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目标,就需要使用互联网协议簇(Internet Protocol Suite)就是通用协议标准。
Internet是由inter和net两个单词组合起来的,原意就是连接“网络”的网络,有了Internet,任何私有网络,只要支持这个协议,就可以联入互联网。
因为互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议。
2 TCP/IP协议族
TCP/IP提供点对点的链接机制,将数据应该如何封装、定址、传输、路由以及在目的地如何接收,都加以标准化。
它通常将软件通信过程抽象化为四个抽象层,采取协议堆栈的方式,分别实现出不同通信协议。协议套组下的各种协议,依其功能不同,被分别归属到这四个层次结构之中,常被视为是简化的七层OSI模型。
OSI模型,即开放式通信系统互联参考模型(Open System Interconnection Reference Model),是国际标准化组织(ISO)提出的一个试图使各种计算机在世界范围内互连为网络的标准框架,简称OSI。
链路层:处理与电缆或其他传输媒介的物理接口
网络层:处理数据在网络中的活动
ip协议【网络互连协议】
用途:将多个包在网络中联系起来,传输数据包(不可靠传输),最基本功能就是寻址和分段功能,不提供端到端,路由到路由的确认,不提供重发和流量控制。是计算机网络能够相互通信的基本规则。出错则像ICMP报告,ICMP在IP模块中实现ICMP协议
用途:面向无连接协议,用于传输错误报告控制信息(控制信息是指网络不通畅,主机是否到达,路由是否可用的这些网络本身的消息,不涉及用户传输的数据)
ARP协议->地址解析协议
用途:根据IP地址获取物理地址的协议(即MAC地址)。在同一子网内通过ARP协议可以实现数据包的互相传递。不在一个子网内则无法获得MAC地址,只有通过网关去处理。
RARP协议->反转地址协议
用途:和ARP协议相反,将主机的物理地址转换成IP地址。
传输层:提供两台主机间端到端的通信
TCP协议->传输控制协议
用途:面向连接、保证高可靠性(数据无丢失、数据无失序、数据无错误、数据无重复到达)传输层协议。
UDP协议->用户数据协议
用途:无连接,不保证可靠的传输层协议。
应用层:用于不同的应用程序
NAT协议->网络地址转换协议【放在这里不完全合适】
用途:实现内网IP地址和公网地址之间的相互转换。将大量的内网IP转换成一个或者少量的公网IP
FTP协议->文件传输协议
用途:通过FTP协议在FTP客户端访问FTP服务端,默认使用20和21端口,20用于传输数据,21用于传输控制信息。
HTTP协议->超文本传输协议
用途:是用于从WWW服务器传输超文本到本地浏览器的传输协议。是客户端浏览器或其他程序与Web服务器之间的应用层通信协议。
TELNET协议
用途:是Internet远程登陆服务的标准协议和主要方式,为用户提供了在本地计算机上完成远程主机工作的能力。
SMTP协议->简单邮件传输协议
用途:控制邮件传输的规则,以及邮件的中转方式。
DNS协议
用途:定义域名规则,将域名和IP相互映射
3.IP地址
一个程序如何在网络上找到另一个程序?
首先,程序必须要启动,其次,必须有这台机器的地址,我们都知道我们人的地址大概就是国家\省\市\区\街道\楼\门牌号这样字。那么每一台联网的机器在网络上也有自己的地址,它的地址是怎么表示的呢?
就是使用一串数字来表示的,例如:100.4.5.6
IP地址是指互联网协议地址(英语:Internet Protocol Address,又译为网际协议地址),是IP Address的缩写。IP地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。
IP地址是一个32位的二进制数,通常被分割为4个“8位二进制数”(也就是4个字节)。IP地址通常用“点分十进制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之间的十进制整数。例:点分十进IP地址(100.4.5.6),实际上是32位二进制数(01100100.00000100.00000101.00000110)。
4.端口
那么TCP/IP协议中的端口指的是什么呢?
端口就好一个房子的门,是出入这间房子的必经之路。
端口号:
端口是通过端口号来标记的,端口号只有整数,范围是从0到65535(2的16次方)
系统预留端口号
预留端口号是广泛使用的端口号,方位从0-1023 开发中不要使用
如http的80端口 ftp的21端口
总结
通过IP在网络内寻找主机、通过端口寻找主机中的应用程序
即:ip地址精确到具体的一台电脑,而端口精确到具体的程序。
socket:
本地的进程间通信有很多种方式,例如队列、同步(互斥锁、条件变量等)
以上通信方式都是在一台机器上不同进程之间的通信方式,那么问题来了网络中进程之间如何通信?
网络中进程之间如何通信:
主要解决的问题是如何唯一标识一个进程,否则通信无从谈起!
在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。
其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。
这样利用ip地址,协议,端口就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互
什么是socket
socket(简称套接字) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:
它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于Socket来完成通信的。
例如我们每天浏览的、QQ 聊天、收发Email等等
创建socket
在Python中使用socket模块的函数socke就可以完成:socket.socket(AddressFamily, Type)
说明:
函数socket.socket创建一个socket,返回该 socket的描述符,该函数带有两个参数:
Address Family:可以选择AF_INET(用于 Internet 进程间通信)或者AF_UNIX(用于同一台机器进程间通信)实际工作中常用AF_INET
Type:套接字类型,可以是SOCK_STREAM(流式套接字,主要用于TCP协议)或者SOCK_DGRAM(数据报套接字,主要用于UDP协议)
eg:
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print("socketCreated")
print(s)
s2 = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
print("socketCreated")
print(s2)
常用方法
socket套接字常用操作
s.bind() 绑定(主机名称、端口)到一个套接字上
s.listen() 设置并启动TCP监听
s.accept() 等待客户端连接
s.connect() 连接指定服务器
s.recv() 接受TCP消息
s.send() 发送TCP消息
s.recvfrom() 接受UDP消息
s.sendto() 发送UDP消息
s.close() 关闭套接字对象
UDP编程
TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。
使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。
虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。
适用情况
UDP是面向消息的协议,通信时不需要建立连接,数据的传输当然是不可靠的,UDP一般用于多点通信和实时的数据业务,例如:
语音广播、视频、QQ、TFTP(简单文件传送)、SNMP(简单网络管理协议)、RIP(路由信息协 议,如:报告股票市场,航空信息)
DNS(域名解释)
服务端实现:
import socket
server_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s_addr = ("127.0.0.1",8080)
server_socket.bind(s_addr)
print("服务器连接成功!")
while True:
data,addr = server_socket.recvfrom(1024)
print(data.decode("utf-8"))
server_socket.close()
客户端实现:
import socket
client_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
addr = ("127.0.0.1",8080)
message = "Good morning! Saturday!!!"
client_socket.sendto(message.encode("utf-8"),addr)
client_socket.close()
TCP编程
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中,“打电话”"
tcp服务器
在程序中,如果想要完成一个tcp服务器的功
能,需要的流程如下:
- socket创建一个套接字
- bind绑定ip和port
- listen使套接字变为可以被动链接
- accept等待客户端的链接
- recv/send接收发送数据
三次握手,四次挥手
TCP 连接三次握手
TCP 三次握手就好比两个人在街上隔着50米看见了对方,但是因为雾霾等原因不能100%确认,所以要通过招手的方式相互确定对方是否认识自己。
张三首先向李四招手(syn),李四看到张三向自己招手后,向对方点了点头挤出了一个微笑(ack)。张三看到李四微笑后确认了李四成功辨认出了自己(进入estalished状态)。
但是李四还有点狐疑,向四周看了一看,有没有可能张三是在看别人呢,他也需要确认一下。
所以李四也向张三招了招手(syn),张三看到李四向自己招手后知道对方是在寻求自己的确认,于是也点了点头挤出了微笑(ack),李四看到对方的微笑后确认了张三就是在向自己打招呼(进入established状态)。
我们看到这个过程中一共是四个动作,张三招手–李四点头微笑–李四招手–张三点头微笑。
其中李四连续进行了2个动作,先是点头微笑(回复对方),然后再次招手(寻求确认),实际上可以将这两个动作合一,招手的同时点头和微笑(syn+ack)。
于是四个动作就简化成了三个动作,张三招手–李四点头微笑并招手–张三点头微笑。这就是三次握手的本质,中间的一次动作是两个动作的合并。
TCP数据传输
TCP 数据传输就是两个人隔空对话,差了一点距离,所以需要对方反复确认听见了自己的话。
张三喊了一句话(data),李四听见了之后要向张三回复自己听见了(ack)。
如果张三喊了一句,半天没听到李四回复,张三就认为自己的话被大风吹走了,李四没听见,所以需要重新喊话,这就是tcp重传。
TCP 四次挥手
TCP断开链接的过程和建立链接的过程比较类似,只不过中间的两部并不总是会合成一步走,所以它分成了4个动作,张三挥手(fin)——李四伤感地微笑(ack)——李四挥手(fin)——张三伤感地微笑(ack)。
服务端实现:
import socket
#socket创建套接字
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s_addr = ("127.0.0.1",8083)
#bind绑定ip和端口
server_socket.bind(s_addr)
#listen控制最大监听数
server_socket.listen(100)
# accept等待客户端的连接
client_socket, c_addr = server_socket.accept()
while True:
print(client_socket,c_addr)
print("连接成功!")
data = client_socket.recv(1024)
msg = data.decode("utf-8")
print("接收到的消息为:",msg)
message = input("请输入信息:")
bytes = message.encode("utf-8")
client_socket.send(bytes)
print("发送消息成功!")
server_socket.close()
客户端实现:
import socket
client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
c_addr = ("127.0.0.1",8083)
client_socket.connect(c_addr)
print("连接成功!")
while True:
message = input("请输入信息:")
bytes = message.encode("utf-8")
client_socket.send(bytes)
print("发送信息成功!")
data = client_socket.recv(1024)
msg = data.decode("utf-8")
print("接收到的消息为:",msg)
client_socket.close()
多进程、多线程网络通信
服务端实现:
import socket
from multiprocessing import Process
s_addr = ("127.0.0.1",8084)
def handle_client(client_socket):
try:
data = client_socket.recv(1024)
print("接收到的消息为:", data.decode("utf-8"))
except Exception as e:
print(e)
if __name__ == '__main__':
server_socket = None
try:
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server_socket.bind(s_addr)
server_socket.listen(100)
print("服务器创建连接成功!")
while True:
client_socket, addr = server_socket.accept()
print(client_socket, addr)
p = Process(target=handle_client,args=(client_socket,))
p.start()
except Exception as e:
print("服务器连接失败")
finally:
if server_socket != None:
server_socket.close()
客户端1实现:
import socket
client_socket = None
try:
client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
c_addr = ("127.0.0.1",8084)
client_socket.connect(c_addr)
while True:
message = input("请输入信息:")
bytes = message.encode("utf-8")
client_socket.send(bytes)
print("发送信息成功!")
# data = client_socket.recv(1024)
# msg = data.decode("utf-8")
# print("接收到的信息为:",msg)
except Exception as e:
print(e)
finally:
if client_socket != None:
client_socket.close()
客户端2实现:
import socket
client_socket = None
try:
client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
c_addr = ("127.0.0.1",8084)
client_socket.connect(c_addr)
while True:
message = input("请输入信息:")
bytes = message.encode("utf-8")
client_socket.send(bytes)
print("发送信息成功!")
data = client_socket.recv(1024)
msg = data.decode("utf-8")
print("接收到的信息为:",msg)
except Exception as e:
print(e)
finally:
if client_socket != None:
client_socket.close()
tcp通信
服务端实现:
import socket,threading
server_socket = None
users = dict() # 建立一个用户空字典
def add_user(client, addr):
global users
users[addr] = client
print("添加新用户")
def func(client, addr):
while True:
try:
message = client.recv(1024)
for key, val in users.items():
if key != addr:
val.send(message)
except Exception as e:
# raise e
print("发送消息失败", e)
if __name__ == '__main__':
try:
server_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s_addr = ("127.0.0.1",8055)
server_socket.bind(s_addr)
server_socket.listen(2)
print("服务器创建连接成功!")
for i in range(2):
client, addr = server_socket.accept()
add_user(client, addr) # 将新用户添加到在线用户列表
t = threading.Thread(target=func, args=(client, addr))
t.start()
input()
except Exception as e:
print("服务器连接失败",e)
finally:
if server_socket is not None:
server_socket.close()
客户端实现:
import socket,threading
c_addr = ("127.0.0.1",8055)
def menu():
menu = """
请选择你要的操作:\n
【1】选择I输入信息\n
【2】选择O查看信息\n
"""
select =input(menu)
return select
def send_msg(client_socket):
try:
msg = input("请输入内容:")
client_socket.send(msg.encode("utf-8"))
except Exception as e:
print("发出错误信息",e)
msg_box = []
lock = threading.Lock()
def func(client_socket):
try:
while True:
data = client_socket.recv(1024)
msg = data.decode("utf-8")
lock.acquire()
msg_box.append(msg)
lock.release()
except Exception as e:
print(e)
def show_receie_msgs():
lock.acquire()
while len(msg_box) != 0:
e =msg_box.pop(0)
print(e)
lock.release()
if __name__ == '__main__':
client_socket = None
try:
client_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client_socket.connect(c_addr)
t_receiver = threading.Thread(target=func,args=(client_socket,))
t_receiver.start()
while True:
select = menu()
if select == "I":
send_msg(client_socket)
elif select == "O":
show_receie_msgs()
else:
print("选项不正确!")
except Exception as e:
print("连接服务器失败",e)
finally:
client_socket.close()