看完本篇博客,你将学会如何用Python制作一个简单的聊天器

套接字(socket)

两种类型:基于文件的和面向网络的

套接字家族:AF_UNIXAF_NETLINKAF_TIPCAF_INET

套接字地址:主机-端口对

有连接的套接字:使用传输控制协议(TCP),使用SOCK_STREAM作为套接字类型

无连接的套接字:使用用户数据报协议(UDP),使用SOCK_DGRAM作为套接字类型

Python中的网络编程

socket()函数

创建套接字的语法:

socket(socket_family, socket_type, protocol=0)

socket_familyAF_UNIXAF_INETsocket_typeSOCK_STREAMSOCK_DGRAMprotocol 通常省略,默认为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模块属性

python socket bind 绑定网卡无效 python socket with_socket

python socket bind 绑定网卡无效 python socket with_套接字_02

socketserver模块

python socket bind 绑定网卡无效 python socket with_socket_03

创建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()

相关模块

python socket bind 绑定网卡无效 python socket with_socket_04

习题

(并非标准答案)

面向连接的套接字和无连接套接字之间的区别是什么?

  1. 面向连接的套接字通信前必须要先建立连接,而无连接套接字不需要
  2. 两者采用的协议不同,前者采用TCP协议,后者采用UDP协议
  3. 前者提供序列化、可靠的和不重复的数据交付,没有记录边界,更加可靠
    后者无法保证顺序性、可靠性、重复性,但保存了数据边界
  4. 后者的成本更加低廉,开销更小

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()方法解码