python 网络通信

# 访问新浪,接收新浪首页内容
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)    # AF_INET指定使用IPv4协议,SOCK_STREAM指定使用面向流的TCP协议
s.connect(('www.sina.com.cn',80))
s.send(b'GET / HTTP/1.1\r\nHost:www.sina.com.cn\r\nConnection: close\r\n\r\n')
buffer = []
while True:
    d = s.recv(1024) # 每次最多接收1k字节:
    if d:
        buffer.append(d)
    else:
        break
data = b''.join(buffer)
s.close
header,html = data.split(b'\r\n\r\n',1) # 把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件
print(header.decode('utf-8'))
with open('sina.html','wb') as f:
    f.write(html)
#   **************tcp服务端程序**************
import socket,threading,time
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 创建一个基于IPv4和TCP协议的Socket
s.bind(('127.0.0.1',9999))   # 监听端口
s.listen(5)     # 调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量
print('Waiting for connection')

def tcplink(sock, addr):    # 每个连接都必须创建新线程(或进程)来处理,\
    # 否则,单线程在处理连接的过程中,无法接受其他客户端的连接
    print('Accept new connection from %s:%s...' % addr)
    sock.send(b'Welcome!')  # 以字节形式发送
    while True:
        data = sock.recv(1024)
        time.sleep(1)
        if not data or data.decode('utf-8') == 'exit':
            break
        sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
    sock.close()
    print('Connection from %s:%s closed.' % addr)
while True:
    sock,addr = s.accept()  # 接受一个新连接
    t = threading.Thread(target=tcplink,args=(sock,addr))   # 创建新线程来处理TCP连接:
    t.start()

#   ****************tcp客户端程序****************
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9999))
# 接收欢迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
    # 发送数据:
    s.send(data)
    print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
# ****************udp服务端程序**************
# TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议\
# 使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。\
# 但是,能不能到达就不知道了
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # SOCK_DGRAM指定了这个Socket的类型是UDP
s.bind(('127.0.0.1',9999))   # 绑定端口,绑定端口和TCP一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据
print('Bind UDP on 9999...')
while True:
    data,addr = s.recvfrom(1024)    # recvfrom()方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端
    print('Received from %s:%s.' % addr)
    s.sendto(b'Hello,%s!'% data,addr)   # 注意这里省掉了多线程,因为这个例子很简单


# *****************udp客户端**********************
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 客户端使用UDP时,首先仍然创建基于UDP的Socket,\
# 然后,不需要调用connect(),直接通过sendto()给服务器发数据
for data in [b'Michael',b'Tracy',b'Sarsh']:
    s.sendto(data,('127.0.0.1',9999))
    print(s.recv(1024).decode('utf-8'))
s.close()

 

架构 -- 程序员开发的一种模式
C/S架构 客户端/服务器模式

B/S架构 Browser浏览器 / 服务器模式
B/S架构的优势: 统一了应用的接口

网络通信编程:
  同一台电脑上两个py文件通信 --- 文件
  两台电脑 如何通信? --- 一根网线
  多台电脑 如何通信? --- 交换机  
  更多台电脑 如何通信? --- 交换机+路由器

mac地址: 物理地址
  在cmd 查看命令 ipconfig -all
  mac地址是12位十六进制组成

ip地址: 是一个四位点分十进制组成(ipv4)    每个十进制数字用一个字节表示,8位,2^8=256种可能,所以ip地址的最大值是 255:255:255:255
  端口: 操作系统为每一个应用程序分配一个端口号
  ip + 端口号 唯一确定某一个电脑上的某一个程序

  arp协议: 通过目标ip地址,获取目标mac地址

  给你一个ip地址,如何确定它是不是在你当前局域网内?
  ip地址 & 子网掩码 ,进行与操作
  ex:
  ip地址 192.168.1.12
    1100 0000 . 1010 1000 . 0000 0001 . 0000 1100

  子网掩码 255.255.255.0
    1111 1111 . 1111 1111 . 1111 1111 . 0000 0000

  结果 1100 0000 . 1010 1000 . 0000 0001 . 0000 0000
  ---> 192.168.1.0 网段 

  用网段来确定一个ip地址是否在你当前的局域网内.

  网段 = ip地址 & 子网掩码

  路由器:
    有一个路由表,记录了归它管理的所有的网段

协议 : 由多人制定的一种规则

