网络编程介绍
网络通信就是两个进程间在用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信。
TCP编程
Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链接”(要指定目标IP+端口号)
主动发起连接的叫客户端,被动响应连接的叫服务器。
客户端client
client端请求新浪网页的例子:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import socket, ssl
# 创建一个socket, AF_INET为ipv4,AF_NET6为ipv6,SOCK_STREAM指面向流的TCP协议
#s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接,注意参数是一个tuple,网页端口80,<1024是Internet标准服务端口,>1024任意用
#s.connect(('www.sina.com.cn', 80))
# 新浪强制HTTPS协议访问 所以 80端口改443 socket 改 ssl
s = ssl.wrap_socket(socket.socket())
s.connect(('www.sina.com.cn', 443))
# 使用HTTP标准格式发送请求给服务器
s.send(b'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 # 当接收数据为空时,表示接收完毕,退出循环
html = b''.join(buffer) # 拼接为完整byte
# 接收完数据后close连接
s.close()
# 接收到的数据包含HTTP头和网页本身,使用‘\r\n\r\n’分割一下
header, content = html.split(b'\r\n\r\n', 1)
# 打印头部
print(header.decode('UTF-8'))
# 保存网页内容
with open('sina.html', 'wb') as f:
f.write(content)
NOTE1: 关于ssl.wrap_socket 用法参照从编程角度看SSL协议(1)ssl库–SSLSocket类_薄荷_红茶的博客-CSDN博客
服务器端host
服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。
所以,服务器会打开固定端口(比如80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。
但是服务器还需要同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理.
我们来编写一个简单的服务器程序,它接收客户端连接,把客户端发过来的字符串加上Hello
再发回去。
服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0
绑定到所有的网络地址,还可以用127.0.0.1
绑定到本机地址(客户端必须同时在本机运行才能连接,外部的计算机无法连接进来。)
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# TCP Server side
import socket
import threading
import time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定server IP和指定端口
s.bind(('127.0.0.1', 7777))
# 调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量
s.listen(5)
print('Waiting for connection...')
# 定义tcplink: 连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。如果客户端发送了exit字符串,就直接关闭连接。
def tcplink(sock, addr):
print("Accpet new connection from %s:%s" % addr) # addr传入的IP:port格式,所以是两个%s
sock.send(b'Welcome')
while True:
data = sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
break
sock.send(('hello, %s' % data.decode('utf-8')).encode('utf-8'))
sock.close() # 记得关闭socket
print('Disconnect the connection from %s:%s' % addr)
# 服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接:
while True:
sock, addr = s.accept()
t = threading.Thread(target=tcplink, args=(sock, addr))
t.setDaemon(True)
t.start()
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# TCP client side
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 7777))
# 接收欢迎语
print(s.recv(1024).decode('utf-8'))
for each in [b'Mike', b'James', b'Brown']:
# 发送数据
s.send(each)
# 接收返回值
data = s.recv(1024)
print(data.decode('utf-8'))
s.send(b'exit')
s.close()
先运行server,再运行client, 注意sever是无限循环需要手动干掉的
UDP编程
UDP则是面向无连接的协议,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。
使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口,创建Socket时,SOCK_DGRAM
指定了这个Socket的类型是UDP。绑定端口和TCP一样,但是不需要调用listen()
方法,而是直接接收来自任何客户端的数据
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# UDP server
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 7777))
print('Binding UDP server on port 7777, wating for connection...')
while True:
# recvfrom()方法返回数据和客户端的地址:端口
data, addr= s.recvfrom(1024)
print('Received data from %s:%s' % addr)
# 直接调用sendto()就可以把数据用UDP发给客户端。发送的是一个tuple,分别是数据和地址
s.sendto(b'Hello, %s' % data, addr)
客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用connect()
,直接通过sendto()
给服务器发数据:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# UDP Client
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for each in [b'Mike', b'John', b'Jackie']:
# 发送数据 tuple(data, (ip, port))
s.sendto(each, ('127.0.0.1', 7777))
# 接收仍然用recv
print(s.recv(1024).decode('utf-8'))
s.close()
UDP的使用与TCP类似,但是不需要建立连接。此外,服务器绑定UDP端口和TCP端口互不冲突,即大家可以用相同的端口号。
eg: UDP 实现一个在线聊天室
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# UDP server
import socket
import threading
import os
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 7777))
print('Host is bind at 127.0.0.1:7777')
clients = set() # 用于保存连接的客户端信息,用集合的方式去重
def chat(data, addr):
for each in clients:
if each != addr: # 给所有其他成员发送消息
s.sendto(('### %s:%s>>>' % addr).encode('utf-8')+data, each)
while True: # 无限循环接收和转发msg
try:
data, addr= s.recvfrom(1024)
clients.add(addr)
content = data.decode('utf-8')
if content == "Register": # Register功能
print('%s:%s is regiestered.' % addr)
s.sendto(('welcome %s:%s to the Fake QQ(127.0.0.1:7777)...' % addr).encode('utf-8') , addr)
s.sendto(b'### Type "Friends" to get friends list, "Exit" to end this session', addr)
elif content == "Friends": # 朋友列表功能
for each in clients:
if each != addr:
s.sendto(('Friends list: %s:%s' % each).encode('utf-8'), addr)
elif content == "Exit": # 退出时从注册列表中移除client
print('%s:%s is disconnected'% addr)
clients.remove(addr)
else:
chat(data, addr)
except KeyboardInterrupt:
print('Server is closed!')
os._exit(0)
except Exception as e:
print(str(e))
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# UDP Client
import socket
import threading
import os
from datetime import datetime
s1 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
host = ('127.0.0.1', 7777)
s1.sendto(b'Register', host) # 自动注册到sever
def my_input(): # 构建一个generator,每次得到用户输入
while True:
try:
temp = input('### ')
yield temp
except Exception as e:
print(str(e))
m = my_input()
def send_msg(client):
while True:
msg = next(m)
client.sendto(msg.encode('utf-8'), host)
if msg == 'Exit':
os._exit(0)
def recv_msg(client): # 接收来自server的消息并添加timestamp
while True:
try:
content = client.recv(1024).decode('utf-8')
dt = datetime.now().strftime('%Y-%m-%d %H:%M')
print(dt+'\n'+content+'\n')
except Exception as e:
print(str(e))
# 发送消息和接收消息各一个进程
t1 = threading.Thread(target=send_msg, args=(s1,))
t2 = threading.Thread(target=recv_msg, args=(s1,))
t1.start()
t2.start()
喜欢就三连哦