day 29

2018-04-20  本文已影响5人  两分与桥

socket 编程
1.客户端服务器架构
2.osi 七层
3.socket 就是封装底层的 TCP 和UDP、IP 等协议的库,我们直接调用 socket 为我们封装的方法就可以利用 TCP 和 UDP 进行通信
4.套接字,也就是 socket 的中文名,ip address + port,百度百科上面这样写:TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口
5.工作流程,看代码吧,TCP/IP 协议通信

socket TCP 的 recv 自己这端的缓冲区为空时,阻塞
socket UDP 的recvfrom 自己这端的缓冲区为空时,就收一个空

# 服务器端
import socket
socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 基于网络,tcp/ip通信

socket1.bind(('127.0.0.1',8088)) #绑定到固定端口,等待客户端来访问
socket1.listen(5)  #最多有5个等待连接

conn,addr = socket1.accept() #没有连接时,会在这句一直等待
print('conn = ',conn) 
print('addr = ',addr) #IP地址和端口 ('127.0.0.1', 1941)

data = conn.recv(1024)
print('data = ', data.decode('utf-8')) 

conn.send('你好啊'.encode('utf-8')) # 发送只能是二进制编码

conn.close()
socket1.close()

输出结果:
conn =  <socket.socket fd=492, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 1941)>
addr =  ('127.0.0.1', 1941)
data =  天天向上

客户端只需要负责连接就行了

# 客户端
import socket

socket1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

socket1.connect(('127.0.0.1', 8088)) # 发起 TCP 三次握手连接

socket1.send('天天向上'.encode('utf-8'))
data = socket1.recv(1024)
print(data.decode('utf-8'))
socket1.close()

输出结果:
你好啊

升级版服务器端、客户端,server 可以捕捉异常并继续运行。

# server
import socket
ip_port = ('127.0.0.1', 8088)
backlog = 5
buffersize = 1024
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 减少地址复用的时间
socket_server.bind(ip_port)
socket_server.listen(backlog)
while True:
    print('wait to connect')
    conn,addr = socket_server.accept()
    print('conn = ', conn)
    print('addr = ', addr)
    try:
        while True:
            data = conn.recv(buffersize)
            print('>>>', data.decode('utf-8'))
            if data.decode('utf-8') == 'bye':
                print('prepare to close connect')
                break
            send = input('>>>').strip()
            conn.send(send.encode('utf-8'))
            if not send or send == 'bye':
                print('prepare to close connect')
                conn.send('bye'.encode('utf-8'))
                break
    except Exception as e:
        print(e)
        conn.close()
        print('close')

socket_server.close()

输出结果:
wait to connect
conn =  <socket.socket fd=404, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 6802)>
addr =  ('127.0.0.1', 6802)
>>> 
>>>d
[WinError 10053] 你的主机中的软件中止了一个已建立的连接。
close
wait to connect
conn =  <socket.socket fd=484, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 6803)>
addr =  ('127.0.0.1', 6803)
>>> 413
# client
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
while(True):
    send = input('>>>').strip()
    socket_client.send(send.encode('utf-8'))
    if not send or send == 'bye':
        print('prepare to close connect')
        socket_client.send('bye'.encode('utf-8'))
        break
    data = socket_client.recv(buffersize)
    print('>>>', data.decode('utf-8'))
    if data.decode('utf-8') == 'bye':
        print('prepare to close connect')
        break
socket_client.close()
print('close')

输出结果:
>>>413
Traceback (most recent call last):
  File "C:/Users/libai/PycharmProjects/begin/test.py", line 14, in <module>
    data = socket_client.recv(buffersize)
KeyboardInterrupt #手动终止客户端,服务端自动断开连接

基于UDP的套接字

#udp_server
import socket
import time
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
udp_server.bind(ip_port)
print('udp is working')
while True:
    data,addr = udp_server.recvfrom(buffersize)
    print('addr = ', addr)
    print("data = ",data.decode('utf-8'))
    udp_server.sendto(time.strftime('%Y-%m-%d %X').encode('utf-8'), addr)

输出结果:
udp is working
addr =  ('127.0.0.1', 50122)
data =  123
addr =  ('127.0.0.1', 50122)
data =  88
# udp_client
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while True:
    data = input('>>>').strip()
    udp_client.sendto(data.encode('utf-8'),ip_port)
    ntp_time, addr = udp_client.recvfrom(buffersize)
    print('ntp返回的时间:',ntp_time.decode('utf-8'))
    print(addr)

