博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
网络编程 ------ 基础
阅读量:6913 次
发布时间:2019-06-27

本文共 14382 字,大约阅读时间需要 47 分钟。

一 . 软件

  客户端: CS架构 , client --> server

  浏览器: BS架构 , browser --> server

二 . socket模块

  socket通常也称作"套接字" , 用于描述IP地址和端口 , 是一个通信链的句柄 , 应用程序通常通过"套接字"向网络发出请求或者应答网络请求.

  socket起源于Unix , 而Unix/Linux基本哲学之一就是"一切皆文件" , 对于文件用[打开] [读写] [关闭] 模式来操作 . socket就是该模式的一个实现 , socket即是一种特殊的文件 , 一些socket函数就是对其进行的操作

  socket和file的区别:

    -- file模块是针对某个指定文件进行[打开] [读写] [关闭]

    -- socket模块是针对 服务器端 和 客户端 Socket 进行[打开] [读写] [关闭]

 

import socket# 创建服务端socket对象server = socket.socket()# 绑定IP和端口server.bind(('192.168.13.155',8000))# 后边可以等5个人server.listen(5)print('服务端准备开始接收客户端的连接')# 等待客户端来连接,如果没人来就傻傻的等待。# conn是客户端和服务端连接的对象(伞),服务端以后要通过该对象进行收发数据。# addr是客户端的地址信息。# #### 阻塞,只有有客户端进行连接,则获取客户端连接然后开始进行通信。conn,addr = server.accept()print('已经有人连接上了,客户端信息:',conn,addr)# 通过对象去获取(王晓东通过伞给我发送的消息)# 1024表示:服务端通过(伞)获取数据时,一次性最多拿1024字节。data = conn.recv(1024)print('已经有人发来消息了',data)# 服务端通过连接对象(伞)给客户端回复了一个消息。conn.send(b'stop')# 与客户端断开连接(放开那把伞)conn.close()# 关闭服务端的服务server.close()
View Code

  服务端和客户端的操作:

import socketserver = socket.socket()server.bind(("IP地址",端口))server.listen(服务次数)while 1:    conn,addr = server.accept()   # 等待与客户端连接        while 1:        data = conn.recv(最大字节数 = 1024)        if data == b"exit":            break        result = data + b"任意"        conn.send(result)     # 返回给客户端的内容        conn.close()
客户端
import socketsk = socket.socket()sk.connect("服务端IP",服务端端口)while 1:    name = input(">>>")    sk.send(name.encode("utf-8"))   # 传给服务端必须是字节        if name == "exit":        break    result = sk.recv(1024)          # 服务端回复给客户端的也是字节    print(result.decode("utf-8"))sk.close()
客户端

  总结:

    python3 : send / recv 都是字节

    python2 : send / recv 都是字符串

    服务端 :

      accept : 阻塞 , 等待客户端来连接

      recv : 阻塞 , 等待客户端发来数据

    客户端 :

      connect : 阻塞 ,一直在连接 , 直到连接成功才往下运行其他代码

      recv : 阻塞 , 等待服务端发来数据    

 三 . Tcp协议和Udp协议

  Tcp : 可靠的 , 面向连接的协议 , 传输效率低全双工通信 , 面向字节流 . 使用Tcp的应用 : Web浏览器 , 电子邮件 , 文件传输程序.

  Udp : 不可靠的 , 无连接的服务 , 传输效率高 , 一对一 , 一对多 , 多对一 , 多对多 , 面向报文 , 尽最大努力服务 , 无阻塞控制 . 使用UDP的应用 : 域名系统(DNS) , 视频流 , IP语音(Volp) . 

四 . 套接字初使用

  1. 基于TCP协议的socket

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

  server端 :

import socketsk = socket.socket()sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字sk.listen()          #监听链接conn,addr = sk.accept() #接受客户端链接ret = conn.recv(1024)  #接收客户端信息print(ret)       #打印客户端信息conn.send(b'hi')        #向客户端发送信息conn.close()       #关闭客户端套接字sk.close()        #关闭服务器套接字(可选)
View Code

 

  client端 :

import socketsk = socket.socket()           # 创建客户套接字sk.connect(('127.0.0.1',8898))    # 尝试连接服务器sk.send(b'hello!')ret = sk.recv(1024)         # 对话(发送/接收)print(ret)sk.close()            # 关闭客户套接字
View Code

 

  问题 : 在重启服务端时可能会遇到

 

 

  解决方法:

