socket相关概念
理解socket
'''
理解socket:
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
在设计模式中,Socket其实就是一个门面模式,
它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,
一组简单的接口就是全部,让Socket去组织数据,
以符合指定的协议。
其实站在你的角度上看,socket就是一个模块。
我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,
而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,
并且使用socket模块来与之通信。
套接字(socket)的发展史
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。
因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。
一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,
或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
基于文件类型的套接字家族:
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,
两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
基于网络类型的套接字家族:
套接字家族的名字:AF_INET
(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,
他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,
所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,
但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
'''
tcp协议和udp协议
'''
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、
传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。
使用TCP的应用:Web浏览器;电子邮件、文件传输程序。
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),
一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。
使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
'''
socket参数的详解
socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
'''
基于socket模块实现网络通信(tcp模式)
为什么要网络通信发送的是字节?而不是字符串?
py3, send/recv 都是字节
py2, send/recv 都是字符串
服务端:
accept,阻塞:等待客户端来连接。
recv, 阻塞:等待客户端发来数据。
客户端:
connect,阻塞:一直在连接,直到连接成功才往下运行其他代码。
recv, 阻塞:等待服务端发来数据。
#基于socket模块实现网络通信(tcp模式)
为什么要网络通信发送的是字节?而不是字符串?
py3, send/recv 都是字节
py2, send/recv 都是字符串
服务端:
accept,阻塞:等待客户端来连接。
recv, 阻塞:等待客户端发来数据。
客户端:
connect,阻塞:一直在连接,直到连接成功才往下运行其他代码。
recv, 阻塞:等待服务端发来数据。
如何查看自己的网络地址:
cmd->ipconfig
cmd->ipconfig/all 可以查看所有的网络信息,找到“以太网适配器”看ipv4首选项;局域网同理
#编码:
ascii 1byte 8位 00000001
unicode 4byte 32位 00000000 00000000 00000000 00000001 #python3中字符串默认使用Unicode
utf-8 可变长 目的是压缩 所以传输和接收时要用这种
gbk 可变长 目的是压缩
'''
套接字(socket)初使用
基于tcp协议的socket
tcp协议的socket使用
tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端
server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8898)) # 把地址绑定到套接字
sk.listen(5) # 监听链接
conn, addr = sk.accept() # 接受客户端链接
ret = conn.recv(1024) # 接收客户端信息
print(ret) # 打印客户端信息
conn.send(b'hi') # 向客户端发送信息
conn.close() # 关闭客户端套接字
sk.close() # 关闭服务器套接字(可选)
client端
import socket
sk = socket.socket() # 创建客户套接字
sk.connect(('127.0.0.1', 8898)) # 尝试连接服务器
sk.send(b'hello!')
ret = sk.recv(1024) # 对话(发送/接收)
print(ret)
sk.close() # 关闭客户套接字
在重启服务端时可能会遇到这种情况:
# 加入一条socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET, SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 就是它,在bind前加
sk.bind(('127.0.0.1', 8898)) # 把地址绑定到套接字
sk.listen(5) # 监听链接
conn, addr = sk.accept() # 接受客户端链接
ret = conn.recv(1024) # 接收客户端信息
print(ret) # 打印客户端信息
conn.send(b'hi') # 向客户端发送信息
conn.close() # 关闭客户端套接字
sk.close() # 关闭服务器套接字(可选)
TCP 客户端 访问网页
# 导入socket模块
import socket
# 创建一个客户端socket
# family = AF_INET, AF_INET 表示支持的ip协议版本, AF_INET表示的是ipv4, AF_INET6表示的是ipv6
# type = SOCK_STREAM, 表示数据以流的方式传输
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 与服务器建立一个连接
# address表示地址, ip,端口号
# address是元组的形式, 元素1是访问的服务器的ip地址(可以是域名),元素2是端口号
# www.sina.com.cn -----> DNS域名解析服务器 --->新浪ip地址
client_socket.connect(("www.sina.com.cn", 80))
# 发送一个网页请求
# 在字符串前加上b 就表是二进制形式的数据
client_socket.send(b"GET / HTTP/1.1\r\nHost:www.sina.com.cn\r\n\r\n")
# 将数据存储到列表中
dataList = []
# 将数据写到文件中去
# wf = open("sina.html","wb")
# 接收数据
while True:
# 接收到的是二进制数据
data = client_socket.recv(1024)
if len(data) == 0: # 读完了
break
dataList.append(data)
# 往文件中写
# wf.write(data)
# wf.flush()
# wf.close()
# 将数据拼接起来
strData = b"".join(dataList).decode(encoding="utf-8")
# print(strData)
# 关闭/释放资源
client_socket.close()
# 将响应头信息 与 html字符串分割
headStr, htmlStr = strData.split("\r\n\r\n", 1)
# print(headStr)
print(htmlStr)
流程图
tcp服务端 与 tcp客户端 的交互
server_socket.py 服务器端
import socket
# 创建服务器端socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址
# 自己主机的IP地址
server_socket.bind(("192.168.0.6", 20001)) # 元祖形式
# 设置监听器
server_socket.listen(5)
# 等待客户端连接服务器
# accept会阻塞程序的运行
# sock,addr=server_socket.accept()
client_sock, address = server_socket.accept()
print("服务器已经启动了")
# 返回值sock是客户端发来的值,只要客户端一连接(connect)就会获取到,此时程序不再阻塞
# address 是客户端的地址(IP地址,端口号)
print(client_sock)
print(address) # 这里都是客户端来接收数据
'''
运行结果:服务器已经启动了
<socket.socket fd=560, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0,
laddr=('192.168.0.6', 20001), raddr=('192.168.0.6', 53385)>
('192.168.0.6', 53385)
'''
while True:
# 接收客户端的消息
recv_data = client_sock.recv(1024)
print("客户端说:" + recv_data.decode(encoding="utf-8"))
# 给客户端回复数据
send_data = input("我说:")
client_sock.send(send_data.encode(encoding="utf-8"))
client_socket.py 客户端
import socket
# 创建一个客户端socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立与服务器的连接,这一步其实也会阻塞,等待到连接成功才会进行下一步
client_socket.connect(("192.168.0.6", 20001)) # 这一步完成,等于到了服务器端的accept
while True:
# 发送数据
send_data = input("客户端说:")
client_socket.send(send_data.encode("utf-8"))
# 接收服务器数据
recv_data = client_socket.recv(1024)
print("服务器说:" + recv_data.decode("utf-8"))
# client_sock.close() 断开接口
# client_socket.close()断开客户端访问
时间服务器
# server端
# _*_coding:utf-8_*_
from socket import *
from time import strftime
ip_port = ('127.0.0.1', 9000)
bufsize = 1024
tcp_server = socket(AF_INET, SOCK_DGRAM)
tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_server.bind(ip_port)
while True:
msg, addr = tcp_server.recvfrom(bufsize)
print('===>', msg)
if not msg:
time_fmt = '%Y-%m-%d %X'
else:
time_fmt = msg.decode('utf-8')
back_msg = strftime(time_fmt)
tcp_server.sendto(back_msg.encode('utf-8'), addr)
tcp_server.close()
# client端:
#_*_coding:utf-8_*_
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024
tcp_client=socket(AF_INET,SOCK_DGRAM)
while True:
msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()
tcp_client.sendto(msg.encode('utf-8'),ip_port)
data=tcp_client.recv(bufsize)
总结
'''
总结:基于socket模块实现网络通信(tcp模式)
为什么要网络通信发送的是字节?而不是字符串?
py3, send/recv 都是字节
py2, send/recv 都是字符串
服务端:
accept,阻塞:等待客户端来连接。
recv, 阻塞:等待客户端发来数据。
客户端:
connect,阻塞:一直在连接,直到连接成功才往下运行其他代码。
recv, 阻塞:等待服务端发来数据。
send 和 sendall 的区别:
send是分次发送,sendall是一次性发送的。这两个其实在使用的时候用户并感知不到区别,
都可以发送任意大小的数据,这是tcp协议的特点。但是这两个方法的返回值不同:send()的返回值是发送的字节数量,
这个数量值可能小于要发送的string的字节数,也就是说可能无法发送string中所有的数据。如果有错误则会抛出异常。
而sendall尝试发送string的所有数据,成功则返回None,失败则抛出异常。
'''
应用
实现用户名、密码登录功能
# login_server.py
import socket
sock = socket.socket() # TCP协议
IP_PORT = ("192.168.0.3", 8899)
sock.bind(IP_PORT)
sock.listen(5)
while 1:
conn, addr = sock.accept()
while 1:
try: # 加异常,是防止客户端突然断开连接,导致服务端接收不到数据
data = conn.recv(1024).decode("utf8")
user, pwd = data.strip().split("|")
# 文件操作
flag = False
with open("account", "r") as f:
for line in f:
username, password = line.strip().split(":")
if username == user and password == pwd:
flag = True
break
if flag:
conn.send(b"success")
else:
conn.send(b"fail")
except Exception as e:
break
# login_client.py
import socket
sock = socket.socket() # TCP
sock.connect(("192.168.0.3", 8899))
while 1:
user = input("用户名>>>>")
pwd = input("密码>>>>")
val = ("%s|%s" % (user, pwd)).encode("utf8")
sock.send(val)
response = sock.recv(1024)
print(response.decode("utf8"))
if response.decode("utf8") == "success":
break # 跳出循环后,客户端会中断,导致服务端不能继续运行,所以会报错,需要异常处理
else:
print("用户名或者密码错误!")
continue
# account.txt
min:123
egon:456
wu:789
基于UDP协议的socket
udp是无链接的,启动服务之后可以直接接受消息,不需要提前建立链接
UDP协议的socket的使用
# udp_server_socket.py
import socket
# 创建UDP的服务端
udp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定地址
udp_server_socket.bind(("192.168.0.6", 22222))
while True:
# 直接接收信息
# data:接收客户端数据
# addre:客户端的地址
data, address = udp_server_socket.recvfrom(1024)
print("客户端说:" + data.decode("utf-8"))
print(address)
# 给客户端回复数据
recv_data = input("服务器说:")
udp_server_socket.sendto(recv_data.encode("utf-8"), address)
# udp_server_socket.sendto(recv_data.encode("utf-8"),("192.168.0.6",22222))
# udp_client_socket.py
import socket
udp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while True:
# 发送消息给服务器端
address = ("192.168.0.6", 22222)
send_data = input("客户端说:")
udp_client_socket.sendto(send_data.encode("utf-8"), (address))
# 接收数据
recv_data, address = udp_client_socket.recvfrom(1024) # recvfrom是一个元祖
print("服务器说:" + recv_data.decode("utf-8"))
应用
# server端
#_*_coding:utf-8_*_
import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server_sock.bind(ip_port)
while True:
qq_msg,addr=udp_server_sock.recvfrom(1024)
print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
back_msg=input('回复消息: ').strip()
udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
# client端
# _*_coding:utf-8_*_
import socket
BUFSIZE = 1024
udp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
qq_name_dic = {
'敖丙': ('127.0.0.1', 8081),
'哪吒': ('127.0.0.1', 8081),
'egg': ('127.0.0.1', 8081),
'yuan': ('127.0.0.1', 8081),
}
while True:
qq_name = input('请选择聊天对象: ').strip()
while True:
msg = input('请输入消息,回车发送,输入q结束和他的聊天: ').strip()
if msg == 'q': break
if not msg or not qq_name or qq_name not in qq_name_dic: continue
udp_client_socket.sendto(msg.encode('utf-8'), qq_name_dic[qq_name])
back_msg, addr = udp_client_socket.recvfrom(BUFSIZE)
print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' % (addr[0], addr[1],
back_msg.decode('utf-8')))
udp_client_socket.close()
黏包
黏包现象
# 基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd)
res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)
'''
结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,
在接收端需要用GBK解码
且只能从管道里读一次结果
'''
同时执行多条命令之后,得到的结果很可能只有一部分,
在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。
基于tcp协议实现的黏包
# tcp-server
#_*_coding:utf-8_*_
from socket import *
import subprocess
ip_port=('127.0.0.1',8888)
BUFSIZE=1024
tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)
while True:
conn,addr=tcp_socket_server.accept()
print('客户端',addr)
while True:
cmd=conn.recv(BUFSIZE)
if len(cmd) == 0:break
res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
stderr=res.stderr.read()
stdout=res.stdout.read()
conn.send(stderr)
conn.send(stdout)
# tcp-client
#_*_coding:utf-8_*_
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8888)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)
while True:
msg=input('>>: ').strip()
if len(msg) == 0:continue
if msg == 'quit':break
s.send(msg.encode('utf-8'))
act_res=s.recv(BUFSIZE)
print(act_res.decode('utf-8'),end='')
基于udp协议实现的黏包
# udp - server
#_*_coding:utf-8_*_
from socket import *
import subprocess
ip_port=('127.0.0.1',9000)
bufsize=1024
udp_server=socket(AF_INET,SOCK_DGRAM)
udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
udp_server.bind(ip_port)
while True:
#收消息
cmd,addr=udp_server.recvfrom(bufsize)
print('用户命令----->',cmd)
#逻辑处理
res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
stderr=res.stderr.read()
stdout=res.stdout.read()
#发消息
udp_server.sendto(stderr,addr)
udp_server.sendto(stdout,addr)
udp_server.close()
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024
udp_client=<