zoukankan      html  css  js  c++  java
  • 网络编程之socket编程

    套接字介绍

    socket介绍

    Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

    所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

    套接字介绍

    套接字的概念很简单,每台主机有一个唯一的主机地址标识,同时主机内还有标识自己进程的序号id,称作端口,将这两个标识符结合就构成了一个套接字(socket),这个套接字能唯一标识网络中的一个进程。(网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket, 又称为“套接字”。)

    一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。

    • 基于文件类型的套接字: AF_UNIX

       unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

    • 基于网络类型的套接字: AF_INET

             还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET

    套接字工作流程

    1. 服务器先初始化Socket对象来建立一个套接字, 用这个套接字完成通信的监听。
    2. 然后用bind函数来绑定一个端口号和IP地址。
    3. 服务器调用listen函数对服务器的这个端口和IP地址进行监听,等待客户机的连接。
    4. 客户机初始化Socket对象, 设定远程 IP 和端口。
    5. 然后调用connect函数连接服务器,如果连接成功,这时客户端与服务器端的连接就建立了。
    6. 服务器用accept函数来接受远程计算机的连接,建立起与客户机之间的通信。
    7. 建立连接以后,客户机用write函数向socket中写入数据。也可以用 read 函数读取服务器发送来的数据。
    8. 服务器用read函数读取客户机发送来的数据,也可以用 write 函数来发送数据。
    9. 完成通信以后,用 close 函数关闭 socket 连接。

    socket模块函数用法

    import socket
    socket.socket(socket_family,socket_type,protocal=0)
    # socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
    
    # 获取tcp/ip套接字
    tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 获取udp/ip套接字
    udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    View Code

    基于TCP协议的套接字通信

    tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端

    一个简单的tcp套接字通信

    # 服务端
    import socket
    
    # 1. 初始化socket对象
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM: 流式协议->tcp协议
    
    # 2. 绑定ip地址和端口
    server.bind(('127.0.0.1', 8888))  # 0-65535 1024以前的都被系统保留使用
    
    # 3. 监听端口
    server.listen(5)  # 5指的是半链接池的大小
    print('服务端启动成功, 监听地址为: %s:%s' % ('127.0.0.1', 8888))
    
    # 4. 等待客户端连接, 拿到连接通道
    print('等待连接.....')
    conn, client_addr = server.accept()
    print('连接的双向通道:', conn)
    print('客户端的ip和端口:', client_addr)
    
    # 5. 接收消息
    data = conn.recv(1024)  # 最大接收的数据量为1024Bytes, 收到的是bytes类型
    print(data.decode())
    
    # 6. 发送消息
    conn.send('你好, 连接成功'.encode())
    
    # 7. 关闭连接conn
    conn.close()
    
    # 8. 关闭服务端连接(可选操作)
    server.close()
    
    
    # 客户端
    import socket
    
    # 1. 初始化socket对象
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM: 流式协议->tcp协议
    
    # 2. 建立连接
    client.connect(('127.0.0.1', 8888))
    
    # 3. 发送消息
    client.send('我发起了建立连接请求...'.encode())
    
    # 4. 接收消息
    data = client.recv(1024)
    print(data.decode())
    
    # 5. 关闭连接
    client.close()
    View Code

    加上通信循环的tcp套接字通信

    # 服务端
    import socket
    
    # 1. 初始化socket对象
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM: 流式协议->tcp协议
    
    # 2. 绑定ip地址和端口
    server.bind(('127.0.0.1', 6666))  # 0-65535 1024以前的都被系统保留使用
    
    # 3. 监听端口
    server.listen(5)  # 5指的是半链接池的大小
    print('服务端启动成功, 监听地址为: %s:%s' % ('127.0.0.1', 6666))
    
    # 4. 等待客户端连接, 拿到连接通道
    print('等待连接.....')
    conn, client_addr = server.accept()
    print('连接的双向通道:', conn)
    print('客户端的ip和端口:', client_addr)
    
    # 5. 接收消息
    while True:
        data = conn.recv(1024)  # 最大接收的数据量为1024Bytes, 收到的是bytes类型
        if len(data) == 0:
            # 在unix系统中, 一旦data收到的是空, 意味着一种异常行为: 客户端非法断开链接
            break
        print('客户端 >>>:', data.decode())
    
        # 6. 发送消息
        msg = input('服务端 >>>:').strip()
    
        conn.send(msg.encode())
    
    # 7. 关闭连接conn
    conn.close()
    
    # 8. 关闭服务端连接(可选操作)
    server.close()
    
    
    # 客户端
    import socket
    
    # 1. 初始化socket对象
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM: 流式协议->tcp协议
    
    # 2. 建立连接
    client.connect(('127.0.0.1', 6666))
    
    # 3. 发送消息
    while True:
        msg = input('客户端 >>>:').strip()
        if msg == 'close':
            break
        if len(msg) == 0:
            continue
        client.send(msg.encode())
    
        # 4. 接收消息
        data = client.recv(1024)
        print('服务端 >>>:', data.decode())
    
    # 5. 关闭连接
    client.close()
    View Code

    加上链接循环的tcp套接字通信

    # 服务端
    # 服务端应该满足的特点:
    # 1. 最好一直提供服务
    # 2. 并发的提供服务
    import socket
    
    # 1. 初始化socket对象
    server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM: 流式协议->tcp协议
    
    # 2. 绑定ip地址和端口
    server.bind(('127.0.0.1', 6666))  # 0-65535 1024以前的都被系统保留使用
    
    # 3. 监听端口
    server.listen(5)  # 5指的是半链接池的大小
    print('服务端启动成功, 监听地址为: %s:%s' % ('127.0.0.1', 6666))
    
    # 4. 等待客户端连接, 拿到连接通道
    # 加上链接循环
    while True:
        print('等待连接.....')
        conn, client_addr = server.accept()
        print('连接的双向通道:', conn)
        print('客户端的ip和端口:', client_addr)
    
        while True:
            # 5. 接收消息
            data = conn.recv(1024)  # 最大接收的数据量为1024Bytes, 收到的是bytes类型
            if len(data) == 0:
                # 在unix系统中, 一旦data收到的是空, 意味着一种异常行为: 客户端非法断开链接
                continue
            print('客户端 >>>:', data.decode())
    
            # 6. 发送消息
            msg = input('服务端 >>>:').strip()
    
            conn.send(msg.encode())
    
        # 7. 关闭连接conn
        conn.close()
    
    # # 8. 关闭服务端连接(可选操作)
    # server.close()
    
    # 客户端
    import socket
    
    # 1. 初始化socket对象
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # SOCK_STREAM: 流式协议->tcp协议
    
    # 2. 建立连接
    client.connect(('127.0.0.1', 6666))
    
    # 3. 发送消息
    while True:
        msg = input('客户端 >>>:').strip()
        if msg == 'close':
            break
        if len(msg) == 0:
            continue
        client.send(msg.encode())
    
        # 4. 接收消息
        data = client.recv(1024)
        print('服务端 >>>:', data.decode())
    
    # 5. 关闭连接
    client.close()
    View Code

    需要注意的是, 加上链接循环的tcp套接字通信中, 因为设置了server.listen(5), 也就是说半连接池中的数量为5, 那么前5个客户端中的后四个客户端会依次阻塞等待前一个客户端与服务端通信结束, 再进行与服务端的通信, 但是前5个客户端后面的客户端,也会进行阻塞, 但是在建立连接的时候就会阻塞, 与前5个客户端不同, 前5个客户端是在发送信息的时候阻塞。而且如果时间过长的话, 后面的会报连接时间过长的错误。

    如果在重启服务器的时候显示端口占用的错误, 是因为服务端仍然存在四次挥手的time_wait状态在占用地址

    解决方法: 

    phone=socket(AF_INET,SOCK_STREAM)
    phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 重用端口
    phone.bind(('127.0.0.1',8080))

    基于UDP协议的套接字通信

    udp是无链接的,先启动哪一端都不会报错。

    一个简单的udp套接字通信

    # 服务端
    import socket
    
    server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # SOCK_DGRAM 数据报协议 -> udp协议
    
    server.bind(('127.0.0.1', 8888))
    
    while True:
        data, client_addr = server.recvfrom(1024)
        print('客户端:', data.decode())
        print(client_addr)
    
        msg = input('>>>:').strip()
        server.sendto(msg.encode(), ('127.0.0.1', 50711))
    
        if msg == 'close':
            break
    
    server.close()
    
    
    # 客户端
    import socket
    
    client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    
    while True:
        msg = input('>>>: ').strip()
        client.sendto(msg.encode(), ('127.0.0.1', 8888))
    
        if msg == 'close':
            break
    
    client.close()
    View Code
  • 相关阅读:
    LC 面试题56
    AspNet 执行存储过程带参数 and Entity Framework 之存储过程篇
    SQL中Charindex和Oracle中对应的函数Instr
    Spring MVC遭遇checkbox的问题解决方案
    Spring MVC 的 multipartResolver 不能同iWebOffice2006 共同使用
    Spring CommonsMultipartResolver 上传文件
    解决自定义文件上传处理与Spring MultipartResolver的冲突问题
    GooFlow
    js判断undefined类型
    mybatis generator tools配置文件解析
  • 原文地址:https://www.cnblogs.com/featherwit/p/13360686.html
Copyright © 2011-2022 走看看