zoukankan      html  css  js  c++  java
  • 基于TCP协议的socket

    一、套接字

    套接字(socket)是一个抽象层,应用程序可以通过它发送或接受数据,可对其进行像文件一样的打开、读写和关闭等操作。网络套接字是IP地址与端口的组合。

    套接字是网络编程中的一种通信机制,是支持TCP/IP得其网络的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单地说就是通信两方的一种约定,用套接字中的相关函数来完成通信过程。

     

    二、两种协议

    TCP协议

    称为流式协议,可靠协议,是一种提供可靠数据传输的通用协议。

    UDP协议

    数据报协议(自带报头),一个面向无连接的协议。采用该协议不需要两个应用程序先建立连接。不能够提供数据互传,因此还协议传输数据安全新差。

     

    三、TCP通信

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

    1.socket通信简易版本

    服务端

    import socket
    server = socket.socket()
    server.bind(('127.0.0.1',8080))  # 插电话卡  绑定IP地址和端口(把地址绑定到套接字)
    server.listen()  # 开机  半连接池
    
    conn,addr = server.accept()  # 接听 (接收客户端链接)
    data = conn.recv(1024)  # 听别人说话(接收客户端信息)
    print(data)  # 打印客户端信息
    conn.send(b'hello girl')  # 给别人回话(向客户端发送信息)
    
    conn.close()  # 挂电话(关闭客户端套接字)
    server.close()  # 关机(关闭服务器套接字)
    server端

    客户端

    import socket
    
    client = socket.socket()  # 拿电话(创建套接字)
    client.connect(('127.0.0.1',8080))  # 拨号 写对方的ip和port(尝试连接服务器)
    
    client.send(b'hello boy')  # 对别人说话(发送信息)
    data = client.recv(1024)  # 听别人说话(接受信息)
    print(data)  # 打印数据,也就是所得信息
    
    client.close()  # 挂电话(关闭客户端套接字)
    client端

    2.通信循环

    服务端

    import socket
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    conn,addr = server.accept()
    while True:
    
        data = conn.recv(1024)
        print(data)
    
        conn.send(data.upper())  # 将客户端传过来的数据转换成大写给客户端
    server端

    客户端

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    while True:  # 利用While 循环来实现
        msg = input('>>>:').encode('utf-8')  # 输入内容
        client.send(msg)  # 将这个内容发送给服务端
        data = client.recv(1024)
        print(data)  # 打印出这个数据
    client端

    3.连接循环

    服务端

    import socket
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)  # 5表示允许客户端最大等待数为5
    
    
    while True:  # 连接循环上一层,下面一层结束了,返回继续执行
    
        conn,addr = server.accept()  # 等待别人来
        print(addr)  # 打印结果为客户端的ip地址
    
        while True:
            try:  # 能够有效地解决服务端报错问题
                data = conn.recv(1024)
                print(data)
                if len(data) == 0:
                    break
    
                conn.send(data.upper())
            except ConnectionResetError as e:
                print(e)
                break
        conn.close()
    server端

    客户端

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:
            continue
        client.send(msg)
        data = client.recv(1024)
        print(data)
    client端

    4.代码健壮性补充

    问题1

    服务端代码

    import socket
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    conn,addr = server.accept()
    while True:
    
        data = conn.recv(1024)
        print(data)
    
        conn.send(data.upper())

    客户端代码

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    while True:  # 利用While 循环来实现
        msg = input('>>>:').encode('utf-8')  # 输入内容
        client.send(msg)  # 将这个内容发送给服务端
        data = client.recv(1024)
        print(data)  # 打印出这个数据

    1.先启动服务端,在启动客户端正常运行

    2.结束客户端后,客户端能够正常退出,服务端会报错

    针对于这个问题,就利用异常处理的方式来解决

    客户端代码不变,服务端需要添加 Try 的方式来解决,这样服务端也能够正常退出,不会报错

    服务端更改后的代码

    import socket
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    conn,addr = server.accept()
    print(addr)
    
    while True:
        try:  # 能够有效地解决服务端报错问题
            data = conn.recv(1024)
            print(data)
            if len(data) == 0:
                break
    
            conn.send(data.upper())
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

    问题2

    1.先启动服务端,在启动客户端正常运行

    产生的问题:

    会发现点击Enter键的时候,客户端不能够输入内容,服务端也一直在等待,这样就导致了程序一直卡在这了

    2.解决方法:

    在服务端等待的原基础上,对客户端代码进行修改,需要对msg进行一个判断

    更改后的代码

    import socket
    
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:
            continue
        client.send(msg)
        data = client.recv(1024)
        print(data)

    进行判断之后就可以输入了

    5.TCP粘包问题

    问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。

    1.使用struct模块解决数据繁多冗杂

     

    由上面两张图可知,打包之前文件大小为48,打包之后为4,无论文件大小是多少,打包结果都是4,

    解包之后就会还原成原来文件的大小

     

    2.使用struct模块解决TCP粘包问题

    借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

    服务端

     

    import struct
    import subprocess
    import socket
    import json
    
    server = socket.socket()
    server.bind(('127.0.0.1',8080))
    server.listen(5)
    
    while True:
        conn,addr = server.accept()
        while True:
            try:
                cmd = conn.recv(1024)
                if len(cmd) == 0:
                    break
                cmd = cmd.decode('utf-8')
                obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                res = obj.stdout.read() + obj.stderr.read()
                d = {'name':'jason','file_size':len(res),'info':'dasdjfhjncksc'}
                json_d = json.dumps(d)
                #1.先制作一个字典的报头
                header = struct.pack('i',len(json_d))
                #2.发送字典报头
                conn.send(header)
                #3.发送字典
                conn.send(json_d.encode('utf-8'))
                #4.发送真实的数据
                conn.send(res)
            except ConnectionRefusedError:
                break
        conn.close()
    server端

     

    客户端

    import socket
    import struct
    import json
    client = socket.socket()
    client.connect(('127.0.0.1',8080))
    while True:
        msg = input('>>>:').encode('utf-8')
        if len(msg) == 0:
            continue
        client.send(msg)
    
        #1.先接受字典报头
        header_dict = client.recv(4)
        #2.解析报头,获取字典长度
        dict_size = struct.unpack('i',header_dict)[0]
        #3.接受字典数据
        dict_bytes = client.recv(dict_size)
        dict_json = json.loads(dict_bytes.decode('utf-8'))
        #4.从字典中获取信息
        print(dict_json)
        recv_size = 0
        real_data = b''
        while recv_size < dict_json.get('file_size'):
            data = client.recv(1024)
            real_data += data
            recv_size += len(data)
        print(real_data.decode('gbk'))
    client端
  • 相关阅读:
    Git checkout on a remote branch does not work
    制作ubuntu U盘安装盘
    Angular 2.0 和 1.x比较
    图文浅谈css3
    前后端数据交互方法
    CSS实现响应式全屏背景图
    html5图片高度自适应解决方法
    2014年最后100天,想说点啥。
    js常用代码
    html5+css3开发总结
  • 原文地址:https://www.cnblogs.com/xiongying4/p/11321253.html
Copyright © 2011-2022 走看看