# socket
socket
1. 概述
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
3. socket基础
2. socket聊天室代码
-
socket server代码的步骤一般如下
-
服务器端
- 服务器首先初始化好socket
- 绑定这个socket服务器运行的ip地址和端口
- 监听这个服务器
- 等待socket连接,这个步骤会一直阻塞,直到有socket链接进来
- 当连接成功之后,服务器就可以读取客户端发送的socket消息
- 服务器可以选择给客户端回传socket消息
- 服务器关闭socket
-
客户端
- 客户端初始化socket
- 客户端去和服务器建立链接
- 客户端向服务器发送数据
- 客户端等待服务器相应数据
- 客户端关系socket
-
按照上述描述,python代码(伪)书写应该如下
-
ss = socket()
ss.bind()
ss.listen()
inf_loop:
cs = ss.accept()
comm_loop:
cs.recv()/cs.send()
cs.close()
ss.close()
-
python socket库基础知识
-
要创建套接字,必须使用 socket.socket()函数,它一般的语法如下。 socket(socket_family, socket_type, protocol=0)。socket.socket是new出来socket套接字对象,但是通信的运输层有tcp或者upd来通信. 网络层有ip协议来传输和本地[非网络的 AF_LOCAL/AF_UNIX]套接字来传输数据。所以我们需要配置socket_family和socket_type
-
套接字是网络编程中的一种通信机制,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
-
socket_family
1. AF_INET表示使用ipv4协议来传输 2. AF_UNIX表示使用非网络的套接字来传输
-
socket_type
1. SOCK_STREAM 表示tcp协议 2. SOCK_DGRAM 表示使用upd协议来连接
-
综合:
所以说 tcpSock = socket(AF_INET, SOCK_STREAM)表示使用ip和tcp来进行通信
-
-
s.bind():
将地址(主机名、端口号对)绑定到套接字上 -
ss.listen():
设置并启动 TCP 监听器
-
ss.accept():
被动接受 TCP 客户端连接,一直等待直到连接到达(阻塞)
-
s.recv():
接受socket消息,请注意 上面的方法是接受连接
-
s.send():
发送 TCP 消息
-
s.close():
关闭套接字
-
-
服务器/客户端实际代码v1(简单的流程演示):
-
服务器代码:
import socket
bind_address = ("0.0.0.0",19527) #或者127.0.0.1本地回环网,0.0.0.0是默认换为内网的网址。
#申明
ss = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定地址和监听
ss.bind(bind_address)
ss.listen()
#获取客户端socket对象和地址
cli,addr = ss.accept()
#获取tcp消息 一次1kb
msg = cli.recv(1024)
print(msg)
#回送消息
cli.send(b'get')
ss.close()
- 客户端代码
import socket
send_address = ("0.0.0.0",19527)
cli = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#直接连接
cli.connect(send_address)
#发送数据
cli.send(b'teset')
#接受数据
msg = cli.recv(1024)
print(msg)
cli.close()
-
服务器/客户端代码(更有交互性的代码)
-
说明:
这次的代码,让服务器和客户端更有交互性,客户端输入消息后,服务器返回消息 -
代码:
服务端:
-
import socket
ss_address = ("0.0.0.0",19534) #地址linux用0.0.0.0:n 实验,
#windows用本机地址cmd中输出 ipconfig 查看
ss = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#socket 连接对象,STREAM(stream)数据流
#socket.AF_INET使用ip4协议 socket.AF_INET6使用ip6
ss.bind(ss_address) #占用一个端口,ip 0.0.0.0:19534 本地用
ss.listen(2) #对端口进行监听,实时接收客户端的请求内容
while True: #建立循环,
cli,addr = ss.accept() #注意:accept()函数会返回一个元组
#元素1为客户端的socket对象,元素2为客户端的地址(ip地址,端口号)
print('收到来自ip为{}的连接'.format(addr)) #提示连接成功
#在连接的时候,进入循环
while True:
# 接受消息
data = cli.recv(1024)
if not data: #当接收的内容为空不存在的时候,
break #跳出最近的一次循环
print("发送请求内容为:{}".format(data.decode("utf-8")))
re_msg = "收到你的消息{}".format(data.decode('utf8')) #定义变量接收客户端的消息
cli.send(re_msg.encode('utf8')) #再将内容传给客户端
cli.close()
客服端:
import socket
cli = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建一个名为cli客户端的socket对象
send_address = ("0.0.0.0",19534) #定义变量send_address 储存ip和端口
cli.connect(send_address) #连接服务端
while True:
neirong = input("输入内容:") #输入传送的内容
cli.sendall(bytes(neirong, encoding="utf8")) #编码并且传送内容刚给服务端
data = cli.recv(1024) #接收服务端返回的内容
if not data: #判断为不为空
break
print(data.decode("utf8")) #把服务端返回内容解码并打印
cli.close()
-
客户端接受http请求,并且返回http代码
-
说明:
我们在获取http代码的时候,获取http代码的库文件,或者浏览器底层,就是用了socket来获取http报文,我们可以模拟底层的报文请求方式,来深一步的了解http报文结构 -
代码:
-
# 使用socket获取http报文
import socket
from urllib.parse import urlparse
# 初始化socket
ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
domain = "https://www.baidu.com"
url = urlparse(domain)
host = url.netloc
path = url.path
if path == "":
path = "/"
ss.connect((host, 80))
#'\r'的本意是回到行首,'\n'的本意是换行
data = "GET {} HTTP/1.1\r\nHost:{}\r\nConnection:close\r\nUser-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36\r\n\r\n".format(
path, host).encode('utf8')
ss.send(data)
res = b""
#报文就不像聊天数据那么小了 需要循环的拼接报文
while True:
d = ss.recv(1024)
if d:
res += d
else:
break
print(res.decode('utf8'))
ss.close()
-
多线程聊天代码演示
-
说明: 刚才的版本只能接受一个连接,我们可以使用多线程版本来优化代码
-
代码:
-
import socket
import threading
def cli_msg(cli,addr):
print('收到来自ip为{}的连接'.format(addr))
#在连接的时候,进入循环
while True:
# 接受消息
data = cli.recv(1024)
if not data:
break
re_msg = "收到你的消息{}".format(data.decode('utf8'))
cli.send(re_msg.encode('utf8'))
cli.close()
return True
ss_address = ("0.0.0.0",19534)
ss = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ss.bind(ss_address)
ss.listen(2)
while True:
#获取连接
cli,addr = ss.accept()
threading.Thread(target=cli_msg,args=(cli,addr)).start()
import socket
ss_address = ("0.0.0.0",19534)
ss = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ss.connect(ss_address)
while True:
data = input('请输入消息:')
if not data:
break
ss.send(data.encode('utf8'))
print(ss.recv(1024).decode('utf8'))
ss.close()
多人聊天多线程
(1)
# 1. 服务端
from queue import Queue #安全队列
import socket #协议库
from concurrent.futsure import ThreadPoolExecutor #多线程标准模块concurrent.futsure
# 线程池ThreadPoolExecutor
class Server:
"""
1. 接收多人的链接进来
2. 收到一个人的消息之后,把消息广播给所有人
"""
def __init__(self, bind_ip='0.0.0.0', port=21805):
"""
初始化函数
:param bind_ip:
:param port:
"""
self.bind_address = (bind_ip, port)# 初始化绑定ip
# 初始化的tcp链接, socket.AF_INET使用ip4协议,socket.SOCK_STREAM对象数据流
self.ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ss.bind(self.bind_address) #服务端绑定ip和端口
self.ss.listen(10) #进行端口监听
self.pools = ThreadPoolExecutor(20) #设置多线程,和线程数上限为20
"""
广播
"""
self.msg_queue = Queue() #初始化一个安全队列
"""
ip榜单
"""
self.ip_list = [] #初始化ip的list
self.black_list = [] #初始化黑名单的ip的list
# 所有人链接进来的表
self.conns = [] #所有人的链接对象
def start(self):
"""
接收链接
:return:
"""
self.pools.submit(self.send_msg) #启动线程池,运行send_msg函数,即发送消息,广播
while True:
conn, addr = self.ss.accept() #获取链接字节套对象 (接收和发送数据),地址ip
if self.is_black(addr): #判断ip信息是否存在黑名单的list里面
continue
self.pools.submit(self.recv_msg, conn, addr) #启用线程池,运行recv_msg,即接收消息
self.conns.append(conn)
def recv_msg(self, conn, addr): #接收消息
while True:
msg = conn.recv(1024) #每一次接收的小大 1024b
if (not msg) or (len(msg) == 0): #判断有误内容
self.remove_conn(conn,addr) #无内容调用函数remove_conn
self.msg_queue.put((msg, addr)) #有内容,则添加msg和addr入安全队列
print('收到ip为{}的消息{}'.format(addr, msg.decode())) #打印
def send_msg(self):
while True:
msg, addr = self.msg_queue.get()
ip, port = addr
for conn in self.conns:
try:
conn.send("ip为{},port为{},说:{}".format(ip, port, msg.decode()).encode())
except Exception:
self.remove_conn(conn,addr)
def remove_conn(self,conn,addr): #用户退出聊天,删除他的list里面的信息
conn.close()
self.conns.remove(conn) #从conns 列表中删除用户信息
msg = '我已经退出聊天'.encode()
self.msg_queue.put((msg, addr)) #添加安全队列,进行广播
def is_black(self, addr):
"""
判断黑名单用户
:return:
"""
ip, port = addr
if ip in self.black_list:
return True
else:
return False
if __name__ == "__main__":
ss = Server()
ss.start()
# 2. 客户端
import socket #协议库
from concurrent.futsure import ThreadPoolExecutor #多线程标准模块concurrent.futsure
# 线程池ThreadPoolExecutor
class Client:
def __init__(self,ip='0.0.0.0',port=21805):
self.ip = ip
self.port = port
self.bind_ip = (ip, port)
self.ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# socket 连接对象,STREAM(stream)数据流
# socket.AF_INET使用ip4协议 socket.AF_INET6使用ip6
self.pools = ThreadPoolExecutor(5) #启用最大五个线程
def start(self):
self.ss.connect(self.bind_ip) #客户端连接服务端ip和端口
self.pools.submit(self.send) #启用线程池运行send函数,发送消息
self.pools.submit(self.recv) #启用线程池运行recv函数,接受消息
def send(self):
while True:
msg = input('输入消息:')
self.ss.send(msg.encode())
def recv(self):
while True:
msg = self.ss.recv(1024)
print('收到消息{}'.format(msg.decode()))
if __name__ == "__main__":
c = Client()
c.start()
(2)
"""
1. 多人聊天室
2. 服务端可以接受多个客户端的链接
3. 客户端发送的消息可以通过服务端广播给所有人
"""
import socket
from concurrent.futures import ThreadPoolExecutor
class Server:
def __init__(self, bind_ip='0.0.0.0', port=30001, listen_num=25):
"""
1. 初始化socket服务
"""
# 初始化tcp链接
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址
self.bind_address = (bind_ip, port)
self.server.bind(self.bind_address)
# 默认放25个客户端进来
self.server.listen(listen_num)
# 所有的客户端链接都放在这里面
self.conns = list()
self.pools = ThreadPoolExecutor(listen_num + 5)
# 字典 对应 conn对应的线程池
self.conn2pool = dict()
def accept(self):
"""
接受客户端的链接
:return:
"""
conn, addr = self.server.accept()
self.conns.append(conn)
return conn, addr
def multiple_accept(self):
"""
接受多个客户端链接
:return:
"""
while 1:
conn, addr = self.accept()
# 让recv_msg 不阻塞 每个conn(链接都有一个独立的线程去收消息)
pool = self.pools.submit(self.recv_msg, conn, addr)
print(addr)
self.conn2pool.update({conn:pool})
def recv_msg(self, conn, addr):
"""
1. 一个客户端接受消息
:param conn:
:return:
"""
while 1:
msg = conn.recv(1024)
if (not msg) or len(msg) == 0:
"""
当消息为空或者没有消息还发送过来的时候,断开链接
"""
self.break_conn(conn)
self.broadcast_msg(msg, addr)
def broadcast_msg(self, msg, addr):
"""
广播消息
:return:
"""
for conn in self.conns:
self.send_msg(conn, msg, addr)
def send_msg(self, conn, msg, addr):
"""
发送消息
:param msg:
:return:
"""
msg = self.handle_msg(msg, addr)
try:
conn.send(self.str2bytes(msg))
except Exception:
self.break_conn(conn)
def bytes2str(self, msg):
"""
二进制转字符串
:param msg:
:return:
"""
try:
return msg.decode()
except Exception:
return msg
def str2bytes(self, msg):
"""
字符串转二进制
:param msg:
:return:
"""
try:
return msg.encode()
except Exception:
return msg
def handle_msg(self, msg, addr):
msg_str = "用户---{}--说:{}".format(addr, self.bytes2str(msg))
print(msg_str)
return msg_str
def break_conn(self, conn):
conn.close()
self.conns.remove(conn)
#回收线程
pool = self.conn2pool[conn]
print('{}断开链接'.format(conn))
pool.shutdown()
def start(self):
print('server start')
self.multiple_accept()
print('server end')
if __name__ == '__main__':
s = Server()
s.start()
import socket
from concurrent.futures import ThreadPoolExecutor
class Client:
def __init__(self, ip='10.2.0.78', port=30001): #自己的机子的ip即可,
self.ip = ip
self.port = port
self.bind_ip = (ip, port)
self.ss = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.pools = ThreadPoolExecutor(5)
def start(self):
self.ss.connect(self.bind_ip)
self.pools.submit(self.send)
self.pools.submit(self.recv)
def send(self):
while True:
msg = input('输入消息:')
self.ss.send(msg.encode())
def recv(self):
while True:
msg = self.ss.recv(1024)
if (not msg) or len(msg) == 0:
"""
当消息为空或者没有消息还发送过来的时候,断开链接
"""
self.ss.close()
break
print('收到消息{}'.format(msg.decode()))
print('链接已经断开')
if __name__ == "__main__":
c = Client()
c.start()