zoukankan      html  css  js  c++  java
  • python----网络编程之server与client实现聊天功能

    Socket套接字方法

    family(socket家族)

    socket.AF_UNIX: # 用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成
    socket.AF_INET: # (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)

    socket type类型

    socket.SOCK_STREAM  # for tcp
    socket.SOCK_DGRAM  # for udp
    socket.SOCK_RAW  # 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。
    socket.SOCK_RDM  # 是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。SOCK_RAM通常仅限于高级用户或管理员运行的程序使用

    服务端套接字函数

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

    客户端套接字函数

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

    公共用途的套接字函数

    s.recv()  # 接收数据
    s.send()  # 发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完,可后面通过实例解释)
    s.sendall()  # 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
    s.recvfrom()  # 从套接字接收数据. 返回值是一对(字节,地址)
    s.getpeername()  # 连接到当前套接字的远端的地址
    s.close()  # 关闭套接字
    socket.setblocking(flag)  # True or False,设置socket为非阻塞模式,以后讲io异步时会用
    socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) # 返回远程主机的地址信息,例子 socket.getaddrinfo('luffycity.com',80)
    socket.getfqdn()  # 拿到本机的主机名
    socket.gethostbyname()  # 通过域名解析ip地址

    基本的套接字例子

    服务端:

    和客户端编程相比,服务器编程就要复杂一些。

    服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。

    所以,服务器会打开固定端口监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。

    首先,创建一个基于IPv4和TCP协议的Socket

    然后,我们要绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址。127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。

    端口号需要预先指定。因为我们写的这个服务不是标准服务,所以用8083这个端口号。请注意,小于1024的端口号必须要有管理员权限才能绑定

    紧接着,调用listen()方法开始监听端口,传入的参数指定等待连接的最大数量

    接下来,服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接

    连接建立后,客户端发来"hello",服务端返回大写的"HELLO"

    import socket
    
    # phone和conn两个套接字
    # 1.买手机  基于网络通信,基于TCP协议
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # print(phone)
    
    # 2.绑定手机  真实情况下,服务端的地址
    phone.bind(('127.0.0.1', 8083))  # 0-65535:0-1024个操作系统使用,1024以后随便用
    
    # 3.开机  监听listen  最大挂起5个
    phone.listen(5)
    
    # 4.等电话链接
    print('staring...')
    conn, client_addr = phone.accept()  # 接收链接对象  等于客户端的connect,底层完成了套接字的三次握手
    
    # 5.收 发消息
    while True:  # 通信循环
        data = conn.recv(1024)  # 1.单位:bytes 2.1024代表最大接受1024个bytes
        print('客户端的数据', data)
        conn.send(data.upper())
    
    # 6.挂电话
    conn.close()
    
    # 7.关机
    phone.close()
    View Code

    客户端:

    创建Socket时,AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功,但是还没有建立连接。

    客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。

    服务器,提供什么样的服务,端口号就必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80端口,因为80端口是Web服务的标准端口。其他服务都有对应的标准端口号,例如SMTP服务是25端口,FTP服务是21端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。

    注意phone.connect参数是一个tuple,包含地址和端口号。

    建立TCP连接后,我们就可以向服务器发送请求。

    接收数据时,调用recv(max)方法,一次最多接收指定的字节数,因此,在一个while循环中反复接收,直到recv()返回空数据,表示接收完毕,退出循环。

    当我们接收完数据后,调用close()方法关闭Socket,这样,一次完整的网络通信就结束了.

    import socket
    
    
    # 1.买手机  基于网络通信,基于TCP协议
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # print(phone)
    
    # 2.拨号
    phone.connect(('127.0.0.1', 8083))
    
    
    # 3.发 收消息
    phone.send('hello'.encode('utf-8'))
    data = phone.recv(1024)
    print(data)
    
    # 4.关闭
    phone.close()
    View Code

    Server与Client循环收发数据

    服务端:

    import socket
    
    
    # phone和conn两个套接字
    # 1.买手机  基于网络通信,基于TCP协议
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # print(phone)
    
    # 2.绑定手机  真实情况下,服务端的地址
    phone.bind(('127.0.0.1', 8083))  # 0-65535:0-1024个操作系统使用,1024以后随便用
    
    # 3.开机  监听listen  最大挂起5个
    phone.listen(5)
    
    # 4.等电话链接
    print('staring...')
    conn, client_addr = phone.accept()  # 接收链接对象
    print(client_addr)
    
    # 5.收 发消息
    while True:
        data = conn.recv(1024)  # 1.单位:bytes 2.1024代表最大接受1024个bytes
        print('客户端的数据', data)
        conn.send(data.upper())
    
    # 6.挂电话
    conn.close()
    
    # 7.关机
    phone.close()
    View Code

    客户端:

    import socket
    
    
    # 1.买手机  基于网络通信,基于TCP协议
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # print(phone)
    
    # 2.拨号
    phone.connect(('127.0.0.1', 8083))
    
    
    # 3.发 收消息
    while True:
        msg = input('>>>:').strip()
        phone.send(msg.encode('utf-8'))
        data = phone.recv(1024)
        print(data)
    
    # 4.关闭
    phone.close()
    View Code

    Server与Client实现简单的聊天功能

    服务端:

    import socket
    
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    phone.bind(('127.0.0.1', 8083))
    
    phone.listen(5)
    
    conn, client_addr = phone.accept()
    
    while True:
        try:
            data = conn.recv(1024)
            print('Client recv:', data)
            if not data:
                break
            response = input('>>>:').strip()
            conn.send(response.encode())
            print('Server send:', response)
        except ConnectionResetError:
            print('客户端强制关闭')
            break
    
    conn.close()
    
    phone.close()
    View Code

    客户端:

    import socket
    
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    phone.connect(('127.0.0.1', 8083))
    
    while True:
        msg = input('>>>:').strip()
        if not msg:continue
        phone.send(msg.encode('utf-8'))
        data = phone.recv(1024)
        print(data)
    
    phone.close()
    View Code

    简单的的聊天功能存在以下问题:

    1.多个客户端与服务端交互的时候,处于一个派对的状态,只有第一个客户端与服务端断开后,下一个客户端与服务端才可以交互.

    2.实际上断开第一个客户端,服务端也跟着断了,原因在于服务端收不到数据,直接就断开了

    下面是优化后的聊天功能代码:

    服务端:

    import socket
    
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    
    phone.bind(('127.0.0.1', 8083))
    
    phone.listen(5)
    while True:
    
        conn, client_addr = phone.accept()
    
        while True:
            try:
                data = conn.recv(1024)
                print('Client recv:', data)
                if not data:  # 服务端收不到数据,就直接break了
                    break
                response = input('>>>:').strip()
                conn.send(response.encode())
                print('Server send:', response)
            except ConnectionResetError:
                print('客户端强制关闭')
                break
    
        conn.close()
    
    phone.close()
    View Code

    客户端:

    import socket
    
    phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    phone.connect(('127.0.0.1', 8083))
    
    while True:
        msg = input('>>>:').strip()
        if not msg:continue
        phone.send(msg.encode('utf-8'))
        data = phone.recv(1024)
        print(data)
    
    phone.close()
    View Code

    重启服务端时可能会遇到端口被占用的问题:

    解决方法:

    sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)  # 一行代码搞定,写在bind之前
    sock_server.bind((HOST, PORT))

    或者服务端和客户端修改端口.

  • 相关阅读:
    ubuntu下Thrift快速入门
    java Future用法和意义一句话击破 [转]
    RPC、基于netty的长连接和websocket
    基于netty的长连接
    IO
    HTML5 中websocket长连接的具体实现方法
    单链表中是否出现环状,使用快慢指针算法。
    SpringMVC 事务配置完全详解
    hibernate annotation 一对多,多对一,实例
    tomcat 内存溢出
  • 原文地址:https://www.cnblogs.com/cnike/p/10726706.html
Copyright © 2011-2022 走看看