通过socket模块去操作tcp和udp协议

TCP协议 : 安全可靠通信方式,面向连接,面向数据流
UDP协议 : 不安全,不可靠的通信方式,快,面向数据包

ip地址:127.0.0.1 : 回环地址:无论什么时候,都可以代表本机的ip地址(只限在本机使用)


tcp协议:
  三次握手 : 注意必须是client先发起请求
    1 client发送给server我想连接你,可以么
    2 server回复client,可以,我也想连接你,可以吗?
    3 client回复server,可以

  四次挥手 : 谁先发起请求都可以
    1 client发送请求:我准备断开连接了,我没有数据需要发送了,
     如果你有数据可以继续发给我
    2 server回复:确认接收到你的请求,我开始着手准备断开事宜
    3 server回复:我准备好了,可以随时断开连接
    4 client回复:断开连接吧


OSI五层模型:
  应用层 py文件
  传输层 tcp/udp协议
  网络层 ip协议
  数据链路层 arp协议,网卡
  物理层 网线,集线器,光纤

面试题会考的:

  1 arp协议

  2 tcp和udp的区别

  3 tcp三次握手和四次挥手

  4 什么是协议

  5 tcp协议编码流程

       服务端            客户端

      sk = socket.socket()      sk = socket.socket()

      sk.bind()        

      sk.listen()

      conn,addr = sk.accept()    sk.connect()

      conn.recv(1024)        sk.send()

      conn.send()          sk.recv()

      conn.close()

      sk.close()           sk.close()

  6 udp协议编码流程

        服务端            客户端

      sk = socket.socket()      sk = socket.socket()

      sk.bind()

      sk.recvfrom()         sk.sendto()

      sk.sendto()          sk.recvfrom()     

      sk.close()             sk.close()

 

# 使用tcp的 1对多 通信
# tcp服务端
from socket import socket
sk = socket(type=socket.SOCK_STREAM)
sk.bind(('127.0.0.1',9090))
sk.listen()

while 1:
    conn,addr = sk.accept() #  等待连接 -- 阻塞,当有客户端连接上时持续通话,结束连接后再次等待连接
    while 1:
        msg_r = conn.recv(1024).decode('utf-8') # 阻塞等待接收客户端发来的消息
        print('接收到来自%s的一个消息:%s' % (addr, msg_r))
        if msg_r == 'q':
            break
        msg_s = input('>>>')
        conn.send(msg_s.encode('utf-8'))# 发送给客户端消息
        if msg_s == 'q':
            break
    conn.close()# 服务器和当前客户端断开连接,程序回到上一层死循环,重新等待客户端的连接
sk.close()


# tcp客户端
from socket import  socket
sk = socket()
sk.connect(('127.0.0.1',9090))

while 1:
    msg_s = input('>>>')
    sk.send(msg_s.encode('utf-8'))
    if msg_s == 'q':
        break
    msg_r = sk.recv(1024).decode('utf-8')
    print(msg_r)
    if msg_r == 'q':
        break

sk.close()
# 控制台输出带颜色的文字
# 格式: '\033[显示方式;前景色;背景色m 文字内容 \033[0m'
'''
    前景色            背景色           颜色
---------------------------------------
30                40              黑色
31                41              红色
32                42              绿色
33                43              黃色
34                44              蓝色
35                45              紫红色
36                46              青蓝色
37                47              白色
显示方式           意义
-------------------------
0                终端默认设置
1                高亮显示
4                使用下划线
5                闪烁
7                反白显示
8                不可见

#例子:
\033[1;31;40m    --  1-高亮显示 31-前景色红色  40-背景色黑色-
\033[0m          --  采用终端默认设置,即取消颜色设置
显示方式;前景色;背景色  不用都写,可以只写显示方式,或者只写显示方式和前景色
'''
print('\033[0;35m欢迎使用学生选课系统\033[0m')

 

print('\033[0;34;45m欢迎使用学生选课系统\033[0m')

 

