socket 套接字编程

 

今日内容

  • socket 套接字编程
  • 简易服务端与客户端代码实现
  • 通信循环
  • 黏包现象(TCP协议)
  • 报头制作、struct 模块、封装形式

内容详细

一、socket 套接字编程

实现一款能够进行数据交互的程序。

他们互通信息就得通过网络传输数据,那就肯定会涉及 OSI 七层协议的操作,而每次传输数据都要对OSI 七层协议进行操作,就会重复很多相似的工作,这时候就出现了 socket 模块,封装了OSI 七层协议的操作代码,我们在传输数据时,就可以通过socket 实例化的对象以点的形式方便快捷调用操作方法。

  • socket 模块

下面,我就用 socket 模块实现一个最简易的套接字编程

注意:先有服务端启动,客户端才能够成功连接服务端,实现数据互通

服务端

import socket

# 实例化一个套接字对象
server = socket.socket()

# 给'服务端(应用程序)'绑定一个 IP + 端口号,作为唯一标识
server.bind(('192.168.11.134', 8080))

# 设置半连接池,最多容量(等待连接数)为5
server.listen(5)

# 监听,当有客户端申请连接,获取它的socket对象和应用程序地址(IP+端口)
sock, address = server.accept()

# 接收数据
data = sock.recv(1024)

print(data.decode('utf8'))

# 发送数据
sock.send('elijah is very good'.encode('utf8'))

# 关闭与客户端连接的socket对象
sock.close()

# 关闭服务端自己的socket对象
server.close()

客户端

import socket


# 实例化客户端的socket对象
client = socket.socket()

# 客户端根据 IP+端口 精准连接服务端
client.connect(('192.168.11.134', 8080))

# 给服务端发送数据
client.send('i am client'.encode('utf8'))

# 接收服务端回传的数据
data = client.recv(1024)

print(data.decode('utf8'))

# 关闭客户端
client.close()

二、通信循环

给简易版本升一下级,让服务端与客户端可以输入自定义的信息,并且不会一传输完数据就结束进程,让服务端一直处于监听状态,随时可以与客户端进行连接。

服务端

import socket

# 实例化一个套接字对象
server = socket.socket()

# 给'服务端(应用程序)'绑定一个 IP + 端口号,作为唯一标识
server.bind(('192.168.11.134', 8080))

# 设置半连接池,最多容量(等待连接数)为5
server.listen(5)

# 监听,当有客户端申请连接,获取它的socket对象和应用程序地址(IP+端口)
sock, address = server.accept()

while True:
    # 接收数据
    client_data = sock.recv(1024)

    print(client_data.decode('utf8'))

    # 发送数据
    server_data = input('输入您的回复>>>: ')
    sock.send(server_data.encode('utf8'))

客户端

import socket


# 实例化客户端的socket对象
client = socket.socket()

# 客户端根据 IP+端口 精准连接服务端
client.connect(('192.168.11.134', 8080))

while True:
    # 给服务端发送数据
    client_data = input('输入您的信息>>>: ')
    client.send(client_data.encode('utf8'))

    # 接收服务端回传的数据
    data = client.recv(1024)

    print(data.decode('utf8'))

实现你一句我一句的通信:

codesys套接字_服务端

代码优化

1、让服务端一直处于监听状态,当服务端回复空消息,则断开与当前客户端的连接,回到监听状态
2、客户端发送的消息不可以为空(避免两者都处于revc等待状态)
3、服务端添加兼容性代码(mac linux)
4、服务端重启频繁报端口占用错误
	from socket import SOL_SOCKET, SO_REUSEADDR
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 在bind前加
5、客户端异常关闭服务端报错的问题
	异常捕获

6.服务端链接循环
7.半连接池
	设置可以等待的客户端数量(超出数量会报错)

服务端

import socket

# 实例化一个套接字对象
server = socket.socket()

