python网络编程之socket

  socket

  应用层和tcp,ucp协议之间的一个接口,用户只需操作接口,复杂的数据组织工作由其内部自行完成。

  TCP协议的socket(套接字):

    服务端

import socket
sk = socket.socket()  # 创建一个套接字对象
sk.bind(('127.0.0.1', 8080))  # 绑定本地IP地址和端口
sk.listen()     # 监听

conn, address = sk.accept()   # 创建连接


while True:
    ret = conn.recv(1024)   # 接收数据  需要指定接收字节数
    if ret == b'bye':
        conn.send(ret)
        break
    print(ret.decode('utf-8'))
    info = input('>>>').encode('utf-8')
    conn.send(info)   # 发送数据  必须是bytes类型
conn.close()   # 关闭连接
sk.close()   # 关闭套接字

    客户端

import socket
sk = socket.socket()  # 创建套接字
sk.connect(('127.0.0.1', 8080))  # 连接服务端

while True:
    info = input('>>>').encode('utf-8')
    sk.send(info)   # 发送数据
    ret = sk.recv(1024)  # 接收数据
    if ret == b'bye':
        break
    print(ret.decode('utf-8'))

sk.close()  # 关闭套接字

 

  UDP协议的scoket(套接字):

    服务端

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)  # 创建ucp套接字对象
sk.bind(('127.0.0.1', 8080))  # 绑定IP和端口

msg, address = sk.recvfrom(1024)  # 等待接收数据  ucp必须先接收数据
print(msg.decode('utf-8'))
sk.sendto(b'hello', address)  # 发送数据  要携带发送数据的地址

sk.close()

    客户端

import socket
sk = socket.socket(type=socket.SOCK_DGRAM)  # 创建ucp套接字对象
ip_port = ('127.0.0.1', 8080)  # 指定服务端IP和端口

sk.sendto(b'hi', ip_port)  # 发送数据到指定服务端
msg, address = sk.recvfrom(1024)  # 接收返回的数据
print(msg.decode('utf-8'))


sk.close()

   

  黏包

  先写个例子看下黏包现象。

import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()

connect, address = sk.accept()

while True:
    cmd = input('>>>')
    if cmd == 'q':
        connect.send(b'q')
        break
    connect.send(cmd.encode('gbk'))
    ret = connect.recv(1024).decode('gbk')
    print(ret)
connect.close()
sk.close()
server
import socket
import subprocess

sk = socket.socket()
sk.connect(('127.0.0.1', 8080))

while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q':
        break
    res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    sk.send(res.stdout.read())
    sk.send(res.stderr.read())
sk.close()
client

  这里用到了一个subprocess模块,它可以创建子进程,并与进程进行各种交互。

import subprocess
ret = subprocess.Popen('dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print('stdout:'+ret.stdout.read().decode('gbk'))  # 读取正常的返回值 解码方式以操作系统而定
print('stderr:'+ret.stderr.read().decode('gbk'))  # 读取错误的返回值 解码方式以操作系统而定
"""
    subprocess.Popen('dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        dir:系统命令
        shell:True确认 False报错
        stdout:接收正常返回值
        stderr:接收错误返回值
"""

 

  当执行命令的时候,会出现上次命令的返回的信息在执行下一个命令时才会显示。这种连续传送数据,数据发生混乱的情况就叫做黏包。

在tcp协议的数据传输中,在时间间隔短暂的情况下进行连续的send,且send数据量小,
由于自身的优化算法,会将这些数据打包在一起才发送出去。
这样接收端就无法分辨数据之间的分界,造成接收到的数据和实际不符合的现象

  UDP协议不建立连接,也没有什么优化算法,所以不会有黏包,但是会出现丢包。

 

  黏包问题解决

  了解黏包问题后,归根结底是因为接收端无法知道发送端所发数据的大小,知道了数据大小我们就可以接收到对应的数据,从而避免黏包造成的数据混乱问题。下面具体代码看下解决办法:

import struct
import socket

sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()

connect, address = sk.accept()
while True:
    cmd = input('>>>')
    if cmd == 'q':
        connect.send(b'q')
        break
    connect.send(cmd.encode('gbk'))
    num = connect.recv(4)  # 因为长度转成了4位的,所以这里接收的一定是要传输数据的长度struct.pack值
    num = struct.unpack('i', num)[0]  # unpack转回原来的数值,结果是个元组,取第一个值
    res = connect.recv(num).decode('gbk')  # 接收对应长度的数据,这样就可以接收到自己想要的数据了
    print(res)

connect.close()
sk.close()
server
import struct
import socket
import subprocess

sk = socket.socket()
sk.connect(('127.0.0.1', 8080))

while True:
    cmd = sk.recv(1024).decode('gbk')
    if cmd == 'q':
        break
    ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    ret_out = ret.stdout.read()
    ret_err = ret.stderr.read()
    len_num = len(ret_err) + len(ret_out)  # 计算传输的数据的长度
    num_bytes = struct.pack('i', len_num)  # 将int长度转换为4位的bytes类型数据
    sk.send(num_bytes)  # 将这个数据传给接收端
    sk.send(ret_out)
    sk.send(ret_err)
sk.close()
client

  这里用到了一个struct模块,它通过pack(‘i’,v)可以将指定类型的数据转换为特定长度的字符串(bytes),在通过unpack重新取回原来的数据,以元组的方式返回。

  我们还可以通过自定制报头来更好的处理TCP数据传输过程中的黏包问题。

import json
import struct
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8090))
sk.listen()
buffer = 4096
connect, address = sk.accept()