#加入一条socket配置,重用ip和端口import socketfrom socket import SOL_SOCKET,SO_REUSEADDRsk = socket.socket()sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加sk.bind(('127.0.0.1',8898))  #把地址绑定到套接字sk.listen()          #监听链接conn,addr = sk.accept() #接受客户端链接ret = conn.recv(1024)   #接收客户端信息print(ret)              #打印客户端信息conn.send(b'hi')        #向客户端发送信息conn.close()       #关闭客户端套接字sk.close()        #关闭服务器套接字(可选)
View Code

  2 . 基于UDP协议的socket

    udp是无链接的 , 启动服务之后可以直接接受消息 , 不需要提前建立链接.

  server端:

import socketudp_sk = socket.socket(type=socket.SOCK_DGRAM)   #创建一个服务器的套接字udp_sk.bind(('127.0.0.1',9000))        #绑定服务器套接字msg,addr = udp_sk.recvfrom(1024)print(msg)udp_sk.sendto(b'hi',addr)                 # 对话(接收与发送)udp_sk.close()                         # 关闭服务器套接字
View Code

 

  client端:

import socketip_port=('127.0.0.1',9000)udp_sk=socket.socket(type=socket.SOCK_DGRAM)udp_sk.sendto(b'hello',ip_port)back_msg,addr=udp_sk.recvfrom(1024)print(back_msg.decode('utf-8'),addr)
View Code

 

  QQ聊天:

#_*_coding:utf-8_*_import socketip_port=('127.0.0.1',8081)udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)udp_server_sock.bind(ip_port)while True:    qq_msg,addr=udp_server_sock.recvfrom(1024)    print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))    back_msg=input('回复消息: ').strip()    udp_server_sock.sendto(back_msg.encode('utf-8'),addr)server
server

 

#_*_coding:utf-8_*_import socketip_port=('127.0.0.1',8081)udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)udp_server_sock.bind(ip_port)while True:    qq_msg,addr=udp_server_sock.recvfrom(1024)    print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))    back_msg=input('回复消息: ').strip()    udp_server_sock.sendto(back_msg.encode('utf-8'),addr)server
client

 

  时间服务器:

# _*_coding:utf-8_*_from socket import *from time import strftimeip_port = ('127.0.0.1', 9000)bufsize = 1024tcp_server = socket(AF_INET, SOCK_DGRAM)tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)tcp_server.bind(ip_port)while True:    msg, addr = tcp_server.recvfrom(bufsize)    print('===>', msg)    if not msg:        time_fmt = '%Y-%m-%d %X'    else:        time_fmt = msg.decode('utf-8')    back_msg = strftime(time_fmt)    tcp_server.sendto(back_msg.encode('utf-8'), addr)tcp_server.close()server
server

 

#_*_coding:utf-8_*_from socket import *ip_port=('127.0.0.1',9000)bufsize=1024tcp_client=socket(AF_INET,SOCK_DGRAM)while True:    msg=input('请输入时间格式(例%Y %m %d)>>: ').strip()    tcp_client.sendto(msg.encode('utf-8'),ip_port)    data=tcp_client.recv(bufsize)client
client

 

五 . 黏包

  同时执行多条命令之后 , 得到的结果很可能只有一部分 , 在执行其他命令的时候又接收到之前执行的另外一部分结果 , 这种现象就是黏包 . 

  注意 : 只有TCP有黏包现象 , UDP永远不会黏包

  TCP是面向连接的 , 面向流的 , 提供高可靠性服务 . 

  收发两端都要有一一承兑的socket . 因此 , 发送端为了将多个发往接收端的包 , 更有效的发到对方 , 使用了优化方法 , 将多次间隔较小且数据量小的数据 , 合并成一个大的数据块 , 然后进行封包 . 

  1. 发生黏包的两种情况

    情况一 : 发送方的缓存机制

      发送端需要等缓冲区满才发送出去 , 造成黏包(发送数据时间间隔很短 , 数据量很小 , 会合到一起 , 产生黏包)

#_*_coding:utf-8_*_from socket import *ip_port=('127.0.0.1',8080)tcp_socket_server=socket(AF_INET,SOCK_STREAM)tcp_socket_server.bind(ip_port)tcp_socket_server.listen(5)conn,addr=tcp_socket_server.accept()data1=conn.recv(10)data2=conn.recv(10)print('----->',data1.decode('utf-8'))print('----->',data2.decode('utf-8'))conn.close()服务端
server
#_*_coding:utf-8_*_import socketBUFSIZE=1024ip_port=('127.0.0.1',8080)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(ip_port)s.send('hello'.encode('utf-8'))s.send('egg'.encode('utf-8'))客户端
client

    情况二 : 接收方的缓存机制

      接受方不及时接受缓冲区的包 , 造成多个包接受(客户端发送了一段数据 , 服务端只收了一小部分 , 服务端下次再收的时候还是从缓冲区拿上次遗留的数据 , 产生黏包)