tcp通信数据发送过程
 
    三次握手:         
         首先,必须先由客户端发起连接的请求
         接下来,服务器接收到请求之后,回复给客户端两个标识,一个syn表示
            服务器接收到请求,一个ack表示服务器在做准备工作,两个标识一起
            回复给客户端
         最后,客户端接收到服务器的回复,客户端准备连接的所有资源,开始进行连接
         发送给服务器一个ack表示客户端的连接准备工作已经完成
         (此时表示客户端和服务器可以相互连接了)
         服务器的accept(),客户端的connect体现了三次握手
          
           
       四次挥手:         
          (1)首先由连接双方任意一方发起断开连接的请求,发起方发送的请求表示
          是我没有数据要继续发送了,可以断开连接了,但是你如果还有数据可以继续向我发送数据.
          (2)接收方回复给发起方,表示接到了发起放的断开请求,开始着手准备断开事宜
          (3)接收方准备完成后,给发起方发送一个标识,表示接受方没有数据继续发送了
             可以断开连接了
          (4)发起方接收到消息后,准备断开连接,回收资源
          服务器的conn.close() 客户端的sk.close()体现了四次挥手
            

 

import os
r = os.popen('dir')
print(r.read())
# os.popen() 在windows系统中输入错误的命令时,读到的字符串是乱码
# 建议使用下面的subprocess模块

import subprocess
# 使用subprocess结合socket通信可以实现远程执行命令,以下是使用方法
cmd = input('输入命令>>>')
res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,
                           stderr=subprocess.PIPE)
std_out = res.stdout.read()# 读取正确的返回信息
std_err = res.stderr.read()# 读取错误的返回信息
# 命令执行后只会返回一个正确信息和空的错误信息或者错误信息和空的正确信息
# subprocess.Popen(a,b,c,d)
# a:要执行的系统命令(str)
# b: shell = True  表示确定我当前执行的命令为系统命令
# c: 表示正确信息的输出管道
# d: 表示错误信息的输出管道
# =============================================
# 调用hmac模块中的加密方法,服务端
import socket
import hashlib
import os
import hmac
sk = socket.socket()
sk.bind(('127.0.0.1',9090))
sk.listen()
conn,addr = sk.accept()
key = '天王盖地虎'# 这个是固定盐
ch = os.urandom(10)
conn.send(ch)# 把随机字符串发给client
obj = hmac.new(key.encode('utf-8'),ch)
re = obj.digest()
# 固定的用盐的加密方式
client_re = conn.recv(1024)# 接收client端加密后的结果

if re == client_re:
    print('你好机油!')
    '''收发数据的逻辑'''
else:
    print('你根本不是老司机')
    conn.close()
sk.close()


# 此代码用hmac模块实现机密,客户端
import socket
import hashlib
import hmac
sk = socket.socket()
sk.connect(('127.0.0.1',9090))

key = '天王盖地虎' #
ch = sk.recv(1024)
obj = hmac.new(key.encode('utf-8'),ch)
re = obj.digest()
sk.send(re)

sk.close()

 

 

socket 的其他方法补充

'''
服务端套接字函数
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听
s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数
s.connect()     主动初始化TCP服务器连接
s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数
tcp 服务端 s = conn    客户端  s= sk
s.recv()            接收TCP数据
s.send()            发送TCP数据
s.sendall()         发送TCP数据  sendall 是一下全部传给缓冲区,然后缓冲区在进行拆包发送,send可能分批次传给缓冲区,然后缓冲区进行拆包发送
s.recvfrom()        接收UDP数据
s.sendto()          发送UDP数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字

面向锁的套接字方法
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操作的超时时间
s.gettimeout()      得到阻塞套接字操作的超时时间

面向文件的套接字的函数
s.fileno()          套接字的文件描述符
s.makefile()        创建一个与该套接字相关的文件

'''
# socketserver 可以实现tcp socket和多个客户端通信
# 服务端
import socketserver
class Myserver(socketserver.BaseRequestHandler):    #
    def handle(self):
        self.data = self.request.recv(1024).strip()    # 使用socketserver self.request 等价于 conn
        print("{} wrote:".format(self.client_address[0]))
        print(self.data)
        self.request.sendall(self.data.upper())

if __name__ == "__main__":
    HOST, PORT = "127.0.0.1", 9999

    # 设置allow_reuse_address允许服务器重用地址
    socketserver.TCPServer.allow_reuse_address = True
    # 创建一个server, 将服务地址绑定到127.0.0.1:9999
    server = socketserver.TCPServer((HOST, PORT),Myserver)
    # 让server永远运行下去,除非强制停止程序
    server.serve_forever()

或者参看下面代码
import socketserver
#sk conn 等效于 self.requset.
class Myserver(socketserver.BaseRequestHandler):
def handle(self):
# print(123)
# self.request.send()
print(self.request.recv(1024).decode('utf-8'))

