zoukankan      html  css  js  c++  java
  • day7 socket网络编程基础

    Socket

    Socket是什么?

        下面来看一下网络的传输过程:

       

        上面图片显示了网络传输的基本过程,传输是通过底层实现的,有很多底层,我们写传输过程的时候,要知道所有的过程那就太复杂了,socket为我们封装了底层的传输流程,让我们直接可以在socket上直接实现数据交换。

        socket本质:对底层网络协议的封装。

        socket实现数据的发送和接收,通过什么建立连接呢?下面看一幅简单的图片:

          

        在计算机上,我们运行了很多线程,我们如何实现数据的定向交换呢?如何实现客户端和服务器的连接呢?连接我们可以通过IP地址进行连接,连接上之后发送给那个程序呢?这时候我们就要通过port(端口号)进行指明,因为实现连接的过程是通过IP+端口号(port)进行连接。

        客户端

    import socket
    while True:
        s = socket.socket()
        '''生成一个socket连接'''
        s.connect(("localhost",6970))   #建立连接,连接本地,端口号是6969端口
    
        message = input("请输入您要发送的信息:").encode('utf-8')
        if message == "quit":
            break
        s.sendall(message)   #python3上只能传输二进制字节
        data = s.recv(1024)      #接收服务器端传来的内容
        print(data)
    
    s.close()   #关闭连接

        上面代码就是客户端,通过客户端发送数据到服务器,实现交互,客户端加上了一个while循环,能够实现多次交互,我们知道,正常情况下,交互一次就退出了,通过While循环,让客户端不停的产生新的连接,就能不断与客户端进行数据交换。

        下面是客户算发送的数据,只能以字符的形式进行发送,所以发送的是字符,汉字看不到,进行转换了。而且不知道为什么,输入空之后,客户端没有响应,静止不动了:

    请输入您要发送的信息:dasfda
    b'dasfda'
    请输入您要发送的信息:不能发送汉字吗
    b'xe4xb8x8dxe8x83xbdxe5x8fx91xe9x80x81xe6xb1x89xe5xadx97xe5x90x97'
    请输入您要发送的信息:放大法是否对
    b'xe6x94xbexe5xa4xa7xe6xb3x95xe6x98xafxe5x90xa6xe5xafxb9'
    请输入您要发送的信息:dfafdas
    b'dfafdas'
    请输入您要发送的信息:dfasdfa
    b'dfasdfa'
    请输入您要发送的信息:dfasfd
    b'dfasfd'
    请输入您要发送的信息:afdasdfas
    fb'afdasdfas'
    请输入您要发送的信息:dasfda
    b'fdasfda'
    请输入您要发送的信息:dfa
    fdab'dfa'
    请输入您要发送的信息:
    b'fda'
    请输入您要发送的信息:afasfda
    b'afasfda'
    请输入您要发送的信息:afdasfd
    b'afdasfd'
    请输入您要发送的信息:afdasdfa
    b'afdasdfa'
    请输入您要发送的信息:

    服务器

    import socket
    
    '''生成socket实例'''
    s = socket.socket()
    s.bind(("localhost",6970))    #绑定本地IP和6969端口号
    s.listen(10)     #监听客户端发送的信息,一旦有客户端发送过来连接,就接收,现在是等待状态,防止堵塞
    print("连接建立完毕,正在等待数据.......")
    while True:
        conn,addr = s.accept()    #接收数据,accept()会接收两个数据,一个是连接conn,一个是地址addr(IP和端口号)
        print("Addr:",addr)
        data = conn.recv(1024)   #通过连接接收数据
        print(data)
        conn.send(data)      #发送数据,把接收到的信息发送
    
    conn.close()
    s.close()

         上面是服务器的代码,我们也使用了一个循环,让服务器一直挂着,等待客户端发送数据,不停的接收:

        下面是服务器接收的数据:

    连接建立完毕,正在等待数据.......
    Addr: ('127.0.0.1', 51924)
    b'dasfda'
    Addr: ('127.0.0.1', 51926)
    b'xe4xb8x8dxe8x83xbdxe5x8fx91xe9x80x81xe6xb1x89xe5xadx97xe5x90x97'
    Addr: ('127.0.0.1', 51928)
    b'xe6x94xbexe5xa4xa7xe6xb3x95xe6x98xafxe5x90xa6xe5xafxb9'
    Addr: ('127.0.0.1', 51930)
    b'dfafdas'
    Addr: ('127.0.0.1', 51932)
    b'dfasdfa'
    Addr: ('127.0.0.1', 51934)
    b'dfasfd'
    Addr: ('127.0.0.1', 51936)
    b'afdasdfas'
    Addr: ('127.0.0.1', 51938)
    b'fdasfda'
    Addr: ('127.0.0.1', 51940)
    b'dfa'
    Addr: ('127.0.0.1', 51942)
    b'fda'
    Addr: ('127.0.0.1', 51944)
    b'afasfda'
    Addr: ('127.0.0.1', 51946)
    b'afdasfd'
    Addr: ('127.0.0.1', 51948)
    b'afdasdfa'
    Addr: ('127.0.0.1', 51950)

        可以看出,数据进行了多次的交互,并且接收了数据,是以字符编码形式进行接收的。

    客户端
    import socket
    
    client = socket.socket()    #声明socket类型,同时生成socket连接对象
    client.connect(("localhost",6969))
    
    client.send("我要下载A片".encode("utf-8"))     #在python3中只能发送字节
    data = client.recv(1024)    #默认接收数据的大小,定义1024个字节
    print("recv:",data.decode())
    
    client.close()    #关闭连接
    
    
    服务器
    #服务器端
    import socket
    
    server = socket.socket()
    server.bind(("localhost",6969))   #绑定要监听端口
    server.listen()    #监听
    
    print("我要开始等电话了")
    conn,addr = server.accept()    #等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
    #conn就是客户端连过来而在服务器端为其生成的一个连接实例
    print(conn,addr)
    
    print("电话来了")
    data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
    print("recv:",data)
    conn.send(data.upper())
    
    server.close()

        下面来看一个循环的代码:

    #服务器端
    import socket
    
    server = socket.socket()
    server.bind(("localhost",6969))   #绑定要监听端口
    server.listen()    #监听
    
    print("我要开始等电话了")
    while True:
        conn,addr = server.accept()    #等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
        #conn就是客户端连过来而在服务器端为其生成的一个连接实例
        print(conn,addr)
    
        print("电话来了")
        data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
        print("recv:",data)
        conn.send(data.upper())
    
    server.close()

        首先启动服务器端,等待客户端发送数据:

        客户端:

    import socket
    
    client = socket.socket()    #声明socket类型,同时生成socket连接对象
    client.connect(("localhost",6969))
    
    while True:
        msg = input(">>:").strip()      #输入空会卡主,我们知道,QQ也是不允许用户输入空的,会提示输入为空
        client.send(msg.encode("utf-8"))     #在python3中只能发送字节
        data = client.recv(1024)    #默认接收数据的大小,定义1024个字节
        print("recv:",data.decode())
    
    client.close()    #关闭连接

        客户端输入:

        >>:我
      recv: 我
      >>:要

        上面可以看出,客户端输入第一次正常,第二次卡住了,只能进行一次通讯,说明客户端的实现一次通讯之后终止了。

        服务端接收:

        我要开始等电话了
      <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6969), raddr=     ('127.0.0.1', 58460)> ('127.0.0.1', 58460)
      电话来了
      recv: b'xe6x88x91'

        可以看出,服务器端第一次接收数据之后,也卡住了,服务器端卡主是由于没有新的连接进来,连接并没有终端,而只是客户端自己断开,找不到新的连接挂在哪里。

        下面来修改服务器代码:

    #服务器端
    import socket
    
    server = socket.socket()
    server.bind(("localhost",7071))   #绑定要监听端口
    server.listen()    #监听
    
    print("我要开始等电话了")
    conn, addr = server.accept()  # 等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
    # conn就是客户端连过来而在服务器端为其生成的一个连接实例
    print(conn, addr)
    print("电话来了")
    
    while True:
        data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
        # if not data:
        #     break
        print("recv:",data)
        conn.send(data.upper())
    
    server.close()

        现在重新启动服务器,然后启动客户端,在客户端输入交互内容,如下:

    客户端输入呢绒:
    >>:wo 
    recv: WO
    >>:yao
    recv: YAO
    >>:zixue
    recv: ZIXUE
    
    
    服务器端接收内容:
    我要开始等电话了
    <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51508)> ('127.0.0.1', 51508)
    电话来了
    recv: b'wo'
    recv: b'yao'
    recv: b'zixue

        现在显示一切都是那么完美,现在来断开客户端,看会怎样,如下:

        ecv: b''
      recv: b''
      recv: b''
      recv: b''
      recv: b''

        ......

        客户端断开之后,服务器端并没有断开,由于服务器端在循环接收消息,由于没有客户端发送消息,因为接收到的消息一直是空的,并且服务器端server = socket.accept()并没有执行接收过程,因为一直在运行循环。

        下面我们来验证服务器端的情况,看客户端断了之后,服务器端是如何执行的,为此我们需要简单修改一下服务器端代码,如下:

    #服务器端
    import socket
    
    server = socket.socket()
    server.bind(("localhost",7071))   #绑定要监听端口
    server.listen()    #监听
    
    print("我要开始等电话了")
    conn, addr = server.accept()  # 等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
    # conn就是客户端连过来而在服务器端为其生成的一个连接实例
    print(conn, addr)
    print("电话来了")
    
    count = 0
    while True:
        data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
        # if not data:
        #     break
        print("recv:",data)
        conn.send(data.upper())
        count += 1
        if count >= 10:
            break
    
    server.close()

      运行结果如下:

      我要开始等电话了
      <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=     ('127.0.0.1', 51532)> ('127.0.0.1', 51532)
      电话来了
      recv: b'wo'
      recv: b'yao'
      recv: b'xe8x87xaaxe5xadxa6'
      recv: b'xe5xbex80'
      recv: b''
      recv: b''
      recv: b''
      recv: b''
      recv: b''
      recv: b''

        从上面代码可以看出,当客户端端口之后,服务器端并没有断开,由于连接断开了,服务器一直在循环接收数据。(在Windows上,如果客户端断开,服务器端也会断开)

        下面来修改一下代码,让客户端断开,服务器端也断开。

        如下:

    #服务器端
    import socket
    
    server = socket.socket()
    server.bind(("localhost",7071))   #绑定要监听端口
    server.listen()    #监听
    
    print("我要开始等电话了")
    conn, addr = server.accept()  # 等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
    # conn就是客户端连过来而在服务器端为其生成的一个连接实例
    print(conn, addr)
    print("电话来了")
    
    while True:
        data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
        if not data:       #加入一个判断,如果接收为空,则断开服务器
            break
        print("recv:",data)
        conn.send(data.upper())
    
    server.close()

        上面代码中,对服务器端代码进行了简单完善,让客户端断开的时候,服务器端也断开,如何实现呢?只需要判断客户端接收到空的时候终止既可以。然后启动服务器,启动客户端进行数据传输如下:

    客户端输入:
    /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
    >>:woyao
    recv: WOYAO
    >>:自学
    recv: 自学
    >>:Traceback (most recent call last):
      File "/home/zhuzhu/第七天/socket_client.py", line 7, in <module>
        msg = input(">>:").strip()      #输入空会卡主,我们知道,QQ也是不允许用户输入空的,会提示输入为空
    KeyboardInterrupt
    
    
    服务器端接收:
    我要开始等电话了
    <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51548)> ('127.0.0.1', 51548)
    电话来了
    recv: b'woyao'
    recv: b'xe8x87xaaxe5xadxa6'

        从上面结果可以看出,客户端断开时候,服务器端也断开了,那么如何实现客户端断开,服务器端接收新的客户端的消息,只断开当前连接,重新生成一个新的连接。如下:

    服务器端

    #服务器端
    import socket
    
    server = socket.socket()
    server.bind(("localhost",7071))   #绑定要监听端口
    server.listen()    #监听
    while True:
        print("我要开始等电话了")
        conn, addr = server.accept()  # 等待接收,conn对方的连接,addr对方的地址,接收会接收到一个IP编号和地址
        # conn就是客户端连过来而在服务器端为其生成的一个连接实例
        print(conn, addr)
        print("电话来了")
    
        while True:
            data = conn.recv(1024)    #接收的数据大小,不能直接调用server接收,打电话新的进来,可以切换
            if not data:
                break
            print("recv:",data)
            conn.send(data.upper())
    
    server.close()

        上面代码进行了两层嵌套,内层嵌套是如果接收消息为空,则断开本次连接,外层循环是内存连接断开之后,重新生成一个新的连接,因此我们首先启动服务器端,现在同时打开三个客户端如下:

        启动服务器:

        我要开始等电话了
      <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=     ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
      电话来了

        客户端1:

        /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
      >>:

        客户端2:

        /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
      >>:

        客户端3:

        /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
      >>:

        首先在客户端1发送消息,如下:

        >>:11
      recv: 11

        服务器:

        /usr/bin/python3.5 /home/zhuzhu/第七天/socket_server.py
      我要开始等电话了
      <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=       ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
      电话来了
      recv: b'11'

        可以看到,服务器接收到了客户端1发送的消息,在客户端2和客户端3发送消息,如下:

        客户端2:

        >>:shibushi

        客户端3:
        /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
      >>:接收不到吗?  
        服务器端:

        /usr/bin/python3.5 /home/zhuzhu/第七天/socket_server.py
      我要开始等电话了
      <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=     ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
      电话来了
      recv: b'11'

        可以看到,服务器并没有接收到客户端2和客户端3的信息,现在终端客户端1,如下:

        客户端1:

        >>:11
      >>:Traceback (most recent call last):
       File "/home/zhuzhu/第七天/socket_client.py", line 7, in <module>
        msg = input(">>:").strip()      #输入空会卡主,我们知道,QQ也是不允许用户输入空的,会提示输入为空
      KeyboardInterrupt

        服务器:

        我要开始等电话了
      <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=     ('127.0.0.1', 51568)> ('127.0.0.1', 51568)
      电话来了
      recv: b'11'
      我要开始等电话了
      <socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1',     7071), raddr=     ('127.0.0.1', 51570)> ('127.0.0.1', 51570)
      电话来了
      recv: b'shibushi'

         从上面可以看出,客户端1断开后,服务器与客户端1的连接也断开了,生成了一个新的连接,连接客户端2,并接收客户端2发送过来的消息。

        因此我们得出结论,上面代码实现了客户端断开后,服务器端重新挂起,等待新的连接的任务。

        当所有客户端中断后,如下:

    /usr/bin/python3.5 /home/zhuzhu/第七天/socket_server.py
    我要开始等电话了
    <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51568)> ('127.0.0.1', 51568)
    电话来了
    recv: b'11'
    我要开始等电话了
    <socket.socket fd=5, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51570)> ('127.0.0.1', 51570)
    电话来了
    recv: b'shibushi'
    我要开始等电话了
    <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 7071), raddr=('127.0.0.1', 51572)> ('127.0.0.1', 51572)
    电话来了
    recv: b'xe6x8exa5xe6x94xb6xe4xb8x8dxe5x88xb0xe5x90x97xefxbcx9f'
    recv: b'shibush'
    recv: b'duankaiyixai'
    recv: b'exit'
    我要开始等电话了

        可以看出,当客户端所有连接断开后,服务器端还在等待消息。等待新的连接介入。

        上面客户端代码是有缺陷的,因为send()是不能发送空的,我们发送空就会让客户端卡主,如下:

        客户端输入:

        >>:eoyoa
      recv: EOYOA
      >>:dfasfd
      recv: DFASFD
      >>:                        #发送空,卡住了

        从上面客户端执行代码可以看出,确实当前卡主了,现在来完善一下客户端,让当用户输入为空的时候,跳过本次过程,告诉用户不能发送空,重新输入:

    import socket
    
    client = socket.socket()    #声明socket类型,同时生成socket连接对象
    client.connect(("localhost",7071))
    
    while True:
        msg = input(">>:").strip()      #输入空会卡主,我们知道,QQ也是不允许用户输入空的,会提示输入为空
        if not msg:
            print("对不起,发送消息不能为空!")
            continue
        client.send(msg.encode("utf-8"))     #在python3中只能发送字节,send()不能发送空
        data = client.recv(1024)    #默认接收数据的大小,定义1024个字节
        print("recv:",data.decode())
    
    client.close()    #关闭连接

        上面代码中,客户端加入了一个判断,当用户输入发送消息为空时,告诉用户不能为空,并跳过本次循环(我们知道,QQ聊天也是不能发送空消息的),运行如下:

        客户端输入:

        /usr/bin/python3.5 /home/zhuzhu/第七天/socket_client.py
      >>:dasfd
      arecv: DASFD
      >>:dfasfd
      recv: ADFASFD
      >>:
      对不起,发送消息不能为空!
      >>:发顺丰达
      recv: 发顺丰达                         #不小心打了一个广告
      >>:
      对不起,发送消息不能为空!
      >>:

        可以看出,上述代码实现了不能发送为空的功能,如果想要空的时候退出,可以把continue改成break,当然这是非主流做法了。任何聊天工具不会让用户输入空就终止聊天的。

  • 相关阅读:
    windows下多个python版本共存,删掉一个
    解决ModuleNotFoundError: No module named 'pip'问题
    Palindrome Linked List 综合了反转链表和快慢指针的解法
    30-Day Leetcoding Challenge Day9
    Longest Common Prefix 五种解法(JAVA)
    30-Day Leetcoding Challenge Day8
    30-Day Leetcoding Challenge Day7
    30-Day Leetcoding Challenge Day6
    30-Day Leetcoding Challenge Day2
    leetcode162 Find Peak Element
  • 原文地址:https://www.cnblogs.com/gengcx/p/7277370.html
Copyright © 2011-2022 走看看