网络编程及socket模块(1)

简介: 网络编程及socket模块(1)

提前小结

用于快速复习 详情请跳过向下看详细

简述交换机,路由器,三层交换机,子网掩码和IP区分是否同一网段,DHCP,内网ip外网ip。

cmd:ipconfig 百度:ip

seq syn等

send和sendall区别

服务端代码

import os
import json
import socket
import struct
def recv_data(conn, chunk_size=1024):
    # 获取头部信息:数据长度
    has_read_size = 0
    bytes_list = []
    while has_read_size < 4:
        chunk = conn.recv(4 - has_read_size)
        has_read_size += len(chunk)
        bytes_list.append(chunk)
    header = b"".join(bytes_list)
    data_length = struct.unpack('i', header)[0]
    # 获取数据
    data_list = []
    has_read_data_size = 0
    while has_read_data_size < data_length:
        size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size
        chunk = conn.recv(size)
        data_list.append(chunk)
        has_read_data_size += len(chunk)
    data = b"".join(data_list)
    return data
def recv_file(conn, save_file_name, chunk_size=1024):
    save_file_path = os.path.join('files', save_file_name)
    # 获取头部信息:数据长度
    has_read_size = 0
    bytes_list = []
    while has_read_size < 4:
        chunk = conn.recv(4 - has_read_size)
        bytes_list.append(chunk)
        has_read_size += len(chunk)
    header = b"".join(bytes_list)
    data_length = struct.unpack('i', header)[0]
    # 获取数据
    file_object = open(save_file_path, mode='wb')
    has_read_data_size = 0
    while has_read_data_size < data_length:
        size = chunk_size if (data_length - has_read_data_size) > chunk_size else data_length - has_read_data_size
        chunk = conn.recv(size)
        file_object.write(chunk)
        file_object.flush()
        has_read_data_size += len(chunk)
    file_object.close()
def run():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # IP可复用
    sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    sock.bind(('127.0.0.1', 8001))
    sock.listen(5)
    while True:
        conn, addr = sock.accept()
        while True:
            # 获取消息类型
            message_type = recv_data(conn).decode('utf-8')
            if message_type == 'close':  # 四次挥手,空内容。
                print("关闭连接")
                break
            # 文件:{'msg_type':'file', 'file_name':"xxxx.xx" }
            # 消息:{'msg_type':'msg'}
            message_type_info = json.loads(message_type)
            if message_type_info['msg_type'] == 'msg':
                data = recv_data(conn)
                print("接收到消息:", data.decode('utf-8'))
            else:
                file_name = message_type_info['file_name']
                print("接收到文件,要保存到:", file_name)
                recv_file(conn, file_name)
        conn.close()
    sock.close()
if __name__ == '__main__':
    run()

客户端代码

import os
import json
import socket
import struct
def send_data(conn, content):
    data = content.encode('utf-8')
    header = struct.pack('i', len(data))
    conn.sendall(header)
    conn.sendall(data)
def send_file(conn, file_path):
    file_size = os.stat(file_path).st_size
    header = struct.pack('i', file_size)
    conn.sendall(header)
    has_send_size = 0
    file_object = open(file_path, mode='rb')
    while has_send_size < file_size:
        chunk = file_object.read(2048)
        conn.sendall(chunk)
        has_send_size += len(chunk)
    file_object.close()
def run():
    client = socket.socket()
    client.connect(('127.0.0.1', 8001))
    while True:
        """
        请发送消息,格式为:
            - 消息:msg|你好呀
            - 文件:file|xxxx.png
        """
        content = input(">>>")  # msg or file
        if content.upper() == 'Q':
            send_data(client, "close")
            break
        input_text_list = content.split('|')
        if len(input_text_list) != 2:
            print("格式错误,请重新输入")
            continue
        message_type, info = input_text_list
        # 发消息
        if message_type == 'msg':
            # 发消息类型
            send_data(client, json.dumps({"msg_type": "msg"}))
            # 发内容
            send_data(client, info)
        # 发文件
        else:
            file_name = info.rsplit(os.sep, maxsplit=1)[-1]
            # 发消息类型
            send_data(client, json.dumps({"msg_type": "file", 'file_name': file_name}))
            # 发内容
            send_file(client, info)
    client.close()
