ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

网络编程(二)

2022-04-16 00:34:26  阅读:137  来源: 互联网

标签:socket recv 编程 网络 dict data 服务端 客户端


网络编程(二)

1.socket套接字简介

	当我们想要编写一个C/S架构的软件,实现数据交互,是需要编写代码操作OSI七层的,相当的复杂,由于操作OSI七层是
所有C/S架构的程序都需要经历的过程,所以有固定的模块,就是socket模块。
	socket套接字是一种技术,socket是一个模块,socket模块提供了快捷方式,不需要自己处理每一层,这里我们只是简
单了解一下,因为socket是最底层的原理,很多框架都被封装了。

2.socket模块

'''C/S架构的软件无论是在编写还是运行,都应该先考虑服务端'''
# 服务端
import socket

server = socket.socket()  # 产生一个socket对象

server.bind(('127.0.0.1', 8888))  # 产生服务端地址
'''通过查看源码得知,括号内不写参数默认就是基于网络的遵循TCP协议的套接字,
服务端应该具备固定地址这个特征
127.0.0.1是计算机的本地回环地址,只有当前计算机本身可以访问'''
server.listen(5)  # 半连接池

sock,addr = server.accept()  # 等待并接收客户端,没有客户端就会原地等待
'''listen和accept对应TCP三次握手服务端的两个状态'''
print(addr)  # 打印客户端地址
data = sock.recv(1024)  # 接受客户端的消息
print(data.decode('utf8'))  # 接受客户端的消息进行解码
sock.send('你好'.encode('utf8'))  # 给客户端发送消息
'''recv和send接受和发送的都是bytes类型的数据'''
sock.close()  # 关闭与客户端的链接
server.close()  # 关闭服务端

# 客户端
import socket

client = socket.socket()  # 产生一个socket对象
client.connect(('127.0.0.1', 8888))  # 根据服务端的地址链接
client.send(b'i am fine')  # 给服务端发信息
data = client.recv(1024)  # 接受服务端回复的消息
print(data.decode('utf8'))  # 接受服务端回复的消息进行解码
client.close()  # 关闭客户端

'''注意:服务端与客户端首次交互一边是recv,那么另一边必须是send,否则程序就会一直卡在原地'''

3.通信循环

	我们上述实现的代码有很多需要优化的地方,比如利用'input'可以动态获取用户输入的信息,还有就是不能发送空信息
,所以就可以用'len'来判断长度是否为空。但是最大的问题就是只能发送一条信息,所以我们就要加一个循环来让他们循环发送信息。
# 服务端
while True:
    data = sock.recv(1024)  # 接受客户端的消息
    print(data.decode('utf8'))  # 接受客户端的消息进行解码
    flg = input('你想要发送什么信息>>>:').strip()
    if len(flg) == 0:  # 判断长度是否为空
        continue
    sock.send(flg.encode('utf8'))  # 给客户端发送消息
    
# 客户端
while True:
    flg = input('你想发送什么信息>>>:').strip()
    if len(flg) == 0:  # 判断长度是否为空
        continue
    client.send(flg.encode('utf8'))  # 给服务端发信息
    data = client.recv(1024)  # 接受服务端回复的消息
    print(data.decode('utf8'))  # 接受服务端回复的消息进行解码

4.链接循环

# 1.关于重启服务端可能会报错问题
	重启服务端可能会报错:'address in use',这个错误在苹果电脑报的比较频繁,Windows报该错误的频率较低。
	解决措施: from socket import SOL_SOCKET,SO_REUSEADDR
  server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) # 在bind前加

# 2.链接循环
	我们刚刚演示的时候有一个情况,就是在客服端异常退出之后服务端会直接报错,因为我们的是Windows系统,如果是
mac或者Linux系统的话服务端就会接收到一个空消息。但是这不是切合生活实际,生活实际应该是客户端如果异常断开,服务端
代码应该重新回到accept等待新的客户端。
    处理方式:
        Windows系统:进行异常处理
        mac、Linux系统:len判断
