网络编程

网络编程基础与高级技巧

网络知识基础介绍:

网络的开发架构2种:

C/S:客户端/服务端 的架构

B/S:基于web可以浏览到的  ,统一入口

参考博客:

www.cnblogs.com/Eva-j/articles/8066842.html

socket模块,基于tcp的通信:

是应用层与tcp/ip协议中间软件的抽象层,它是一组接口,socket其实就是一个门面模式他把负载的tcp/ip协议隐藏在socket接口后面,让socket去组织数据

站在你的角度 socket就是一个模块,我们通过调用模块实现的方法建立两个进程之间的链接和通信

参考博客:

www.cnblogs.com/Eva-j/articles/8244551.html

基于socker模块的代码,需要有server  ,client 两个代码块 ,

信息交互中必须对应着一发一收  ,  一收一发

# server
import socket
sk = socket.socket()
#sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDE,1)    # 允许这个链接被重用,否则避免服务重启时会报,这个链接已占用错误
#服务端绑定一个地址和端口
sk.bind(('127.0.0.1',8080))
sk.listen()   # 监听  客户端对这个地址的请求
conn,addr = sk.accept()   # 服务端先接受客户端请求,几下客户端的地址 ,和一个会话

ret = conn.recv(1024)   # 服务器接受客户发来的信息
print(ret)
conn.send(b'hello')  # 服务器发送一个消息
ret1 = conn.recv(1024)
print(ret1.decode('utf-8'))    # 接受中文
conn.close()      # 关闭会话链接
sk.close()      # 关闭socket
# 客户端
import socket
ck = socket.socket()
ck.connect(('127.0.0.1',8080))    # 创建个链接  这里写服务器的地址
ck.send(b'hello')
ret = ck.recv(1024)
print(ret)
ck.send(bytes('中午吃什么',encoding='utf-8'))
ck.close()
# 服务端
# server
import socket
sk = socket.socket()
#服务端绑定一个地址和端口
sk.bind(('127.0.0.1',8081))
sk.listen()   # 监听  客户端对这个地址的请求
conn,addr = sk.accept()   # 服务端先接受客户端请求,几下客户端的地址 ,和一个会话
while True:
    ret = conn.recv(1024).decode('utf-8')   # 服务器接受客户发来的信息
    print(ret)
    if ret == 'bye':
        conn.send(b'bye')
        break
    ret = input('>>>>>')
    conn.send(bytes(ret,encoding='utf-8'))  # 服务器发送一个消息
conn.close()      # 关闭会话链接
sk.close()      # 关闭socket


#客户端
# 客户端
import socket
ck = socket.socket()
ck.connect(('127.0.0.1',8081))    # 创建个链接  这里写服务器的地址
while True:
    ret = input(">>>>>>")
    ck.send(bytes(ret,encoding='utf-8'))
    ret1 = ck.recv(1024).decode('utf-8')
    print(ret1)
    if ret1 == 'bye':
        ck.send(b'bye')
        break
ck.close()
View Code

 

基于UDP 编码

udp 是短连接,所以可以同时与多个进行会话,不会有阻塞现象

UDP 的server 不需要进行监听 ,也不需要建立连接
在启动服务之后只能被动的等待客户端发送消息过来
客户端发送消息的同时会自带地址
消息回复时不仅需要发送消息,还需要把对方的地址带上,才知道发送给那个客户端

#server

import  socket
sk = socket.socket(type=socket.SOCK_DGRAM)     #udp
sk.bind((127.0.0.1,8080))

miss,adrr = sk.recvfrom(1024)    #接受客户的信息
print(miss.decode('utf-8') )   #打印消息
sk.sendto(b'bye',addr)           # 服务器给客户端发必须带这客户端地址
sk.coled()
客户端
impoert  socket
ck = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)
ck.sendto(b'hello',ip_port)
ret,addr = ck.recvfrom(1024)    每次接受消息都会带着地址发过来
print(ret)
ck.close()

 

执行命令模块subprocess 

注意:这个模块是gbk编码的