if __name__ == '__main__':
    run()

OSI7层模型:

应用层 表示层 会话层 传输层 网络层 数据链路层 物理层


应用层:规定数据的格式。


表示层:对应用层数据的编码、压缩(解压缩)、分块、加密(解密)等任务。


会话层:负责与目标建立、中断连接。


传输层:建立端口到端口的通信,其实就确定双方的端口信息。


网络层:标记目标IP信息(IP协议层)


数据链路层:对数据进行分组并设置源和目标mac地址


物理层:将二进制数据在物理媒体上传输。UDP、TCP协议

UDP、TCP协议

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

TCP三次握手和四次挥手

粘包

两台电脑在进行收发数据时,其实不是直接将数据传输给对方。


对于发送者,执行 sendall/send 发送消息时,是将数据先发送至自己网卡的 写缓冲区 ,再由缓冲区将数据发送给到对方网卡的读缓冲区。


对于接受者,执行 recv 接收消息时,是从自己网卡的读缓冲区获取数据。


所以,如果发送者连续快速的发送了2条信息,接收者在读取时会认为这是1条信息,即:2个数据包粘在了一起。

如何解决粘包问题?、

每次发送的消息时,都将消息划分为 头部(固定字节长度) 和 数据 两部分。例如:头部,用4个字节表示后面数据的长度。


发送数据,先发送数据的长度,再发送数据(或拼接起来再发送)。


接收数据,先读4个字节就可以知道自己这个数据包中的数据长度,再根据长度读取到数据。


对于头部需要一个数字并固定为4个字节,这个功能可以借助python的struct包来实现。

阻塞和非阻塞

默认情况下我们编写的网络编程的代码都是阻塞的(等待)。如果想要让代码变为非阻塞,需要这样写: sock.setblocking(False) # 加上就变为了非阻塞


如果代码变成了非阻塞,程序运行时一旦遇到 accept、recv、connect 就会抛出 BlockingIOError 的异常。


这不是代码编写的有错误,而是原来的IO阻塞变为非阻塞之后,由于没有接收到相关的IO请求抛出的固定错误。


非阻塞的代码一般与IO多路复用结合,可以迸发出更大的作用。

I/O多路复用及作用

I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。


监测多个 IO对象 是否发生变化(可读/可写)。


IO多路复用 + 非阻塞 + socket服务端,可以让服务端同时处理多个客户端的请求。


IO多路复用 + 非阻塞 + socket客户端,可以向服务端同时发起多个请求。

网络编程及socket模块

目标:掌握网络相关的基础知识并可以基于Python开发程序(基于网络进行数据传输)。

课程概要:

  • 网络必备基础
  • 网络编程(Python代码)
  • B/S和C/S架构

1.必备基础

你必须了解的网络相关设备和基础概念。

1.1 网络架构

假设 lbw 上了一个野鸡大学买了一台电脑,电脑里存了1部小电影,整宿整宿的在宿舍反复的看。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

lbw 如何想要和室友 uzi 进行收发数据,可以通过一根网线来进行连接,并进行数据的传输。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

1.1.1 交换机

其他2位室友如何也想和他们的电脑相互连接然后进行资源的共享,此时就需要一个设备 【二层交换机】组件一个局域网。

当电脑接入交换机之后,我们需要为每台电脑分配一个IP,例如:
    - 电脑1:192.168.10.1
    - 电脑2:192.168.10.2
    - 电脑3:192.168.10.3
    - 电脑4:192.168.10.4
局域网内容个电脑之间是基于ARP协议来进行通信,例如:A电脑向 IP为192.168.10.3的另一个电脑发送消息。
第一步:A封装数据包,此时只知道目标IP不知道目标mac地址(未知mac地址时默认会设置为FF)。
第二步:将数据包发送到交换机,交换机通过广播的形式将数据发送给所有电脑。
第三步:目标电脑接收到数据包后,监测自己是否是目标IP。
        - 是,收到数据并回复。
        - 不是,则丢弃包。