# 给'服务端(应用程序)'绑定一个 IP + 端口号,作为唯一标识
server.bind(('192.168.11.134', 8080))

# 设置半连接池,最多容量(等待连接数)为5
server.listen(5)

while True:
    # 监听,当有客户端申请连接,获取它的socket对象和应用程序地址(IP+端口)
    sock, address = server.accept()

    while True:
        # 处理客户端异常断开错误
        try:
            # 接收数据
            client_data = sock.recv(1024)

            print(client_data.decode('utf8'))

            # 发送数据
            server_data = input('输入您的回复>>>: ')
            if len(server_data) == 0:
                break
            sock.send(server_data.encode('utf8'))
        except Exception as e:
            print(e)
            break

客户端

import socket


# 实例化客户端的socket对象
client = socket.socket()

# 客户端根据 IP+端口 精准连接服务端
client.connect(('192.168.11.134', 8080))

while True:
    # 给服务端发送数据
    client_msg = input('输入您的信息>>>: ')
    if len(client_msg) == 0:
        continue
    client.send(client_msg.encode('utf8'))

    # 接收服务端回传的数据
    data = client.recv(1024)

    print(data.decode('utf8'))

三、黏包问题

由于TCP协议也是一个流式协议,数据是会像流水一样进行传输

当客户端向服务端发起命令请求,服务端返回很大容量的数据,而客户端一次只接收1024字节,显然接收不完,剩余的数据不会丢失,而是会堵在传输通道中,等客户端下一次发起申请命令时,剩余的数据就会涌入,导致答非所问的问题。

TCP协议有一个特性

当发送的数据量比较少,多次发送,且发送时间间隔比较短
那么TCP会自动把这些数据全部打包成一个数据包接收


sock.send('litle')
sock.send('ll')
sock.send('a')

client.recv()
client.recv()
client.recv()
---》 litlella

解决黏包问题

  • 制作报头

报头用于标识即将到来的数据具体信息

比如,数据的具体大小,这样,接收方就可以根据这个数据大小的具体信息,决定接收多大的数据

client.recv(4343534)

简易版本报头

