tcp 实现聊天功能

  • server端
import socket
sk = socket.socket() #初始化socket对象 ,默认TCP的方式
sk.bind(('127.0.0.1',22000)) #先绑定
sk.listen() #再监听

while True: #同时连多个人
    obj,addr = sk.accept() #三次握手在这里完成
    while True:
        msg = input('>>>')
        if msg.upper() == 'Q':break
        obj.send(msg.encode('utf-8'))
        content = obj.recv(1024).decode('utf-8')
        print(content)
    obj.close()

sk.close()
  • client端
import socket
sk = socket.socket() #实例化socket对象
sk.connect(('127.0.0.1',22000)) #连接

while True:
    content = sk.recv(1023).decode('utf-8') #接受内容
    print(content) #打印内容
    msg = input('>>>')
    if msg.upper() == 'Q':break
    sk.send(msg.encode('utf-8')) #发送内容

sk.close() #关闭

udp 实现聊天功能

  • server端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',22000))

while True:
    msg,addr = sk.recvfrom(1204) #UDP方式使recvfrom,不是recv
    print(msg.decode('utf-8'))
    msg = input('>>>')
    if msg.upper()=='Q':break
    sk.sendto(msg.encode('utf-8'),addr)

sk.close()
  • client端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM) #实例化socket对象

while True:
    msg = input('>>>')
    if msg.upper()=='Q':break
    sk.sendto(msg.encode('utf-8'),('127.0.0.1',22000))
    msg,addr = sk.recvfrom(1111)
    print(msg.decode('utf-8'))

sk.close() #关闭

粘包现象

import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
conn,addr = sk.accept()
conn.send(b'aa') #分别发送aa  和 bb
conn.send(b'bb')
conn.close()
sk.close()
import socket
import time
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
time.sleep(0.1) #sleep 0.1s
msg1 = sk.recv(1024)
print(msg1) #b'aabb' 本来aa 和bb 应该分别在msg1 和msg2 打印出来的,现在放在一起打印了,就因为前面有了一个sleep了0.1秒,这就是粘包现象
msg2 = sk.recv(1024)
print(msg2) #b''
sk.close()
#粘包现象(只发生在tcp协议中)
#发生在发送端,消息很短,放在缓存池中,操作系统固定间隔再发送
#发生在接收端,接收不及时
#只发生在tcp协议,因为tcp协议多条消息之间没有边界,并且还有一大堆优化算法

#tcp协议中为什么存在没有边界,
#udp协议 网络最大带宽限制 MTU = 1500字节
#tcp没有上限,如果文件比较大,可以拆开

#解决粘包关键是设置边界

#【作业】 socket 发送文件
#治疗一下粘包
#server端
import socket
ipaddr = '127.0.0.1'
port = 10001
sk = socket.socket()
sk.bind((ipaddr,port))
sk.listen()

conn,addr = sk.accept()
msg1 = input('>>>').encode('utf-8') #输入‘你是猴子请来的救兵么’并转成bytes
msg2 = input('>>>').encode('utf-8') #输入‘是的’
num = str(len(msg1)) #计算第一次输入的字符串字节数,限制字节数为9999
print(num) #30 因为一个汉字utf-8 占3个字节,10个字
len1 = num.zfill(4) #补全数字到4位,client客户端就要先接收4位
conn.send(len1.encode('utf-8'))
conn.send(msg1)
conn.send(msg2)

conn.close()
sk.close()
#client端
import socket
import time
ipaddr = '127.0.0.1'
port = 10001
sk = socket.socket()
sk.connect((ipaddr,port))
time.sleep(0.2)
len1 = int(sk.recv(4).decode('utf-8')) #先接收4位
msg1 = sk.recv(len1).decode('utf-8')
msg2 = sk.recv(1024).decode('utf-8')
print(msg1) #打印 ‘你是猴子请来的救兵么’
print(msg2) #打印‘是的’