为防止每次发送消息都是广播形式,每台电脑的内部都为维护了一个ARP表,接受到数据时(无论是否自己的)都会记录自己了解的IP和MAC的对应关系,例如:
    Internet地址                 物理地址
    192.168.10.1            14-9d-da-2a-dd-0a
    192.168.10.3            14-9d-da-2a-dd-0c
    ...
以便于下次在发送消息时,就知道了目标的mac地址,直接让交换机转发给指定的电脑(单播)。
同时,当有消息发送经过二层交换机时他的内容也会维护记录了交换机接口和连接的电脑的mac地址的对应关系,例如:
    接口(网卡)               mac地址
      接口1              14-9d-da-2a-dd-0A
      接口2              14-9d-da-2a-dd-0B
      接口3              14-9d-da-2a-dd-0C
      ...
这样一来,交换机在进行数据转发时,效率就更高了。
注意:每台电脑出厂时在网卡中都设置了唯一的mac地址(不重复),网卡集成在主板上,如果更换了主板则mac地址也会变更。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

头部信息:xxx
数据:你好
头部信息:xxx
数据:收到

1.1.2 路由器

多个宿舍之间想想要组建一个相互可以通信网络,此时需要【二层交换机】和【企业路由器】配合组建稍微大一点的局域网(同时也可缓解广播风暴)。

划分好网络结构之后,其实会给各宿舍的电脑分配IP和网关,例如:
   宿舍A:
        - 电脑1:192.168.10.1  网关:192.168.10.254
        - 电脑2:192.168.10.2  网关:192.168.10.254
        - 电脑3:192.168.10.3  网关:192.168.10.254
        - 电脑4:192.168.10.4  网关:192.168.10.254
   宿舍B
        - 电脑1:192.168.20.1  网关:192.168.20.254
        - 电脑2:192.168.20.2  网关:192.168.20.254
        - 电脑3:192.168.20.3  网关:192.168.20.254
        - 电脑4:192.168.20.4  网关:192.168.20.254
然后再在路由器中配置路由表(包含网段和路由器上的接口的对应关系),例如:
     接口             IP
     eth0         192.168.10.254(192.168.10网段)
     eth1         192.168.20.254(192.168.20网段)
想与外部网络通信,需要配置网关,网关就是路由表中配置的指向此网段的IP。其实就类似于贸易出口都需要经过海关。
数据通信的过程结合了APR协议和IP协议,例如:宿舍A的电脑1向宿舍B的电脑3发送消息(目标IP:192.168.20.3)。
简化过程:
    - 宿舍A的电脑1,通过广播或单播将数据发送到网管(路由器)
    - 路由器接收到数据之后,再通过对应的接口把数据通过广播的形式发送到宿舍B。
注意:各自局域网内通过学习并记录相关mac地址后,就可以不再使用广播形式,而是使用单播来发送消息了。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

1.1.3 三层交换机

三层交换机集成了 交换机 & 路由器的功能(大部分路由器功能),上述的三个设备其实可以用一个三层交换机就可以搞定。

按照下图,在三层交换机上分别做如下几件事:
1. 划分两个vlan,模拟出来路由器的两个接口。
2. 将交换机的接口划分给指定的vlan,例如:
    接口1、2、3划分给一个vlan,相当于交换机连接上了路由器。
    接口4、5、6划分给一个vlan,相当于交换机连接上了路由器。
3. 电脑连接上交换机。
4. 进行相应的配置。
    宿舍A(左边)电脑配置:
        - 电脑1:192.168.10.1  网关:192.168.10.254   对应交换机接口:1
        - 电脑2:192.168.10.2  网关:192.168.10.254   对应交换机接口:2
        - 电脑3:192.168.10.3  网关:192.168.10.254   对应交换机接口:3
    宿舍B(右边)电脑配置:
        - 电脑1:192.168.20.1  网关:192.168.20.254   对应交换机接口:4
        - 电脑2:192.168.20.2  网关:192.168.20.254   对应交换机接口:5
        - 电脑3:192.168.20.3  网关:192.168.20.254   对应交换机接口:6
    交换机中的路由配置:
          接口               IP
         左vlan        192.168.10.254(192.168.10网段)
         右vlan        192.168.20.254(192.168.20网段)