import subprocess
dis = input('>>>>')
#设定一个命令的项,包含正确输出和错误输出
res = subprocess.Popen(dis,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
#输出
print(res.stdout.read().decode('gbk'))     #输出是bytes类型,需要转为gbk
print(res.stderr.read().decode('gbk'))

python中,我们使用decode()和encode()来进行解码和编码

在python中,使用unicode类型作为编码的基础类型。即

     decode              encode

str ---------> unicode --------->str

好消息来了,对,那就是python3,在新版本的python3中,取消了unicode类型,代替它的是使用unicode字符的字符串类型(str),字符串类型(str)成为基础类型如下所示,而编码后的变为了字节类型(bytes)但是两个函数的使用方法不变:

     decode              encode

bytes ------> str(unicode)------>bytes
---------------------
基于tcp实现远程执行命令 ,在server端下发命令   ,在客户端执行,并将结果返回服务端

#服务器端发送命令
import socket
#建立tcp链接
sk = socket.socket()
sk.bind(('127.0.0.1',8080))   #绑定ip与端口
sk.listen()
conn,addr = sk.accept()    #建立链接

while True:
    dis = input('>>>>>>>>>>>')
    if dis == 'q':
        conn.send(b'q')
        break
    conn.send(dis.encode('gbk'))
    ret = conn.recv(1024).decode('gbk')
    print(ret)
conn.close()
sk.close()
#客户端执行命令
import socket
import subprocess
ck = socket.socket()
ck.connect(('127.0.0.1',8080))

while True:
    dis = ck.recv(1024).decode('gbk')
    if dis == 'q':
        break
    ret = subprocess.Popen(dis,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    ck.send(ret.stdout.read())
    ck.send(ret.stderr.read())
ck.close()

黏包现象

在上面执行的代码中就出现了黏包现象

黏包现象是指:基于tcp的链接,udp是不会产生黏包的,当发送的数据包多大,会就数据包切分发送,将未发送的部分放在内核空间中,当下次客户端在请求时,将剩下部分发送过去,这样就会造成黏包,数据混乱

而udp传输时会将未发送的部分数据包直接丢弃,所以不会造成黏包,但会有丢包现象

基于udp的远程命令传输:

#服务器端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',8080))
mis,addr = sk.recvfrom(1024)

while True:
    dis = input('>>>>')
    if dis == 'q':
        sk.sendto(dis.encode('gbk'),addr)
        break
    sk.sendto(dis.encode('gbk'),addr)
    ret = sk.recvfrom(4096)[0].decode('gbk')
    print(ret)
sk.close()
客户端
import socket
import subprocess
ck = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8080)
ck.sendto(b'hello',ip_port)
while True:
    dis = ck.recvfrom(1024)[0].decode('gbk')
    if dis == 'q':
        break
    ret = subprocess.Popen(dis,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    ck.sendto(ret.stdout.read(),ip_port)
    ck.sendto(ret.stderr.read(),ip_port)
    #print((ret.stderr.read(),ip_port))
ck.close()

 

黏包的触发

有两种现象:

1 连续收包

 

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
ret1 = conn.recv(2)
ret2 = conn.recv(10)              #在服务端有两次接受
print(ret1)
print(ret2)
sk.close()

import socket
ck = socket.socket()
ck.connect(('127.0.0.1',8080))
ck.send(b'hello you')
ck.close()

#连续的小数据包会被合并

 

2 连续的发送小包

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
conn,addr = sk.accept()
ret1 = conn.recv(12)
print(ret1)
print(ret2)
sk.close()

import socket
ck = socket.socket()
ck.connect(('127.0.0.1',8080))
ck.send(b'xiao')
ck.send(b'hello you')
ck.close()

解决黏包方法2个:

确定我到底要接收多大的数据

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

conn,addr = sk.accept()
while True:
    cmd = input('>>>>')
    if cmd == 'q':
        conn.send(b'q')
        break
    conn.send(cmd.encode('gbk'))
    num = conn.recv(1024).decode('utf-8')
    conn.send(b'ok')
    ret = conn.recv(int(num)).decode('gbk')
    print(ret)
conn.close()
sk.close()
服务端
import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
    dis = sk.recv(1024).decode('gbk')
    if dis == 'q':
        break
    res = subprocess.Popen(dis,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    std_out = res.stdout.read()
    std_err = res.stderr.read()
    num = str(len(std_out) + len(std_err)).encode('utf-8')
    sk.send(num)
    sk.recv(1024)
    sk.send(std_out)
    sk.send(std_err)
sk.close()
客户端

方法2 用struct模块

该模块可以把数据类型,转为固定长度的bytes

import  struct
ret = struct.pack('i',4096)   # i 代表int  ,就是即将要把数字转为固定长度的bytes,4个字节
print(ret)

num = struct.unpack('i',ret)    #拿到一个元祖
print(num[0])        # 拿到这个数

只有pack  与unpack 两个方法

pack 将int  转为4位二进制

unpack 将4为二进制转为 int

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

conn,addr = sk.accept()
while True:
    cmd = input('>>>>')
    if cmd == 'q':
        conn.send(b'q')
        break
    conn.send(cmd.encode('gbk'))
    num = conn.recv(4)
    num = struct.unpack('i',num)[0]
    ret = conn.recv(int(num)).decode('gbk')
    print(ret)
conn.close()
sk.close()
服务器
import socket
import subprocess
import struct
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
    dis = sk.recv(1024).decode('gbk')
    if dis == 'q':
        break
    res = subprocess.Popen(dis,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    std_out = res.stdout.read()
    std_err = res.stderr.read()
    len_num = len(std_out) + len(std_err)
    num = struct.pack('i',len_num)
    sk.send(num)
    sk.send(std_out)
    sk.send(std_err)
sk.close()
客户端

博客参考:

www.cnblogs.com/Eva-J/articles/8244551.html#_label4

 

自己定制报头:
在一些复杂的应用上会用到
如传输文件上,带上文件的属性,一起发给对方
head = {'filename':'test','filesize':409600,'filetype':'txt','filepath':r'\usr\bin'}
#1先发送报头的长度
2 send(head) #发送时先发送我的报头
3 从报头中获取filesize
4 根据size 去接受文件

 

验证客户端的合法性

hmac模块

使用:

h = hmac.new()    #你想进行加密的bytes
密文 = h.digest()
hmac.compare_digest()   #对比  密文对比
import socket
import os
import hmac
key = b'egg'
sk = socket.socket()
sk.bind(('127.0.0.1',8080))
sk.listen()
def check_conn(conn):
    msg = os.urandom(32)   #随机生成一个32为二进制
    conn.send(msg)
    h = hmac.new(key,msg)    #对key进行加密
    dis = h.digest()            # 生成一个密文
    client_dis = conn.recv(1024)    #接受客户端发来的密文

    return hmac.compare_digest(dis,client_dis)    #比较这两个密文一样返回TRue
conn,addr = sk.accept()
res = check_conn(conn)
if res:
    print("客户端合法")
    conn.close()
else:
    print('客户端不合法')
    conn.close()
sk.close()
服务器端
import socket
import hmac
key = b'egg'
ck = socket.socket()
ck.connect(('127.0.0.1',8080))

msg = ck.recv(1024)    # 服务端传来的那个32位bytes
h = hmac.new(key,msg)
dis = h.digest()
ck.send(dis)
ck.close()
客户端

socketserver模块   他可以实时与多个客户端通信,是在socket 外进行了一层封装

 看源码:
1 多个类之间的继承关系先整理
2 每个类中的有哪些方法
3 所有的self的对象调用要清楚了解到底是谁的对象
4 所有的方法调用哪个要退回最子类的类中开始逐级寻找

import socketserver
#需要先创建个类
class myserver(socketserver.BaseRequestHandler):    # 类必须继承这个父类
    def handle(self):                #必须有这个方法
        while True:
            msg = self.request.recv(1024).decode('utf-8')      #self.request   就相当于conn.recv()
            if msg == 'q':
                break
            print(msg)
            miss = input('>>>>')
            self.request.send(miss.encode('utf-8'))

#这个模块引入了线程的概念,来实现并发

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),myserver)
    server.serve_forever()   # 永远启动这个线程
服务端

 

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

sk.close()
客户端

 

import json
import hashlib
import socketserver
def md5_pwd(user,pwd):
    md5_obj = hashlib.md5(user.encode('utf-8'))
    md5_obj.update(pwd.encode('utf-8'))
    ret = md5_obj.hexdigest()
    return ret

def login(userinfo):
    user_dic = json.loads(userinfo)
    passwd = md5_pwd(user_dic['username'], user_dic['passwd'])
    with open('userinfo') as f:
        for line in f:
            user, pwd = line.split('|')
            if user_dic['username'] == user and passwd == pwd:
                print('登录成功')
                break

class MyServer(socketserver.BaseRequestHandler):
    def handle(self):
        userinfo = self.request.recv(1024).decode('utf-8')
        login(userinfo)

server = socketserver.ThreadingTCPServer(
                ('127.0.0.1',9000),
                MyServer)
server.serve_forever()
登录认证 server
import json
import socket
ADDR = ('127.0.0.1',9000)
def get_socket():
    sk = socket.socket()
    sk.connect(ADDR)
    return sk
# 输入账号
username = input('username >>>')
passwd = input('password >>>')
if username.strip() and passwd.strip():
    sk = get_socket()
    dic = {'username':username,'passwd':passwd}
    str_dic = json.dumps(dic)
    sk.send(str_dic.encode('utf-8'))
sk.close()
# 连接socket
登录认证client

 

ftp 作业分析

www.cnblogs.com/Eva-J/articles/7642557.html

转载于:https://www.cnblogs.com/huxl1/p/11093118.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值