上一节简单提到过套接字,本篇阐述如何使用套接字进行数据传输。任意两台计算机都可以,简便起见,服务端和客户端均以自己计算机为例。
1.创建套接字
一般,用户认为数据可以直接在应用之间进行传输,实际则是应用调用套接字,数据通过套接字进行传输。
简单套接字实例
In [1]: import socket
In [2]: sock = socket.socket()
In [3]: sock
Out[3]: <socket.socket fd=512, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
三种套接字
服务端监听套接字持续监听客户端套接字的状态,当客户端套接字发送连接请求时,监听套接字生成对应对等连接套接字(此时监听套接字可继续监听其他客户端套接字),实现与客户端套接字的对等连接,数据可互相传输。
2.建立套接字连接
流程:1)服务端监听套接字创建,并启动监听
2)客户端套接字创建,连接监听套接字并绑定
3)生成对等连接套接字
提示:打开两个CMD,或者两个Xshell,一个模拟服务端,一个模拟客户端。
服务端套接字绑定
首先监听套接字创建,这里先不监听
In [2]: import socket
In [3]: address = ('127.0.0.1', 8080) #127.0.0.1是本地计算机IP,8080是端口号(可以为其他如
8081)
In [4]: socket_server = socket.socket()
In [5]: socket_server.bind(address) #bind函数即绑定套接字,参数是一个元组
In [6]: socket_server
Out[6]: <socket.socket fd=992,
family=AddressFamily.AF_INET,
type=SocketKind.SOCK_STREAM,
proto=0,
laddr=('127.0.0.1', 8080)> #绑定后,socket就获得了address
In [7]: socket_server.close() #使用完之后记得关闭
然后客户端连接
In [1]: import socket
In [2]: socket_client = socket.socket()
In [3]: socket_client
Out[3]: <socket.socket fd=924, family=AddressFamily.AF_INET,
type=SocketKind.SOCK_STREAM,
proto=0> #注意,此时客户端还不具备自己的address
In [4]: socket_client.connect(('127.0.0.1', 8080))
---------------------------------------------------------------------------
ConnectionRefusedError Traceback (most recent call last)
<ipython-input-4-05bbfed8720f> in <module>()
----> 1 socket_client.connect(('127.0.0.1', 8080))
ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。
#之所以拒绝,是因为监听套接字还没有监听
服务端监听套接字监听
服务端启动监听
In [9]: import socket
In [10]: address = ('127.0.0.1', 8080)
In [11]: socket_server = socket.socket()
In [12]: socket_server.bind(address)
In [13]: socket_server.listen(5) #监听数5,最多允许5个客户端连接
客户端连接
In [5]: socket_client.connect(('127.0.0.1', 8080))
In [6]: socket_client #此时再连接就不再报错了
Out[6]: <socket.socket fd=924, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0,
laddr=('127.0.0.1', 59311), #连接后随机分配了address
raddr=('127.0.0.1', 8080)> #也有了对应服务端套接字地址
此时服务端套接字内容变为
In [14]: socket_server
Out[14]: <socket.socket fd=736,
family=AddressFamily.AF_INET,
type=SocketKind.SOCK_STREAM,
proto=0, l
addr=('127.0.0.1', 8080)> #raddr消失
服务端接受连接并生成对等连接套接字
In [15]: res = socket_server.accept() #接受连接
In [16]: type(res) #查看类型,返回一个元组
Out[16]: tuple
In [17]: conn, addr = res
In [18]: conn #元组第一个是对等连接套接字
Out[18]: <socket.socket fd=972,
family=AddressFamily.AF_INET,
type=SocketKind.SOCK_STREAM,
proto=0,
laddr=('127.0.0.1', 8080),
raddr=('127.0.0.1', 59311)> #远端对等套接字地址
In [19]: addr #元组第二个是对等套接字地址元组
Out[19]: ('127.0.0.1', 59311)
In [20]: conn is socket_server
Out[20]: False #对等连接套接字和服务端套接字是不同的套接字
服务端accept会阻塞
若服务端监听并处于accept状态而无客户端连接,便会阻塞
若此时有客户端连接,立刻结束阻塞变为可执行
连接的常见错误
In [28]: socket_server.bind(('127.0.0.1', 8080))
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
<ipython-input-28-b4da0b656584> in <module>()
----> 1 socket_server.bind(address)
OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
每个端口只允许使用一次,此时需要更换端口号8080为其他端口(如8081)
3.使用套接字传输
客户端发送数据,
In [7]: socket_client.send(b'hello python') #只能发送bytes类型
Out[7]: 12 #返回发送出去的字节数
服务端接收数据
In [21]: conn.recv(1024) #指明一次最多接受的字节数量
Out[21]: b'hello python'
服务端也可发送数据
In [22]: conn.send('你好'.encode()) #发送中文需要编码
Out[22]: 6
客户端接收
In [8]: data = socket_client.recv(1024)
In [9]: data.decode()
Out[9]: '你好'
recv阻塞
若无数据传输而进行recv接收则会阻塞
4.断开套接字连接
客户端主动断开连接
In [9]: socket_client.close()
主要操作
- 操作一: 服务器套接字绑定IP端口:bind
- 操作二: 服务器套接字监听:listen
- 操作三: 客户端套接字连接服务器:connect
- 操作六: 套接字发送消息:send
- 操作七: 套接字接受消息:recv
- 操作八: 套接字关闭连接:close
命令行简单版本完整代码
服务端
In [9]: import socket
In [10]: address = ('127.0.0.1', 8080)
In [11]: socket_server = socket.socket()
In [12]: socket_server.bind(address)
In [13]: socket_server.listen(5)
In [14]: socket_server
Out[14]: <socket.socket fd=736, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)>
In [15]: res = socket_server.accept()
In [16]: type(res)
Out[16]: tuple
In [17]: conn, addr = res
In [18]: conn
Out[18]: <socket.socket fd=972, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 59311)>
In [19]: addr
Out[19]: ('127.0.0.1', 59311)
In [20]: conn is socket_server
Out[20]: False
In [21]:
In [21]: conn.recv(1024)
Out[21]: b'hello python'
In [22]: conn.send(b'hi')
Out[22]: 2
客户端
In [1]: import socket
In [2]: socket_client = socket.socket()
In [3]: socket_client
Out[3]: <socket.socket fd=924, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0>
In [4]: socket_client.connect(('127.0.0.1', 8080))
In [5]: socket_client
Out[5]: <socket.socket fd=924, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 59311), raddr=('127.0.0.1', 8080)>
In [6]: socket_client.send(b'hello python')
Out[6]: 12
In [7]: socket_client.recv(1024)
Out[7]: b'hi'
In [8]: socket_client.close()
python版本完整代码(pycharm)
服务端
import socket
sock= socket.socket() #生成套接字
sock.bind(('',8086)) #绑定
sock.listen(10) #监听
while True:
while True:
try: #无连接,进入except异常,pass,继续循环判断有无连接,有连接break去接收数据
con,addr=sock.accept()
print(addr,'已连接')
break
except BlockingIOError:
pass
while True:
try:
data = con.recv(1024) #查询有无数据发送,try捕获异常
if data:
print(data.decode())
con.send(data) #将客户端发送的数据返还
else:
con.close() #接收数据为空,则关闭连接,无数据发送
break
except Exception as e:
print(e)
print(addr,'用户退出')
break
客户端
import socket
client = socket.socket()
client.connect(('127.0.0.1',8086))
while True:
data = input('please write the message:')
client.send(data.encode())
print(client.recv(1024).decode()) #接收服务端返还的数据
if data == 'Q' or data == 'q': #输入q或Q退出
break
client.close()
运行结果