通过上述的配置之后,就可以实现宿舍A和宿舍B的网络通信了。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

1.1.4 小型企业基础网络架构

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

1.1.5 家庭网络架构

家用路由器集成了是交换机和路由的功能(性能差、价格便宜)。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

1.1.6 互联网

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

1.2 网络核心词汇

1.2.1 子网掩码和IP

之前说过,接入网络设备后,需要一个IP来代指次电脑,例如:192.168.10.1 。

IP其是一个32位的二进制,为了便于记忆就将它分为4组,每组8位,由小数点分开,例如:

二进制表示:00000000.10010111.11111111.00001111
十进制表示:251.151.255.15
0~255
192.178.11.211
192.178.11.311

在网络中的每台电脑都会有一个IP与之绑定,这样通过IP就可以找到相应的电脑。

一个IP地址可以划分为两个部分,即:网络地址 + 主机地址。

  • 问题1:如何确定网络地址和主机地址呢?
通过子网掩码就可以确定IP的网络地址和主机地址。
示例1:
      IP:192.168.1.199      11000000.10101000.00000001.11000111
  子网掩码:255.255.255.0     11111111.11111111.11111111.00000000
此时,网络地址就是前24位 + 主机地址是后8位。你可能见过有些IP这样写 192.168.1.199/24,意思也是前24位是网络地址。
示例2:
      IP:192.168.99.254     11000000.10101000.01100011.11111110
  子网掩码:255.255.240.0     11111111.11111111.11111100.00000000
此时,网络地址就是前22位 + 主机地址是后10位。你可能见过有些IP这样写 192.168.99.254/22,意思也是前22位是网络地址。
问题2:划分 网络地址 + 主机地址 的意义是什么?
网络地址相同的IP,也称为属于同一个网段。
在局域网内只有同一个网段的IP才能相互通信,不同网段IP想要通信需要借助路由的转发才能通信。
当了解子网掩码之后,其实就可以确定某个网段可以容纳的主机个数,例如:
【IP: 192.168.10.2  掩码:255.255.255.0】 和 【192.168.10.251 掩码:255.255.255.0】 数据同一个网段。
  示例网段的主机范围:11000000.10101000.00001010. 00000001  ~  11000000.10101000.00001010.  11111110
                   --------------------------              --------------------------
                            网络地址                                   网络地址
                   192.168.10.1                 ~           192.168.10.254
【IP: 192.168.8.1  掩码:255.255.240.0】 和 【192.168.11.254 掩码:255.255.240.0】 数据同一个网段。
  子网掩码:255.255.240.0
  示例网段的主机范围:11000000.10101000.000010 00.00000001  ~  11000000.10101000.000010 11.11111110
                   11111111.11111111.111111 00.00000000
                   ------------------------                 ------------------------
                            网络地址                                   网络地址
                   192.168.8.1                 ~           192.168.11.254
【IP: 192.168.96.1  掩码:255.255.240.0】 和 【192.168.99.254  掩码:255.255.240.0】 数据同一个网段。
  示例网段的主机范围:11000000.10101000.011000 00.00000001  ~  11000000.10101000.011000 11.11111110
                   ------------------------                 ------------------------
                            网络地址                                   网络地址
                   192.168.96.1                 ~           192.168.99.254    

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

1.2.2 DHCP

在一个局域网内想要给某台电脑分配IP有两种方式:

  • 手动设置,打开指定菜单栏在里面输入相应的IP信息。
  • 自动获取
- 在电脑端,IP地址获取方式设置为自动。
- 在路由器或三层交换机,开启DHCP服务,并设置IP地址池。(家用路由器上也是基于DHCP服务自动分配的IP)
这样,电脑只要连接只该网络,DHCP服务就会为它自动分配IP、子网掩码、网关。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

image.png

1.2.3 内网和公网IP

一般情况下,内网IP都用这些(潜规则):
  - 10.0.0.0 到 10.255.255.255
  - 172.16.0.0 到172.31.255.255
  - 192.168.0.0 到192.168.255.255

之前我们自己在一个局域网内为电脑分配的IP都称为内网IP,基于内网IP可以在一个局域网内进行相互通信(也需要相关的配置)。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