输出结果:
>>>123
ntp返回的时间: 2018-04-21 11:04:03
('127.0.0.1', 8088)
>>>88
ntp返回的时间: 2018-04-21 11:04:08
('127.0.0.1', 8088)
>>>

远程执行系统命令,调用 subprocess 模块,十分有意思

# server_tcp
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)

while True:
    print('watiing for connect')
    conn, addr = socket_server.accept()
    print('conn = ', conn)
    print('addr = ', addr)
    while True:
        data = conn.recv(buffersize)
        print('输入>>>', data.decode('utf-8'))
        if not data or data.decode('utf-8')=='bye':
            break
        response = subprocess.Popen(data.decode('utf-8'),shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         stdin=subprocess.PIPE)
        error = response.stderr.read()
        if error:
            cmd_res = error
        else:
            cmd_res = response.stdout.read()
        conn.send(cmd_res)
    conn.close()

输出结果:
watiing for connect
conn =  <socket.socket fd=456, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 27419)>
addr =  ('127.0.0.1', 27419)
输入>>> dir
输入>>> 156
输入>>> bye
watiing for connect
# client_tcp
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024

socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
while True:
    data = input('>>>')
    socket_client.send(data.encode('utf-8'))
    if not data: continue
    if data == 'bye':
        break
    response = socket_client.recv(buffersize)
    print(response.decode('gbk'))
print('connect close')

输出结果:

2018/04/21  15:25    <DIR>          .
2018/04/21  15:25    <DIR>          ..
2018/04/21  15:28    <DIR>          .idea
2018/04/20  17:23    <DIR>          a
2018/04/18  16:03                25 a.txt
2018/04/21  15:25             1,028 begin
2018/04/21  15:00                19 process.py
2018/04/21  15:23               392 test.py
               4 个文件          1,464 字节
               4 个目录 68,050,141,184 可用字节

>>>156
'156' 不是内部或外部命令,也不是可运行的程序
或批处理文件。

>>>bye
connect close

socket TCP 会出现粘包现象,也就是recv命令收取的buffersize没有全部收完,就会等到下次再继续收
例如:先运行ipconfig 命令,没有收完缓冲区使得打印信息不全,再运行别的命令,下次的命令会打印运行ipconfig命令剩余的信息。
而UDP不会出现粘包现象

解决粘包 第一个版本

# server
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)

while True:
    print('watiing for connect')
    conn, addr = socket_server.accept()
    print('conn = ', conn)
    print('addr = ', addr)
    while True:
        data = conn.recv(buffersize)
        print('输入>>>', data.decode('utf-8'))
        if not data or data.decode('utf-8')=='bye':
            break
        response = subprocess.Popen(data.decode('utf-8'),shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         stdin=subprocess.PIPE)
        error = response.stderr.read()
        if error:
            cmd_res = error
        else:
            cmd_res = response.stdout.read()
        if not cmd_res:
            cmd_res = 'execution succeed'.encode('gbk')
        length = len(cmd_res)
        conn.send(str(length).encode('gbk'))
        ready = conn.recv(buffersize)
        if ready.decode('gbk') == 'ready':
            conn.send(cmd_res)
    conn.close()

#client
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024

socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
try:
    while True:
        data = input('>>>')
        socket_client.send(data.encode('utf-8'))
        if not data: continue
        if data == 'bye':
            break
        length = socket_client.recv(buffersize)
        length = int(length.decode('gbk'))
        socket_client.send('ready'.encode("gbk"))
        response = b''
        while len(response) < length:
            response += socket_client.recv(buffersize)
        print(response.decode('gbk'))
except Exception as e:
    print(e)
    print('connect close')
    socket_client.close()

解决粘包 第二个版本

# server
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)

while True:
    print('watiing for connect')
    conn, addr = socket_server.accept()
    print('conn = ', conn)
    print('addr = ', addr)
    while True:
        data = conn.recv(buffersize)
        print('输入>>>', data.decode('utf-8'))
        if not data or data.decode('utf-8')=='bye':
            break
        response = subprocess.Popen(data.decode('utf-8'),shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         stdin=subprocess.PIPE)
        error = response.stderr.read()
        if error:
            #response.stderr.read() 返回的值为系统编码的bytes类型,需要解码
            cmd_res = error.decode('gbk')
        else:
            cmd_res = response.stdout.read().decode('gbk')
        if not cmd_res:
            cmd_res = 'execution succeed'
        length = len(cmd_res)
        send_data = str(length)+'!o!'+cmd_res
        # 字符长度,'!o!'符号作为分隔符,再加上返回的数据一起发送
        conn.send(send_data.encode('gbk'))
    conn.close()

