python-socket
socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
通俗来讲,socket表示一个网络连接,通过这个连接,使得主机间或者一台计算机上的进程间可以通讯。不管是不同主机,还是同一主机。既然是通信,必定有一个发送方,一个接收方。对应一个客户端,和一个服务端。
Socket
import socket
# 指定IPv4协议(AF_INET),IPv6协议请使用AF_INET6
# 指定使用TCP协议(SOCK_STREAM),UDP协议请使用SOCK_DGRAM
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
格式 : socket(family, type, protocal=0)
三个参数 : 1.常用协议族, 2.socket 类型, 3.指定的协议(默认0)
AF_INET(默认值)、AF_INET6、AF_UNIX、AF_ROUTE等
SOCK_STREAM(TCP类型)(默认值)、SOCK_DGRAM(UDP类型)、SOCK_RAW(原始类型)
通常不写, 默认为"0", 使用默认的协议和类
方法函数
发送数据
# 发送TCP数据,返回值:发送的字节当量
sk.send("data string")
# 完整发送TCP数据,频繁调用send方法,确保数据发送完成
sk.sendall("data string")
# 发送UDP数据
sk.sendto("data string",address)
接受数据
# 接收TCP数据,一次最大只接收1k数据
sk.recv(1024)
# 接收UDP数据,一次只接收1k数据,
# 返回值:数据和发送方ip
(data,address) = sk.recvfrom(1024)
获取socket信息
import socket
# 获取远程socket的addr,port
(addr, port) = sk.getpeername()
# 获取本地socket的addr,port
(addr, port) = sk.getsockname()
# 获取当前主机名
HostName = socket.gethostname()
# 获取当前主机的ip
HOST = socket.gethostbyname(HostName)
# 获取当前socket连接的文件描述符
file_no = sk.fileno()
设置socket
# 设置连接的超时时间
sk.settimeout(timeout)
sk.gettimeout()
# 设置为非阻塞模式,默认是0(阻塞)
# 非阻塞下,accept和recv时一旦无数据,则报错:socket.Error
sk.setblocking(1)
# 设置socket内部参数,
# 具体有哪些参数,可以查看socket类的python源码sk.setsockopt(level,optname,value)
sk.getsockopt(level,optname)
函数 | 说明 |
---|---|
s.bind() | 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址 |
s.listen() | 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量,该值至少为1,一般为5就够用了 |
s.accept() | 被动接受TCP客户端连接,(阻塞式)等待连接的到来 |
s.connect() | 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误 |
公共函数
s.recv() | 接收TCP数据,数据以字符串形式返回,缓冲区(bufsize)指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略 |
---|---|
s.send() | 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小(提示: 在输入空的时候小于) |
s.sendall() | 完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常 |
s.recvfrom() | 接收UDP数据,与recv()类似,但返回值是(data_bytes,address)。其中data_bytes是接收的bytes类型的数据,address是发送数据的地址, 以元组('IP', port)形式表示 |
s.sendto() | 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数 |
s.close() | 关闭套接字 |
s.getpeername() | 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port) |
s.getsockname() | 返回套接字自己的地址。通常是一个元组(ipaddr,port) |
s.setsockopt(level,optname,value) | 设置给定套接字选项的值。 |
s.getsockopt(level,optname[.buflen]) | 返回套接字选项的值。 |
socket工作原理
1、服务器绑定IP 并建立监听
客户端初始化Socket动态库后创建套接字,然后指定客户端Socket的地址,循环绑定Socket直至成功,然后开始建立监听,此时客户端处于等待状态,实时监控网络状态
2、客户端向服务端发送请求
客户端的Socket向服务器端提出连接请求,此时客户端描述出它所要连接的Socket,指出要连接的Socket的相关属性,然后向服务器端Socket提出请求
3、连接请求确认并建立
当服务器端套接字监听到来自客户端的连接请求之后,立即响应请求并建立一个新进程,然后将服务器端的套接字的描述反馈给客户端,由客户端确认之后连接就建立成功
4、连接建立成功之后发送数据
然后客户端和服务器两端之间可以相互通信,传输数据,此时服务器端的套接字继续等待监听来自其他客户端的请求(下面会一步步实现)
创建socket客户端
import socket
# 指定IPv4协议(AF_INET),IPv6协议请使用AF_INET6
# 指定使用TCP协议(SOCK_STREAM),UDP协议请使用SOCK_DGRAM
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 参数是一个tuple,tuple里指定服务器地址(域名或ip)和端口号
sock.connect(('www.sina.com.cn', 80))
# 注意这里str格式要遵循HTTP协议标准。
# 注意结尾一定要用 \r\n\r\n
sock.send("GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\n\r\n".encode("utf-8"))
1buffer = []
while True:
# 每次最多接收1k字节
d = sock.recv(1024)
if d:
# Python3接收到为bytes类型,要转为str
buffer.append(str(d))
else:
break
data = ''.join(buffer)
创建socket服务端
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 注意以元组格式传入,可以是某网卡的公网ip,或0.0.0.0,或127.0.0.1
sock.bind(('127.0.0.1', 9999))
while True:
# 接受一个新连接,阻塞的,只有接收到新连接才会往下走
sock, addr = s.accept()
# 每一次连接,都要创建新线程,否则一次只能处理一个连接
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
def tcplink(sock, addr):
while True:
data = sock.recv(1024)
if data == 'exit' or not data:
break
sock.send('Hello, %s!' % data)
sock.close()
一个简单的聊天对话服务
客户端
import socket
import time
class ChatClient:
def __init__(self, username, port):
self.username = username
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect(("127.0.0.1", port))
def send_msg(self, msg):
self.socket.send("{username}::{msg}".format(username=self.username,msg=msg).encode("utf-8"))
def recv_msg(self):
data=self.socket.recv(1024)
if data:
print("\聊天室"+" "+time.strftime('%Y-%m-%d:%H:%M:%S',time.localtime(time.time())))
print(data.decode("utf-8"))
return True
return False
def main(self):
data = self.socket.recv(1024)
print(data.decode("utf-8"))
msg = input("请输入消息:")
self.send_msg(msg)
while True:
if self.recv_msg():
msg=input("\n我:")
self.send_msg(msg)
if msg == "exit":
print("聊天室已关闭")
break
if __name__ == '__main__':
cc = ChatClient(username="小明", port=8080)
cc.main()py
服务端
import socket
import time
import threading
import requests
import json
class ChatServer:
def __init__(self, port):
# 绑定服务器的ip和端口,注意以tuple的形式
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.bind(("0.0.0.0", port))
self.socket.listen(5)
#授权码
self.key = "your tuling robot key"
print("正在监听 127.0.0.1 :{}...".format(port))
def tcplink(self, sock, addr):
# 每次连接,开始聊天前,先欢迎下。
sock.send("你好,欢迎来到聊天室!".encode("utf-8"))
while True:
data = sock.recv(1024).decode("utf-8")
print(sock.getpeername())
print(sock.getsockname())
print(sock.fileno())
username = data.split("::")[0]
msg = data.split("::")[1]
if msg == "exit":
break
if msg:
print(+username+time.strftime('%Y-%m-%d:%H:%M:%S',time.localtime(time.time())))
print(msg)
response = self.get_response(msg)
sock.send(response.encode("utf-8"))
sock.close()
print("与 {} 结束聊天!".format(username))
def get_response(self, info):
# 调用图灵机器人API
url = 'http://www.tuling123.com/openapi/api?key=' + self.key + '&info=' + info
res = requests.get(url)
res.encoding = 'utf-8'
jd = json.loads(res.text)
return jd['text']
def main(self):
while True:
sock, addr = self.socket.accept()
t=threading.Thread(target=self.tcplink, args=(sock, addr))
t.start()
if __name__ == '__main__':
cs = ChatServer(port=8080)
cs.main()