num = connect.recv(4)
head_len = struct.unpack('i', num)[0]
json_head = connect.recv(head_len).decode('utf-8')
head = json.loads(json_head)
file_size = head['file_size']
with open(head['file_name'], 'wb') as f:
    while file_size:
        if file_size > buffer:
            content = connect.recv(buffer)
            f.write(content)
            file_size -= len(content)
        else:
            content = connect.recv(file_size)
            f.write(content)
            file_size -= len(content)

connect.close()
sk.close()
server
import os
import json
import struct
import socket

sk = socket.socket()
sk.connect(('127.0.0.1', 8090))
buffer = 4096

head = {'file_name': r'CentOS-7-x86_64-DVD-1708.iso',
        'file_path': r'D:\下载\安装包',
        'file_size': None}
path = os.path.join(head['file_path'], head['file_name'])
file_size = os.path.getsize(path)
head['file_size'] = file_size
json_head = json.dumps(head)
head_len = struct.pack('i', len(json_head))
sk.send(head_len)
sk.send(json_head.encode('utf-8'))
with open(path, 'rb') as f:
    while file_size:
        if file_size >= buffer:
            content = f.read(buffer)
            sk.send(content)
            file_size -= buffer
        else:
            content = f.read(buffer)
            sk.send(content)
            break
sk.close()
client

 

  验证客户端合法性

  验证客户端的合法性:为了防止一些违法的客户端连接服务端进行数据的盗取或破坏

import os
import hmac
import socket
sk = socket.socket()
sk.bind(('127.0.0.1', 8080))
sk.listen()
secret_key = b'secret'   # 创建秘钥


def check_connect(conn):
    msg = os.urandom(32)  # 随机生成一个32字节的bytes类型的字符串
    conn.send(msg)  # 将随机字符串发送给连接端
    h = hmac.new(secret_key, msg)  # 通过一个秘钥创建一个加密对象
    digest = h.digest()    # 获取加密值
    client_digest = conn.recv(1024)
    return hmac.compare_digest(digest, client_digest)  # 对比连接端和服务端的加密值是否相同 返回bool值


connect, address = sk.accept()
if check_connect(connect):  # 相同 说明为自己的合法客户端
    print('合法')
else:                        # 不相同说明为违法客户端
    print('不合法')
connect.close()
sk.close()
server
import hmac
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))
secret_key = b'secret'
msg = sk.recv(1024)  # 接收服务端的随机字符串
h = hmac.new(secret_key, msg)  # 创建加密对象
digest = h.digest()  # 获取加密值
sk.send(digest)  # 发给服务端验证
sk.close()
client

  秘钥是验证合法性的关键,所以秘钥要加密保存,自己合法的客户端要加密保存好秘钥,这里只是简单的演示。

  这里的hmac模块提供hmac算法,作用和hashlib的加盐摘要类似,使用秘钥对内容进行加密算法。

 

  socketserver模块

  socketserver模块:可以使tcp服务端同时与多个客户端进行数据传输工作

import socketserver


class MY_socket(socketserver.BaseRequestHandler):  # 创建一个自己的类,一定要继承socketserver.BaseRequestHandler
    def handle(self):  # 一定要创建一个handle函数
        while True:
            msg = self.request.recv(1024).decode('utf-8')   # 这里的self.request就是建立的连接
            if msg == 'q':
                break
            print(msg)
            info = input('>>>')
            self.request.send(info.encode('utf-8'))


if __name__ == '__main__':
    # 创建一个socketserver里TCP多线程服务的对象  参数为IP端口元组以及自己定义的类
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8080), MY_socket)
    # server.allow_reuse_address = True
    server.serve_forever()  # 永远启动服务,除非强制关闭或意外停止。
server

  服务端要使用socketserver模块来处理,客户端则不需要,和正常的连接和数据传输方式一样。

import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))

while True:
    info = input('>>>')
    if info == 'q':
        sk.send(b'q')
        break
    sk.send(('client:'+info).encode('utf-8'))
    msg = sk.recv(1024).decode('utf-8')
    print(msg)
sk.close()
client
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8080))

while True:
    info = input('>>>')
    if info == 'q':
        sk.send(b'q')
        break
    sk.send(('client1:'+info).encode('utf-8'))
    msg = sk.recv(1024).decode('utf-8')
    print(msg)
sk.close()
client1

 

  

 

 

  

posted @ 2018-01-28 17:21  蔠缬艸  阅读(189)  评论(0编辑  收藏  举报