输出结果:
watiing for connect
conn =  <socket.socket fd=456, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8088), raddr=('127.0.0.1', 28581)>
addr =  ('127.0.0.1', 28581)
输入>>> ipconfig
输入>>> bye
watiing for connect
import socket
ip_port = ('127.0.0.1', 8088)
buffersize = 1024

socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
try:
    while True:
        data = input('>>>')
        socket_client.send(data.encode('utf-8'))
        if not data: continue
        if data == 'bye':
            break
        send_data = socket_client.recv(buffersize)
        # 不知道发送的字符长度,从收取的第一个data中截取出来,没读完再继续读取
        send_data = send_data.decode('gbk')
        key_line = send_data.find('!o!')# 找到分隔符,返回的是第一个!的位置
        length = int(send_data[0:key_line]) # 切割出返回字符长度
        response = send_data[key_line+3:].encode('gbk') # 切割出返回字符,再次编码以便和未发送完的字符串拼接
        while len(response) < length:
            response += socket_client.recv(buffersize)
        print(response.decode('gbk'))
        print('返回%s个字节' %length)
except Exception as e:
    print(e)
    print('connect close')
    socket_client.close()

输出结果:#只复制了一部分
以太网适配器 VMware Network Adapter VMnet8:

   连接特定的 DNS 后缀 . . . . . . . :
   本地链接 IPv6 地址. . . . . . . . : fe80::8dae:48ae:e19c:207d%9
   IPv4 地址 . . . . . . . . . . . . : 192.168.1.1
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . :

无线局域网适配器 本地连接* 12:

   连接特定的 DNS 后缀 . . . . . . . :
   本地链接 IPv6 地址. . . . . . . . : fe80::7ca0:36c4:83dd:d3ac%22
   IPv4 地址 . . . . . . . . . . . . : 192.168.23.1
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   IPv4 地址 . . . . . . . . . . . . : 192.168.137.1
   子网掩码  . . . . . . . . . . . . : 255.255.255.0
   默认网关. . . . . . . . . . . . . :

返回1631个字节
>>>bye

解决粘包 第三版

#server
import socket
import subprocess
import struct
ip_port = ('127.0.0.1', 8088)
buffersize = 1024
backlog = 5
socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
socket_server.bind(ip_port)
socket_server.listen(backlog)

while True:
    print('waiting for connect')
    conn, addr = socket_server.accept()
    print('conn = ', conn)
    print('addr = ', addr)
    while True:
        data = conn.recv(buffersize)
        print('输入>>>', data.decode('utf-8'))
        if not data or data.decode('utf-8')=='bye':
            break
        response = subprocess.Popen(data.decode('utf-8'),shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE,
                         stdin=subprocess.PIPE)
        error = response.stderr.read()
        if error:
            #response.stderr.read() 返回的值为系统编码的bytes类型,需要解码
            cmd_res = error.decode('gbk')
        else:
            cmd_res = response.stdout.read().decode('gbk')
        if not cmd_res:
            cmd_res = 'execution succeed'
        length = len(cmd_res)
        length_pack = struct.pack('i', length) #struct.pack 编码出来的length_pack 为四个字节
        conn.send(length_pack)
        conn.send(cmd_res.encode('gbk'))
    conn.close()

#client
import socket
import struct

ip_port = ('127.0.0.1', 8088)
buffersize = 1024

socket_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
socket_client.connect(ip_port)
try:
    while True:
        data = input('>>>')
        socket_client.send(data.encode('utf-8'))
        if not data: continue
        if data == 'bye':
            break
        length_pack = socket_client.recv(4) #取出四字节的编码字符数据长度
        length = int(struct.unpack('i',length_pack)[0])
        data = b''
        while len(data) < length:
            data += socket_client.recv(buffersize)
        print(data.decode('gbk'))
        print('返回%s个字节' %length)
except Exception as e:
    print(e)
    print('connect close')
    socket_client.close()

day 29 结束

上一篇下一篇

猜你喜欢

热点阅读