Socket简介
在网络上的两个程序通过一个双向的通信连接实现数据的交换,这个链接的一端称为一个Socket(套接字),用于描述IP地址和端口。
建立网络通信连接至少要一对端口号(Socket),Socket本质是编程接口(API),对TCP/IP的封装,提供了网络通信能力。
每种服务都打开一个Socket,并绑定到端口上,不同的端口对应不同的服务,就像http对应80端口。
Socket是面向C/S(客户端/服务器)模型设计,客户端在本地随机申请一个唯一的Socket号,服务器拥有公开的socket,任何客户端都可以向它发送连接请求和信息请求。
比如:用手机打电话给10086客服,你的手机号就是客户端,10086客服是服务端。必须在知道对方电话号码前提下才能与对方通讯。
Socket数据处理流程如图:
17.1 socket
在Python中提供此服务的模块是socket和SocketServer,下面是socket常用的类、方法:
方法 | 描述 |
socket.socket([family[, type[, proto]]]) | socket初始化函数,(地址族,socket类型,协议编号)协议编号默认0 |
socket.AF_INET | IPV4协议通信 |
socket.AF_INET6 | IPV6协议通信 |
socket.SOCK_STREAM | socket类型,TCP |
socket.SOCK_DGRAM | socket类型,UDP |
socket.SOCK_RAW | 原始socket,可以处理普通socker无法处理的报文,比如ICMP |
socket.SOCK_RDM | 更可靠的UDP类型,保证对方收到数据 |
socket.SOCK_SEQPACKET | 可靠的连续数据包服务 |
socket.socket()对象有以下方法:
accept() | 接受连接并返回(socket object, address info),address是客户端地址 |
bind(address) | 绑定socket到本地地址,address是一个双元素元组(host,port) |
listen(backlog) | 开始接收连接,backlog是最大连接数,默认1 |
connect(address) | 连接socket到远程地址 |
connect_ex(address) | 连接socket到远程地址,成功返回0,错误返回error值 |
getpeername() | 返回远程端地址(hostaddr, port) |
gettimeout() | 返回当前超时的值,单位秒,如果没有设置返回none |
recv(buffersize[, flags]) | 接收来自socket的数据,buffersize是接收数据量 |
send(data[, flags]) | 发送数据到socket,返回值是发送的字节数 |
sendall(data[, flags]) | 发送所有数据到socket,成功返回none,失败抛出异常 |
setblocking(flag) | 设置socket为阻塞(flag是true)或非阻塞(flag是flase) |
温习下TCP与UDP区别:
TCP和UDP是OSI七层模型中传输层提供的协议,提供可靠端到端的传输服务。
TCP(Transmission Control Protocol,传输控制协议),面向连接协议,双方先建立可靠的连接,再发送数据。适用于可靠性要求高的应用场景。
UDP(User Data Protocol,用户数据报协议),面向非连接协议,不与对方建立连接,直接将数据包发送给对方,因此相对TCP传输速度快 。适用于可靠性要求低的应用场景。
17.1.1 TCP编程
下面创建一个服务端TCP协议的Socket演示下。
先写一个服务端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import
socket
HOST
=
''
# 为空代表所有可用的网卡
PORT
=
50007
# 任意非特权端口
s
=
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(
1
)
# 最大连接数
conn, addr
=
s.accept()
# 返回客户端地址
print
'Connected by'
, addr
while
1
:
data
=
conn.recv(
1024
)
# 每次最大接收客户端发来数据1024字节
if
not
data:
break
# 当没有数据就退出死循环
print
"Received: "
, data
# 打印接收的数据
conn.sendall(data)
# 把接收的数据再发给客户端
conn.close()
|
再写一个客户端:
1
2
3
4
5
6
7
8
9
10
11
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import
socket
HOST
=
'192.168.1.120'
# 远程主机IP
PORT
=
50007
# 远程主机端口
s
=
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.sendall(
'Hello, world'
)
# 发送数据
data
=
s.recv(
1024
)
# 接收服务端发来的数据
s.close()
print
'Received: '
, data
|
写好后,打开一个终端窗口执行:
1
2
3
4
5
|
# python socket-server.py
监听中...
# 直到客户端运行会接收到下面数据并退出
Connected by (
'192.168.1.120'
,
37548
)
Received: Hello, world
|
再打开一个终端窗口执行:
# 如果端口监听说明服务端运行正常
1
2
3
4
|
# netstat -antp |grep 50007
tcp
0
0
0.0
.
0.0
:
50007
0.0
.
0.0
:
*
LISTEN
72878
/
python
# python socket-client.py
Received: Hello, world
|
通过实验了解搭到Socket服务端工作有以下几个步骤:
1)打开socket
2)绑定到一个地址和端口
3)监听进来的连接
4)接受连接
5)处理数据
17.1.2 UDP编程
服务端:
1
2
3
4
5
6
7
8
9
10
11
|
import
socket
HOST
=
''
PORT
=
50007
s
=
socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((HOST, PORT))
while
1
:
data, addr
=
s.recvfrom(
1024
)
print
'Connected by'
, addr
print
"Received: "
, data
s.sendto(
"Hello %s"
%
repr
(addr), addr)
conn.close()
|
客户端:
1
2
3
4
5
6
7
8
|
import
socket
HOST
=
'192.168.1.99'
PORT
=
50007
s
=
socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(data, (HOST, PORT))
data
=
s.recv(
1024
)
s.close()
print
'Received: '
, data
|
运行方式与TCP编程一样。
使用UDP协议时,服务端就少了listen()和accept(),不需要建立连接就直接接收客户端的数据,也是把数据直接发送给客户端。
客户端少了connect(),同样直接通过sendto()给服务器发数据。
而TCP协议则前提先建立三次握手。
17.1.3 举一个更直观的socket通信例子
客户端发送bash命令,服务端接收到并执行,把返回结果回应给客户端。
服务端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import
sys
import
subprocess
import
socket
HOST
=
''
PORT
=
50007
try
:
s
=
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(
1
)
except
socket.error as e:
s.close()
print
e
sys.exit(
1
)
while
1
:
conn, addr
=
s.accept()
print
'Connected by'
, addr
while
1
:
# 每次读取1024字节
data
=
conn.recv(
1024
)
if
not
data:
# 客户端关闭服务端会收到一个空数据
print
repr
(addr)
+
" close."
conn.close()
break
print
"Received: "
, data
cmd
=
subprocess.Popen(data, stdout
=
subprocess.PIPE, stderr
=
subprocess.PIPE, shell
=
True
)
result_tuple
=
cmd.communicate()
if
cmd.returncode !
=
0
or
cmd.returncode
=
=
None
:
result
=
result_tuple[
1
]
# result = cmd.stderr.read()
else
:
result
=
result_tuple[
0
]
# result = cmd.stdout.read() # 读不到标准输出,不知道为啥,所以不用
if
result:
conn.sendall(result)
else
:
conn.sendall(
"return null"
)
s.close()
|
客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
#!/usr/bin/python
# -*- coding: utf-8 -*-
import
sys
import
socket
HOST
=
'192.168.1.120'
PORT
=
50007
try
:
s
=
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
except
socket.error as e:
s.close()
print
e
sys.exit(
1
)
while
1
:
cmd
=
raw_input
(
"Please input command: "
)
if
not
cmd:
continue
s.sendall(cmd)
recv_data
=
s.recv(
1024
)
print
'Received: '
, recv_data
s.close()
|
查看运行效果,先运行服务端,再运行客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
# python socket-server.py
Connected by (
'192.168.1.120'
,
45620
)
Received: ls
Received: touch a.txt
Received: ls
# python socket-client.py
Please
input
command: ls
Received:
socket
-
client.py
socket
-
server.py
Please
input
command: touch a.txt
Received:
return
null
Please
input
command: ls
Received:
a.txt
socket
-
client.py
socket
-
server.py
Please
input
command:
|
我想通过上面这个例子你已经大致掌握了socket的通信过程。
再举一个例子,通过socket获取本机网卡IP:
1
2
3
4
5
6
7
8
9
10
|
>>> socket.gethostname()
'ubuntu'
>>> socket.gethostbyname(socket.gethostname())
'127.0.1.1'
>>> s
=
socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
>>> s.connect((
'10.255.255.255'
,
0
))
>>> s.getsockname()
(
'192.168.1.120'
,
35765
)
>>> s.getsockname()[
0
]
'192.168.1.120'
|
博客地址:http://lizhenliang.blog.51cto.com
QQ群:323779636(Shell/Python运维开发群)
17.2 SocketServer
ScoketServer是Socket服务端库,比socket库更高级,实现了多线程和多线程,并发处理多个客户端请求。
下面是几个常用的类:
RequestHandlerClass, bind_and_activate=True) |
服务器类,TCP协议 |
RequestHandlerClass, bind_and_activate=True) |
服务器类,UDP协议 |
RequestHandlerClass) |
这个是所有服务器对象的超类。它定义了接口,不提供大多数方法,在子类中进行。 |
SocketServer . BaseRequestHandler |
这个是所有请求处理对象的超类。它定义了接口,一个具体的请求处理程序子类必须定义一个新的handle()方法。 |
SocketServer.StreamRequestHandler | 流式socket,根据socket生成读写socket用的两个文件对象,调用rfile和wfile读写 |
SocketServer.DatagramRequestHandler | 数据报socket,同样生成rfile和wfile,但UDP不直接关联socket。这里rfile是由UDP中读取的数据生成,wfile则是新建一个StringIO,用于写数据 |
SocketServer.ForkingMixIn/ThreadingMixIn | 多进程(分叉)/多线程实现异步。混合类,这个类不会直接实例化。用于实现处理多连接 |
SocketServer
.
BaseServer()对象有以下方法:
fileno() | 返回一个整数文件描述符上服务器监听的套接字 |
handle_request() | 处理一个请求 |
serve_forever(poll_interval=0.5) | 处理,直至有明确要求shutdown()的请求。轮训关机每poll_interval秒 |
shutdown() | 告诉serve_forever()循环停止并等待 |
server_close() | 清理服务器 |
address_family | 地址族 |
server_address | 监听的地址 |
RequestHandlerClass | 用户提供的请求处理类 |
socket | socket对象上的服务器将监听传入的请求 |
allow_reuse_address | 服务器是否允许地址的重用。默认False |
request_queue_size | 请求队列的大小。 |
socket_type | socket类型。socket.SOCK_STREAM或socket.SOCK_DGRAM |
timeout | 超时时间,以秒为单位 |
finish_request() | 实际处理通过实例请求RequestHandleClass并调用其handle()方法 |
get_request() | 必须接受从socket的请求,并返回 |
handle_error(request, client_address) | 如果这个函数被条用handle() |
process_request(request, client_address) | ? |
server_activate() | ? |
server_bind() | 由服务器构造函数调用的套接字绑定到所需的地址 |
verify_request(request, client_address) | 返回一个布尔值,如果该值是True,则该请求将被处理,如果是False,该请求将被拒绝。 |
创建一个服务器需要几个步骤:
1)创建类,继承请求处理类(BaseRequestHandler),并重载其handle()方法,此方法将处理传入的请求
2)实例化服务器类之一,它传递服务器的地址和请求处理程序类
3)调用handle_request()或serve_forever()服务器对象的方法来处理一个或多个请求
4)调用server_close()关闭套接字
17.2.1 TCP编程
服务端:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#!/usr/bin/python
# -*- coding: utf-8 -*
import
SocketServer
class
MyTCPHandler(SocketServer.BaseRequestHandler):
"""
请求处理程序类。
每个连接到服务器都要实例化一次,而且必须覆盖handle()方法来实现与客户端通信
"""
def
handle(
self
):
# self.request 接收客户端数据
self
.data
=
self
.request.recv(
1024
).strip()
print
"%s wrote:"
%
(
self
.client_address[
0
])
print
self
.data
# 把接收的数据转为大写发给客户端
self
.request.sendall(
self
.data.upper())
if
__name__
=
=
"__main__"
:
HOST, PORT
=
"localhost"
,
9999
# 创建服务器并绑定本地地址和端口
server
=
SocketServer.TCPServer((HOST, PORT), MyTCPHandler)
# 激活服务器,会一直运行,直到Ctrl-C中断
server.serve_forever()
|
另一个请求处理程序类,利用流(类文件对象简化通信提供标准文件接口):
1
2
3
4
5
6
7
8
|
class
MyTCPHandler(SocketServer.StreamRequestHandler):
def
handle(
self
):
# self.rfile创建的是一个类文件对象处理程序,就可以调用readline()而不是recv()
self
.data
=
self
.rfile.readline().strip()
print
"%s wrote:"
%
(
self
.client_address[
0
])
print
self
.data
# 同样,self.wfile是一个类文件对象,用于回复客户端
self
.wfile.write(
self
.data.upper())
|
客户端:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
import
socket
import
sys
HOST, PORT
=
"localhost"
,
9999
data
=
" "
.join(sys.argv[
1
:])
sock
=
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try
:
sock.connect((HOST, PORT))
sock.sendall(data
+
"\n"
)
received
=
sock.recv(
1024
)
finally
:
sock.close()
print
"Sent: %s"
%
data
print
"Received: %s"
%
received
|
服务端结果:
1
2
3
4
5
|
# python TCPServer.py
127.0
.
0.1
wrote:
hello
127.0
.
0.1
wrote:
nice
|
客户端结果:
1
2
3
4
5
6
|
# python TCPClient.py hello
Sent: hello
Received: HELLO
# python TCPClient.py nice
Sent: nice
Received: NICE
|
17.2.2 UDP编程
服务端:
1
2
3
4
5
6
7
8
9
10
11
12
|
import
SocketServer
class
MyTCPHandler(SocketServer.BaseRequestHandler):
def
handle(
self
):
self
.data
=
self
.request[
0
].strip()
self
.socket
=
self
.request[
1
]
print
"%s wrote:"
%
(
self
.client_address[
0
])
print
self
.data
self
.socket.sendto(
self
.data.upper(),
self
.client_address)
if
__name__
=
=
"__main__"
:
HOST, PORT
=
"localhost"
,
9999
server
=
SocketServer.UDPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()
|
客户端:
1
2
3
4
5
6
7
8
9
|
import
socket
import
sys
HOST, PORT
=
"localhost"
,
9999
data
=
" "
.join(sys.argv[
1
:])
sock
=
socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(data
+
"\n"
, (HOST, PORT))
received
=
sock.recv(
1024
)
print
"Sent: %s"
%
data
print
"Received: %s"
%
received
|
与TCP执行结果一样。
17.2.3 异步混合
创建异步处理,使用ThreadingMixIn和ForkingMixIn类。
ThreadingMixIn类的一个例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
#!/usr/bin/python
# -*- coding: utf-8 -*
import
socket
import
threading
import
SocketServer
class
ThreadedTCPRequestHandler(SocketServer.BaseRequestHandler):
def
handle(
self
):
data
=
self
.request.recv(
1024
)
cur_thread
=
threading.current_thread()
response
=
"%s: %s"
%
(cur_thread.name, data)
self
.request.sendall(response)
class
ThreadedTCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
pass
def
client(ip, port, message):
sock
=
socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((ip, port))
try
:
sock.sendall(message)
response
=
sock.recv(
1024
)
print
"Received: %s"
%
response
finally
:
sock.close()
if
__name__
=
=
"__main__"
:
# 端口0意味着随机使用一个未使用的端口
HOST, PORT
=
"localhost"
,
0
server
=
ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler)
ip, port
=
server.server_address
# 服务器启动一个线程,该线程将开始。每个线程处理每个请求
server_thread
=
threading.Thread(target
=
server.serve_forever)
# 作为守护线程
server_thread.daemon
=
True
server_thread.start()
print
"Server loop running in thread:"
, server_thread.name
client(ip, port,
"Hello World 1"
)
client(ip, port,
"Hello World 2"
)
client(ip, port,
"Hello World 3"
)
server.shutdown()
server.server_close()
|
1
2
3
4
5
|
# python socket-server.py
Server loop running
in
thread: Thread
-
1
Received: Thread
-
2
: Hello World
1
Received: Thread
-
3
: Hello World
2
Received: Thread
-
4
: Hello World
3
|