#_*_coding:utf-8_*_from socket import *ip_port=('127.0.0.1',8080)tcp_socket_server=socket(AF_INET,SOCK_STREAM)tcp_socket_server.bind(ip_port)tcp_socket_server.listen(5)conn,addr=tcp_socket_server.accept()data1=conn.recv(2) #一次没有收完整data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的print('----->',data1.decode('utf-8'))print('----->',data2.decode('utf-8'))conn.close()
server
#_*_coding:utf-8_*_import socketBUFSIZE=1024ip_port=('127.0.0.1',8080)s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)res=s.connect_ex(ip_port)s.send('hello egg'.encode('utf-8'))
client

    总结 :

      黏包现象只发生在tcp协议中 :

        1. 从表面上看 , 黏包问题主要是发送方和接受方的缓存机制 , tcp协议面向流通信的特点

        2. 实际上 , 主要还是因为接收方不知道消息之间的界限 , 不知道一次性提取多少字节的数据所造成的

六 . 黏包的解决的方案

1. struct模块

import structres=struct.pack("i","")print(res)print(len(res))obj=struct.unpack("i",res)print(obj[0])"""结果:b'\xb3\xb5V\x07'4123123123"""
View Code

  该模型可以把一个类型转换成固定长度的bytes(固定长度4)

2. subprocess模块

import subprocessres=subprocess.Popen("dir",                     shell=True,                     stderr=subprocess.PIPE,                     stdout=subprocess.PIPE)print(res.stdout.read().decode("gbk"))"""结果: 驱动器 C 中的卷是 Windows 卷的序列号是 A786-D135 C:\Users\Administrator\PycharmProjects\untitled1\基础\28day 的目录2018/09/04  15:50    
.2018/09/04 15:50
..2018/09/04 15:50 218 1.py2018/09/04 14:53 0 __init__.py 2 个文件 218 字节 2 个目录 215,588,208,640 可用字节"""
View Code

3. 使用struct模块解决黏包

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

发送时 接收时
先发送struct转换好的数据长度4字节 先接受4字节使用struct转换成数字来获取要接收的数据长度
再发送数据 再按照长度接受数据
import socketimport subprocessserver = socket.socket()server.bind(('127.0.0.1',8008))server.listen(5)while True:    print("server is working.....")    conn,addr = server.accept()    # 字节类型    while True:        # 针对window系统        try:            cmd = conn.recv(1024).decode("utf8") # 阻塞            if cmd == b'exit':                break            res=subprocess.Popen(cmd,                             shell=True,                             stderr=subprocess.PIPE,                             stdout=subprocess.PIPE,                             )            # print("stdout",res.stdout.read())            # print("stderr",res.stderr.read().decode("gbk"))            out=res.stdout.read()            err=res.stderr.read()            print("out响应长度",len(out))            print("err响应长度",len(err))            if err:                 import struct                 header_pack = struct.pack("i", len(err))                 conn.send(header_pack)                 conn.send(err)            else:                 #构建报头                 import struct                 header_pack=struct.pack("i",len(out))                 print("header_pack",header_pack)                 # # 发送报头                 conn.send(str(len(out)).encode("utf8"))                 # 发送数据                 conn.send(out)        except Exception as e:            break    conn.close()
server
import socketimport structsk = socket.socket()sk.connect(('127.0.0.1',8008))while 1:    cmd = input("请输入命令:")    sk.send(cmd.encode('utf-8')) # 字节    if cmd=="":        continue    if cmd == 'exit':        break    header_pack=sk.recv(4)    data_length=struct.unpack("i",header_pack)[0]    print("data_length",data_length)    data_length=int(sk.recv(1024).decode("utf8"))    print("data_length",data_length)    recv_data_length=0    recv_data=b""    while recv_data_length
client

 4. 解决黏包的两种方法

方法一:

import socketimport jsonsock = socket.socket()sock.bind(("121.12.11.11",5555))sock.listen(5)while 1:    print("server is working....")    conn,addr = sock.accept()        while 1:        data = conn.recv(1024).decode("utf-8")        file_info = josn.loads(data)        # 文件信息        action = file_info.get("action")        filename = file_info.get("filename")        filesize = file_info.get("filesize")        connsend(b"succes")        # 接收文件数据        with open("put/" +filename , "wb") as f:            recv_data_length = 0            while recv_data_lengh < filesize:                data = conn.recv(1024)                recv_data_length += len(data)                f.write(data)                print("文件总大小: %s , 已接收 %s" %(filesize,recv_data_length))        print("接收成功!")        break        sock.close()
FTP -- server

 