server = socketserver.TCPServer(('192.168.19.200',9090),Myserver)

server.serve_forever()

socketserver.TCPServer() 和 socketserver.ThreadingTCPServer() 和 ForkingTCPServer() 的区别

  TCPServer是接收到请求后执行handle方法,如果前一个的handle没有结束,那么其他的请求将不会受理,新的客户端也无法加入。

  而ThreadingTCPServer和ForkingTCPServer则允许前一连接的handle未结束也可受理新的请求和连接新的客户端,区别在于前者用建立新线程的方法运行handle,后者用新进程的方法运行handle

# 客户端 import socket HOST, PORT = "127.0.0.1", 9999 data = "hello" # 创建一个socket链接,SOCK_STREAM代表使用TCP协议 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.connect((HOST, PORT)) # 链接到客户端 sock.sendall(bytes(data + "\n", "utf-8")) # 向服务端发送数据 bytes(str,'utf-8')将字符串转化为'utf-8'的byte类型 received = str(sock.recv(1024), "utf-8")# 从服务端接收数据 str(bytes,'utf-8')将bytes数据解码成字符串 print("Sent: {}".format(data)) print("Received: {}".format(received)) # 启动了一个socket的server端 # socketserver启动之后就会一直对外提供服务 # 一个client服务结束了之后,socketserver会帮助你conn.close # 但是sk对象永远不会停止服务

 

 

 

 

 

 

 自己写的MySocket类,封装了发送字符串,json数据类型,文件,文件夹的方法

my_socket.py

import socket
import os
import struct
import json
import time
import zipfile

def get_size(file_path):
    '''参数:文件夹名  返回一个文件夹的大小'''
    ret = os.listdir(file_path)
    total = 0
    for name in ret:
        abs_pass = os.path.join(file_path, name)

        if os.path.isdir(abs_pass):
            total += get_size(abs_pass)
        else:
            total += os.path.getsize(abs_pass)  # 计算文件的大小
    return total

def make_zip(source_dir, output_filename):
    '''功能:打包压缩文件夹
    参数:文件夹路径,压缩后的文件名(可以包含绝对路径)
    示例:
    make_zip(r'D:\desk\python\project\day35 socket其他方法,大作业\大作业\\ftp作业\download\alex\mysocket',r'D:\desk\python\project\day35 socket其他方法,大作业\大作业\ftp作业\download\perfey\yasuo.zip')
'''
    zipf = zipfile.ZipFile(output_filename, 'w',zipfile.ZIP_DEFLATED)
    pre_len = len(os.path.dirname(source_dir))
    for parent, dirnames, filenames in os.walk(source_dir):
        for filename in filenames:
          pathfile = os.path.join(parent, filename)
          arcname = pathfile[pre_len:].strip(os.path.sep)
          zipf.write(pathfile, arcname)
    zipf.close()

def un_zip(file_name,output_dir='.'):   # file_name 是zip文件,output_dir 是解压后保存路径
    '''解压zip文件,默认解压到当前文件夹,可以给第二个参数一个绝对路径指定文件夹'''
    zip_file = zipfile.ZipFile(file_name)
    os.chdir(output_dir)
    for names in zip_file.namelist():
        zip_file.extract(names,output_dir)
    zip_file.close()

# 显示进度条
# print('\r%d%% %s>' % (round(i / s, 2) * 100, '=' * int(i / s * 40)), end='')  显示进度条, \
# i是已经读过的数据,s是总数据
# 注:这里面为了比较明显的显示进度条加入了 time.sleep(),如果觉得影响了效率可以去掉

