网络编程基础:网络基础之网络协议、socket模块

时间:2022-10-28 07:48:42

操作系统(简称OS)基础:

应用软件不能直接操作硬件,能直接操作硬件的只有操作系统;所以,应用软件可以通过操作系统来间接操作硬件

网络基础之网络协议:

网络通讯原理:

  连接两台计算机之间的Internet实际上就是一系列统一的标准,这些标准称之为互联网协议;互联网的本质就是一系列的协议,总称为“互联网协议” (Internet Protocol Suite)

  互联网协议的功能:定义计算机何如接入Internet,以及接入Internet的计算机通信的标准。

  osi七层协议: 互联网协议按照功能不同分为OSI七层或TCP/IP五层或TCP/IP四层

网络编程基础:网络基础之网络协议、socket模块

用户感知到的只是最上面的一层应用层,自上而下每层都依赖于下一层;每层都运行特定的协议,越往上越靠近用户,越往下越靠近硬件

  物理层功能:主要是基于电气特性发送高低电压(电信号),高电压对应的数字为1,低电压对应数字0

  数据链路层:

    数据链路层的由来: 单纯的电信号0、1没有任何意义,必须要规定电信号多少位一组,每组什么意思

    数据链路层的功能:定义了电信号的分组方式

    以太网协议(Ethernet):Ethernet协议规定了:1. 一组电信号构成一个数据包,叫做“帧”;2. 每一数据帧分成“报头”head和数据data两部分

       head包含(固定18个字节):1. 发送者/原地址,6个字节; 2. 数据类型,6个字节; 3. 接受者/目标地址,6个字节

       data包含:数据包的具体内容

    Mac地址:head中包含的源、目标地址的由来:Ethernet规定接入Internet的设备都必须具备网卡,发送端和接收端的地址便是指网卡的地址,即Mac地址;

     (每块网卡出厂时都被烧制上一个世界唯一的Mac地址,长度为48位2进制,通常由12位16进制数表示(前六位是厂商编号,后六位是流水线号))

    广播: 有了Mac地址,同一网络内的两台主机就可以通信了;Ethernet采用最原始的广播的方式进行通信,即计算机通信基本靠吼

  网络层:有了Ethernet、Mac地址、广播的发送方式,同一个局域网内的计算机就可以彼此通讯了,但世界范围内的互联网是由一个个彼此隔离的小的局域网(子网)组成的,所以不能所有的通信都采用以太网的广播方式

网络编程基础:网络基础之网络协议、socket模块

    从上图可以看出:必须找出一种方法来区分哪些计算机属于同一广播域、哪些不是,如果是就采用广播的方式发送;如果不是就采用路由的方式(向不同广播域/子网分发数据包),Mac地址是无法区分的,它只跟厂商有关

    网络层功能:引入一套新的地址来区分不同的广播域(子网),这套地址即网络地址

    IP协议:1. 规定网络地址的协议叫IP协议,它定义的地址称为IP地址,广泛采用的v4版本即ipv4,它规定网咯地址由32位2进制表示;

        2. 范围0.0.0.0-255.255.255.255

        3. 一个IP地址通常写成四段十进制数,例如:172.16.10.1

      IP地址分成两部分: 1. 网络部分:标识子网; 2. 主机部分:标识主机

        注:单纯的IP地址段只是标识了IP地址的种类,从网络部分或主机部分都无法辨识一个IP所处的子网

      子网掩码:表示子网络特征的一个参数;知道了“子网掩码”,我们就能判断任意两个IP地址是否处在同一个子网络。

      网络层作用总结:IP协议的主要作用有两个:1. 为每台计算机分配IP地址;2.确定哪些地址在同一个子网络

    IP数据包:分为head和data两个部分,然后直接放入以太包的data部分,如下所示:

网络编程基础:网络基础之网络协议、socket模块

    ARP协议:由来:计算机通信基本靠吼,即广播的方式,所有上层的包到最后都要封装上以太网头,然后通过以太网协议发送;通信是基于Mac的广播方式实现,计算机在发包时获取自身的Mac容易,如何获取目标主机的Mac就需要通过ARP协议。

          ARP协议功能:广播的方式发送数据包,获取目标主机的Mac地址

         协议工作方式: 每台主机IP都是已知的

           1. 首先通过IP地址和子网掩码区分出自己所处的子网

           2. 分析是否处于同一网络(如果不是同一网络。通过ARP获取的是网关的Mac)

          网络编程基础:网络基础之网络协议、socket模块

           3. 这个包以广播的方式在发送端所处的子网内传输,所有主机接收后拆开包,发现目标IP是自己的就响应返回自己的Mac(这点还不是很理解,发送端所处的子网??)

    传输层:由来:网络层的IP帮我们区分子网,以太网层的Mac帮我们找到主机,然后大家使用的都是应用程序,那么我们通过IP和Mac找到了一台特定的主机;然后,标识这台主机上的应用程序就是端口,端口即应用程序和网卡关联的编号。

        传输层功能:建立端口到端口的通信

        补充:端口范围0-65535,0-1023为操作系统占用端口

        TCP协议: 可靠传输,需要挖双向“通道“,””3次“握手”和4次“挥手”;流式协议

        UDP协议:不可靠传输,不需要挖“通道”;又称“数据报协议”

    

    应用层:由来:用户使用的都是应用程序,均工作于应用层,互联网是开发的,大家都可以开发自己的应用程序,数据多种多样,必须规定好数据的组织形式

        应用层功能: 规定应用程序的数据格式

          例如: TCP协议可以为各种各样的程序传递数据,比如Email、www、FTP等;那么必须有不同协议规定电子邮件、网页、发图片数据的格式,这些应用程序协议就构成了“应用层”

          网络编程基础:网络基础之网络协议、socket模块

Socket:

socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,socket是一个门面模式,他把复杂的TCP/IP协议族隐藏在socket接口的后面,对于用户来说,一组简单的接口就是全部,让socket去组织数据,以符合指定的协议。

网络编程基础:网络基础之网络协议、socket模块

所以我们无需深入理解TCP/UDP协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循TCP、UDP标准的

附:也有人将socket说成IP+port,IP是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,IP地址是配置到网卡上的,而port是应用程序开启的,IP与port的绑定就标识了互联网中独一无二的一个应用程序; 而程序的pid是同一台机器上不同进程或线程的标识

套接字: 套接字有两种(或者说两个种族),分别是基于文件型的和基于网络型的。

基于文件类型的套接字家族: 套接字家族的名字是 : AF_UNIX

基于网络类型的套接字家族: 套接字家族的名字是:AF_INET  

  还有AF_INET6被用于ivp6;AF_INET是使用最广泛的一个,python支持很多地址家族,但是由于我们只关心网络编程,所以大部分时候我们只是用AF_INET

套接字(socket) 工作流程:以打电话为例说明:

客户端代码如下:

import socket

# 1. 买“手机”
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print(phone)
# 打印结果:
# <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0> # 2. “拨号” (客户端不需要绑定IP和端口)
phone.connect(("127.0.0.1",8080))
"""
# connect 发起连接, ("127.0.0.1",8080)是服务端的IP和端口;
# 客户端的connect对应服务端的accept(),connect()和服务端的accept()底层进行的就是TCP的“三次握手”
# 服务端accept()之后,客户端的phone就相当于服务端的那个 conn,就是那根“电话线”
"""
print(phone)
# 运行结果: # 服务端accept()之后客户端的phone就发生了变化,变得和服务端中的conn对应
# <socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 62064), raddr=('127.0.0.1', 8080)> # 发、收消息;发收的消息都是bytes类型
phone.send("hello".encode("utf-8")) # 发
"""
# 不能直接发字符串, 物理层传输的0101,这一步需要发送bytes类型;
# 字符串转bytes: string.encode(编码格式)
# phone.send() 对应服务端的 conn.recv()
"""
data = phone.recv(1024) # 收
print(data) # 关闭
phone.close() # 先启动服务端,再启动客户端,运行结果如下:
# b'HELLO'

服务端代码如下:

import socket

