服务端(chat_server.py)
import json
import socket
import traceback
online_users = {}
def decode_msg(msg):
return json.loads(msg.decode('utf-8'))
def encode_msg(msg):
return json.dumps(msg).encode('utf-8')
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(('localhost', 8888))
while True:
try:
recv_message, recv_address = server_socket.recvfrom(1024)
print("收到用户消息: %s, 网络地址 %s" % (recv_message, recv_address))
recv_message = decode_msg(recv_message)
msg_type = recv_message['type']
if msg_type == 'login':
recv_user_name = recv_message['user']
online_users[recv_user_name] = recv_address
for user_name, address in online_users.items():
send_message = {'type': 'online', 'all_users': online_users, 'user': recv_user_name}
server_socket.sendto(encode_msg(send_message), address)
elif msg_type == 'quit':
网络聊天室
服务端代码
# 1. 服务器要有多个线程, 分别负责每个客户端
# 2. 客户端一个线程
'''
功能:
1. 退出当前用户聊天窗口 (:unselect)
服务器阻塞点:
recvfrom: 接受所有用户消息的, 当recvfrom阻塞时, 意味着所有客户端都没有消息过来
客户端阻塞点:
1. input等待用户输入, 阻塞时服务器推送消息就无法处理啦
2. 接收服务器消息:
消息类型:
- 登录
- 聊天
- 退出
- 新用户上线
- 用户下线
xx={"type":"chat","to_user": "user01", "message":"xxxx"}
xxx['key']=value
{"type":"login","user": "user01"}
python: {"type": True}
json: {"type": true}
1. 实现用户登录功能
客户端: {"type":"login","user": "user01"}
服务器: 收到login消息->在线用户列表->所有用户发送用户上线消息
{"type":"online","new_user":"user001","all_users":{"user01":"127.0.0.1:8888"}}
'''
import json
import socket
def encode_msg(msg):
return json.dumps(msg).encode('utf-8') # dict->str->bytes
def decode_msg(msg):
return json.loads(msg.decode('utf-8')) # bytes->str->dict
online_users = {}
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(('localhost', 8888))
while True:
recv_message, recv_address = server_socket.recvfrom(1024)
print("
udp_server.py
import socket
# 1. 初始化socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # SOCK_DGRAM指定UDP协议
# 2. 绑定地址(ip+端口)
host = '127.0.0.1'
port = 50000
s.bind((host, port))
# 3. 收发数据
message, address = s.recvfrom(1024)
print("收到客户端消息: " + message.decode('utf-8'))
s.sendto(b'hello', address)
# 4. 结束交互
s.close()
'''
cpu / 内存 / 磁盘 文件10G->内存(8G)
2行-->操作系统认为后2行->操作把后2行读到cache LRU cache满了,
会把最近没有使用的缓存给清掉
1. 内存-> 磁盘缓存
2. cpu-->寄存器-->高速缓存 -> 内存
3. redis缓存系统, memcache 缓存
android --> linux
'''
udp_client.py
import socket
# 1. 初始化socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # SOCK_DGRAM指定UDP协议
# 2. 收发数据
host = '127.0.0.1'
port = 50000
s.sendto(b'hello', (host, port))
message, address = s.recvfrom(1024)
print("收到服务器的消息: " + message.decode('utf-8'))
# 3. 结束交互
s.close()
TCP用于建立可靠连接,并且通信双方可以以流的形式发送数据。相对于TCP,UDP面向无连接的协议。
使用UDP协议时不需要建立连接,只需要知道对方的IP地址和端口号就可以直接发送数据包。但是发送的数据包是否能到达就不 知道了。
虽然用UDP传输数据不可靠,但是优点是速度快。对于不要求可靠到达的数据可以使用UDP协议。
下面来看如何通过UDP协议传输数据。和TCP类似,使用UDP的通信双方也分为客户端和服务器。服务器首先需要绑定端口, 操作如下:
创建socket时,SOCK_DGRAM指定了socket的类型是UDP。绑定端口和TCP一样,不过不需要调用listen()方法,而是直接接 收来自任何客户端的数据,操作如下:
recvfrom()方法返回数据和客户端的地址与端口。这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发送给客户 端。
客户端使用UDP时,首先仍然是创建基于UDP的socket,然后不需要调用connect(),直接通过sendto()给服务器发送数据,操 作如下:
什么是线程,什么是进程?
进程是程序(软件,应用)的一个执行实例,每个运行中的程序,可以同时创建多个进程,但至少要有一个。每个进程都提供执 行程序所需的所有资源,都有一个虚拟的地址空间、可执行的代码、操作系统的接口、安全的上下文(记录启动该进程的用户和 权限等等)、唯一的进程ID、环境变量、优先级类、最小和最大的工作空间(内存空间)。进程可以包含线程,并且每个进程 必须有至少一个线程。每个进程启动时都会最先产生一个线程,即主线程,然后主线程会再创建其他的子线程。
线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指 令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不独 立拥有系统资源,但它可与同属一个进程的其它线程共享该进程所拥有的全部资源。每一个应用程序都至少有一个进程和一个线 程。在单个程序中同时运行多个线程完成不同的被划分成一块
socketserver.py
import socket
# 1.初始化socket对象
import time
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# SOCK_STREAM: TCP面向连接的, 流式传输; 可靠,性能有损失.
# SOCK_DGRAM: UDP,面向于非连接的, 数据报文的传输; 不可靠, 性能非常好.
# www.baidu.com(ip)
# 2. 选址
ip = "127.0.0.1"
port = 50000 # 0-65535
server_socket.bind((ip, port))
# 3. 监听/建商店
# backlog 可以同时接受的等待连接数有多少
server_socket.listen(5)
# 4.接受客户端连接/开门营业
# client_socket专门负责与客户端通信的socket连接(导购员)
# address 客户端地址
# 这个方法是阻塞的 (blocked), 直到有客户端进来才会执行后续的代码
client_socket, address = server_socket.accept()
# 5. 数据传输
# 字符串->字节数组 1. b'' 2. encode方法,编码
# 字节数组转字符串: decode 解码
# ascii 0-9 a-b A-B $%^&*
# gbk/gb2312 中文
# 'utf-8'
client_socket.send("欢迎来超市购物".encode('utf-8')) # bytes而不是字符串
data = client_socket.recv(1024)
print("收到来自客户端的消息:" + data.decode('utf-8'))
# 6. 传输结束
client_socket.close()
server_socket.close()
socketclient.py
import socket
# 1.初始化socket对象, 客户端必须和服务端参数保持一致
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STR
前面我们了解了TCP/IP协议、IP地址和端口的基本概念,下面我们开始了解网络编程。
网络编程中有一个基本组件——套接字(socket)。套接字主要是两个程序之间的“信息通道”。 程序(通过网络连接)可能 分布在不同的计算机上,通过套接字相互发送信息。
socket是网络编程的一个抽象概念。通常我们用一个socket表示“打开了一个网络连接”,而打开一个socket需要知道目标计 算机的IP地址和端口号,并且指定协议类型。
大多数连接都是可靠的TCP连接。创建TCP连接时,主动发起连接的是客户端,被动响应连接的是服务器。
socket编程交互模型如下图所示
服务器编程首先要绑定一个端口,监听来自其他客户端的连接。如果某个客户端发起连接了,服务器就与该客户端建立socket 连接,随后的通信就靠这个socket连接了。
服务器会打开固定端口监听,每发起一个客户端连接,就创建该socket连接。由于服务器有大量来自客户端的连接,因此要能 够区分一个socket连接是和哪个客户端绑定的。确定4项唯一的socket依赖:服务器地址、服务器端口、客户端地址、客户端端 口。
服务端创建流程
一般来说,建立服务器连接需要6个步骤。
在Python中, import socket 后,用 socket.socket() 方法来创建套接字,语法格式如下
s = socket.socket([family[, type[, proto]]])
参数说明:
直接socket.socket(),则全部使用默认值。
下面是具体的参数定义:
将socket绑定(指派)到指定地址上
socket.bind(address)
address必须是一个双元素元组((host,
'''
c/s模式编程: c客户端, s服务端
TCP套接字
套接字模块: socket
netfamily: socket.AF_INET(跨网络通信)/AF_UNIX(计算机本地通信,效率更高,对操作系统有一定要求,
需要unix-like(uninx/linux/macos)操作系统)
SOCK_STREAM: tcp套接字类型
SOCK_DGRAM: UDP套接字类型
域名和ip的关系:
域名: www.baidu.com, 通俗的说域名是ip的一个别名, 103.235.46.39
www.baidu.com -> 103.235.46.39
www.baidu.com -> 103.235.46.40
1. 域名容易记忆, ip不容易记忆
2. ip会发生变化, 域名相对不会发生变化
变量是内存地址的一个别名, 0x12345678, 非常容易识别好记
实现一个tcp的客户端
'''
import socket
# 1. 初始化套接字实例(socket)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 连接服务器, ip+端口(port)
baidu_ip = 'www.baidu.com'
baidu_port = 80
s.connect((baidu_ip, baidu_port))
# 3. 发送请求数据
s.send(b'GET / HTTP/1.1\r\nUser-Agent: curl/7.29.0\r\nHost: www.baidu.com\r\nAccept: */*\r\nConnection: close\r\n\r\n')
# 4. 接收响应数据, receive
d = s.recv(1024) # buffer缓冲区的大小, 容器大小
print(d)
# 5. 关闭网络连接, 释放资源
s.close()
计算机网络是独立自主的计算机互联而成的系统的总称,组建计算机网络最主要的目的是实现多台计算机之间的通信和资源共 享。
今天计算机网络中的设备和计算机网络的用户已经多得不可计数,而计算机网络也可以称得上是一个“复杂巨系统”.
1960s - 美国国防部ARPANET (阿帕网络(Advanced Research Project Agency Network))项目问世,奠定了分组交 换网络的基础。
1980s - 国际标准化组织(ISO)发布OSI/RM,奠定了网络技术标准化的基础。
1990s - 英国人蒂姆·伯纳斯-李发明了图形化的浏览器,浏览器的简单易用性使得计算机网络迅速被普及。
在没有浏览器的年代,上网是这样的。
有了浏览器以后,上网是这样的。
实现网络通信的基础是网络通信协议,这些协议通常是由互联网工程任务组 (IETF)制定的。
所谓“协议”就是通信计算机双方必须共同遵从的一组约定,例如怎样建立连接、怎样互相识别等,网络协议的三要素是:语 法、语义和时序。
构成我们今天使用的Internet的基础的是TCP/IP协议族,所谓协议族就是一系列的协议及其构成的通信模型,我们通常也把这套 东西称为TCP/IP模型。
与国际标准化组织发布的OSI/RM这个七层模型不同,TCP/IP是一个四层模型,也就是说,该模型将我们使用的网络从逻辑上分 解为四个层次,自底向上依次是:网络接口层、网络层、传输层和应用层,如下图所示。
IP通常被翻译为网际协议,它服务于网络层,主要实现了寻址和路由的功能。
接入网络的每一台主机都需要有自己的IP地址,IP地址就是主机在计算机网络上的身份标识
当然由于IPv4地址的匮乏,我们平常在家里、办公室以及其他可以接入网络的公共区域上网时获得的IP地址并不是全球唯一的IP 地址,而是一个局域网(LAN)中的内部IP地址,通过网络地址转换(NAT)服务我们也可以实现对网络的访问。
计算机网络上有大量的被我们称为“路由器”的网络中继设备,它们会存储转发我们发送到网络上的数据分组,让从源头发出的 数据最终能够
import string
import tkinter
from tkinter import ttk
import random as r
window = tkinter.Tk()
window.title("随机密码生成器")
'''
gui 事件驱动
1. 根据用户的操作去进行响应的处理
2. 定时器, after, 在多少时间之后触发一个函数的回调, 格式: win.after(delay_ms,func=函数,*args), 只会执行一次
3. 事件绑定, bind, 专门将事件和一个函数进行绑定, 格式: win.bind(sequence=<事件名>,func=函数)
3.1 按键: 键盘敲击某个按键去调用一个函数
3.2 鼠标: 鼠标按某个键(左/右/中)去调用一个函数
事件名如何表示:
- 鼠标事件: Button: 代表所有鼠标键的点击
Button-1: 代表鼠标左键的点击
Button-2/3: 代表鼠标右键的点击
ButtonPress: 鼠标按下事件
ButtonPress-1: 代表鼠标左键的点击
ButtonPress-2/3: 代表鼠标右键的点击
ButtonRelease: 鼠标抬起事件
ButtonRelease-1: 代表鼠标左键的点击
ButtonRelease-2/3: 代表鼠标右键的点击
- 按键事件: KeyPress: 按键按下
KeyRelease: 按键抬起
KeyPress-a: 按字母按下事件, 区分大小写
Control-Shift-KeyPress-H: 组合键
KeyPress-Return: 回车键Enter
函数: bind函数有固定格式, 一个参数event
'''
def func01():
print("after function")
window.after(100, func=func01)
# window.after(5000, func=func01)
def f
根据本节课所学的知识, 思考如何实现一个滚动抽奖器? (注: 鼓励同学们动手尝试,不要求全部实现)
效果如下:
图中: 即将开始
为显示区1
, 幸运儿是你吗?
为显示区2
要求:
显示区2
更新为正在抽奖
, 并且会一直在显示区1
上滚动显示候选人的名字, 滚动频率为100毫秒,每次滚动随机从候选者名单中抽取一个名字显示区1
停止滚动, 此时显示区1
上显示的候选人的名字即为幸运观众, 同时将幸运观众的名字显示在显示区2上
, 例如: 恭喜xxx!!!