class MySocket():
    def __init__(self, ip, port, type='server'):
        '''参数: IP号,端口号,socket类型 是服务器端(server)还是客户端(client),默认是服务器, '''
        sk = socket.socket()
        if type == 'server':
            sk.bind((ip, port))
            sk.listen()
            self.conn, self.addr = sk.accept()
        elif type == 'client':
            sk.connect((ip, port))
            self.conn = sk

    def send_str(self, str):
        '''
        把一个字符串以 (struct字节数 + 字节内容 ) 的形式发送
        :param str:
        :return:
        '''
        byte_str = str.encode('utf-8')
        sfile_str_size = struct.pack('i', len(byte_str))
        self.conn.send(sfile_str_size + byte_str)
        return True

    def recv_str(self):
        '''
        接收(struct字节数 + 字节内容 ) 形式的字符串,并返回这个字符串
        :return:
        '''
        s_str_size = struct.unpack('i', self.conn.recv(4))[0]
        str = self.conn.recv(s_str_size).decode('utf-8')
        return str

    def send_json(self, dic):
        '''
        将其他数据类型的变量(非字符串,当然字符串也可以用这个,不过字符串一般不用json直接发就好了)以json形式的字符串发送
        :param dic:
        :return:
        '''
        json_str_byte = json.dumps(dic).encode('utf-8')
        json_str_size = len(json_str_byte)  # 这里可以用encode()后的bytes类型,也可以直接用json的字符串,因为json出来的字符串的len()长度都是以字节计算的
        sjson_str_size = struct.pack('i', json_str_size)
        self.conn.send(sjson_str_size + json_str_byte)
        return True

    def recv_json(self):
        '''
        json 是操作字符串
        网络通信是操作bytes类型
        :return:
        '''
        sjson_str_size = self.conn.recv(4)
        json_str_size = struct.unpack('i', sjson_str_size)[0]
        json_str = self.conn.recv(json_str_size).decode('utf-8')
        dic = json.loads(json_str)
        return dic

    def send_file(self, file_name):
        '''
        传入文件名,将这个文件以 (struct文件字节数 + 文件内容字节)形式发送
        不再当前文件夹,可发送绝对路径
        :return:
        '''
        flag = False
        if os.path.isdir(file_name):  # 判断如果传入的是文件夹,打包成zip后发送,发送完后删除打包的zip文件
            old_name = file_name
            file_name = os.path.basename(file_name) + '.zip'
            make_zip(old_name, './' + file_name)
            flag = True

        self.send_str(file_name)
        with open(file_name, 'rb') as f:
            file_size = os.path.getsize(file_name)
            sfile_size = struct.pack('i', file_size)
            self.conn.send(sfile_size)
            s = file_size
            while file_size:
                msg_s = f.read(1024)
                self.conn.send(msg_s)
                file_size -= len(msg_s)
                i = s - file_size
                print('\r%d%% %s>' % (round(i / s, 2) * 100, '=' * int(i / s * 40)), end='')
        if flag:
            os.remove(file_name)
        return True

    def recv_file(self):
        '''
        接收文件,输入保存的文件,可输入路径保存在该路径下
        :return:
        '''
        # file_name = input('保存为>>>')
        file_name = os.path.split(self.recv_str())[1]  # 当接收的file_name是绝对路径时,把文件名取出来
        while os.path.exists(file_name):
            file_name = os.path.splitext(file_name)[0] + '附件' + os.path.splitext(file_name)[1]
        with open(file_name, 'wb') as f:
            sfile_size = self.conn.recv(4)
            file_size = struct.unpack('i', sfile_size)[0]
            s = file_size
            if file_size < 1024:  # 防止连续接收两个文件,文件大小均小于1024,出现黏包
                msg_r = self.conn.recv(file_size)
                f.write(msg_r)
                i = file_size
                print('\r%d%% %s>' % (round(i / s, 2) * 100, '=' * int(i / s * 40)), end='')
                file_size = 0
            while file_size:
                msg_r = self.conn.recv(1024)
                f.write(msg_r)
                file_size -= len(msg_r)
                i = s - file_size
                print('\r%d%% %s>' % (round(i / s, 2) * 100, '=' * int(i / s * 40)), end='')
                time.sleep(0.5)
        if os.path.splitext(file_name)[1] == '.zip':
            un_zip(file_name)  # 如果接收到的是zip文件的话,认为是传输的文件夹,将zip文件解压,然后删除
            os.remove(file_name)

        return file_name

 引用上面的模块进行通信

from my_socket import MySocket
s1 = MySocket('127.0.0.1',9090,type='server')
s1.send_str('你好')
s1.recv_file()
dic = {'name':'alex','age':18}
s1.send_json(dic)
from my_socket import MySocket
c1 = MySocket('127.0.0.1',9090,type='client')
print(c1.recv_str())
c1.send_file('D:\desk\python\project\day35 socket其他方法,大作业\大作业\mysocket\第一版')
dic = c1.recv_json()
print(dic)

 

转载于:https://www.cnblogs.com/perfey/p/9317293.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值