# 1. 买“手机”
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 基于网络通讯的、基于TCP协议的套接字;phone就是一个套接字对象 # 这一步得到一个服务端的套接字phone
"""
全称是: phone = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
socket.socket() # socket下面的socket类;
family=socket.AF_INET # 地址家族(socket的类型)是基于网络通讯的AF_INET
type=socket.SOCK_STREAM # 用的是流式的协议,即 TCP协议
"""
# print(phone)
# 打印结果
# <socket.socket fd=316, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0> # 2. 绑定“手机卡”(服务端的IP地址需要固定下来、并公示给别人;客户端虽然有IP和端口但是不需要绑定)
phone.bind(("127.0.0.1",8080))
"""
# 服务端需要绑定IP和Port(IP和端口),ip和端口需要以元祖的形式传进来;
其中第一个参数是字符串形式的IP地址; 127.0.0.1是指本机,专门用于测试的,IP写成这个就意味着服务端和客户端都必须在同一台主机上;
第二个参数是端口;端口范围是0-65535,其中0-1023是给操作系统使用的,2014以后的你可以使用
""" # 3. “开机”
phone.listen(5) # 开始TCP监听
"""
# 5代表最大挂起的链接数; 通常这个数写在配置文件中
# Enable a server to accept connections. If backlog is specified, it must be at least 0 (if it is lower, it is set to 0); it specifies the number of unaccepted connections that the system will allow before refusing new connections. If not specified, a default reasonable value is chosen.
""" # 4. 等电话
# res = phone.accept()
# print(res)
# print(phone)
"""
# 等待链接; 等待的结果赋值给一个变量
# 服务端程序启动后,程序会停在这一步;
# 服务端的accept()对应客户端的connect()
# accept()底层建立的就是TCP的“三次握手”,“三次握手”之后会建成一个双向的链接(下面的conn),然后客户端得到一个对象(新的phone)、服务端得到一个对象(conn),这两个对象都可以收、发消息
"""
# 客户端的程序启动后,服务端的程序也从 res = phone.accept()这一步接着往下运行
# 其中一次的运行结果:
# (<socket.socket fd=356, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 56572)>, ('127.0.0.1', 56572))
# 元祖的形式,元祖里面有2个元素,第一个元素是发送端的链接对象(套接字对象),第二个元素是客户端的IP和端口 # <socket.socket fd=312, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080)> """
由于phone.accept()得到的结果是元祖的形式,里面有两个元素:第一个、客户端的链接对象(相当于拨号人的电话线);第二个、客户端的IP和端口,所以phone.accept()可以写成如下形式
""" conn,client_addr = phone.accept()
print(conn)
# 打印结果:
# <socket.socket fd=328, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8080), raddr=('127.0.0.1', 62064)> # 5. 收、发消息(基于刚刚建好的那根“电话线”(conn)收发消息),收发的消息都是bytes类型
data = conn.recv(1024) # 收
print("客户端的数据",data)
"""
# conn.recv(1024):接收conn这个发送端对象发来的数据(或者理解成沿着conn这根“电话线”接收消息)
# 括号内的数字需要注意两个地方: 1. 数字单位:bytes;2. 数字2014代表最大接收1024个bytes
# conn.recv(1024)接收到的数据赋值给变量 data
""" conn.send(data.upper()) # 发
"""
# conn.send(data.upper()):给conn的客户端发送消息(沿着conn这个“电话线”发送消息)
# .upper() # 把字符串里面的都变成大写
""" # 挂电话(关闭)
conn.close() # 关机
phone.close() # 运行结果:
# 客户端的数据 b'hello' """
1. 服务端有两种套接字对象:服务端的phone和conn
服务端的phone用于:绑定(IP和端口)、监听TCP和最重要的接收接收客户端的链接和客户端的IP、端口
conn用于收发消息
2. 客户端有一种套接字对象:客户端的phone(其实客户端的phone在服务端accept之后也发生了变化),它的作用是:发起建链接请求(.connect())和发、收消息
"""

简单套接字加上通信循环:

把上面的代码加上 while True 就变成了循环通信,如下所示

客户端代码:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(("127.0.0.1",8080))
while True:
msg = input(">>>").strip()
phone.send(msg.encode("utf-8"))
data = phone.recv(1024)
  
print(data) # 也是bytes形式
   """
   如果想要打印正常的形式,可利用利用:
   print(data.decode("utf-8))
   """
phone.close()

服务端代码:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(("127.0.0.1",8080))
phone.listen(5) conn,client_addr = phone.accept()
print(client_addr) while True:
data = conn.recv(1024) # 在收到消息之前,程序也“卡”在这一步; 所以,recv()的具体含义是“等待接收消息”
print("客户端的数据",data)
conn.send(data.upper()) conn.close() phone.close()

重启服务端的时候可能出现端口仍然被占用的情况,原因是端口被操作系统回收需要时间,解决办法如下:

为服务端加一句代码:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 在绑定之前加上这句代码; # reuseaddr表示重新用该端口
phone.bind(("127.0.0.1",8081))
phone.listen(5) conn,client_addr = phone.accept()
data = conn.recv(1024)
print("客户端的数据",data)
conn.send(data.upper())
conn.close()
print(phone)
phone.close()

客户端和服务端代码bug修复:

客户端可以发空消息,但服务端却收不到空消息,如下代码:

客户端:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(("127.0.0.1",8080))
while True:
msg = input(">>>").strip()
"""
客户端可以发空数据,但是服务端却收不到空数据
解决客户端发空消息可以用如下代码:
"""
if not msg:continue # 如果发的消息为空,则重新发
phone.send(msg.encode("utf-8"))
data = phone.recv(1024)
print(data.decode("utf-8"))    """
   recv和send都是python(应该说是应用程序)发给操作系统的命令
   收发消息需要通过Internet进行传输,而Internet需要通过网卡去发送、接收数据,只有操作系统才能调用网卡这个硬件
   所以,具体执行发送、接收消息动作的是操作系统(就如文件处理中的open(file)一样),python(应用程序)把发送的消息的内存原封不动地复制给操作系统,然后操作系统去发送消息;    当客户端发送空消息时,应用程序会把这个空消息复制给操作系统,正常情况下操作系统会根据TCP协议调用网卡,但由于操作系统收到的是空消息,所以操作系统没有调用任何硬件,
   也就是说,python(应用程序)发送的空消息只发到了客户端操作系统这一步,然后客户端的操作系统并没有接着往下发这个空消息;所以客户端的程序就卡在了这一步
   """
phone.close()

服务端:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(("127.0.0.1",8080))
phone.listen(5) conn,client_addr = phone.accept()
print(client_addr) while True:
data = conn.recv(1024)
print("客户端的数据",data)
conn.send(data.upper()) conn.close() phone.close()

还有一种情况:以上面的代码为例, 由于conn是基于客户端和服务端建立起来的一个双向通道,假如客户端被强行终止掉了,那么这个双向通道conn就没有意义了;在Windows系统下,假如客户端被强行终止,那么服务端就会报错,但在Linux系统下,服务端不会报错,而是进入了while的死循环,为了防止Linux的这个死循环,可以利用如下方法解决:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(("127.0.0.1",8080))
phone.listen(5) conn,client_addr = phone.accept()
print(client_addr) while True:
data = conn.recv(1024)
if not data:break
"""
由上面的分析可知:正常情况下服务端不可能收到空消息,因为假如客户端发了空消息,那么客户端的操作系统根本不会把这个空消息发出去;
所以,假如data变成了空消息,那一定是因为conn这个双向通道少了一方,也就是客户端单方面终止了;
所以 if not data:break # 就是说,假如客户端已经终止了,那就结束服务端的这个while True循环
"""
print("客户端的数据",data)
conn.send(data.upper()) conn.close() phone.close()

上述方法是针对Linux的;Windows下客户端当方面终止程序,服务端直接报错,所以应该用 try...except...去解决:

while True:
try:
data = conn.recv(1024) print("客户端的数据",data)
conn.send(data.upper())
except ConnectionResetError:
break conn.close()
phone.close()

服务端为多个客户端提供服务:

服务端代码如下:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(("127.0.0.1",8080))
phone.listen(5)
"""
这个服务端可以为多个服务端服务,但同一时间只能服务于一个客户端;
当有其他服务端发来建链接请求时就挂起,当正在被服务的服务端退出后,挂起的其他服务端建链接的请求就会执行;
5为最大的挂起链接数
""" while True: # 链接循环
conn,client_addr = phone.accept()
print(client_addr) while True: # 通讯循环
try:
data = conn.recv(1024)
print("客户端的数据",data)
conn.send(data.upper())
except ConnectionResetError:
break conn.close()
phone.close()

模拟ssh远程执行命令:

关于系统命令的知识点补充:

# 一、系统命令:

# windows:
# dir # 查看某个文件夹下的子文件名和子文件夹名
# ipconfig # 查看本地网卡的IP信息
# tasklist # 查看运行的进程 # Linux系统对应的是:
# ls
# ifconfig
# ps aux """
系统命令不能直接在pycharm上写,而应该在cmd上输入(Windows系统);
cmd也是一个程序,它的功能非常单一,就是来接收你输入的有特殊意义的单词(命令),然后把你输入的有特殊意义的单词(命令)解析成操作系统认识的指令去执行;所以这个程序称之为“命令解释器”
如: dir f;\learning
Linux系统中: / 代表c盘
""" # 二、执行系统命令:
# 1、考虑使用os模块
# import os
# os.system("dir f;\learning") # 字符串形式的命令 # 但是这种方法是在服务端的终端上打印了dir f:\learning 的子文件和子文件夹名;而我们想要的结果是把命令结果拿到客户端然后再客户端打印 """
res = os.system("dir f;\learning") # res 只是 os.system("dir f:\learning") 的执行状态结果:0或者非0(0代表命令执行成功),并不是命令的查看结果
""" # 执行系统命令,并拿到命令的结果
# 2. subprocess模块的Popen
import subprocess
obj = subprocess.Popen("dir f:\learning",shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) # 命令的结果赋值给obj
"""
# 第一个参数是字符串格式的命令
要写: shell = True # shell是指命令解释器 # 启动一个程序来解析前面的字符串,把这个字符串解析成相应的命令去执行 # 相当于起了一个cmd
这个事例中,不管执行结果正确与否,命令的结果都只有一个,你没告诉subprocess把命令的结果给谁,它就把结果默认给了终端;但我们想要的是把命令的结果给客户端,而不是终端
所以我们需要通过某种手段告诉subprocess不要把结果给终端,而是把结果先存到一个地方,等我调用的时候发送给客户端,所以就用到了“管道”的概念;
把命令的结果放到一个管道里面(操作系统的内存),等你需要的时候再去管道里面取
让subprocess把结果放到管道里的方法:
stdout = subprocess.PIPE # stdout是命令的正确执行结果 # 命令的正确执行结果放到一个管道里面
stderr = subprocess.PIPE # stderr是命令的错误执行结果 # 每次的 .PIPE都触发一次PIPE的功能,从而产生一个新的管道;so 这两个 PIPE是不一样的
""" print(obj)
# 打印结果:
# <subprocess.Popen object at 0x0000007994CEA8D0> print("stdout---->",obj.stdout.read()) # obj从stdout(正确结果)这个管道里面读 (从管道读取一次之后再取就没有了) # 打印结果:(bytes格式)(不管服务端还是客户端,收、发消息都得是bytes形式)
# stdout----> b' \xc7\xfd\xb6\xaf\xc6\xf7 F \xd6\xd0\xb5\xc4\xbe\xed\xc3\xbb\xd3\xd0\xb1\xea\xc7\xa9\xa1\xa3\r\n \xbe\xed\xb5\xc4\xd0\xf2\xc1\xd0\xba\xc5\xca\xc7 BCA5-0E10\r\n\r\n f:\\learning \xb5\xc4\xc4\xbf\xc2\xbc\r\n\r\n2018/01/17 16:19 <DIR> .\r\n2018/01/17 16:19 <DIR> ..\r\n2018/01/12 01:04 <DIR> funny\r\n2018/01/15 14:12 <DIR> IDLE\xd7\xf7\xd2\xb5\xb2\xe2\xca\xd4\r\n2018/02/05 12:04 <DIR> pycharm_pro\r\n2018/01/19 09:16 <DIR> pythontest\r\n2018/03/10 11:05 <DIR> \xd7\xf7\xd2\xb5\xcc\xe1\xbd\xbb\r\n2018/03/08 11:45 <DIR> \xb2\xa9\xbf\xcd\xa1\xa2\xb4\xed\xce\xf3\xa1\xa2\xd2\xc9\xce\xca\xbd\xd8\xcd\xbc\r\n2018/01/27 18:01 <DIR> \xbd\xd8\xcd\xbc\r\n2018/01/16 10:33 <DIR> \xd7\xd4\xd1\xa7\r\n2018/01/11 14:53 <DIR> \xc4\xac\xd0\xb4\r\n 0 \xb8\xf6\xce\xc4\xbc\xfe 0 \xd7\xd6\xbd\xda\r\n 11 \xb8\xf6\xc4\xbf\xc2\xbc 115,108,585,472 \xbf\xc9\xd3\xc3\xd7\xd6\xbd\xda\r\n' # obj.stdout.read()是bytes格式,如果想看bytes格式里面具体是什么内容,则需要 decode();
print("stdout---->",obj.stdout.read().decode("gbk"))
"""
encode()是按照什么编码,decode()也需要按照相应的编码;
subprocess.Popen("dir f;\learning")执行的是系统命令,这个命令是提交给操作系统的,由操作系统执行完后拿到一个结果;
由于没告诉操作系统命令的结果用什么格式编码,所以系统会用它默认的编码格式;所以:
obj.stdout.read().decode("gbk")
""" print("stderr--->",obj.stderr.read().decode("gbk")) # 执行结果不一定正确,所以也要从obj.stderr 读取 # 打印结果:
# stdout---->
# stderr--->

模拟ssh远程执行命令具体代码:

客户端:

import socket

phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(("127.0.0.1",8080))
while True:
# 1. 发命令
cmd = input(">>>").strip() # 客户端在这行代码输入一条命令
if not cmd:continue
phone.send(cmd.encode("utf-8")) # 2. 得到命令的结果,并打印
data = phone.recv(1024) # data是bytes格式,打印需要解码 # 1024是个坑,待优化
print(data.decode("gbk"))
"""
data 解码需要是gbk,因为:data是由服务端传来的(stdout+stderr),而stdout和stderr是由 subprocess.Popen()得到的
subprocess.Popen("命令")是把命令交给了操作系统去处理,操作系统处理命令后会按照自己默认的编码把处理结果encode,而Windows的默认编码是 gbk
""" phone.close()

服务端:

import subprocess
import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
phone.bind(("127.0.0.1",8080))
phone.listen(5) while True: # 链接循环
conn,client_addr = phone.accept() while True: # 通讯循环
try:
# 1. 接收命令
cmd = conn.recv(1024) # cmd是bytes格式 # 2. 执行命令,拿到执行后的结果
obj = subprocess.Popen(cmd.decode("utf-8"), shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE) # subprocess.Popen()中需要的是字符串格式的命令,所以需要把cmd decode;由于客户端是按照utf-8进行的encode,所以这步需要decode("utf-8")
stdout = obj.stdout.read() # obj.stdout需要read
stderr = obj.stderr.read() # stderr和stdout都是bytes格式的 # 3. 把命令的结果返回给客户端
conn.send(stdout+stderr) # + 会影响效率;因为 + 是重新创建了一份stdout和stderr的新的内存空间(把stdout和stderr的内存空间重新copy了一遍)# 所以+是一个可以优化的点
except ConnectionResetError:
break conn.close()
phone.close()

网络编程基础:网络基础之网络协议、socket模块的更多相关文章

  1. &lbrack;C&num;网络编程系列&rsqb;专题一:网络协议简介

    转自:http://www.cnblogs.com/zhili/archive/2012/08/11/NetWorkProgramming.html 因为这段时间都在研究C#网络编程的一些知识, 所以 ...

  2. Python网络编程02 &sol;基于TCP、UDP协议的socket简单的通信、字符串转bytes类型

    Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes类型 目录 Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes ...

  3. 脑残式网络编程入门&lpar;二&rpar;:我们在读写Socket时,究竟在读写什么?

    1.引言 本文接上篇<脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手>,继续脑残式的网络编程知识学习 ^_^. 套接字socket是大多数程序员都非常熟悉的概念,它是计算机 ...

  4. &lbrack;转帖&rsqb;脑残式网络编程入门&lpar;二&rpar;:我们在读写Socket时,究竟在读写什么?

    脑残式网络编程入门(二):我们在读写Socket时,究竟在读写什么?     http://www.52im.net/thread-1732-1-1.html   1.引言 本文接上篇<脑残式网 ...

  5. 【TCP&sol;IP网络编程】:01理解网络编程和套接字

    1.网络编程和套接字 网络编程与C语言中的printf函数和scanf函数以及文件的输入输出类似,本质上也是一种基于I/O的编程方法.之所以这么说,是因为网络编程大多是基于套接字(socket,网络数 ...

  6. python基础&lpar;29&rpar;&colon;网络编程&lpar;软件开发架构、网络基础、套接字初使用&rpar;

    1. 软件开发架构 我们了解的程序之间通讯的应用可分为两种: 第一种是应用类:qq.微信.百度网盘.腾讯视频这一类是属于需要安装的桌面应用. 第二种是web类:比如百度.知乎.博客园等使用浏览器访问就 ...

  7. 总结day23 ---- 网络编程&comma;以及计算机基础概念

    计算机网络的发展及基础网络概念 问题:网络到底是什么?计算机之间是如何通信的? 早期 : 联机 以太网 : 局域网与交换机 广播 主机之间“一对所有”的通讯模式,网络对其中每一台主机发出的信号都进行无 ...

  8. 网络编程入坑基础-BIO总结

    IO总结 前提 参考资料: <Java I/O> -- 这本书没有翻译版,需要自己啃一下. <Java I/O>这本书主要介绍了IO和NIO的相关API使用,但是NIO部分并不 ...

  9. 网络编程中TCP基础巩固以及Linux打开的文件过多文件句柄的总结

    1.TCP连接(短链接和长连接) 什么是TCP连接?TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的.可靠的.基于字节流的传输层通信协议. 当网络通信 ...

  10. Python高级网络编程系列之基础篇

    一.Socket简介 1.不同电脑上的进程如何通信? 进程间通信的首要问题是如何找到目标进程,也就是操作系统是如何唯一标识一个进程的! 在一台电脑上是只通过进程号PID,但在网络中是行不通的,因为每台 ...

随机推荐

  1. split 转义

    问题:str.split("?");报错了 原因: .?*+!^$[]()\在正则里需要转义 转义需双反斜线.splt("\\+");

  2. 第13章 Windows内存体系结构

    13.1 Windows的虚拟地址空间安排 13.1.1虚拟地址空间的分区(即虚拟地址空间布局) 进程的地址空间划分 分区 x86 32位 Windows 3GB用户模式下的x86 32位Window ...

  3. ORACLE 临时表空间清理

    Oracle临时表空间主要用来做查询和存放一些缓冲区数据.临时表空间消耗的主要原因是需要对查询的中间结果进行排序.临时表空间的主要作用: 索引create或rebuildOrder by 或 grou ...

  4. JS中的this用法详解

    随着对js的深入学习和使用,你会发现它里面包含了很多令人困惑的机制,比如对象.闭包.原型链继承等等,而这其中肯定包含令你现在或者曾经费解的this,如果你不把心一横,花点时间还真不明白这个this的用 ...

  5. 用XAML做网页!!—开篇

    原文:用XAML做网页!!-开篇 这几日一直没发表新文章,一来是因为事比较多,二来就是我在研究使用XAML挑战传统HTML来做网页,这很可能是在全球的首次尝试,至少我从未找到任何可供参考的相关资料. ...

  6. java获取当前日期时间代码总结

    1.获取当前时间,和某个时间进行比较.此时主要拿long型的时间值.  方法如下: 要使用 java.util.Date .获取当前时间的代码如下  代码如下 复制代码 Date date = new ...

  7. Vue项目搭建及原理一

    一. Vue简介 Vue简介 Vue是近两年来比较火的一个前端框架(渐进式框架),与reactjs和angularjs三国鼎立,根据不完全统计,包括饿了么.稀土掘金.苏宁易购.美团.天猫.荔枝FM.房 ...

  8. Docker容器和本机之间的文件传输

    docker cp 本地文件路径 ID全称:容器路径

  9. Linux查看版本(centos)

    1.查看内核版本 1.1.uname -a 1.2.cat /proc/version 2.查看发行版本 cat /etc/redhat-release

  10. 【BZOJ 3456】城市规划

    http://www.lydsy.com/JudgeOnline/problem.php?id=3456 设\(f(n)\)表示n个点有标号无向连通图的数目. dp:\(f(n)=2^{n\choos ...