计算机网络就是将各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信,网络通信是两台计算机上的两个进程之间的通信。
用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
要想实现交流,首先得要有一套各自都能理解的语言,同理,要想实现网络通信,就必须得规定通信协议。在早起的计算机网络中,由各厂商自己规定一套协议,只能用于自己,互不兼容。为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议——互联网协议簇(Internet Protocol Suite)即通用协议标准。
互联网协议包含了上百种协议,其中最为重要的就是TCP协议和IP协议,简称TCP/IP协议。
互联网上每个计算机的唯一标识就是IP地址,IP地址对应的实际上是计算机的网络接口,通常是网卡。
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。IP包的特点是按块发送,途经多个路由,但不保证能到达,也不保证顺序到达。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。
一个IP包除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。
端口有什么作用?
在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个IP包来了之后,到底是交给浏览器还是QQ,就需要端口号来区分。每个网络程序都向操作系统申请唯一的端口号,这样,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。
一个进程也可能同时与多个计算机建立链接,因此它会申请很多端口。
Python 提供了两个级别访问的网络服务:
1.低级别的网络服务支持基本的 Socket,它提供了标准的 BSD Sockets API,可以访问底层操作系统Socket接口的全部方法。
2.高级别的网络服务模块 SocketServer, 它提供了服务器中心类,可以简化网络服务器的开发。
所谓socket,通常也称为“套接字”,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过“套接字“向网络发出请求或者应答网络请求。
说白了socket就是一种通信机制。它类似于移动啊电信啊的电话客服,当你拨打10086选择人工服务,那边会配置一个人来回答你的问题,此时,客服部门就相当于socket的服务器端,你这边就相当于客户端,在和你通话期间,如果有人也想找那个客服通话,这是不可能的,因为你正在和该客服通信。
socket起源于Unix,而Unix/Linux的基本哲学就是“一切皆文件”,都可以用“打开-读写-关闭”模式来进行操作。本质上,socket就是对该模式的一个实现,socket是一种特殊的文件,一些socket函数实际上就是对其进行的操作(打开,读写,关闭)。
socket.AF_UNIX 只能用于单一的Unix系统进程间通信,无法进行网络通信
socket.AF_INET 服务器之间的网络通信
socket.AF_INET6 IPv6
socket.SOCK_STREAM 流式socket,for TCP(相对可靠的链接)
socket.SOCK_DGRAM 数据报式socket,for UDP(不可靠,不管收到没收到,都把信息往出发)
socket.SOCK_RAW 原始套接字,普通的套接字无法处理ICMP,IGMP等网络报文,而socket.SOCK_RAW可以;其次SOCK_RAW也可以处理特殊的IPv4报文,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头(篡改IP头)。通常仅限于高级用户或管理员运行的程序使用。
socket.SOCK_RDM 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RDM用来提供对原始协议的低级访问。
…..
大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。
使用socket实现服务端与客户端的简单交互
<客户端编程>
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Name: TCP编程.py
# Author: zhuzhuzhu time:2018/4/29
# Connect: [email protected]
# Desc: TCP编程
# 导入socket库
import socket
# 创建一个socket库
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接(连接新浪服务器)
s.connect(("www.sina.com.cn", 80))
# 发送数据
s.send("GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n")
# 接收数据:
buffer = []
while True:
# 每次最多接收1k字节:
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
# 使用空字节把buffer这个字节列表连接在一起,成为一个新的字节串
# 编码问题:b''是字节流(byte流),主要用在处理二进制数据,它是按字节来处理的;而字符流采用Unicode编码
data = b''.join(buffer)
# 关闭连接
s.close()
# 接收到的数据包括HTTP头和网页本身,这里将二者分离,打印HTTP头并将网页内容保存至文件
# 后面那个参数表示分割的次数,不指定的话,就是分割所有。
header, html = data.split('\r\n\r\n', 1)
print header
# 把接收的数据写入文件:
with open('sina.html', 'wb') as f:
f.write(html)
# 现在,只需要在浏览器中打开sina.html这个文件,就可以看到新浪的首页面了
<服务端编程>
< client >
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Name: TCP编程_client.py
# Author: zhuzhuzhu time:2018/4/29
# Connect: [email protected]
# Desc: 服务器编程--客户端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9999))
# 接收欢迎消息:
print s.recv(1024)
for data in ['Michael', 'Tracy', 'Sarah']:
# 发送数据:
s.send(data)
print s.recv(1024)
s.send('exit')
s.close()
< sever >
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Name: TCP编程_sever.py
# Author: zhuzhuzhu time:2018/4/29
# Connect: [email protected]
# Desc: TCP编程:服务器编程--服务器端
import socket
import time
import threading
# <服务器编程>
def tcplink(sock, addr):
print 'Accept new connection from %s:%s...' % addr
sock.send('Welcome!')
while True:
data = sock.recv(1024)
time.sleep(0.1)
if data == 'exit' or not data:
break
sock.send('Hello, %s!' % data)
sock.close()
print 'Connection from %s:%s closed.' % addr
# 创建socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 监听端口
"""
绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址。127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。
端口号需要预先指定。因为我们写的这个服务不是标准服务,所以用9999这个端口号。请注意,小于1024的端口号必须要有管理员权限才能绑定:
"""
s.bind(('127.0.0.1', 9999))
# 调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量
s.listen(5)
print "Waiting for connection...."
# 服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接
while True:
# 接受一个新连接:
sock, addr = s.accept()
# 创建新线程来处理TCP连接:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
3.查看Server端的结果!
注意
多次运行上述程序之后会遇到一个错误: socket.error: [Errno 10048]
这是因为在服务器程序中创建一个Socket打开一个端口后,在程序结束的时候没有关闭这个Socket,因此下次启动程序就会出现这个错误提示。
解决办法:
1、在服务器程序结束的时候要关闭Socket
2、或者更换端口号
3、重启机器
关闭socket的方法详见:https://blog.csdn.net/wf592523813/article/details/78897874
总结:
用TCP协议进行Socket编程的主要思路:
对于客户端,要主动连接服务器的IP和指定端口,对于服务器,要首先监听指定端口,然后,对每一个新的连接,创建一个线程或进程来处理。通常,服务器程序会无限运行下去,也正因如此,一般在本次服务器程序结束后需要关闭socket。
TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。
在使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。
虽然用UDP传输数据不可靠,但其优点也很明显,同TCP比,UDP的速度快,因此对于不要求可靠到达的数据,可以使用UDP协议。
UDP编程同样也分为客户端和服务器端。
< client >
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Name: UDP编程_client.py
# Author: zhuzhuzhu time:2018/4/29
# Connect: [email protected]
# Desc: UDP编程——客户端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in ['Michael', 'Tracy', 'Sarah']:
# 发送数据:
s.sendto(data, ('127.0.0.1', 9999))
# 接收数据:
print s.recv(1024)
s.close()
< sever >
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Name: UDP编程_sever.py
# Author: zhuzhuzhu time:2018/4/29
# Connect: [email protected]
# Desc: UDP编程——服务器端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定端口:
s.bind(('127.0.0.1', 9999))
print 'Bind UDP on 9999...'
while True:
# 接收数据:
data, addr = s.recvfrom(1024)
print 'Received from %s:%s.' % addr
s.sendto('Hello, %s!' % data, addr)
这样也就完成了服务器与客户端的简单交互。
注意
TCP编程中,发送数据用send(),接收数据用recv()
UDP编程中,发送数据用sendto(),接收数据用recvfrom(),但是,客户端编程中从服务端接收数据还是用recv()。