文章目录
一、套接字(socket)概述
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
socket有很多种类型,但是咱们只需要知道两种就可以了
sk = socket.socket(family = AF_INET, type=SOCK_STREAM)
family :
1.AF_UNIX基于文件类型的套接字(早期socket是源自于unix系统而研发的一个功能,主要是为了同一台电脑上,多个程序直接通信) unix系统的中心思想是 : 一切皆文件
2.AF_INET基于网络类型的套接字
type:
一种是基于TCP协议 SOCK_STREAM
一种是基于UDP协议 SOCK_DGRAM
tcp 协议 : 可靠的,面向连接的,面向字节流形式的传输方式
udp协议 : 不可靠的,不面向连接的,面向数据报的传输方式,但是它快
打开socket源码可以看到:
二、基于TCP协议的socket
所有的业务逻辑都在接收连接内进行,其他的都是一样的。
socket初使用
程序示例:
server_.py
import socket
import time
sk = socket.socket()# 不传参数,默认使用基于网络类型的套接字, 协议 : TCP
# 127.0.0.1
sk.bind(('127.0.0.1',8080))# 端口的范围是0-65535 但是 0-1023 这些是保留的,别用
sk.listen()# 监听,同时能接受的连接
print(123)
conn,addr = sk.accept()# 等待接受客户端的连接 阻塞等待
print(456) # 客户端运行连接之后才执行
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
print('conn:',conn)
print('addr:',addr)
time.sleep(20)
conn.close()
sk.close()
client_.py
import socket
import time
sk = socket.socket()
sk.connect(('127.0.0.1',8080))# 连接
sk.send(b'hello!')
ret = sk.recv(1024) # 对话(发送/接收)
print(ret)
time.sleep(20)
sk.close()
刚开始运行服务器端(server_.py):
由于客户端还没连接上服务器端, 所以会先等待接受客户端的连接 (阻塞等待)
运行客户端(client_.py):
发现已经连接上服务器端,并接收到服务器端发来的数据(b’hi’)
再打开server_.py的控制台:
可以看到客户端连接之后,456被打印出来,并且收到了客户端发来的数据(b’hello!’)
conn显示的是连接信息,里面的laddr是服务器端的ip地址和端口号,raddr是客户端的ip地址和客户端运行之后自己产生的端口号
addr:客户端的ip地址和端口号, ip地址 + 端口 能唯一找到某台电脑上的某一个服务程序
如果发送的是中文数据,则使用编解码
程序示例:
sever_.py
import socket
import time
sk = socket.socket()
sk.bind(('127.0.0.1',18080))
sk.listen()# 开机
print(123)
conn,addr = sk.accept()
msg_r = conn.recv(10)# 接受数据,接受10个字节
print(msg_r.decode('utf-8'),addr)
conn.close()
sk.close()
client_.py
import socket
import time
sk = socket.socket()
sk.connect(('127.0.0.1',18080))# 连接
sk.send('中文'.encode('utf-8'))
sk.close()
服务器端收到效果:
简易聊天程序
基于以上代码,将此改成,可以一直聊天的逻辑,并且带 标识退出的的逻辑,程序示例:
sever_2.py
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',10086))
sk.listen()
conn,addr = sk.accept()
while 1:
mess = conn.recv(1024)
print(mess.decode('utf-8'))
if mess == 'q':
break
mess2 = input('服务器端发送消息:')
conn.send(mess2.encode('utf-8'))
if mess2 == 'q':
break
conn.close()
sk.close()
client_2.py
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',10086))
while 1:
mess = input('客户端发送消息:')
sk.send(mess.encode('utf-8'))
if mess == 'q':
break
mess2 = sk.recv(1024)
print(mess2.decode('utf-8'))
if mess2 == 'q':
break
sk.close()
效果展示:
客户端:
服务器端:
能实现聊天,但是,这样也有一种问题,如果再开一个客户端给服务器发消息,则服务器端收不到第二个客户端发的消息,就像服务器在接电话,第三者给他打电话就不行。那如果想接第三者的电话,必须先挂断正在进行的通话。
服务器端的代码可以试着这样改:
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',10086))
sk.listen()
while 1:
conn,addr = sk.accept() # 一直接电话
while 1:
mess = conn.recv(1024)
print(mess.decode('utf-8'))
if mess == 'q':
break
mess2 = input('服务器端发送消息:')
conn.send(mess2.encode('utf-8'))
if mess2 == 'q':
break
conn.close()
sk.close()
效果:
客户1:
服务器端:
客户2:
这样能实现服务器与多个客户端轮流聊天,但是这里明显有一个缺陷,服务器端需要先挂断当前聊天才能与下一个客户端聊天,不能实现同时与多个客户端聊天。udp 就没有这个问题,可以说是相当强大了。
三、基于UDP协议的socket
可以看出,udp相比于tcp逻辑更简单,这意味着代码量会更少,并且最最重要的是服务器端可以同时和多个客户端通信。
基于UDP协议的简易聊天程序
sever_3.py
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',8990))
while 1:
s ,addr =sk.recvfrom(1024)
print(s.decode('utf-8'))
mess = input('服务器端发送消息:')
sk.sendto(mess.encode('utf-8'), addr)
sk.close()
client_3.py
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
while 1:
mess = input('客户端发送消息:')
sk.sendto(mess.encode('utf-8'),('127.0.0.1',8990))
s, addr = sk.recvfrom(1024)
print(s.decode('utf-8'))
sk.close()
效果:
客户端1:
服务器端:
客户2:
这样一个服务器端可以和多个客户端同时通信,从实用性上看,可以明显感觉到UDP用着更便捷。
改进,署名,输出带颜色
上面的客户端看着比较丑陋,咱们把客户端名字调整一下,并在服务器端输出文字带颜色,看起来更炫酷。
首先看一下颜色表,
图片截自:pycharm控制台输出带颜色
示例:
print('\033[31m你好~\033[0m')
效果:
万事具备,开搞:
sever_.py
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)# udp协议
sk.bind(('127.0.0.1',8090))
# 需求: 根据每个客户端的名字,去加上颜色
dic = {'悟空':'\033[32m','张三丰':'\033[33m','赵敏':'\033[35m'}
# 收发
while 1:
msg_r,addr = sk.recvfrom(1024)# 接收来自于哪里的消息
msg_r = msg_r.decode('utf-8')
name = msg_r.split(':')[0].strip()
color = dic.get(name,'')# get(key,default) 获取字典中key对应的value,如果没有key则返回default
print('%s %s \033[0m'%(color,msg_r))
msg_s = input(('>>>'))
sk.sendto(msg_s.encode('utf-8'),addr)# 发给谁的消息
sk.close()
client_.py
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)# udp协议
name = input('请输入您的名字:')
# 收发
while 1:
msg_s = input(('>>>'))
info = name + ' : ' + msg_s
sk.sendto(info.encode('utf-8'), ('127.0.0.1',8090)) # 发给谁的消息
msg_r,addr = sk.recvfrom(1024)# 接收来自于哪里的消息
print(msg_r.decode('utf-8'))
sk.close()
客户1:
客户2:
最终服务器端效果:
读书正能量分享
越来越糟,还是会侥幸变好,取决于你的努力,以及面对大方向的选择,这基本取决于一个人一生的浮沉。做一个努力的人的好处在于,人人见了你都想帮你,如果你自己不做出一点努力的样子,人家想拉你一把,都不知道你的手在哪里。
——刘同 《向着光亮那方》