看完本篇博客,你将学会如何用Python制作一个简单的聊天器
套接字(socket)
两种类型:基于文件的和面向网络的
套接字家族:AF_UNIX
、AF_NETLINK
、AF_TIPC
和AF_INET
套接字地址:主机-端口对
有连接的套接字:使用传输控制协议(TCP),使用SOCK_STREAM
作为套接字类型
无连接的套接字:使用用户数据报协议(UDP),使用SOCK_DGRAM
作为套接字类型
Python中的网络编程
socket()函数
创建套接字的语法:
socket(socket_family, socket_type, protocol=0)
socket_family
是AF_UNIX
或AF_INET
,socket_type
是SOCK_STREAM
或SOCK_DGRAM
。protocol
通常省略,默认为0。
import socket
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建TCP/IP套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 创建UDP/IP套接字
套接字对象方法
名称 | 描述 |
---|---|
服务器套接字方法 | |
s.bind() | 将地址(主机名、端口号对)绑定到套接字上 |
s.listen() | 设置并启动TCP 监听器 |
s.accept() | 被动接受TCP 客户端连接,一直等待直到连接到达(阻塞) |
客户端套接字方法 | |
s.connnect() | 主动发起TCP 服务器连接 |
s.connect_ex() | connect()的扩展版本,此时会以错误码的形式返回问题,而不是抛出一个异常 |
普通套接字方法 | |
s.recv() | 接收TCP 消息 |
s.recv_into() | 接收TCP 消息到指定的缓冲区 |
s.send() | 发送TCP 消息 |
s.sendall() | 完整地发送TCP 消息 |
s.recvfrom() | 接收UDP 消息 |
s.recvfrom_into() | 接收UDP 消息到指定的缓冲区 |
s.sendto() | 发送UDP 消息 |
s.getpeername() | 连接到套接字(TCP)的远程地址 |
s.getsockname() | 当前套接字的地址 |
s.getsockopt() | 返回给定套接字选项的值 |
s.setsockopt() | 设置给定套接字选项的值 |
s.shutdown() | 关闭连接 |
s.close() | 关闭套接字 |
s.detach() | 在未关闭文件描述符的情况下关闭套接字,返回文件描述符 |
s.ioctl() | 控制套接字的模式(仅支持Windows) |
面向阻塞的套接字方法 | |
s.setblocking() | 设置套接字的阻塞或非阻塞模式 |
s.settimeout() | 设置阻塞套接字操作的超时时间 |
s.gettimeout() | 获取阻塞套接字操作的超时时间 |
面向文件的套接字方法 | |
s.fileo() | 套接字的文件描述符 |
s.makefile() | 创建与套接字关联的文件对象 |
数据属性 | |
s.family | 套接字家族 |
s.type | 套接字类型 |
s.proto | 套接字协议 |
创建TCP服务器
伪码描述
ss = socket() # 创建服务器套接字
ss.bind() # 套接字与地址绑定
ss.listen() # 监听连接
inf_loop: # 服务器无限循环
cs = ss.accept() # 接受客户端连接
comm_loop: # 通信循环
cs.recv()/cs.send() # 对话(接收/发送)
cs.close() # 关闭客户端套接字
ss .close() # 关闭服务器套接字#(可选)
TCP时间戳服务器
# tsTserv.py
from socket import *
from time import ctime
HOST = ''
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpSerSock = socket(AF_INET, SOCK_STREAM)
tcpSerSock.bind(ADDR) # 绑定服务器地址
tcpSerSock.listen(5) # 开启TCP监听器,参数表示在连接被转接或拒绝之前,传入连接请求的最大数
while True:
print('waiting for connection...')
tcpCliSock, addr = tcpSerSock.accept() # 等待客户端连接
print('...connected form:{}'.format(addr))
while True:
data = tcpCliSock.recv(BUFSIZ) # 接收TCP消息
if not data:
break
tcpCliSock.send(bytes('[%s] %s' % (ctime(), str(data)), 'utf-8'))
# 添加事件戳
tcpCliSock.close()
tcpServerSock.close() # 永远不会被执行
支持IPV6
:将AF_INET
改为AF_INET6
创建TCP客户端
伪码描述
cs = socket() # 创建客户端套接字
cs.connect() # 尝试连接服务器
comm_loop: # 通信循环
cs.send()/cs.recv() # 对话(发送/接收)
cs .close() # 关闭客户端套接字
TCP时间戳客户端
# tsTclnt.py
from socket import *
HOST = 'localhost' # or '127.0.0.1'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)
while True:
data = input('> ')
if not data:
break
tcpCliSock.send(bytes(data, 'utf-8')
data = tcpCliSock.recv(BUFSIZ)
if not data:
break
print(data.decode('utf-8'))
tcpCliSock.close()
支持IPV6
:将本地主机改成::1
,同时将AF_INET
改为AF_INET6
执行TCP服务器和客户端
先运行tsTserv.py
waiting for connection...
再运行tsTclnt.py
> hi
[Sat Aug 29 10:08:30 2020] b'hi'
> Hello
[Sat Aug 29 10:08:34 2020] b'Hello'
>
即可获取时间戳,至于字符串前的b
应该是使用bytes
转换时导致的
创建UDP服务器
伪码描述
ss = socket() # 创建服务器套接字
ss.bind() # 绑定服务器套接字
inf_loop: # 服务器无限循环
cs = ss.recvfrom()/ss.sendto() # 关闭(接收/发送)
ss.close() # 关闭服务器套接字
对比TCP
服务器
ss = socket() # 创建服务器套接字
ss.bind() # 套接字与地址绑定
ss.listen() # 监听连接
inf_loop: # 服务器无限循环
cs = ss.accept() # 接受客户端连接
comm_loop: # 通信循环
cs.recv()/cs.send() # 对话(接收/发送)
cs.close() # 关闭客户端套接字
ss .close() # 关闭服务器套接字#(可选)
UDP时间戳服务器
# tsUserv.py
from socket import *
from time import ctime
HOST = ''
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
udpSerSock = socket(AF_INET, SOCK_DGRAM)
udpSerSock.bind(ADDR)
while True:
print('waiting for message...')
data, addr = udpSerSock.recvfrom(BUFSIZ)
udpSerSock.sendto(bytes('[%s] %s' % (ctime(), data), 'utf-8'), addr)
print('...received form and return to:{}'.format(addr))
udpSerSock.close()
创建UDP客户端
伪码描述
cs = socket() # 创建客户端套接字
comm_loop: # 通信循环
cs.sendto()/cs.recvfrom() # 对话(发送/接收)
cs .close() # 关闭客户端套接字
UDP时间戳客户端
# tsUclnt.py
from socket import *
HOST = 'localhost'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
udpCliSock = socket(AF_INET, SOCK_DGRAM)
while True:
data = input('> ')
if not data:
break
udpCliSock.sendto(bytes(data, 'utf-8'), ADDR)
data, ADDR = udpCliSock.recvfrom(BUFSIZ)
if not data:
break
print(data.decode('utf-8'))
udpCliSock.close()
执行UDP服务器和客户端
与TCP
一致
socket模块属性
socketserver模块
创建socketserver TCP服务器
# tsTservSS.py
from socketserver import (TCPServer as TCP, StreamRequestHandler as SRH)
from time import ctime
HOST = ''
PORT = 21567
ADDR = (HOST, PORT)
class MyRequestHandler(SRH): # 继承SRH
def handle(self) -> None: # 接收到客户端消息时调用该程序
print('...connected from:{}'.format(self.client_address))
self.wfile.write(bytes('[%s] %s' % (ctime(), self.rfile.readline()), 'utf-8'))
tcpServ = TCP(ADDR, MyRequestHandler) # 创建服务器
print('waiting for connect...')
tcpServ.serve_forever()
创建socketserverTCP客户端
# tsTclntSS.py
from socket import *
HOST = 'localhost'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
while True:
tcpCliSock = socket(AF_INET, SOCK_STREAM) # 每次都新建一个套接字
tcpCliSock.connect(ADDR)
data = input('> ')
if not data:
break
tcpCliSock.send(bytes('%s\r\n' % data, 'utf-8'))
data = tcpCliSock.recv(BUFSIZ)
if not data:
break
print(data.decode().strip())
tcpCliSock.close()
相关模块
习题
(并非标准答案)
面向连接的套接字和无连接套接字之间的区别是什么?
-
面向连接的套接字通信前必须要先建立连接,而无连接套接字不需要
-
两者采用的协议不同,前者采用
TCP
协议,后者采用UDP
协议 -
前者提供序列化、可靠的和不重复的数据交付,没有记录边界,更加可靠
后者无法保证顺序性、可靠性、重复性,但保存了数据边界
-
后者的成本更加低廉,开销更小
TCP 和UDP 之中,哪种类型的服务器接受连接,并将它们转换到独立的
套接字进行客户端通信?
TCP
制作一个简易的聊天器
# chatSer.py, tcp服务器
from socket import *
HOST = ''
PORT = 21567
ADDR = (HOST, PORT)
BUFSIZ = 1024
tcpSerSock = socket(AF_INET, SOCK_STREAM)
tcpSerSock.bind(ADDR)
tcpSerSock.listen(5)
while True:
print("waiting for connection...")
tcpCliSock, addr = tcpSerSock.accept()
print("connected from addr:{}".format(addr))
while True:
data = tcpCliSock.recv(BUFSIZ)
if not data:
break
print(data.decode('utf-8'))
messa = input("> ")
tcpCliSock.send(bytes(messa, "utf-8"))
tcpCliSock.close()
# chatCli.py, tcp客户端
from socket import *
HOST = 'localhost'
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)
while True:
messa = input("> ")
if not messa:
break
tcpCliSock.send(bytes(messa, "utf-8"))
data = tcpCliSock.recv(BUFSIZ)
if not data:
break
print(data.decode("utf-8"))
tcpCliSock.close()
socketserver实现:
# chatSerSS.py
from socketserver import TCPServer as TCP, StreamRequestHandler as SRH
HOST = ""
PORT = 21567
ADDR = (HOST, PORT)
class MyRequestHandler(SRH):
def handle(self) -> None:
self.data = self.request.recv(1024)
if not self.data:
return
print(self.data.decode("utf-8"))
mess = input("> ")
self.wfile.write(bytes(mess, "utf-8"))
tcpSer = TCP(ADDR, MyRequestHandler)
print("waiting for connect ...")
tcpSer.serve_forever()
# charCliSS.py
from socket import *
HOST = "localhost"
PORT = 21567
ADDR = (HOST, PORT)
while True:
tcpCli = socket(AF_INET, SOCK_STREAM)
tcpCli.connect(ADDR)
mess = input("> ")
if not mess:
break
tcpCli.send(bytes(mess, "utf-8"))
ret = tcpCli.recv(1024)
if not ret:
break
print(ret.decode('utf-8'))
tcpCli.close()
如果你在字符串中见到了b''
类型的字符串,可以使用decode()
方法解码
如果您觉得我的文章对您有帮助的话,可以点个赞,点个关注,也可以扫描下方二维码关注我。我将在这个公众号上更新自己的学习笔记,以及分享一些算法知识
Study and progress together with me!