如果想要通过互联网进行通信,就必须借助公网IP。例如,右边家庭电脑想访问左边某公司服务器上的部署的网站:


第一步:左边公司,去运营商申请公网的固定IP(办理专线宽带时运营商会分配至少1个固定的IP地址),其实运营商就是将你拉的这个专线和固定IP创建绑定关系。(假设公网IP:123.206.15.88)


第二步:配置公网IP与指定服务器的转发规则。


第二步:右边家庭,如果想要访问某个公司服务器上的网网站,只需要执行指定IP:123.206.15.88,运营商就会根据IP找到与之关联的公司专线,并通过公司路由器、防火墙等设备找到指定的服务器。


按理说,每个从运营商接入网的用户都可以有一个外网IP,但由于全球用户太多而IP根本就不够分配,所以,运营商网络会进行划分,让多个家庭宽带用户共用一个公网IP(动态,可能每次上网公网IP都不一样)。


让家庭用户想要通过网络访问访问其他IP时,先发给运营商由运营商向外转发到其他IP。


注意:外部用户想要访问家庭宽带的IP时,运营商不会把请求转发到我们的电脑。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

所以,以后如果你想开发一个网站供全球的用户访问,那你就需要做以下几件事:

  • 拉专线,申请固定公网IP
  • 买一台服务器(就是性能好的电脑)
  • 公网IP绑定至此服务器
  • 将写好的代码放在服务器上并运行起来

这样就可以搞定了...

扩展:IPv4和IPv6

IPv4,长度为 32 位(4 个字节), 格式:A.B.C.D
IPv6,长度为 128 位(16 个字节),用":"分成8段,格式:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX(每个X是一个16进制数)。

1.2.4 云服务器

大家可能之前听说过:阿里云、腾讯云、亚马逊aws等之类的平台都在搞云服务器,那是个啥?

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

简单的说:他们造了一个机房(网吧),买了很多很多的服务器(高性能电脑),然后将他们放在机房,然后通电+通网,主要对外去租赁这些服务器资源,让用户不必再自己 拉专线+配置网络+买服务器。


假设,你想要在腾讯云租一台服务器,就可以根据自己的需求去选择配置,腾讯云会根据配置在他的物理机上虚拟出一个服务器,并进行相应的环境初始化并绑定公网固定IP,这样你就可以快速拥有一台可以被大家访问的服务器了。


注意:一台性能非常高的物理机虚拟出很多虚拟机,类似于你在自己电脑上通过vmware、parallel等搞出多个虚拟机。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

1.2.5 端口

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

假设,你在腾讯租了一台云服务器(外网IP:123.206.15.88),然后又开发了 2 个网站运行在服务器上。


那么问题来了,用户在自己的电脑或手机上如何来分别访问同一台服务器上两个程序呢?


其实,在计算机中有一个 端口 的概念,每个程序想要通过网络进行通讯都必须要指定一个端口,例如:


网站A:使用8001端口,那么用户在自己电脑上或手机上访问时指定 IP和端口 即可,如: 123.206.15.88:8001


网站B:使用8002端口,那么用户在自己电脑上或手机上访问时指定 IP和端口 即可,如: 123.206.15.88:8002


注意:端口的取值范围:0 ~ 65535,很多端口在计算机的内部已被使用,我们平时自定义时尽量选择5000之后的端口。


示例:访问百度

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

提示:如果在浏览器上只写IP不写端口,则默认是80端口。


1.2.6 域名


