4

    科技2024-01-10  72

    文章目录

    网络编程基本概念ip地址 与端口ipv4地址分类局域网 ipv6端口 网络通信协议OSI 模型TCP/IP 模型 tcp 通信连接过程tcp 三次握手 tcp 和 udp 编程`UDP` 接收数据`UDP` 示例udp实现简单聊天循环接收实现聊天 UDP(TFTP 客户端 服务器)请求过程`TFTP`下载器客户端一些文档链接格式字符串实现代码`TFTP`下载器 客户端 `TCP`服务器端 接收数据TCP客户端发送数据并接收数据TCP模拟QQ服务端TCP模拟QQ客户端 TCP多线程完成聊天服务器端TCP多线程完成聊天客户端


    网络编程

    用到的工具下载链接

    基本概念

    ip地址 与端口

    ipv4

    目前常见的都是 ipv4

    当然也有ipv6

    地址分类
    A 类 0网络号7位+24位主机号B 类 10网络号14位+16位主机号C 类 110网络号21位+8位主机号 最常用的是c类D 类 主要用于一对多发送消息E 类 主要用于 网络开发实验室

    IP地址根据网络号和主机号来分,分为A、B、C三类及特殊地址D、E。 全0和全1的都保留不用。

    A类:(1.0.0.0-126.0.0.0)(默认子网掩码:255.0.0.0或 0xFF000000)第一个字节为网络号,后三个字节为主机号。该类IP地址的最前面为“0”,所以地址的网络号取值于1~126之间。一般用于大型网络。

    B类:(128.0.0.0-191.255.0.0)(默认子网掩码:255.255.0.0或0xFFFF0000)前两个字节为网络号,后两个字节为主机号。该类IP地址的最前面为“10”,所以地址的网络号取值于128~191之间。一般用于中等规模网络。

    C类:(192.0.0.0-223.255.255.0)(子网掩码:255.255.255.0或 0xFFFFFF00)前三个字节为网络号,最后一个字节为主机号。该类IP地址的最前面为“110”,所以地址的网络号取值于192~223之间。一般用于小型网络。

    D类:是多播地址。该类IP地址的最前面为“1110”,所以地址的网络号取值于224~239之间。一般用于多路广播用户[1] 。

    E类:是保留地址。该类IP地址的最前面为“1111”,所以地址的网络号取值于240~255之间。

    在IP地址3种主要类型里,各保留了3个区域作为私有地址,其地址范围如下: A类地址:10.0.0.0~10.255.255.255 B类地址:172.16.0.0~172.31.255.255 C类地址:192.168.0.0~192.168.255.255

    回送地址:127.0.0.1。 也是本机地址,等效于localhost或本机IP。一般用于测试使用。例如:ping 127.0.0.1来测试本机TCP/IP是否正常。

    这个地址是不是特殊地址 还是要看 注册表

    比如我们的家庭宽带 被 nat 转发 看起来是公网ip , 但其实 是一个 局域网ip 当然 他肯定是 A 类地址 100 (10进制)打头的 其实有一些是 共享地址空间

    具体指可以看这个链接。

    ipv4 专用地址注册表


    局域网

    局域网是指 几台机器ip地址 网络号一样的但是 他们的 主机号不一样 192.168.0.0 最后一位 是 主机号, 0 代表没有 计算机, 255 用于广播,所以 0和 255 不用做 ip地址, 所以只有254个位置

    大型企业计算机多的时候就可以使用 A 类 或者 B 类地址


    ipv6

    所以推出了ipv6 ipv6 是一个128位(bit)的整数 换算成正常地址也有32个数字 分为 8段 特殊地址

    127.0.0.1 本机地址 192.168.0.0 -192.168.255.255 位私有地址

    属于非注册地址,专门为组织机构内部使用

    端口

    ip区分不同的计算机,要区分不同的程序,就需要 用端口 端口使用一个16位的二进制整数来表示

    对应的十进制是0-65535 FFFF(hex) 16个1

    0-1023 都是我们的保留端口 特殊指定的,比如 电话号码,110等,都是有特殊使用的

    比喻 ip相当于(地址)门牌号,端口相当于 房间号

    网络通信协议

    约定的意思

    比如朋友与朋友之间的约定

    计算机之间也需要约定,比如人沟通需要使用同一种语言,

    除非双方同时懂对方的语言,共同语言

    OSI 模型

    国际标准化组织iso,定义了网络通信协议的基本框架 OSI 模型 7层

    TCP 属于传输层 IP 属于网络层 用的最多的是TCP/IP是一个协议族,也就是多个协议

    具体科普可以看下方链接

    百度百科- ISO模型

    TCP/IP 模型

    tcp/ip 的 4层 和 iso 是一样的

    应用层(就是iso 的应用层 表示层 会话层 )传输层互联网络层网络接口层(就是 iso 模型的 物理+数据链路层)

    百度百科 科普链接 TCP-IP


    tcp 通信连接过程


    tcp 三次握手

    tcp 和 udp 编程

    是传输层的两种协议 基于socket的编程接口

    tcp类似于拨号打电话,需要建立虚拟连接,发送的数据是可靠的,核心数据 使用TCP,速度比 udp 稍慢udp类似于发短信,不需要建立专门的虚拟连接,发送数据是不可靠的,传输数据是面向无连接的 非核心数据则使用 udp

    python 基于 socket 套接字进行通信

    TCP的Socket名称是 SOCK_STREAM 创建套接字可以使用 socket.socket()

    tcp代码如下

    tcpSocket=socket.socket(AF_INET,SOCK_STREAM)

    创建无连接的UDP 名字是 SOCK_DGRAM

    UDP 代码如下:

    udpSocket=socket.socket(AF_INET,SOCK_DGRAM)

    UDP 接收数据

    recvfrom() # 返回数据和客户端的地址与端口 # 服务器收到后直接调用 sendto() 就可以把数据用 UDP 发给客户端

    UDP 示例

    # 示例 UDP # 使用udp 发送数据 from socket import socket,AF_INET,SOCK_DGRAM # 创建接受信息的地址 addr=('192.168.3.2',8080)# IP地址和端口 这是元组对象 # 这里的8080是端口数字,不能加引号,不是字符串。 # 创建udp 套接字 udp_socket=socket(AF_INET,SOCK_DGRAM) # 键盘接受发送的消息 data=input("请输入要发送的信息:") # 调用sendto()方法 发送信息 , 编码要指定 响应的正确编码 udp_socket.sendto(data.encode('gb2312'),addr) # 发送完毕之后 要关闭套接字 udp_socket.close() # 但是你发出去 你这个消息没有人接 哈哈 # udp 发送数据 再接收数据 #导入模块 from socket import * #创建UDP套接字 udp_socket=socket(AF_INET,SOCK_DGRAM) #绑定一个端口 udp_socket.bind(('',8989)) # 绑定的是本机,端口是8989,单引号就代表本机 # 这是从哪里发出去。如果你不指定那么。发出的时候就是随机 端口了。 # 助手接收的时候 显示的 就是随机的。 addr=('192.168.3.2',8080)# 目标地址 data=input('请输入要发送的信息:') #发送数据 udp_socket.sendto(data.encode('gb2312'),addr) recv_data=udp_socket.recvfrom(1024) #表示本次接收的最大字节数1024 print('接收到%s的消息是%s'%(recv_data[1],recv_data[0].decode('gb2312'))) #关闭套接字 udp_socket.close()

    用这个软件来 接收 8989 是我们 代码这里的地址。 而 8080 是 调试助手的地址。

    udp实现简单聊天

    循环接收

    # UDP实现简单的聊天 # 循环接收 from socket import * udp_socket=socket(AF_INET,SOCK_DGRAM) #绑定本机 及一个端口 udp_socket.bind(('',8989)) while True: recv_data=udp_socket.recvfrom(1024) msg=recv_data[0].decode('gb2312') print('接收到{}的消息:{}'.format(recv_data[1],msg)) if msg == 'bye': break #关闭套接字 udp_socket.close()


    实现聊天

    # 使用udp多线程完成聊天 #导入模块 from socket import * from threading import Thread #创建UDP套接字对象 udp_socket=socket(AF_INET,SOCK_DGRAM) #绑定本机和端口 udp_socket.bind(('',8989)) #接收 def recv_fun(): while True: recv_data=udp_socket.recvfrom(1024) print('>>%s:%s'%(recv_data[1],recv_data[0].decode('gb2312'))) #发送 def send_fun(): while True: addr=('192.168.3.2',8080) data=input('<<:') udp_socket.sendto(data.encode('gb2312'),addr) if __name__ == '__main__': #创建两个线程 # 这里是函数的引用 而不是调用 t1=Thread(target=send_fun) t2=Thread(target=recv_fun) t1.start() t2.start() t1.join() t2.join()


    UDP(TFTP 客户端 服务器)

    请求过程

    读写请求读写请求传输数据包 切割数据ack 确认包 确认收到 通过 块编号 也就是数据切割的编号错误包

    通过操作码识别 是什么包

    TFTP下载器客户端

    其实使用 http 模块也可以实现文件共享。

    python -m http.server 8080 开启一个 web 服务 然后 用 ip地址访问就可以了

    一些文档链接

    关于套接字socket编程的文档

    python 中的传输 和 协议文档

    http- 模块 - 文档

    http.server —HTTP服务器 文档

    http.client —HTTP协议客户端 文档

    socket ——低层组网接口 文档

    socketserver ——网络服务器框架 文档

    struct 文档


    格式字符串

    格式字符具有以下含义;鉴于C和python值的类型,它们之间的转换应该是显而易见的。“标准大小”列是指使用标准大小时打包值的大小(以字节为单位);也就是说,当格式字符串以 '<''>''!''=' . 使用本机大小时,打包值的大小取决于平台。


    单位都为 字节

    格式

    C类型

    Python 类型

    标准尺寸

    笔记

    x

    填充字节 pad byte

    无价值 no value

    c

    char

    长度为1的字节 bytes of length 1

    1

    b

    signed char

    整数 integer

    1

    (1) ,(二)

    B

    unsigned char

    整数

    1

    (2)

    ?

    _Bool

    布尔 bool

    1

    (1)

    h

    short

    整数

    2

    (2)

    H

    unsigned short

    整数

    2

    (2)

    i

    int

    整数

    4

    (2)

    I

    unsigned int

    整数

    4

    (2)

    l

    long

    整数

    4

    (2)

    L

    unsigned long

    整数

    4

    (2)

    q

    long long

    整数

    8

    (2)

    Q

    unsigned long long

    整数

    8

    (2)

    n

    ssize_t

    整数

    (3)

    N

    size_t

    整数

    (3)

    e

    (6)

    浮动 代表 浮点数 float

    2

    (4)

    f

    float

    浮动

    4

    (4)

    d

    double

    浮动

    8

    (4)

    s

    char[]

    bytes 类型

    p

    char[]

    bytes 类型

    P

    void*

    整数

    (5)


    类型

    字节顺序

    尺寸

    对准

    @

    本地的

    本地的

    本地的

    =

    本地的

    标准

    没有人

    <

    小字节

    标准

    没有人

    >

    大字节

    标准

    没有人

    !

    网络(=big endian)

    标准

    没有人

    实现代码

    文件名是 face.jpg 操作码文件名0模式0

    所以发送 读写请求的格式为 1test.jpg0octet0 操作码 1 要求占位 两个 字节 上面的表格 中 H 表示整数两个字节 然后网络格式 ! test.jpg 长度为 8个字节 所以是 8个 s 可以缩写 8s %d 格式化字符串。 根据 len 函数 计算长度。然后 0 占位 1 个字节 用 b 为啥不用 s b代表整数类型。 然后 模式 octet 5个字节 所以 5s

    TFTP下载器 客户端

    然后得到如下

    struct.pack('!H%dsb5sb'%len(filename),1,filename.encode(),0,'octet'.encode(),0)

    import struct from socket import * filename='test.jpg' server_ip='127.0.0.1' # 格式 值 操作码 文件名转化为字节 0 模式 0 # b 代表一个字节 H代表两个字节, ds是一个占位符, 5s 是octet的长度格式 # 这就是我们封装的 读的请求 send_data=struct.pack('!H%dsb5sb'%len(filename),1,filename.encode(),0,'octet'.encode(),0) #创建udp_socket套接字 udp_socket=socket(AF_INET,SOCK_DGRAM) udp_socket.sendto(send_data,(server_ip,69))#读写端口默认是69 #本地创建一个文件 f=open(filename,'ab') #a追加模式 b二进制 while True: recv_data=udp_socket.recvfrom(1024) # print(recv_data) ('19\x1a&\'()*56789:CDEFGHI', ('127.0.0.1', 63568)) #获取操作码及数据块编号 # 使用struct 模块 转换格式 caozuoma,ack_num=struct.unpack('!HH',recv_data[0][:4]) #序列解包,操作码和 数据块编号 #判断操作码是否是5如果是5,则是错误信息 if caozuoma == 5: print('文件不存在') break #将接收到的数据写入到文件中 f.write(recv_data[0][4:]) if len(recv_data[0])<516: #表示读取完 break # 如果没有读取完 我们要发送确认包 # 发送确认包 ack_data=struct.pack('!HH',4,ack_num) rand_port=recv_data[1][1] # 从收到的数据 获取服务器发送数据的随机端口 udp_socket.sendto(ack_data,(server_ip,rand_port))

    TCP服务器端 接收数据

    #导入模块 from socket import * #创建服务器端套接字对象 server_socket=socket(AF_INET,SOCK_STREAM) #绑定端口 server_socket.bind(('',8989)) # 这里如果没有绑定IP # 你打开网络助手切换到 tcp client 连接 我们 代码创建的服务器。 # 无需自己输入ip 改下 端口就行。 # 当然如果代码里面绑定了 那么就需要输入相应的 ip 和端口。 # 监听 #监听 server_socket.listen() #接收客户端的连接 client_socket,client_info=server_socket.accept() #接收客户端发送的消息 recv_data=client_socket.recv(1024) print('接收到%s的消息%s'%(client_info,recv_data.decode('gb2312'))) #关闭连接 client_socket.close() server_socket.close()

    TCP客户端发送数据并接收数据

    from socket import * #创建客户端套接字对象 client_socket=socket(AF_INET,SOCK_STREAM) client_socket.connect(('192.168.221.1',8989)) #客户端发送消息 client_socket.send('haha'.encode('gb2312')) #客户接收服务器端消息 recv_data=client_socket.recv(1024) print('接收到消息:',recv_data.decode('gb2312')) #关闭套接字 client_socket.close()


    TCP模拟QQ服务端

    # 这样只能 一问一答 # 需要多线程才可以 实现 qq 那样的 from socket import * #创建服务器端套接字对象 server_socket=socket(AF_INET,SOCK_STREAM) #绑定端口 server_socket.bind(('127.0.0.1',8989)) # 这里如果没有绑定IP # 你打开网络助手切换到 tcp client 连接 我们 代码创建的服务器。 # 无需自己输入ip 改下 端口就行。 # 当然如果代码里面绑定了 那么就需要输入相应的 ip 和端口。 # 127.0.0.1 是本地 ip # 监听 server_socket.listen() #等待客户端的连接 client_socket,client_info=server_socket.accept() while True: #接收客户端的消息 recv_data=client_socket.recv(1024) print('客户端fa说:',recv_data.decode('utf-8')) if recv_data.decode('utf-8') == 'bye': break #发送消息f msg=input('>') client_socket.send(msg.encode('utf-8')) client_socket.close() server_socket.close()

    TCP模拟QQ客户端

    # 这样只能一问一答 from socket import * # 创建客户端套接字对象 client_socket=socket(AF_INET,SOCK_STREAM) #调用connect方法与服务器建立连接 client_socket.connect(('127.0.0.1',8989)) # 这个ip 就看你的 网络助手上那个 服务器 ip while True: #客户端发送消息 msg=input('>') client_socket.send(msg.encode('utf-8')) if msg == 'bye': break #客户端接收消息 recv_data=client_socket.recv(1024) print('服务器端说:',recv_data.decode('utf-8')) client_socket.close()


    TCP多线程完成聊天服务器端

    这里用到的多线程 知识 马上就会写了。

    下一篇就是。

    # 客户端之间聊天 from socket import * from threading import Thread # 把所有链接的客户端都添加到此列表 sockets=[] def main(): # 创建server_socket套接字对象 server_socket = socket(AF_INET, SOCK_STREAM) # 绑定端口 server_socket.bind(('127.0.0.1', 8989)) # 监听 server_socket.listen() # 接收客户端的请求 while True: # 接收客户端的连接 # 循环 接收 客户端连接 client_socket, client_info = server_socket.accept() # 将客户端连接 添加到sockets 列表中。 sockets.append(client_socket) # 开启线程处理当前客户端的请求 t = Thread(target=readMsg, args=(client_socket,)) t.start() def readMsg(client_socket): #读取客户端发送来的消息 while True: recv_data=client_socket.recv(1024) #如果接收到的消息中结尾是bye,则在线客户端列表移除该客户端 if recv_data.decode('utf-8').endswith('bye'): # 如果此客户端。发送来 bye 则 移除此客户端连接。 并关闭连接。 sockets.remove(client_socket) client_socket.close() break # 将消息发送给所有在线的客户端 # 变量所有在线客户端列表 if len(recv_data)>0: for socket in sockets: socket.send(recv_data) if __name__ == '__main__': main()

    TCP多线程完成聊天客户端

    # 客户端之间 聊天 from socket import * from threading import Thread flag=True def readMsg(client_socket): while flag: recv_data = client_socket.recv(1024) print('收到:', recv_data.decode('utf-8')) def writeMsg(client_socket): global flag while flag: msg = input('>') msg=user_name+'说:'+msg client_socket.send(msg.encode('utf-8')) #如果输入bye时候下线 if msg.endswith('bye'): flag=False break #创建客户端套接字对象 client_socket=socket(AF_INET,SOCK_STREAM) #调用connect连接服务器 client_socket.connect(('127.0.0.1',8989)) user_name=input('请输入用户名:') #开启一个线程处理客户端的读取消息 t1=Thread(target=readMsg,args=(client_socket,)) t1.start() #开启一个线程处理客户端的发送消息 t2=Thread(target=writeMsg,args=(client_socket,)) t2.start() t1.join() t2.join() client_socket.close()

    Processed: 0.012, SQL: 8