import socket
import subprocess
import json
import struct

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    sock, address = server.accept()
    while True:
        data = sock.recv(1024)  # 接收cmd命令
        command_cmd = data.decode('utf8')
        sub = subprocess.Popen(command_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        res = sub.stdout.read() + sub.stderr.read()  # 结果可能很大
        # 1.制作报头
        data_first = struct.pack('i', len(res))
        # 2.发送报头
        sock.send(data_first)
        # 3.发送真实数据
        sock.send(res)
     
import socket
import struct

client = socket.socket()  # 买手机
client.connect(('127.0.0.1', 8080))  # 拨号

while True:
    msg = input('请输入cmd命令>>>:').strip()
    if len(msg) == 0:
        continue
    client.send(msg.encode('utf8'))
    # 1.先接收固定长度为4的报头数据
    recv_first = client.recv(4)
    # 2.解析报头
    real_length = struct.unpack('i',recv_first)[0]
    # 3.接收真实数据
    real_data = client.recv(real_length)
    print(real_data.decode('gbk'))

可以把报头制作成字典的形式,这样,除了可以接收数据具体大小信息,还可以接收更多其它的数据信息,并且strut 打包时 'i' 模式也不会数据过大而报错

拓展知识

在阅读源码的时候
	1.变量名后面跟冒号 表示的意思是该变量名需要指代的数据类型
    2.函数后更横杆加大于号表示的意思是该函数的返回值类型

解决粘包问题比较完善的措施(运用字典解决)

 服务端

服务端

import socket
import subprocess
import json
import struct

server =socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
    sock,address = server.accept()
    while True:
        data = sock.recv(1024)
        command_cmd = data.decode('utf8')
        sub = subprocess.Popen(command_cmd,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        res = sub.stdout.read()+sub.stderr.read()
        #定义一个字典
        data_dict = {
            'decs':'这是一个非常重要的数据',
            'size':len(res),
            'info':'姣姣打工日记'

        }
        data_json = json.dumps(data_dict) #json序列化的字符串,统计它的长度好打包
        #制作字典报头
        data_first = struct.pack('i',len(data_json))
        #发送字典包头
        sock.send(data_first)
        #发送字典
        sock.send(data_json.encode('utf8'))
        #发送真实的数据
        sock.send(res)

 

客户端

import json
import socket
import struct

client = socket.socket()
client.connect(('127.0.0.1',8080))
while True:
    msg = input('请输入cmd命令>>>:').strip()
    if len(msg) == 0:
        continue
    client.send(msg.encode('utf8'))
    #先接受固定长度为4的字典包头数据
    recv_first = client.recv(4)
    #解析字典报头
    real_length = struct.unpack('i',recv_first)[0]
    #接受字典数据
    real_data = client.recv(real_length)
    print(real_data.decode('gbk'))
    #解析字典报头(json格式的bytes数据 loads方法会自动先解码 后反序列化
    real_dict = json.loads(real_data) #json格式的二进制不需要解码,只需要json.loads即可解码
    print(real_dict)
    #获取字典中的各项数据
    data_length = real_dict.get('size')
    data_bytes = client.recv(data_length)
    print(data_bytes.decode('gbk'))
    print(data_bytes.decode('gbk'))






文件传输
服务端
  import socket
import subprocess
import json
import struct
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
    sock, address = server.accept()
    while True:
        # 1.先接收固定长度为4的字典报头数据
        recv_first = sock.recv(4)
        # 2.解析字典报头
        dict_length = struct.unpack('i', recv_first)[0]
        # 3.接收字典数据
        real_data = sock.recv(dict_length)
        # 4.解析字典(json格式的bytes数据 loads方法会自动先解码 后反序列化)
        real_dict = json.loads(real_data)
        # 5.获取字典中的各项数据
        data_length = real_dict.get('size')
        file_name = real_dict.get("file_name")

        recv_size = 0
        with open(file_name, 'wb') as f:
            while recv_size < data_length:
                data = sock.recv(1024)
                recv_size += len(data)
                f.write(data)

客户端

import json
import socket
import struct
import os

client = socket.socket()  # 买手机
client.connect(('127.0.0.1', 8080))  # 拨号
while True:
    data_path = r'D:\  py20\py\day2\数据列表'
    # print(os.listdir(data_path))  # [文件名称1 文件名称2 ]
    movie_name_list = os.listdir(data_path)
    for i, j in enumerate(movie_name_list, 1):
        print(i, j)
    choice = input('请选择您想要上传的电影编号>>>:').strip()
    if choice.isdigit():
        choice = int(choice)
        if choice in range(1, len(movie_name_list) + 1):
            # 获取文件名称
            movie_name = movie_name_list[choice - 1]
            # 拼接文件绝对路径
            movie_path = os.path.join(data_path, movie_name)
            # 1.定义一个字典数据
            data_dict = {
                'file_name': 'XXX老师合集.mp4',
                'desc': '这是非常重要的数据',
                'size': os.path.getsize(movie_path),
                'info': '下午挺困的,可以提神醒脑'
            }
            data_json = json.dumps(data_dict)
            # 2.制作字典报头
            data_first = struct.pack('i', len(data_json))
            # 3.发送字典报头
            client.send(data_first)
            # 4.发送字典
            client.send(data_json.encode('utf8'))
            # 5.发送真实数据
            with open(movie_path, 'rb') as f:
                for line in f:
                    client.send(line)

 







今日内容

  • socket 套接字编程
  • 简易服务端与客户端代码实现
  • 通信循环
  • 黏包现象(TCP协议)
  • 报头制作、struct 模块、封装形式

内容详细

一、socket 套接字编程

实现一款能够进行数据交互的程序。

他们互通信息就得通过网络传输数据,那就肯定会涉及 OSI 七层协议的操作,而每次传输数据都要对OSI 七层协议进行操作,就会重复很多相似的工作,这时候就出现了 socket 模块,封装了OSI 七层协议的操作代码,我们在传输数据时,就可以通过socket 实例化的对象以点的形式方便快捷调用操作方法。

  • socket 模块

下面,我就用 socket 模块实现一个最简易的套接字编程

注意:先有服务端启动,客户端才能够成功连接服务端,实现数据互通

服务端

import socket

# 实例化一个套接字对象
server = socket.socket()

# 给'服务端(应用程序)'绑定一个 IP + 端口号,作为唯一标识
server.bind(('192.168.11.134', 8080))

# 设置半连接池,最多容量(等待连接数)为5
server.listen(5)

# 监听,当有客户端申请连接,获取它的socket对象和应用程序地址(IP+端口)
sock, address = server.accept()

# 接收数据
data = sock.recv(1024)

print(data.decode('utf8'))

# 发送数据
sock.send('elijah is very good'.encode('utf8'))

# 关闭与客户端连接的socket对象
sock.close()

# 关闭服务端自己的socket对象
server.close()

客户端

import socket


# 实例化客户端的socket对象
client = socket.socket()

# 客户端根据 IP+端口 精准连接服务端
client.connect(('192.168.11.134', 8080))

# 给服务端发送数据
client.send('i am client'.encode('utf8'))

# 接收服务端回传的数据
data = client.recv(1024)

print(data.decode('utf8'))

# 关闭客户端
client.close()

二、通信循环

给简易版本升一下级,让服务端与客户端可以输入自定义的信息,并且不会一传输完数据就结束进程,让服务端一直处于监听状态,随时可以与客户端进行连接。

服务端

import socket

# 实例化一个套接字对象
server = socket.socket()

# 给'服务端(应用程序)'绑定一个 IP + 端口号,作为唯一标识
server.bind(('192.168.11.134', 8080))

# 设置半连接池,最多容量(等待连接数)为5
server.listen(5)

# 监听,当有客户端申请连接,获取它的socket对象和应用程序地址(IP+端口)
sock, address = server.accept()

while True:
    # 接收数据
    client_data = sock.recv(1024)

    print(client_data.decode('utf8'))

    # 发送数据
    server_data = input('输入您的回复>>>: ')
    sock.send(server_data.encode('utf8'))

客户端

import socket


# 实例化客户端的socket对象
client = socket.socket()

# 客户端根据 IP+端口 精准连接服务端
client.connect(('192.168.11.134', 8080))

while True:
    # 给服务端发送数据
    client_data = input('输入您的信息>>>: ')
    client.send(client_data.encode('utf8'))

    # 接收服务端回传的数据
    data = client.recv(1024)

    print(data.decode('utf8'))

实现你一句我一句的通信:

codesys套接字_服务端

代码优化

1、让服务端一直处于监听状态,当服务端回复空消息,则断开与当前客户端的连接,回到监听状态
2、客户端发送的消息不可以为空(避免两者都处于revc等待状态)
3、服务端添加兼容性代码(mac linux)
4、服务端重启频繁报端口占用错误
	from socket import SOL_SOCKET, SO_REUSEADDR
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 在bind前加
5、客户端异常关闭服务端报错的问题
	异常捕获

6.服务端链接循环
7.半连接池
	设置可以等待的客户端数量(超出数量会报错)

服务端

import socket

# 实例化一个套接字对象
server = socket.socket()

# 给'服务端(应用程序)'绑定一个 IP + 端口号,作为唯一标识
server.bind(('192.168.11.134', 8080))

# 设置半连接池,最多容量(等待连接数)为5
server.listen(5)

while True:
    # 监听,当有客户端申请连接,获取它的socket对象和应用程序地址(IP+端口)
    sock, address = server.accept()

    while True:
        # 处理客户端异常断开错误
        try:
            # 接收数据
            client_data = sock.recv(1024)

            print(client_data.decode('utf8'))

            # 发送数据
            server_data = input('输入您的回复>>>: ')
            if len(server_data) == 0:
                break
            sock.send(server_data.encode('utf8'))
        except Exception as e:
            print(e)
            break

客户端

import socket


# 实例化客户端的socket对象
client = socket.socket()

# 客户端根据 IP+端口 精准连接服务端
client.connect(('192.168.11.134', 8080))

while True:
    # 给服务端发送数据
    client_msg = input('输入您的信息>>>: ')
    if len(client_msg) == 0:
        continue
    client.send(client_msg.encode('utf8'))

    # 接收服务端回传的数据
    data = client.recv(1024)

    print(data.decode('utf8'))

三、黏包问题

由于TCP协议也是一个流式协议,数据是会像流水一样进行传输

当客户端向服务端发起命令请求,服务端返回很大容量的数据,而客户端一次只接收1024字节,显然接收不完,剩余的数据不会丢失,而是会堵在传输通道中,等客户端下一次发起申请命令时,剩余的数据就会涌入,导致答非所问的问题。

TCP协议有一个特性

当发送的数据量比较少,多次发送,且发送时间间隔比较短
那么TCP会自动把这些数据全部打包成一个数据包接收


sock.send('litle')
sock.send('ll')
sock.send('a')

client.recv()
client.recv()
client.recv()
---》 litlella

解决黏包问题

  • 制作报头

报头用于标识即将到来的数据具体信息

比如,数据的具体大小,这样,接收方就可以根据这个数据大小的具体信息,决定接收多大的数据

client.recv(4343534)

简易版本报头

import socket
import subprocess
import json
import struct

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    sock, address = server.accept()
    while True:
        data = sock.recv(1024)  # 接收cmd命令
        command_cmd = data.decode('utf8')
        sub = subprocess.Popen(command_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        res = sub.stdout.read() + sub.stderr.read()  # 结果可能很大
        # 1.制作报头
        data_first = struct.pack('i', len(res))
        # 2.发送报头
        sock.send(data_first)
        # 3.发送真实数据
        sock.send(res)
     
import socket
import struct

client = socket.socket()  # 买手机
client.connect(('127.0.0.1', 8080))  # 拨号

while True:
    msg = input('请输入cmd命令>>>:').strip()
    if len(msg) == 0:
        continue
    client.send(msg.encode('utf8'))
    # 1.先接收固定长度为4的报头数据
    recv_first = client.recv(4)
    # 2.解析报头
    real_length = struct.unpack('i',recv_first)[0]
    # 3.接收真实数据
    real_data = client.recv(real_length)
    print(real_data.decode('gbk'))

可以把报头制作成字典的形式,这样,除了可以接收数据具体大小信息,还可以接收更多其它的数据信息,并且strut 打包时 'i' 模式也不会数据过大而报错

拓展知识

在阅读源码的时候
	1.变量名后面跟冒号 表示的意思是该变量名需要指代的数据类型
    2.函数后更横杆加大于号表示的意思是该函数的返回值类型

解决粘包问题比较完善的措施(运用字典解决)

 服务端

服务端

import socket
import subprocess
import json
import struct

server =socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
    sock,address = server.accept()
    while True:
        data = sock.recv(1024)
        command_cmd = data.decode('utf8')
        sub = subprocess.Popen(command_cmd,shell=True,stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        res = sub.stdout.read()+sub.stderr.read()
        #定义一个字典
        data_dict = {
            'decs':'这是一个非常重要的数据',
            'size':len(res),
            'info':'姣姣打工日记'

        }
        data_json = json.dumps(data_dict) #json序列化的字符串,统计它的长度好打包
        #制作字典报头
        data_first = struct.pack('i',len(data_json))
        #发送字典包头
        sock.send(data_first)
        #发送字典
        sock.send(data_json.encode('utf8'))
        #发送真实的数据
        sock.send(res)