day08
socket 网络编程初级
这个图片是socket 的简单演示
什么是socket
socket 起源于linux,Linux中的进程间的通信就是靠socket 来实现的. 在Linux中,一切皆文件,也就是说我所有的东西都可以通过文件来实现, 网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。 Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
如果还是想研究看这个连接
socket 如何通信(网络中的)
首要解决的问题是如何唯一标识一个进程,否则通信无从谈起! 在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的。 其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。 这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中需要互相通信的进程, 就可以利用这个标志在他们之间进行交互。请看下面这个TCP/IP协议结构图
从这个图来看,当我们写有关网络程序的时候,我们可以通过socket来做,这样一些TCP/IP协议 之类我们可以暂且不用管他. 只需关心socket就好
socket的家族:
socket.AF_UNIX unix本机进程间通信 socket.AF_INET IPV4 socket.AF_INET6 IPV6
Socket Types
socket.SOCK_STREAM #for tcp socket.SOCK_DGRAM #for udp下面两个不常用: socket.SOCK_RAW #原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以; 其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 socket.SOCK_RDM #是一种可靠的UDP形式,即保证交付数据报但不保证顺序。SOCK_RAM用来提供对原始协议的低级访问,在需要执行某些特殊操作时使用,如发送ICMP报文。 SOCK_RAM通常仅限于高级用户或管理员运行的程序使用。
开始一个简单的socket
#!/usr/bin/env python #coding:utf-8 '''这个是服务端''' from socket import * #注意导入包的方式 phone = socket(AF_INET,SOCK_STREAM) phone.bind(('127.0.0.1',8080)) phone.listen(5) #制定backlog 的值 print('starting....') #这样就开始接客了.. conn,addr=phone.accept() #打印一下,这两个东西是什么 print('连接号码',conn) print('client addr',addr) print('ready to read msg' ) client_msg=conn.recv(1024) #收消息 注意此时的1024 print('client msg: %s' %client_msg) conn.send((str(client_msg)+'我是土豆,我是土豆').encode('utf-8')) #发消息 conn.close() #关闭这个连接 phone.close() #关闭这个服务 #!/usr/bin/env python #coding:utf-8 '''这个是客户端''' from socket import * #注意导入包的方式 phone = socket(AF_INET,SOCK_STREAM) phone.connect(('127.0.0.1',8080)) phone.send('hello'.encode('utf-8')) back_msg=phone.recv(1024).decode('utf-8') print('返回的消息: '+back_msg) phone.close()
-
上面程序很简单,所以暴露出的问题也很多:
-
当我重复开启端口的时候,突然报了这个问题.说是端口被占用,但是我的程序明明是终止了啊..
这种情况就是你的tcp链接还在,可以修改内核参数啊(linux)或者添加下面一句话 phone.setsockopt(SQL_SOCKET,SO_REUSEADDR,1) -
当我们发送一个空消息的时候,server 端就会不停的等待,一直等待. 解决方法,那就判断如果发过来的是空的,那就终端这个链接
try:#针对windows平台下客户端断开链接 client_msg=conn.recv(1024) #收消息 if not client_msg:break #针对linux系统平台下客户端断开链接 print(‘client msg: %s’ %client_msg) conn.send(client_msg.upper()) #发消息 except Exception: break
-
-
当我们用上面的程序进行拓展的时候,比如远程执行个命令之类的时候可能会出现 粘包问题
代码如下:
from socket import * import subprocess '''这个是服务端''' phone = socket(AF_INET,SOCK_STREAM) phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) phone.bind(('127.0.0.1',8080)) phone.listen(5) while True: print('开始接客......') conn,addr=phone.accept() print('cliet addr', addr) while True: try: cmd = conn.recv(1024) print(cmd.decode('utf-8')) if not cmd:break print('开始执行命令') res = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) err = res.stderr.read() if err: send_res =err else: send_res = res.stdout.read() conn.send(send_res) except TypeError: #这个的异常处理不用管它 break conn.close() phone.close() #!/usr/bin/env python #coding:utf-8 '''这个是客户端''' from socket import * import subprocess phone = socket(AF_INET,SOCK_STREAM) phone.connect(('127.0.0.1',8080)) while True: msg = input('cmd: ').strip() if not msg:continue phone.send(msg.encode('utf-8')) back_msg = phone.recv(1024) print(back_msg.decode('gbk')) #这个的gbk 编码是因为我在win的平台下,默认是gbk编码 phone.close() 在做测试的时候就输入两遍dir 命令,在输入ipconfig 就会出现粘包现象
-
粘包问题
可以看这篇文章 说的很细致主要是怎么去解决这个问题.()如果你是udp 的协议,那你就不用管了)如果是tcp的协议: 1. 自定义时间间隔,也就是说隔一段时间在发 2. 自定义收取的大小但是问题很明显,处理的过程都是很死的.而且效率很差其实这个问题的根源在于: 接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕如何让发送端在发送数据前, 把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据
-
解决粘包问题
可以为字节流加上自定义固定长度报头,报头中包含字节流长度,然后一次send到对端, 对端在接收时,先从缓存中取出定长的报头,然后再取真实数据 在py中,有一个模块 struct 该模块可以把一个类型,如数字,转成固定长度的bytes . 看代码: #!/usr/bin/env python #coding:utf-8 '''解决粘包的问题''' from socket import * import json import subprocess import struct phone = socket(AF_INET,SOCK_STREAM) phone.bind(('127.0.0.1',8080)) phone.listen(5) while True: print('开始接听.....') conn,addr = phone.accept() while True: cmd = conn.recv(1024) if not cmd:break res = subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) err = res.stderr.read() if err : cmd_res = err else: cmd_res = res.stdout.read() #先制作报头,里面有着各种各样的说明 head_dic={'filename':None,'hash':None,'total_size':len(cmd_res)} #序列化一下 head_json = json.dumps(head_dic) head_bytes = head_json.encode('utf-8') #先发送抱头的长度 conn.send(struct.pack('i',len(head_bytes))) #再发送包头的bytes conn.send(head_bytes) #最后发送真实的数据 conn.send(cmd_res) conn.close() phone.close() #!/usr/bin/env python #coding:utf-8 from socket import * import json import struct client = socket(AF_INET,SOCK_STREAM) client.connect(('127.0.0.1',8080)) while True: msg = input('cmd: ').strip() if not msg:continue client.send(msg.encode('utf-8')) #收包头的长度了 head_struct=client.recv(4) head_len=struct.unpack('i',head_struct)[0] #再接收抱头的bytes head_bytes = client.recv(head_len) head_json=head_bytes.decode('utf-8') head_dic= json.loads(head_json) #最后根据报头取真实的信息和数据 print(head_dic) total_size=head_dic['total_size'] recv_size = 0 data = b'' while recv_size < total_size: recv_data = client.recv(1024) data += recv_data recv_size += len(recv_data) print(data.decode('gbk')) client.close()这样就可以完美的解决粘包问题