1、socket 套接字工作流程图
2、收发功能
3、不间断一发一收
4、多客户端连接
5、UDP:收发功能
6、UDP:实现时间功能
7、执行命令
8、黏包
socket 套接字工作流程图
先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束
AF_UNIX: 基于文件编程
基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
AF_INET: 基于网络编程 有AF_INET6 ipv6
CS架构
SOCK_STREAM: TCP协议 数据流式通信
SOCK_DGRAM: UDP协议 数据报式的套接字
## 收发功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
socket服务端
import
socket
cat
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.bind((
'192.168.0.12'
,
9090
))
cat.listen(
4
)
conn,addr
=
cat.accept()
msg
=
conn.recv(
1024
)
conn.close()
print
(
'接收到的信息: %s'
%
msg)
conn.send(msg.upper())
cat.close()
socket客户端
import
socket
cat
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.connect((
'192.168.0.12'
,
9090
))
cat.send(
'xiong'
.encode(
'utf-8'
))
data
=
cat.recv(
1024
)
print
(
'接收到的信息: %s'
%
data)
|
#服务端会主动断开 已经传输完客户端数据的连接,将状态改变为TIME_WAIT, 四次挥手之后确定数据已经完全传输完,直接断开并清理状态连接信息
# 收发都是在操作自己的缓存区
# recv 接收的字节, 由recv用户态的应用程序发起
# 回车: 当前 socket 内核态缓存没数据 对端内核态也就没法收到数据,自然也就卡死了
##不间断一发一收
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
|
socket服务端
import
socket
cat
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.bind((
'127.0.0.1'
,
9009
))
cat.listen(
5
)
conn,addr
=
cat.accept()
while
True
:
msg
=
conn.recv(
1024
)
#回车: 当前 socket 内核态缓存没数据 对端内核态也就没法收到数据,自然也就卡死了
print
(
'接收到的信息: %s'
%
msg.decode(
'utf-8'
))
if
msg
=
=
b
'q'
:
break
inp
=
input
(
'输入一个值: '
)
conn.send(inp.encode(
'utf-8'
))
continue
conn.close()
cat.close()
socket客户端
import
socket
cat
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.connect((
'127.0.0.1'
,
9009
))
while
True
:
inp
=
input
(
'输入一个值: '
)
if
inp
=
=
'q'
:
cat.send(inp.encode(
'utf-8'
))
break
cat.send(inp.encode(
'utf-8'
))
msg
=
cat.recv(
1024
)
print
(
'接收到的信息: %s'
%
msg.decode(
'utf-8'
))
continue
cat.close()
|
## 多客户端连接
# 1、当客户端与服务端建立连接,每次最大客户端连接数由listen控制,我这里最大是5个连接
# 2、多个客户端与服务端建立连接,每次只能有一个客户端与服务端通信,其它队列都会保持在队列中
# 3、unix有些系统使用try except无法解决客户端conn连接突然中断, 可以使用 if not conn: break
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
|
socket服务端
import
socket
cat
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.bind((
'192.168.2.192'
,
9009
))
cat.listen(
5
)
while
True
:
# 客户端退出,如果队列中还有连接那么再重新建立连接
conn,addr
=
cat.accept()
# 连接一次
while
True
:
# 与单个用户建立连接
try
:
msg
=
conn.recv(
1024
)
# 当客户端关掉连接,而服务端连接却没有中断,它就直接报错 ConnectionResetError: [WinError 10054]
except
Exception:
break
print
(
'接收到的信息: %s'
%
msg.decode(
'utf-8'
))
if
msg
=
=
b
'q'
:
break
conn.send(msg.upper())
conn.close()
# 关闭连接
cat.close()
# 关闭程序
socket客户端
import
socket
cat
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cat.connect((
'192.168.2.192'
,
9009
))
while
True
:
inp
=
input
(
'输入一个值: '
)
if
inp
=
=
'q'
:
cat.send(inp.encode(
'utf-8'
))
break
cat.send(inp.encode(
'utf-8'
))
msg
=
cat.recv(
1024
)
print
(
'接收到的信息: %s'
%
msg.decode(
'utf-8'
))
continue
cat.close()
|
UDP:收发功能
1、udp不需要建立accept连接,因为无需三次握手建立一条固定的通道
2、多个客户端同时连接服务端,可同时收发信息
3、udp可以接受空 (直接回车)???
recv在自己这端的缓冲区为空时,阻塞
recvfrom在自己这端的缓冲区为空时,就收一个空
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
UDP_socket服务端
import
socket
ip_port
=
(
'127.0.0.1'
,
9999
)
udp_sock
=
socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_sock.bind(ip_port)
while
True
:
data,addr
=
udp_sock.recvfrom(
1024
)
print
(data)
udp_sock.sendto(data,addr)
UDP_socket客户端
import
socket
ip_port
=
(
'127.0.0.1'
,
9999
)
udp_sock
=
socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while
True
:
inp
=
input
(
'>>: '
)
udp_sock.sendto(inp.encode(
'utf-8'
),ip_port)
data,addr
=
udp_sock.recvfrom(
1024
)
print
(data)
|
UDP:实现时间功能
# 1、实现时间功能
# 2、data传递进来是二进制的格式,在strftime之前需要先将格式转换回来
# 3、注意格式转换,发送都是encode,接受基本都是recvfrom
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
|
# udp_socket_server端
import
socket
import
time
ip_port
=
(
'127.0.0.1'
,
9001
)
udp_sock
=
socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_sock.bind(ip_port)
while
True
:
data,addr
=
udp_sock.recvfrom(
1024
)
print
(data)
if
not
data:
default_time
=
'%Y-%m-%d %X'
else
:
default_time
=
data.decode(
'utf-8'
)
udp_back_time
=
time.strftime(default_time)
udp_sock.sendto(udp_back_time.encode(
'utf-8'
),addr)
# udp_socket_client端
import
socket
udp_client
=
socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
ip_port
=
(
'127.0.0.1'
,
9001
)
#TypeError: sendto() takes 2 or 3 arguments (1 given) 需要带地址
while
True
:
inp
=
input
(
'>>: '
)
udp_client.sendto(inp.encode(
'utf-8'
),ip_port)
data,addr
=
udp_client.recvfrom(
1024
)
print
(
'现在时间是: %s'
%
data.decode(
'utf-8'
))
|
1
2
3
4
5
6
7
8
|
# 1、先运行服务端
# 2、再运行客户端打印结果如下:
现在时间是:
18
>>:
%
Y
现在时间是:
2018
>>:
现在时间是:
2018
-
01
-
04
20
:
49
:
53
>>:
|
### 执行命令
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
# socket TCP服务端
import
socket
import
subprocess
ip_port
=
(
'127.0.0.1'
,
9001
)
ip_connect
=
5
buff_size
=
1024
command
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
command.bind(ip_port)
command.listen(ip_connect)
while
True
:
# 客户端断开之后保持重连
data,addr
=
command.accept()
print
(
'客户端连接信息: %s'
%
data)
while
True
:
try
:
# 接收客户端传递过来的值
cmd_value
=
data.recv(buff_size)
except
Exception:
break
# 执行传递过来的命令,将结果保存到管道对象中赋值给res
res
=
subprocess.Popen(cmd_value.decode(
'utf-8'
),shell
=
True
,
stdout
=
subprocess.PIPE,
stdin
=
subprocess.PIPE,
stderr
=
subprocess.PIPE)
# 取出stderr的值,如果是空那么执行stdout,不为空说明报错了
err
=
res.stderr.read()
if
err:
cmd_res
=
err
else
:
cmd_res
=
res.stdout.read()
if
not
cmd_res:
# 判断如果是类似 cd .. 的命令,它到subprocess值为空
cmd_res
=
'命令为空'
.encode(
'gbk'
)
data.send(cmd_res)
continue
data.close()
command.close()
# socket_客户端
import
socket
ip_port
=
(
'127.0.0.1'
,
9001
)
ip_connect
=
5
buff_size
=
1024
command
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
command.connect(ip_port)
while
True
:
cmd_inp
=
input
(
'请输入命令: '
).strip()
if
not
cmd_inp: command
if
cmd_inp
=
=
'quit'
:
break
command.send(cmd_inp.encode(
'utf-8'
))
data
=
command.recv(buff_size)
print
(data.decode(
'gbk'
))
command.close()
# 最后在客户端这边输入 dir就能看到结果了# 最后在客户端这边输入 dir就能看到结果了
|
黏包
udp不会黏包,一个recvfrom对应一个sendto,每发送一个包就接收一个包
tcp黏包:优化算法合并了每次send发送的数据, 更优的减少网络负载量, 如果第一次没有收完,那么第二次发送过来的,还是第一次缓存冲区的数据
###### 黏包解决办法
解决粘包的思路:
1、判断数据的长度,
2、封闭消息头
UDP 数据报: TCP数据流的方式 封装了一个消息头(消息来源地址,发送端的IP+端口)
应用程序永远不能操作硬件,能操作硬件的只能是操作系统的内核
用户态内存: socket程序
内核态内存: 内核操作硬件
Forking 进程
threading 线程
多进程比多线程的开销更大
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
### 服务端配置
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from
socket
import
*
import
subprocess
import
struct
ip_port
=
(
'127.0.0.1'
,
9002
)
ip_listen
=
5
buff_size
=
1024
# TCP 流式协议
tcp_server
=
socket(AF_INET,SOCK_STREAM)
# 绑定并监听端口
tcp_server.bind(ip_port)
# 允许back_log 允许有多少个队列
tcp_server.listen(ip_listen)
while
True
:
# 客户端连接信息,当客户端断开之后,服务端应允许其它客户端连接
conn,addr
=
tcp_server.accept()
print
(
'客户端连接信息: %s'
%
conn)
while
True
:
try
:
# 如果是空的,或者客户端非法退出,那么就直接退出程序
data
=
conn.recv(buff_size)
if
not
data:
break
except
Exception:
break
# subprocess接收到的命令应该是str格式,客户端发送过来的值是bytes格式,
res
=
subprocess.Popen(data.decode(
'utf-8'
),shell
=
True
,
stdout
=
subprocess.PIPE,
stderr
=
subprocess.PIPE,
stdin
=
subprocess.PIPE)
err
=
res.stderr.read()
# 如果这个值里有东西,说明它报错了直接返回它
if
err:
cmd_res
=
err
else
:
cmd_res
=
res.stdout.read()
# 如果为空比如cd .. 那么应该给它返回一个空值
if
not
cmd_res:
cmd_res
=
'value is null '
.encode(
'utf-8'
)
# 取出这个值最大的长度,并传递给客户端
length
=
len
(cmd_res)
# --------------- 方法一 ---------------
# # length=str(length).encode('utf-8')
# conn.send(length)
# 将最大值给客户端,返回一个值过来,避免黏包
# retu_data=conn.recv(buff_size)
# if retu_data == b'ok':
# conn.send(cmd_res)
# --------------- 方法二 ---------------
# 黏包发送,第一次发送的值,固定为4字节,客户端接收的时候先接收4字节,再取数据
length_data
=
struct.pack(
'i'
,length)
conn.send(length_data)
conn.send(cmd_res)
conn.close()
tcp_server.close()
|
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
40
41
42
43
44
|
### 客户端配置
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from
socket
import
*
import
struct
ip_port
=
(
'127.0.0.1'
,
9002
)
ip_listen
=
5
buff_size
=
1024
tcp_client
=
socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while
True
:
inp
=
input
(
'请输入命令: '
)
tcp_client.send(inp.encode(
'utf-8'
))
# 如果直接回车,为空,那么应该直接让它退出循环重新再来一次
if
not
inp:
continue
# 如果输入quit,那么直接退出程序
if
tcp_client
=
=
'quit'
:
break
# --------------- 方法一 ---------------
# 解决黏包
# length=tcp_client.recv(buff_size)
# tcp_client.send('ok'.encode('utf-8'))
#
# length=int(length.decode('utf-8'))
# --------------- 方法二 ---------------
# 服务端发送的第一个值是4字节大小的,手动调置一下
length
=
tcp_client.recv(
4
)
# 取出大小
length
=
struct.unpack(
'i'
,length)[
0
]
# 设置初始值,用于保存
recv_size
=
0
# 配置二进制的大小
recv_msg
=
b''
while
recv_size < length:
recv_msg
+
=
tcp_client.recv(buff_size)
recv_size
=
len
(recv_msg)
# data=tcp_client.recv(buff_size)
print
(recv_msg.decode(
'gbk'
))
|
1
2
3
4
5
6
7
|
# 结果
请输入命令: cd ..
value
is
null
请输入命令: ipconfig
Windows IP 配置
以太网适配器 本地连接: xxxxxxxxxxxxxxxxxxxx
|
本文转自812374156 51CTO博客,原文链接:http://blog.51cto.com/xiong51/2057579,如需转载请自行联系原作者