网络编程(二)


网络编程(二)

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小白陆禄绯,欢迎各位大佬的指点!!!

网络编程(二)

原创文章,作者:sunnyman218,如若转载,请注明出处:https://blog.ytso.com/244404.html

(0)
上一篇 2022年4月17日
下一篇 2022年4月17日

相关推荐

发表回复

登录后才能评论