sk.close()
#以上就是我自定义的协议,第一次发送最多9999字节的字符串
#下面学习一个新的模块 struct
import struct
num1 = 129469649
num2 = 2342
num3 = 1
ret = struct.pack('i',num1) #i代表4个字节   b代表1个字节 H 和h 代表2个字节 d是8位
print(ret) #b'\xd1\x8c\xb7\x07' 4个字节
ret2 = struct.pack('i',num2)
print(ret2) #b'&\t\x00\x00'   4个字节
ret3 = struct.pack('i',num3)
print(ret3) #b'\x01\x00\x00\x00' 4个字节
#struct.pack() 可以把任意数字转成4个字节
#还可以转回来
print(struct.unpack('i',ret)) #(129469649,)  得到的是一个元组,第一个元素就是
print(struct.unpack('i',ret2)) # (2342,)
print(struct.unpack('i',ret3))  #(1,)
#那么上面的server端 自定义协议处,先发送字节就可以变更一下
#server端
import socket
import struct
ipaddr = '127.0.0.1'
port = 10001
sk = socket.socket()
sk.bind((ipaddr,port))
sk.listen()

conn,addr = sk.accept()
msg1 = input('>>>').encode('utf-8') #输入‘你是猴子请来的救兵么’并转成bytes
msg2 = input('>>>').encode('utf-8') #输入‘是的’
# num = str(len(msg1)) #计算第一次输入的字符串字节数,限制字节数为9999
# len1 = num.zfill(4) #补全数字到4位,client客户端就要先接收4位
blen = struct.pack('i',len(msg1))
# msg1是转成bytes后的类型,主要是先发到对方msg1的长度len(msg1)比如等于233,client端要先接收到233
# 然后根据233,recv(233)接收233个字节数的字节,再展示出来
# 我不能send一个len(msg1)这样的int数字过去,但是可以把len(msg1)用struct.pack()转成byte类型传过去,
# 过去之后再struct.unpack(),再转成int类型len1,送给recv(len1)
conn.send(blen) # 这里blen已经是bytes类型,就不用encode了
conn.send(msg1)
conn.send(msg2)

conn.close()
sk.close()
#client端
import socket
import struct
import time
ipaddr = '127.0.0.1'
port = 10001
sk = socket.socket()
sk.connect((ipaddr,port))

len1 = struct.unpack('i',sk.recv(4))[0]
#先接收4位byte类型,sk.recv(4) ,完了以后再把他struct.unpack()一下,得到是元组,取第一个元素[0],就是首先发过来的长度
#struct 可以一句话搞定自定义发送协议
msg1 = sk.recv(len1).decode('utf-8')
msg2 = sk.recv(1024).decode('utf-8')
print(msg1) #打印 ‘你是猴子请来的救兵么’
print(msg2) #打印‘是的’
sk.close()
#后续为了防止粘包,岂不是每次都要先发送长度,再根据长度接收内容?
#【练习】
#1、基于tcp协议的登陆认证:客户端输入用户名密码,发送到服务器端,服务器端认证,发送结果到客户端
#2、基于udp协议的多人聊天,自动识别用户 不能用ip和port
#3、基于tcp协议完成一个文件的上传,先处理小文件,在处理大文件
#4、选课系统


#总结
#tcp协议
    #socket 模块引用
    #sk的创建
    #conn的创建
    #接收多个客户端的请求,while True的位置
    #怎么退出
#udp协议
    #语法 socket创建的区别,加参数type
    #涉及的发送和接收新方法  sendto  recvfrom
    #和tcp协议区别
    #聊天程序
#粘包现象
    #tcp协议的特点?三次握手连接,四次挥手断开
    #什么是粘包,发送端发送数据频率高,下层来不及发送,固定时间间隔统一发送,接收端来不及接收
    #怎么处理?先发送长度,根据长度接收内容
    #自定义协议
    #struct模块应用
    #不用struct能不能自定义协议?根据发送字节长度int用zfill方法换算成固定字节长度的数值,传到接收端,接收端根据数值转成int,再根据int接收内容
posted on 2020-08-07 18:17  94小渣渣  阅读(197)  评论(0编辑  收藏  举报