假设你创业开发了一个网站,用户很难记住你的公网IP:123.206.15.88:80 `123.206.15.88。


所以,域名就诞生了,让域名和IP创建对应关系,用户只需要记住域名就可以了,例如:

www.baidu.com   -->  110.242.68.3
www.taobao.com  --> 121.18.239.232
...

注意:域名只是和IP创建了对应关系,与端口无关 www.baidu.com:80

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

在用户在自己的电脑或手机上输入域名去访问时,其实要执行两个步骤:


根据域名寻找IP。(寻找IP)


获得IP之后,再通过IP再去访问指定服务器。


在电脑上属如域名后,寻找IP的过程如下:


第一步:在自己电脑的DNS缓存记录中寻找 域名对应的IP,如果未命中,则执行下一步。


第二步:在自己电脑的hosts文件中寻找,如果未命中,则执行下一步。

- mac系统:/etc/hosts 文件中
- win系统:C:\Windows\System32\drivers\etc\hosts 文件中
# 内容示例
127.0.0.1 localhost
255.255.255.255 broadcasthost
127.0.0.1 kubernetes.docker.internal
192.168.1.55 www.pythonav.com
  • 第三步:在自己电脑上找到DNS配置的地址(本地域名服务器),去这个地址寻找域名对应的IP,如果未命中,则执行下一步。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

常见的DNS服务器地址:

114.114.114.114(114 DNS)

   223.5.5.5(阿里 AliDNS)

   8.8.8.8(Google DNS,随着Google在中国的没落和国内官方的限制,已经不是太好用了)

   ...

   各大运营商也有相应的DNS服务器...

 

如果你选择的是自动获得DNS,那么就会使用本地运营商的DNS服务器了。第四步:去根域名服务器中询问(全球共13台根域名服务器,距离中国最近的一台是在日本)

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

问题来了

了解域名是怎么回事之后?现在你如果想要让自己的网站通过域名来访问,应该怎么办呢?【目前了解即可】

  • 租一个域名
  • ICANN,域名的总管理者(美国一个非营利机构),它仅制定域名政策,注册业务它会授权给一些顶级注册商。

顶级注册商,可以对外销售域名,但要受国家 互联网络信息中心的管理。例如:中国万网(阿里云收购),中国新网,新网互联,商务中国,中国频道等。

代理注册商,顶级注册上可以再招一些代理帮助他们卖域名。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

备案


现在国内注册域名后,需要进行备案(提交一些网站、个人或企业 等信息)后才能使用。

注册成功后,可按照引导备案:https://beian.aliyun.com/


注意:国外的域名无需备案就能使用。

域名解析


让域名和IP创建关联关系,并将关系同步到相关:本地域名服务器 和 根域名服务器(含顶级和二级域名服务器)。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

2. 网络编程 socket模块

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

Python中内置了一个socket模块,可以快速实现网络之间进行传输数据。例如:

  • 服务端,放在左边云服务器中(有固定IP)
import socket
# 1.监听本机的IP和端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('123.206.15.88', 8001)) # IP,端口
sock.listen(5) # 支持排队等待5人
while True:
    # 2.等待,有人来连接(阻塞)
    conn, addr = sock.accept() # 等待客户端来连接(阻塞)
    # 3.等待,连接者发送消息(阻塞)
    client_data = conn.recv(1024) # 等待接收客户端发来数据
    print(client_data.decode('utf-8')) # 字节
    # 4.给连接者回复消息
    conn.sendall("hello world".encode('utf-8'))
    # 5.关闭连接
    conn.close()
# 6.停止服务端程序
sock.close()

客户端,放在右边用户电脑上

import socket
# 1. 向指定IP发送连接请求
client = socket.socket()
client.connect(('123.206.15.88', 8001)) # 向服务端发起连接(阻塞)10s
# 2. 连接成功之后,发送消息
client.sendall('hello'.encode('utf-8'))
# 3. 等待,消息的回复(阻塞)
reply = client.recv(1024)
print(reply)
# 4. 关闭连接
client.close()

上述示例需要借助于互联网,你至少需要租一台云服务器才能通信。

为了节省学习成本,大家可以在自己电脑上模拟【服务端】和【客户端】,等以后项目开发完毕后,再租服务器并部署到服务器上。

注意:在自己本地运行上述代码时,要监听和连接时的IP地址。

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

20210527153548522.png

96447814-120fc980-1245-11eb-938d-6ea408716c72.png

当然,你也可以把在自己的局域网内找两台电脑,A作为服务端,B作为客户端,这样两者也可以通信。


服务端的代码需修改:监听的IP修改为A的IP地址。

客户端的代码需修改:连接的IP修改为A的IP地址(客户端要去找到服务端,并与服务端创建连接)。

注意事项:


本机:


服务端IP:127.0.0.1  / 192.168.28.92(局域网IP)

局域网:


服务端IP:192.168.28.92(局域网IP)    

互联网


服务端IP:123.206.15.88(外网IP)

案例:智障客服

  • 服务端
import socket
# 1.监听本机的IP和端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))  # 127.0.0.1 或 查看自己局域网本地IP地址
sock.listen(5)
while True:
    # 2.等待,有人来连接(阻塞)
    conn, addr = sock.accept()
    print("有人来连接了...")
    # 3.连接成功后立即发送
    conn.sendall("欢迎使用xx系统,请输入您想要办理的业务!".encode("utf-8"))
    while True:
        # 3.等待接受信息
        data = conn.recv(1024)
        if not data:
            break
        data_string = data.decode("utf-8")
        # 4.回复消息
        conn.sendall("你说啥?".encode("utf-8"))
    print("断开连接了")
    # 5.关闭与此人的连接
    conn.close()
# 6.停止服务端程序
sock.close()

客户端

import socket
# 1. 向指定IP发送连接请求
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8001))
# 2.连接成功后,获取系统登录信息
message = client.recv(1024)
print(message.decode("utf-8"))
while True:
    content = input("请输入(q/Q退出):")
    if content.upper() == 'Q':
        break
    client.sendall(content.encode("utf-8"))
    # 3. 等待,消息的回复
    reply = client.recv(1024)
    print(reply.decode("utf-8"))
# 关闭连接,关闭连接时会向服务端发送空数据。
client.close()

案例:文件上传

  • 服务端
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))  # 127.0.0.1 或 查看自己局域网本地IP地址
sock.listen(5)
conn, addr = sock.accept()
# 接收文件大小
data = conn.recv(1024)
total_file_size = int(data.decode('utf-8'))
# 接收文件内容
file_object = open('xxx.png', mode='wb')
recv_size = 0
while True:
    # 每次最多接收1024字节
    data = conn.recv(1024)
    file_object.write(data)
    file_object.flush()
    recv_size += len(data)
    # 上传完成
    if recv_size == total_file_size:
        break
# 接收完毕,关闭连接
conn.close()
sock.close()

客户端

import time
import os
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8001))
file_path = input("请输入要上传的文件:")
# 先发送文件大小
file_size = os.stat(file_path).st_size
client.sendall(str(file_size).encode('utf-8'))
print("准备...")
time.sleep(2)
print("开始上传..")
file_object = open(file_path, mode='rb')
read_size = 0
while True:
    chunk = file_object.read(1024) # 每次读取1024字节
    client.sendall(chunk)
    read_size += len(chunk)
    if read_size == file_size:
        break
client.close()

案例:用户登录认证

  • 服务端
import socket
import json
# 1.监听本机的IP和端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 8001))  # 127.0.0.1 或 查看自己局域网本地IP地址
sock.listen(5)
while True:
    # 2.等待,有人来连接(阻塞)
    conn, addr = sock.accept()
    # 3.连接成功后立即发送
    conn.sendall("欢迎使用xx系统".encode("utf-8"))
    while True:
        # 3.等待接受信息
        data = conn.recv(1024)
        if not data:
            break
        data_string = data.decode("utf-8")
        username, password = data_string.split('|')
        file_object = open("db.csv", mode='r', encoding='utf-8')
        is_success = False
        for line in file_object:
            user, pwd = line.strip().split(",")
            if user == username and pwd == password:
                is_success = True
        file_object.close()
        if is_success:
            info = {"status": True, 'msg': "登录成功"}
            conn.sendall(json.dumps(info).encode("utf-8"))
            break
        else:
            info = {"status": False, 'msg': "登录失败"}
            conn.sendall(json.dumps(info).encode("utf-8"))
    # 5.关闭与此人的连接
    conn.close()
# 6.停止服务端程序
sock.close()
  • 客户端
import socket
import json
# 1. 向指定IP发送连接请求
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8001))
# 2.连接成功后,获取系统登录信息
message = client.recv(1024)
print(message.decode("utf-8"))
while True:
    user = input("请输入用户名:")
    pwd = input("请输入密码:")
    content = "{}|{}".format(user, pwd)
    client.sendall(content.encode("utf-8"))
    reply = client.recv(1024)
    info = json.loads(reply.decode("utf-8"))
    if info['status']:
        print(info['msg'])  # 登录成功
        break
    else:
        print(info['msg'])  # 登录失败
# 关闭连接
client.close()
相关文章
|
10月前
|
机器学习/深度学习 计算机视觉
RT-DETR改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度
RT-DETR改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度
345 3
RT-DETR改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度
|
10月前
|
机器学习/深度学习 计算机视觉 网络架构
RT-DETR改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 StarNet,超级精简高效的轻量化模块
RT-DETR改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 StarNet,超级精简高效的轻量化模块
783 63
RT-DETR改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 StarNet,超级精简高效的轻量化模块
|
10月前
|
机器学习/深度学习 计算机视觉
RT-DETR改进策略【模型轻量化】| 替换骨干网络为 MobileViTv1高效的信息编码与融合模块,获取局部和全局信息
RT-DETR改进策略【模型轻量化】| 替换骨干网络为 MobileViTv1高效的信息编码与融合模块,获取局部和全局信息
517 62
RT-DETR改进策略【模型轻量化】| 替换骨干网络为 MobileViTv1高效的信息编码与融合模块,获取局部和全局信息
|
9月前
|
存储 人工智能 编解码
Deepseek 3FS解读与源码分析(2):网络通信模块分析
2025年2月28日,DeepSeek 正式开源其颠覆性文件系统Fire-Flyer 3FS(以下简称3FS),重新定义了分布式存储的性能边界。本文基于DeepSeek发表的技术报告与开源代码,深度解析 3FS 网络通信模块的核心设计及其对AI基础设施的革新意义。
Deepseek 3FS解读与源码分析(2):网络通信模块分析
|
10月前
|
机器学习/深度学习 计算机视觉
RT-DETR改进策略【Neck】| ECCV-2024 RCM 矩形自校准模块 优化颈部网络
RT-DETR改进策略【Neck】| ECCV-2024 RCM 矩形自校准模块 优化颈部网络
438 10
RT-DETR改进策略【Neck】| ECCV-2024 RCM 矩形自校准模块 优化颈部网络
|
10月前
|
机器学习/深度学习 计算机视觉
YOLOv11改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度
YOLOv11改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度
367 9
YOLOv11改进策略【Neck】| ASF-YOLO 注意力尺度序列融合模块改进颈部网络,提高小目标检测精度
|
10月前
|
机器学习/深度学习 计算机视觉
YOLOv11改进策略【模型轻量化】| 替换骨干网络为 MobileViTv1高效的信息编码与融合模块,获取局部和全局信息
YOLOv11改进策略【模型轻量化】| 替换骨干网络为 MobileViTv1高效的信息编码与融合模块,获取局部和全局信息
395 9
YOLOv11改进策略【模型轻量化】| 替换骨干网络为 MobileViTv1高效的信息编码与融合模块,获取局部和全局信息
|
10月前
|
机器学习/深度学习 计算机视觉
YOLOv11改进策略【Neck】| ECCV-2024 RCM 矩形自校准模块 二次创新C3k2 改进颈部网络
YOLOv11改进策略【Neck】| ECCV-2024 RCM 矩形自校准模块 二次创新C3k2 改进颈部网络
673 6
YOLOv11改进策略【Neck】| ECCV-2024 RCM 矩形自校准模块 二次创新C3k2 改进颈部网络
|
10月前
|
机器学习/深度学习 计算机视觉 网络架构
YOLOv11改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 StarNet,超级精简高效的轻量化模块
YOLOv11改进策略【模型轻量化】| 替换骨干网络 CVPR-2024 StarNet,超级精简高效的轻量化模块
860 19
|
Kubernetes 网络协议 Python
Python网络编程:从Socket到Web应用
在信息时代,网络编程是软件开发的重要组成部分。Python作为多用途编程语言,提供了从Socket编程到Web应用开发的强大支持。本文将从基础的Socket编程入手,逐步深入到复杂的Web应用开发,涵盖Flask、Django等框架的应用,以及异步Web编程和微服务架构。通过本文,读者将全面了解Python在网络编程领域的应用。
214 1