import socketimport osimport jsonsock=socket.socket()sock.connect(("121.12.11.11",5555))while 1:    cmd = ("请输入命令:")    action,filename = cmd.strip().split(" ")    file_info = {
"action":action,"filename":filename,"filesize":filesize} file_info_josn = josn.dumps(file_info).encode("utf-8") sock.send(file_info_josn) # 确认服务端接收到了文件信息 code = sock.recv(1024).decode("utf-8") if code == "succes": # 发送文件数据 with open("filename","rb") as f: for line in f: sock.send(line) else: print("服务器异常!") breaksock.close()
FTP -- client

 

方法二:

import structimport socketimport josnimport hashlibsock = socket.socket()sock.bind(("121.21.12.11",5555))sock.listen(5)while 1:    print("server is working...")    conn,addr = sock.accept()        while 1:                # 接收josn的打包长度        file_info_length_pack = conn.recv(4)        file_info_length = struct.unpack("i",file_info_length_pack)[0]        # 接收josn字符串        file_info_josn = conn.recv(file_info_length)        file_info = josn.loads(file_info_josn)        action=file_info.get("action")        filename=file_info.get("filename")        filesize=file_info.get("filesize")        # 循环接收文件        md5 = hashlib.md5()        with open("put/" + filename,"wb") as f:            recv_data_length = 0            while recv_data_length < filesize:                data = conn.recv(1024)                recv_data_length += len(data)                f.write()                                # md5摘要                md5.update(data)                print("文件总大小:%s,已成功接收%s" %(filesize,recv_data_length))        print("接收成功!")        conn.send(b"ok")                md5_val = md5.hexdigest()        client_md5 = conn.recv(1024).decode("utf-8")                if md5_val == client_md5:            conn.send(b"203")        else:            conn.send(b"204")            break        sock.close()
FTP -- server

 

import socketimport osimport jsonimport structimport hashlibsock=socket.socket()sock.connect(("121.21.12.11",5555))while 1:    cmd = input("请输入命令:")    action , filename = cmd.strip().split(" ")    filesize = os.patn.getsize(filename)    file_info = {
"action":action,"filename":filename,"filesize":filesize} file_info_josn = josn.dumps(file_info).encode("utf-8") res = struct.pack("i",len(file_info_josn)) # 发送file_info_josn 的打包长度 sock.send(res) # 发送 file_info_josn字节串 sock.send(file_info_josn) md5 = hashlib.md5() # 发送文件数据 with open(filename,"rb") as f: for line in f: sock.send(line) md5.update(line) data = sock.recv(1024) md5_val = md5.hexdigest() sock.send(md5_val.encode("utf-8")) is_val = sock.recv(1024).decode("utf-8") if is_val == "203": print("文件上传成功!") else: print("文件上传失败!") breaksock.close()
FTP -- client

 

七 . 并发编程(socketserver模块)

import socketserverclass Myserver(socketserver.BaseRequestHandler):    def handle(self):        # 字节类型        while 1:            # 针对window系统            try:                print("等待信息")                data = self.request.recv(1024)  # 阻塞                # 针对linux                if len(data) == 0:                    break                if data == b'exit':                    break                response = data + b'SB'                self.request.send(response)            except Exception as e:                break        self.request.close()# 1 创建socket对象 2 self.socket.bind()  3 self.socket.listen(5)server=socketserver.ForkingUDPServer(("127.0.0.1",8899),Myserver)server.serve_forever()
FTP -- server

 

import socketsk = socket.socket()sk.connect(('127.0.0.1',8899))while 1:    name = input(">>>>:")    sk.send(name.encode('utf-8')) # 字节    response = sk.recv(1024) # 字节    print(response.decode('utf-8'))
FTP -- client

 

转载于:https://www.cnblogs.com/xiangweilai/p/9579066.html

你可能感兴趣的文章
归并排序
查看>>
maven仓库介绍
查看>>
spring的corn表达式
查看>>
数学符号注音
查看>>
linux命令行关机
查看>>
Lync 小技巧-38-Lync Server 2013与Exchange Server高可用环境-集成
查看>>
[Everyday Mathematics]20150102
查看>>
Android学习笔记PreferenceFragment的使用
查看>>
用开源项目circular progress button实现有进度条的Button
查看>>
java基础篇---枚举详解
查看>>
UpdatePanel的用法
查看>>
Ehcache(07)——Ehcache对并发的支持
查看>>
关于Eclipse中配置产品启动的插件
查看>>
在循环中创建网页元素的问题
查看>>
ACM零散知识
查看>>
【转】Spring@Autowired注解与自动装配
查看>>
JVM学习笔记(一)------基本结构
查看>>
【Intel AF 2.1 学习笔记三】
查看>>
知名黑客组织Anonymous(匿名者)的装备库
查看>>
Mac OS中Java Servlet与Http通信
查看>>