网络编程介绍
网络通信其实就是两台计算机上的两个进程之间的通信。
例如通过浏览器访问百度,就是浏览器进程和百度服务器的某个进程之间进行网络通信。
网络通信需要遵守一定的规则也就是网络协议,互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议。
通信双方必须知道对方的标识,也就是互联网上每个计算机的唯一标识即IP地址。但是一台计算机可以同时接入多个网络,即多网卡,所以IP地址对应的实际上是计算机的网络接口。
IP协议负责把数据按块分割并以块为单位从一台计算机通过网络发送到另一台计算机。IP包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP的三次握手和四次拜拜就不再这里详细描述。许多常用的更高级的协议都是建立在TCP协议基础上的,比如用于浏览器的HTTP协议、发送邮件的SMTP协议等。
一个TCP报文除了包含要传输的数据外,还包含源IP地址
和目标IP地址
,源端口
和目标端口
。
端口有什么作用?在两台计算机通信时,只发IP地址是不够的,因为同一台计算机上跑着多个网络程序。一个TCP报文来了之后,到底是交给浏览器还是QQ,就需要端口号来区分。
一个进程也可能同时与多个计算机建立链接,因此它会申请很多端口。
TCP编程
Socket是网络编程的一个抽象概念
。通常我们用一个Socket表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址
和端口号
,再指定协议类型
即可。
客户端
import socket
# 创建一个socket
# socket.AF_INET指定使用IPV4协议 socket.SOCK_STREAM指定面向流的TCP协议
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 和web服务器建立连接 参数是一个tuple (ip, port)
s.connect(("www.qq.com", 80))
# 建立连接后就可以发送数据了
s.send(r"GET / HTTP/1.1\r\nHost: www.qq.com\r\nConnection: close\r\n\r\n".encode())
# 接受数据
buffer = []
while True:
# 返回的数据类型是bytes str
d = s.recv(1024)
if d:
buffer.append(d.decode())
else:
break
data = "".join(buffer)
header, html = data.split("\r\n\r\n", maxsplit=1)
print(header)
print(html)
服务器
服务端代码server.py
如下
注意:服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0
绑定到所有的网络地址,还可以用127.0.0.1
绑定到本机地址。127.0.0.1
是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。
# -*- coding:UTF-8 -*-
import socket
from threading import Thread
class ServerRunnable(Thread):
def __init__(self, conn_socket, c_address_info):
self.conn_socket = conn_socket
self.c_address_info = c_address_info
Thread.__init__(self)
def run(self):
print("Accept new connection from {}".format(self.c_address_info))
self.conn_socket.send("Hello new friend".encode())
try:
while True:
data = self.conn_socket.recv(1024)
print("Content from {} is '{}'".format(self.c_address_info, data.decode("utf8")))
if data.decode("utf8") == "exit":
self.conn_socket.close()
break
self.conn_socket.send("Server Has Received '{}'".format(data.decode("utf8")).encode())
print("Connection from {} has closed.".format(self.c_address_info))
except ConnectionResetError as e:
print(e)
pass
# 创建一个基于IPv4和TCP协议的Socket
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 绑定ip和port
ip = input("请输入绑定的ip: ")
# 小于1024的端口号必须要有管理员权限才能绑定
port = input("请输入绑定的端口(必须大于1024): ")
t = (ip, int(port))
s.bind(t)
# 设置最多等待5个连接如果连接满了
s.listen(5)
print("Wait for connections ...")
while True:
# 接受客户端的请求 获取当前客户端和服务器的连接socket和客户端ip/port
conn_socket, c_address_info = s.accept()
# 为每个连接上服务器的客户端都开启一个线程去处理该连接。
t = ServerRunnable(conn_socket, c_address_info)
t.start()
客户端代码client.py
如下
# -*- coding:UTF-8 -*-
import socket
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
ip = input("请输入要连接的服务器ip: ")
port = input("请输入要连接的服务器进程的端口: ")
s.connect((ip, int(port)))
# 打印收到的消息
print(s.recv(1024).decode())
while True:
content = input("请输入(exit表示退出): ")
s.send(content.encode())
if content == "exit":
break
print(s.recv(1024).decode())
效果图如下所示
下面只展示监听的端口
即服务器代码占用的2345
端口
UDP编程
TCP是建立可靠连接,并且通信双方都以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。
使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是不保证是否能到达。
虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。
UDP编程和TCP编程非常类似!
服务端代码如下
由于UDP是面向无连接的协议,为了方便也就没有为每个接收到的消息像TCP一样建立一个线程去处理。
# -*- coding:UTF-8 -*-
import socket
# 创建一个基于IPv4和TCP协议的Socket
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
# 绑定ip和port
ip = input("请输入绑定的ip: ")
port = input("请输入绑定的端口(必须大于1024): ")
t = (ip, int(port))
s.bind(t)
print("Wait for connections ...")
while True:
# 返回接收到的数据 和 客户端信息
data, address_info = s.recvfrom(1024)
print("Content from {} is '{}'".format(address_info, data.decode("utf8")))
# 通过sendto 方法向指定的(ip, port)发送消息
s.sendto("Server Has Received '{}'".format(data.decode("utf8")).encode(), address_info)
客户端代码如下
# -*- coding:UTF-8 -*-
import socket
s = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
ip = input("请输入要连接的服务器ip: ")
port = input("请输入要连接的服务器进程的端口: ")
t = (ip, int(port))
while True:
content = input("请输入(exit表示退出): ")
# 通过sendto 方法向指定的(ip, port)发送消息
ct = s.sendto(content.encode(), t)
print("has sended ", ct)
if content == "exit":
break
print(s.recv(1024).decode())
效果图如下所示
服务器绑定UDP端口和TCP端口互不冲突。如下所示,TCP绑定了1234端口,UDP也可以绑定1234端口。