while True:
    sock,addr = server.accept()  # 等待并接收客户端,没有客户端就会原地等待
    '''listen和accept对应TCP三次握手服务端的两个状态'''
    print(addr)  # 打印客户端地址
    while True:
        try:
            data = sock.recv(1024)  # 接受客户端的消息
            if len(data) == 0:
                break
            print(data.decode('utf8'))  # 接受客户端的消息进行解码
            flg = input('你想要发送什么信息>>>:').strip()
            if len(flg) == 0:
                continue
            sock.send(flg.encode('utf8'))  # 给客户端发送消息
        except Exception:
            break
'''目前我们的服务端只能实现一次服务一个客户端,不能做到同时服务多个,等我们学完并发之后才可以'''

5.半链接池

	'listen(参数)':参数的设置就是最大链接客户端的个数,虽然已经链接多个,但是只能一个一个交互,一个结束之
后才能开始下一个。

6.黏包问题

# 服务端
import socket

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

conn,addr = server.accept()
data1 = conn.recv(1024)
print(data1)
data2 = conn.recv(1024)
print(data2)
data3 = conn.recv(1024)
print(data3)

# 客户端
import socket

client = socket.socket()
client.connect(('127.0.0.1', 8888))

client.send(b'jason')
client.send(b'oscar')
client.send(b'kevin')

# 打印结果:
b'jasonoscarkevin'
b''
b''
	这是因为TCP协议的特点,TCP又叫流式协议,意思就是跟水流一样不间断,会将数据量比较小的并且时间间隔较短的
数据整合到一起发送,并且还会受制于'recv'括号内参数的大小。
    '黏包问题'产生的原因其实就是因为recv括号内的参数我们不知道要填多大的,因为我们不知道要接受的数据有多大,如
果我们能够精确的知道要接收数据的大小,就不会出现黏包问题。

7.解决黏包问题

import struct

data1 = 'hello oscar'
print(len(data1))  # 11
res1 = struct.pack('i', len(data1))  # 第一个参数是格式,固定写i就行
print(len(res1))  # 4
ret1 = struct.unpack('i', res1)  # 第一个参数是格式,固定写i就行
print(ret1)  # (11,)

data2 = 'hello oscar hello oscar hello oscar hello oscar'
print(len(data2))  # 47
res2 = struct.pack('i', len(data2))  # 第一个参数是格式,固定写i就行
print(len(res2))  # 4
ret2 = struct.unpack('i', res2)  # 第一个参数是格式,固定写i就行
print(ret2)  # (47,)
'''
pack:可以将任意长度的数字打包成固定长度。
unpack:可以将固定长度的数据解包成打包之前的真实长度。
'''
# 解决黏包问题思路
	1.先将真实数据打包成固定长度的包
    2.将固定长度的包先发给对方
    3.对方接收到包之后再解包获取真实数据长度
    4.接收真实数据长度

8.代码演示

	'recv'括号内的数字尽量不要太大,1024、2048、4096就足够了,所以针对大文件的接收应该采用循环的形式一次
接收一点点。
# 服务端
import os
import socket
import json
import struct

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

conn,addr = server.accept()

data_dict = {'file_name': '**文件.txt',
             'file_desc': '土狗',
             'file_size': os.path.getsize(r'a.py')}

# 1.先打包字典
dict_json_str = json.dumps(data_dict)
dict_bytes = dict_json_str.encode('utf8')
dict_package = struct.pack('i', len(dict_bytes))
# 2.发送报头
conn.send(dict_package)
#3.发送字典
conn.send(dict_bytes)
# 4.发送真实的数据
with open(r'a.py','rb') as f:
    for line in f:
        conn.send(line)
        
# 客户端
import socket
import struct
import json

client = socket.socket()
client.connect(('127.0.0.1', 8888))

# 1.先接收固定长度的字典的报头
dict_len = client.recv(4)
# 2.解析出字典的真实长度
dict_len_real = struct.unpack('i', dict_len)[0]
# 3.接收字典数据
dict_data_bytes = client.recv(dict_len_real)
dict_data = json.loads(dict_data_bytes)
print(dict_data)
# 4.循环接收文件数据,不要一次性接收
recv_size = 0
with open(dict_data.get('file_name'),'wb') as f:
    while recv_size < dict_data.get('file_size'):
        data = client.recv(1024)
        recv_size += len(data)
        f.write(data)

这里是IT小白陆禄绯,欢迎各位大佬的指点!!!

标签:socket,recv,编程,网络,dict,data,服务端,客户端
来源: https://www.cnblogs.com/pyqsy/p/16151642.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有