美文网首页
Python50_网络编程(UDP、TCP)

Python50_网络编程(UDP、TCP)

作者: jxvl假装 | 来源:发表于2019-09-28 10:57 被阅读0次

    网络编程基础

    网络

    • 使用网络能够把多方链接在一起,然后可以进行数据传递
    • 所谓的就是,让在不同的电脑上的软件能够进行数据传递,即进程之间的通信

    ip地址

    • 目的:用来标记网络上的一台电脑(不允许重复)

    • 如何查看当前电脑的ip:

      • Linux:ifconfig,中的inet项
      • Windows:ipconfig
    • ip地址的分类

      • ipv4:v4就是第四个版本,由四部分组成,eg:192.168.14.91,每组数里面的最大值为255,最小值为0。通常,前三部分为网络号,最后一位为主机号,前三部分均相同即表示电脑在同一个网络下,主机号的0和255不能使用(是用于回路测试的)。
        分类:
        如果用仅用第一部分作为网络号,后三部分为主机号,则为A类地址;
        如果使用前两部分作为网络号,后两部分为主机号,则为B类地址;
        前三部分作为网络网,后一部分作为主机号则为C类地址【最常用】;
        D类地址用于多点广播,第一字节以1110开始,是一个专门的保留地址,它并不指向特定的网络,多点广播地址用来一次寻址一组计算机s地址范围为224.0.0.1-239.255.255.254;
        E类ip地址以1111开始,为将来使用保留,进作实验和开发用。
        私有ip:在这么多网络中,国际规定有一部分ip地址是用于我们的局域网使用,也就是属于私网ip。他们的范围是10.*和172.*以及192.*

      • ipv6:第六个版本,ip1235均为实验版本

    端口

    端口号也是一个数字,用来对电脑上的进程进行标识(从0-65536(2的16次方)的整数)

    • 分类

      • 知名端口:众所周知的端口(0-1023),eg:80分配给HTTP服务,21分配给FTP服务,即大于1024的端口随便使用
      • 动态端口:从1024-65535,之所以称之为动态端口,是因为它一般不固定分配,而是动态分配(当一个系统程序或应用程序需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用,当程序关闭时,同时也就释放了所占用的端口号)

    socket简介

    socket:套接字

    socket是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:
    它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于socke来完成通信的,例如QQ,收发email

    在不同的语言中,socket的流程是一样的

    创建一个socket(tcp套接字)

    import socket
    
    skt = socket.socket(socket.AF_INEF, socket.SOCK_STREAM)#创建tcp套接字,因为tcp协议是流式传输,所以第二个参数为SOCK_STREAM
    #第一个参数为协议族,AF_INEF表示使用的是ipv4的协议族
    #socket.socket()是一个类,此语句创建了一个套接字对象
    
    #这里是使用套接字的功能(省略)
    
    skt.close() #不用的时候关闭套接字
    

    创建一个udp socket(udp套接字)

    import socket
    udp_skt = socket.socket(socket.AF_INEF, SOCK_DGRAM) #因为udp协议传输的为报文,所以第二个参数为SOCK_DGRAM
    
    #使用套接字的功能(省略)
    
    udp_skt.close() #不用的时候,关闭
    

    UDP

    UDP(User Datagram Protocol)通信模型中,不需要建立相关的链接,只需要发送数据即可,类似于生活中“写信”——不安全

    udp发送数据demo

    from socket import *
    
    #1.创建套接字
    udp_socket = socket(AF_INET,SOCK_DGRAM)
    
    #2.准备接受方地址
    #'192.168.205.11'表示目的ip
    #8080表示目的端口
    dest_ip = "192.168.205.1"
    dest_port = 8080
    dest_addr = (dest_ip, dest_port)    #对方的ip和端口用元组
    
    #3.从键盘获得数据
    send_data = input(">")
    
    #4.发送数据到指定电脑上的指定程序中
    udp_socket.sendto(send_data.encode("utf-8"), dest_addr) #两个参数:要发送的内容,接受方地址
    
    #关闭套接字
    udp_socket.close()
    
    

    可配合网络调试助手接收到以上程序发送的消息

    udp接收数据demo

    如果程序要接收数据,必须要一个固定的端口:使用bind绑定

    客户端:

    from socket import  *
    
    HOST = "192.168.0.113"  #因为是自己写程序在本机上验证,所以这里的ip就是本机ip
    PORT = 8888
    
    BUFSIZE = 1024
    ADDR = (HOST, PORT)
    
    udpCliSock = socket(AF_INET, SOCK_DGRAM)    #第一个参数表示网络编程,第二个参数表示报文传输
    #udpCliSock.bind(("",7788)) #可以使用此句给自己绑定端口,这样就不会使用系统随机分配的端口
    while True:
        data = input(">")
        if not data:
            break   #如果没有输入数据,跳出循环,结束程序
        udpCliSock.sendto(data.encode("utf-8"),ADDR)    #将数据发送给目的地,
        # 因为在发送数据之前没有给自己绑定端口号,所以系统会随机分配一个端口号
        data, ADDR = udpCliSock.recvfrom(BUFSIZE)   #BUFSIZE表示本次接收的最大字节数
        #接收从服务器端返回的数据,recvfrom在数据没有到来的时候会阻塞
    
        if not data:
            break
    
        print(data.decode("utf-8"))
    
    udpCliSock.close()
    

    服务器端:

    from socket import *
    import time
    
    HOST = ""   #本机ip,因为是做服务器,所以可以不用写,表示本机的任意一个ip
    PORT = 8888 #端口号,不是小于1024的都可以
    BUFSIZ = 1024   #缓冲区大小
    ADDR = (HOST, PORT) 
    
    
    udpServSock = socket(AF_INET, SOCK_DGRAM)   #获得一个socket
    
    udpServSock.bind(ADDR)  #绑定本堤的相关信息(ip与端口),如果一个网络程序不绑定,则系统会随机分配
    
    while True:
        print("waitting for message...")
        data, addr = udpServSock.recvfrom(BUFSIZ)   #bufsize表示本次接收的最大字节数,recvfrom的返回值为数据和发送端的地址(ip与端口组成的元组)
        #程序会在这里一直等待,直到接收到数据
        data = data.decode("utf-8")
        content = '[%s] %s' %(bytes(time.ctime(),"utf-8"), data)
        print("收到数据:", data)
        udpServSock.sendto(content.encode("utf-8"), addr)   #把收到的数据发送回客户端
        print("received from and return to:", addr)
    
    udpServSock.close()
    

    注意:严格来说,UDP并没有服务器和客户端的概念,想要收就recfrom,想要发就sendto

    ps:代码不是从上到下写的,而是用到什么导入什么,用到什么定义什么

    注意:同一个端口在同一时刻只能被一个程序所使用

    • 单工:要么只能收数据,要么只能发数据
    • 半双工:可以发送也可以接收数据,但是同一时刻只能收或者发
    • 全双工:同一时刻既可以发也可以收

    TCP(更常用)

    简介:

    • TCP协议,传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议
    • TCP通信需要经过创建连接、数据传送、终止连接三个步骤
    • TCP通信模型中,在通信开始之前,一定要先建立相关的链接,才能发送数据,类似于生活中“打电话”

    TCP和UDP的不同点:

    • 面向连接
    • 有序数据传输
    • 重发丢失的数据包
    • 舍弃重复的数据包
    • 无差错的数据传输
    • 阻塞/流量控制

    TCP注意点

    1. tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器
    2. tcp客户端一般不绑定,因为是主动链接服务器,所以只要确定好服务器的ip,port等信息就好,本地客户端可以随机
    3. tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动的,这是做tcp服务器时必须要做的
    4. 当客户端需要连接服务器时,就需要使用connect进行链接,udp是不需要链接的而是直接发送,但是tcp必须先链接,只有链接成功才能通信
    5. 当一个tcp客户端连接服务器时,服务器端会有1个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务
    6. listen后的套接字是被动套接字,用来接收新的客户端的链接请求的,而accept返回的新套接字是标记这个新客户端的
    7. 关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能够链接服务器,但是之前已经链接成功的客户端正常通信
    8. 关闭accept返回的套接字意味着这个客户端已经服务完毕
    9. 当客户端的套接字调用close后,服务器端会recv接堵塞,并且返回的长度为0,因此服务器可以通过返回数据的长度来区别客户端是否已经下线

    ps:tcp有三次握手和四次挥手 (由操作系统底层自己完成)

    客户端:

    from socket import *
    
    
    def creat_link():
        """
        建立链接
        :return: 套接字
        """
        # 1. 创建tcp的套接字,ps:客户端一般不绑定端口号,但是可以绑定
        tcp_socket = socket(AF_INET, SOCK_STREAM)
        #tcp_socket.setsockopt(socket.SOL__SOCKET, socket.SO_REUSERADDR, 1) # 让服务器端可以先close,而不会出现地址正在使用中的问题(具体涉及到四次挥手)
        # 2. 链接服务器
        server_ip = input("server ip:")
        server_port = None
        try:
            server_port = int(input("server port:"))
        except:
            print("Error! The port must be integer!")
            exit()
        server_addr = (server_ip, server_port)
        tcp_socket.connect(server_addr)
        print("connect has created")
        return tcp_socket
    
    def send_data(tcp_socket):
        # 3. 发送数据/接收数据
        send_data = input(">")
        tcp_socket.send(send_data.encode("utf-8"))  # udp用sendto方法(因为没有链接,所以每次发送都必须知道目的地),
        # tcp因为有链接,所以使用send方法即可
        if send_data == "exit()":
            exit()
    
    def recv_data(tcp_socket):
        recv_msg = tcp_socket.recv(1024)
        print("received msg from server: {}".format(recv_msg.decode("utf-8")))
    
    def main():
        tcp_socket = creat_link()
        while True:
            send_data(tcp_socket)
            recv_data(tcp_socket)
    
        #4. 关闭tcp套接字
        tcp_socket.close()
    
    if __name__ == "__main__":
        main()
    

    服务器端:
    tcp服务器的流程如下(以买手机类比):

    • socket创建一个套接字(买了手机)
    • bind绑定ip和port(插上手机卡)
    • listen使套接字变为可以被动链接(设置手机为正常接听状态——即能够响铃)
    • accept等待客户端的链接(等待别人拨打)
    • recv/send接收或发送数据(进行通话)
    #服务器端必须绑定 
    from socket import *
    
    def creat_server():
        # 1. 买个手机(创建套接字)
        tcp_server_socket = socket(AF_INET, SOCK_STREAM)
        # 2. 插入手机卡(绑定本地信息 bind)
        server_port = None
        try:
            server_port = int(input("set server port:"))
        except:
            print("port must be integer!")
            exit()
        tcp_server_socket.bind(("", server_port))
        # 3. 将手机设置为正常的响铃模式(让默认的套接字由主动变为被动 listen)
        print("waiting connect...")
        tcp_server_socket.listen(128)
        return tcp_server_socket
    
    def creat_link(tcp_server_socket):
        # 4. 等待别人的电话到来(等待客户端的链接 accept)
        new_client_socket, client_addr = tcp_server_socket.accept()  # accept的返回值是一个元组(client_socket, client_addr)
        print(str(client_addr) + " has connected")
        # 监听套接字默认会阻塞,直到有返回值
        # a, b = (11, 22)类似这样的形式被称之为拆包,前后数量必须对应
        # 通常是由一个套接字见监听,然后每accept到一个链接请求,就创建一个新的套接字与其链接(为客户端服务)
        # 通常是服务器先接收,客户端先发送
        return (new_client_socket, client_addr)
    
    def recv_and_return_msg(new_client_socket, client_addr):
        recv_data = new_client_socket.recv(1024)    #如果客户端已经close(),则recv也会解阻塞,此时recv_data为空,
        #如果是因为客户端发送了数据导致recv解阻塞,则recv_data不为空
        #ps:客户端无法发送空内容
        if recv_data.decode("utf-8") == "exit()":
            return "exit()"
        print(str(client_addr) + ":", end="")
        print(recv_data.decode("utf-8"))
        new_client_socket.send(recv_data)
    
    def close_all_socket(tcp_server_socket, new_client_socket):
        # 关闭套接字
        tcp_server_socket.close()
        new_client_socket.close()
        print("Link has be closed")
    
    def main():
        tcp_server_socket = creat_server()
        new_client_socket, client_addr = creat_link(tcp_server_socket)
    
        while True:
            tag = recv_and_return_msg(new_client_socket,client_addr)
            if tag == "exit()":
                close_all_socket(tcp_server_socket, new_client_socket)
                exit()
    
    if __name__ == "__main__":
        main()
    

    案例:文件下载器

    客户端:

    from socket import *
    
    def main():
        #1. 创建套接字
        tcp_socket = socket(AF_INET, SOCK_STREAM)
        #2. 获取服务器的ip, port
        dest_ip = input("server ip:")
        dest_port = int(input("server port:"))
        #3. 链接服务器
        tcp_socket.connect((dest_ip, dest_port))
        #4. 获取下载文件的名字
        file_name = input("请输入要下载的文件名字:")
    
        #5. 将文件名字发送给服务器
        tcp_socket.send(file_name.encode("utf-8"))
        #6. 接收文件中的数据
        recv_data = tcp_socket.recv(1024*1024)  #为了使程序简化,假设文件最大1M
        #7. 保存接收到的数据到文件中
        with open("[副件-]" + file_name, "wb") as f:
            f.write(recv_data)
            print("文件:[{}]下载完成!".format(file_name))
        #8. 关闭套接字
        tcp_socket.close()
    if __name__ == "__main__":
        main()
    

    服务器端:

    from socket import *
    
    def creat_server():
        # 1. 买个手机(创建套接字)
        tcp_server_socket = socket(AF_INET, SOCK_STREAM)
        # 2. 插入手机卡(绑定本地信息 bind)
        server_port = None
        try:
            server_port = int(input("set server port:"))
        except:
            print("port must be integer!")
            exit()
        tcp_server_socket.bind(("", server_port))
        # 3. 将手机设置为正常的响铃模式(让默认的套接字由主动变为被动 listen)
        print("waiting connect...")
        tcp_server_socket.listen(128)   #listen的参数一般写128,和同一时刻服务器端建立的连接数有关系,但是主要由操作系统决定,并不是说写128就是最多128个连接
        return tcp_server_socket
    
    def creat_link(tcp_server_socket):
        # 4. 等待别人的电话到来(等待客户端的链接 accept)
        new_client_socket, client_addr = tcp_server_socket.accept()  # accept的返回值是一个元组(client_socket, client_addr)
        print(str(client_addr) + " has connected")
        # 监听套接字默认会阻塞,直到有返回值
        # a, b = (11, 22)类似这样的形式被称之为拆包,前后数量必须对应
        # 通常是由一个套接字见监听,然后每accept到一个链接请求,就创建一个新的套接字与其链接(为客户端服务)
    
        # 通常是服务器先接收,客户端先发送
        return (new_client_socket, client_addr)
    
    def recv_and_return_msg(new_client_socket, client_addr):
        file_name = new_client_socket.recv(1024)
        if file_name.decode("utf-8") == "exit()":
            return "exit()"
        print(str(client_addr) + ":", end="")
        print("正在发送文件:" + file_name.decode("utf-8"))
        with open(file_name,"r") as f:
            content = f.read()
            new_client_socket.send(content.encode("utf-8"))
    
    def close_all_socket(tcp_server_socket, new_client_socket):
        # 关闭套接字
        tcp_server_socket.close()
        new_client_socket.close()
        print("Link has be closed")
    
    def main():
        tcp_server_socket = creat_server()
        new_client_socket, client_addr = creat_link(tcp_server_socket)
        recv_and_return_msg(new_client_socket,client_addr)
        close_all_socket(tcp_server_socket, new_client_socket)
    
    if __name__ == "__main__":
        main()
    

    结合多线程实现群聊

    客户端:

    from socket import *
    import time
    import logging as logger
    import concurrent.futures as futures
    
    logger.basicConfig(level=logger.DEBUG)
    
    class Tcp_Client:
        def __init__(self, host = "127.0.0.1", port = 9999):
            self.sock = socket(AF_INET, SOCK_STREAM)
            self.sock.connect((host, port))
    
        def send(self, msg):
            """
            向服务器发送信息
            :param msg:
            :return:
            """
            self.sock.send(msg.encode("utf-8"))
    
        def read(self):
            """
            :return:
            """
            while True:
                data = self.sock.recv(1024)
                print(data.decode("utf-8"))
    
    
    def main():
        ex = futures.ThreadPoolExecutor(max_workers = 5)
        tc = Tcp_Client()
        ex.submit(tc.read)
    
        while True:
            data = input(">")
            tc.send(data)
    
    
    main()
    

    服务器端:

    from socket import *
    import logging as logger
    import concurrent.futures as futures
    
    logger.basicConfig(level=logger.DEBUG)
    
    class TcpServer:
        def __init__(self, port = 9999):
            self.host = ""  #由于是服务器端,所以host为空
            self.port = port
            self.clients = []
            self.ex = futures.ThreadPoolExecutor(max_workers = 5)
    
        def run(self):
            logger.info("服务器启动,监听端口:%d" % self.port)
            self.server_sock = socket(AF_INET, SOCK_STREAM)
            self.server_sock.bind((self.host, self.port))
            self.server_sock.listen(5)
            while True:
                client_socket, client_addr = self.server_sock.accept()
                self.ex.submit(self.read, client_socket)
                logger.info("客户端{}:{}建立连接".format(client_addr[0],str(client_addr[1])))
                self.clients.append((client_socket, client_addr))
    
        def read(self, client_socket):
            while True:
                data = client_socket.recv(1024)
                print(data.decode("utf-8"))
                for client in self.clients:
                    sock = client[0]
                    addr = client[1]
                    if sock != client_socket:
                        info = addr[0] + ":"  + str(addr[1]) + ":" + data.decode("utf-8")
                        logger.info("向客户端{}:{}发送信息".format(addr[0], str(addr[1])))
    
                        sock.send(info.encode("utf-8"))
    
    
    TcpServer().run()
    
    

    TCP与UDP的流程对比

    udp:                    tcp客户端:         tcp服务器端:
    socket                  socket              socket
    bind                    connect             bind
    recvfrom/sendto         send/recv           listen
    close                   close               accept
                                                recv/send
                                                close
    

    相关文章

      网友评论

          本文标题:Python50_网络编程(UDP、TCP)

          本文链接:https://www.haomeiwen.com/subject/rohuuctx.html