网络编程

知识储备

 

 

 

C/S和B/S架构

1 # C/S架构
2 client<---->server
3 # B/S架构
4 browser<---->server
View Code

物理层:

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

数据链路层

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

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

以太网协议:

形成统一标准,以太网协议ethernet

ethernet规定

  • 一组电信号构成一个数据包,叫做’帧‘
  • 每一数据帧分成:包头head和数据data两部分

head包含:(固定18个字节)

  • 源,6个字节
  • 目,6个字节
  • 数据类型,6个字节

data包含:(最短46字节,最长1500字节)

  • 数据包的具体内容
head长度+data长度=最短64字节,最长1518字节,超过最大限制就分片发送
数据包内容 

网络层

网络层由来:有了mac地址,同一网络内的两台主机就可以通信了(一台主机通过arp协议获取另外一台主机的mac地址),ethernet采用最原始的方式,广播的方式进行通信,即计算机通信基本靠吼。

现在还需要一种手段来判定,必须找出一种方法来区分哪些计算机属于同一广播域,哪些不是,如果是就采用广播的方式发送,如果不是,就采用路由的方式(向不同广播域/子网分发数据包)。

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

传输层

传输层的由来:传输层以下都是由操作系统控制,网络层的ip帮我们区分子网,以太网层的mac帮我们找到主机,然后大家使用的都是应用程序,你的电脑上可能同时开启qq,暴风影音,等多个应用程序,

那么我们通过ip和mac找到了一台特定的主机,如何标识这台主机上的应用程序,答案就是端口端口即应用程序与网卡关联的编号

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

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

tcp协议:

可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割。

以太网头 ip 头               tcp头               数据                                                    

 

udp协议:

不可靠传输,”报头”部分一共只有8个字节,总长度不超过65,535字节,正好放进一个IP数据包。

以太网头 ip头                      udp头                            数据                                           

 

tcp报文

tcp三次握手和四次挥手

tcp建立的是双向链接

C -----------------------》 S
C <----------------------- S
TCP建立的是双向链接
C ---- syn=1 seq=x ----> S
S ---- ack=1+x syn=1 seq=y ----> C
C ---- ack=1+y ----> S
TCP三次握手连接
S ---- fin=1 ---->C
C ---- ack=1 ----> S
C ---- fin=1 ----> S
S ---- ack=1 ----> C
四次挥手断连

应用层

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

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

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

 

 TCP/UDP

 什么是网络?

1、物理连接介质;

2、互联网协议。

 MAC+IP:定位全世界一台独一无二的计算机

MAC+IP+PORT:定位全世界一台独一无二的计算机上的程序

但在程序中,因为操作系统配备了ARP协议,可实现IP和MAC的转换

总结:因此【IP+PORT】即可定位定位全世界一台独一无二的计算机上的程序

TCP UDP
TCP与UDP基本区别
  1.基于连接与无连接
  2.TCP要求系统资源较多,UDP较少; 
  3.UDP程序结构较简单 
  4.流模式(TCP)与数据报模式(UDP); 
  5.TCP保证数据正确性,UDP可能丢包 
  6.TCP保证数据顺序,UDP不保证 
  
UDP应用场景:
  1.面向数据报方式
  2.网络数据大多为短消息 
  3.拥有大量Client
  4.对数据安全性无特殊要求
  5.网络负担非常重,但对响应速度要求高
 
具体编程时的区别
   1.socket()的参数不同 
   2.UDP Server不需要调用listen和accept 
   3.UDP收发数据用sendto/recvfrom函数 
   4.TCP:地址信息在connect/accept时确定 
   5.UDP:在sendto/recvfrom函数中每次均 需指定地址信息 
   6.UDP:shutdown函数无效
 
编程区别
   通常我们在说到网络编程时默认是指TCP编程,即用前面提到的socket函数创建一个socket用于TCP通讯,函数参数我们通常填为SOCK_STREAM。即socket(PF_INET, SOCK_STREAM, 0),这表示建立一个socket用于流式网络通讯。 
   SOCK_STREAM这种的特点是面向连接的,即每次收发数据之前必须通过connect建立连接,也是双向的,即任何一方都可以收发数据,协议本身提供了一些保障机制保证它是可靠的、有序的,即每个包按照发送的顺序到达接收方。 

  而SOCK_DGRAM这种是User Datagram Protocol协议的网络通讯,它是无连接的,不可靠的,因为通讯双方发送数据后不知道对方是否已经收到数据,是否正常收到数据。任何一方建立一个socket以后就可以用sendto发送数据,也可以用recvfrom接收数据。根本不关心对方是否存在,是否发送了数据。它的特点是通讯速度比较快。大家都知道TCP是要经过三次握手的,而UDP没有。 

基于上述不同,UDP和TCP编程步骤也有些不同,如下:

TCP: 
TCP编程的服务器端一般步骤是: 
  1、创建一个socket,用函数socket(); 
  2、设置socket属性,用函数setsockopt(); * 可选 
  3、绑定IP地址、端口等信息到socket上,用函数bind(); 
  4、开启监听,用函数listen(); 
  5、接收客户端上来的连接,用函数accept(); 
  6、收发数据,用函数send()和recv(),或者read()和write(); 
  7、关闭网络连接; 
  8、关闭监听; 

TCP编程的客户端一般步骤是: 
  1、创建一个socket,用函数socket(); 
  2、设置socket属性,用函数setsockopt();* 可选 
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 
  4、设置要连接的对方的IP地址和端口等属性; 
  5、连接服务器,用函数connect(); 
  6、收发数据,用函数send()和recv(),或者read()和write(); 
  7、关闭网络连接;

UDP:
与之对应的UDP编程步骤要简单许多,分别如下: 
  UDP编程的服务器端一般步骤是: 
  1、创建一个socket,用函数socket(); 
  2、设置socket属性,用函数setsockopt();* 可选 
  3、绑定IP地址、端口等信息到socket上,用函数bind(); 
  4、循环接收数据,用函数recvfrom(); 
  5、关闭网络连接; 

UDP编程的客户端一般步骤是: 
  1、创建一个socket,用函数socket(); 
  2、设置socket属性,用函数setsockopt();* 可选 
  3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选 
  4、设置对方的IP地址和端口等属性; 
  5、发送数据,用函数sendto(); 
  6、关闭网络连接;

TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。

UDP补充:
   UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。

TCP补充:
  TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。


TCP与UDP区别总结:
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保   证可靠交付
3、TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的
  UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低(对实时应用很有用,如IP电话,实时视频会议等)
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
5、TCP首部开销20字节;UDP的首部开销小,只有8个字节
6、TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
TCP/UDP 编程区别

套接字

 

 TCP套接字工作流程:

 套接字程序编码:

  1、client.connect() ----》server.accept(),此过程就实现封装三次握手过程

  2、服务器端的conn套接字对象起到收发消息的作用,即可收消息,可发消息

socket服务端(单步)详细步骤说明:

 1 import socket
 2 
 3 # 买电话
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 # 默认socket.AF_INET
 6 # socket.SOCK_STREAM称为流式协议(TCP),socket.SOCK_DGRAM为UDP协议
 7 
 8 # 插手机卡
 9 phone.bind(('127.0.0.1',8080)) # 手机卡里面关键是绑定自己的手机号,教室IP:192.168.12.112,端口,0-65535,0-1024给系统用
10 
11 # 开机
12 phone.listen(5) # 同一时间请求数,开启半链接池,以后一般都是写在配置文件里,并不是只能建立5个链接,
13 print('start...')
14 
15 # 等电话链接
16 conn,client_addr = phone.accept() # conn,套接字的收发消息对象,客户端client_addr(随机变)
17 print('链接来了:',conn,client_addr)
18 
19 # 收发消息
20 msg = conn.recv(1024) #收消息,1024是一个最大的限制,这里假设不超过1024的情况
21 print('客户端的消息:',msg)
22 conn.send(msg.upper())
23 
24 # 挂电话
25 conn.close() #回收系统资源,跟打开文件后关闭文件是一个道理
26 
27 # 关机
28 phone.close()
Socket服务端(单步)详细说明

服务端Sever(循环):

 1 import socket
 2 
 3 # 买电话
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 
 6 # 插手机卡
 7 phone.bind(('127.0.0.1',8081))
 8 
 9 # 开机
10 phone.listen(5)
11 print('start...')
12 # 链接循环
13 while True:
14     conn,client_addr = phone.accept()
15     print('客户端:',client_addr)
16 
17     # 收发消息,通信循环
18     while True:
19         try:
20             msg = conn.recv(1024) #收消息,1024是一个最大的限制,这里假设不超过1024的情况
21             print('客户端的消息:',msg)
22             conn.send(msg.upper())
23         except ConnectionResetError:
24             break # 此刻异常出现,conn是无意义对象,故不可用continue,而采用break
25 
26     conn.close()
27     # 运行至此步,进行下一步链接
28 
29 phone.close()
Socket服务器端

客户端Client:

(补充:程序结束后,回收程序资源的同时也要回收系统资源。最后应补充一段代码:client.close())

 1 import socket
 2 
 3 # 买电话
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 
 6 # 拨电话,服务端IP和端口
 7 phone.connect(('127.0.0.1',8081))
 8 
 9 # 发消息
10 while True:
11 
12     msg = input('>>>:').strip()
13     phone.send(msg.encode('utf-8'))
14     # 收消息
15     data = phone.recv(1024)
16     print(data.decode('utf-8'))
Socket客户端

 编写C/S架构的程序,实现远程执行命令: 

#subprocess使用当前系统默认编码,得到结果为bytes类型,在windows下需要用gbk解码
 1 import socket
 2 import subprocess
 3 
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 phone.bind(('127.0.0.1',8082))
 6 phone.listen(5)
 7 print('连线中...')
 8 
 9 while True:
10     conn,client_addr = phone.accept()
11     print('建立连接客户端:',client_addr)
12 
13     while True:
14         try:
15             cmd = conn.recv(1024)
16             print('客户端的消息:',cmd.decode('utf-8'))
17             obj = subprocess.Popen(cmd.decode('utf-8'),
18                              shell=True,
19                              stdout=subprocess.PIPE,
20                              stderr=subprocess.PIPE
21                              )
22             stdout = obj.stdout.read() # 成功结果
23             stderr = obj.stderr.read() # 错误结果
24 
25             conn.send(stdout+stderr)
26 
27         except ConnectionResetError:
28             print('一个客户端中止链接')
29             break
30     conn.close()
31 
32 phone.close()
远程执行命令-服务器端
 1 import socket
 2 import subprocess
 3 
 4 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 5 phone.connect(('127.0.0.1',8082))
 6 
 7 
 8 while True:
 9     cmd = input('请输入通信内容>>:').strip()
10 
11     phone.send(cmd.encode('utf-8'))
12     msg = phone.recv(1024)
13     print(msg.decode('gbk')) # 服务器端返回命令行内容,需gbk解码
14 
15 phone.close()
远程执行命令-客户端

粘包问题

这里,会出现一个问题,当命令执行结果大于1024字节时,会有消息冗余问题。

在找到解决方案前,需了解,不管收发,其实数据都是先要经过应用程序内存操作系统缓存的处理过程。 

其次,tcp流协议有两个特点:

1,像水流一样,源源不断从源头向对方堆砌数据

2,negal算法:优化数据传输,将时间间隔比较短和数据比较小的数据预留

那么,现在只要知道对方要发多少数据,那么我就能知道如何判断是否收到全部数据

在此之前补充一下,使用stuct模块可高度定制化的报头数据

最后,解决粘包的方法见下:

 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 # @Time    : 2018/04/19 14:03
 4 # @Author  : MJay_Lee
 5 # @File    : Server.py
 6 # @Contact : limengjiejj@hotmail.com
 7 
 8 import socket
 9 import subprocess
10 import struct
11 import json
12 
13 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
14 phone.bind(('127.0.0.1',8082))
15 phone.listen(5)
16 print('连线中...')
17 
18 while True:
19     conn,client_addr = phone.accept()
20     print('建立连接客户端:',client_addr)
21 
22     while True:
23         try:
24             cmd = conn.recv(1024)
25             print('客户端的消息:',cmd.decode('utf-8'))
26             obj = subprocess.Popen(cmd.decode('utf-8'),
27                              shell=True,
28                              stdout=subprocess.PIPE,
29                              stderr=subprocess.PIPE
30                              )
31             stdout = obj.stdout.read() # 成功结果
32             stderr = obj.stderr.read() # 错误结果
33 
34             print(len(stdout)+len(stderr)) # 打印出服务器端返回消息的长度
35             # conn.send(stdout+stderr)
36 
37 
38             #1、发送数据长度,发送数据长度,制作报头
39             header_dic = {
40                 'total_size':len(stdout) + len(stderr),
41                 'md5':'asdasdqasdasd',
42                 'filename':'test.txt'
43             }
44             header_json = json.dumps(header_dic)
45             header_bytes = header_json.encode('utf-8')
46 
47             # 2 先发送报头的长度
48             header_size = len(header_bytes)
49             conn.send(struct.pack('i',header_size))
50 
51             #3、发送报头
52             conn.send(header_bytes)
53 
54             #4、发送真实数据
55             conn.send(stdout)
56             conn.send(stderr)
57 
58         except ConnectionResetError:
59             print('一个客户端中止链接')
60             break
61     conn.close()
62 phone.close()
服务器端终极版
 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 # @Time    : 2018/04/19 14:03
 4 # @Author  : MJay_Lee
 5 # @File    : Client.py
 6 # @Contact : limengjiejj@hotmail.com
 7 
 8 import socket
 9 import subprocess
10 import struct
11 import json
12 
13 client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
14 client.connect(('127.0.0.1',8082))
15 
16 
17 while True:
18     cmd = input('请输入通信内容>>:').strip()
19     client.send(cmd.encode('utf-8'))
20     #1、先收报头长度
21     header_size = struct.unpack('i',client.recv(4))[0]
22     #2、接收报头
23     header_bytes = client.recv(header_size)
24     #3、解析报头
25     header_json = header_bytes.decode('utf-8')
26     header_dic = json.loads(header_json)
27     print(header_dic)
28 
29 
30     total_size = header_dic['total_size']
31     print(total_size)
32     #4、根据报头内的信息,收取真实的数据
33     recv_size = 0
34     res = b''
35     while recv_size < total_size:
36         recv_data = client.recv(1024)
37         res += recv_data
38         recv_size += len(recv_data)
39     print(res.decode('gbk'))
40 
41 client.close()
客户端终极版

补充:客户端连接个数超出服务器设置的backlog值,出现:“ConnectionRefusedError: [WinError 10061] 由于目标计算机积极拒绝,无法连接。”,解决方案如下“优化客户端终极版”

 1 #!/usr/bin/env python
 2 # -*- coding: utf-8 -*-
 3 # @Time    : 2018/04/23 14:08
 4 # @Author  : MJay_Lee
 5 # @File    : client.py
 6 # @Contact : limengjiejj@hotmail.com
 7 
 8 import socket
 9 import time
10 import json
11 import struct
12 
13 while True:
14     try:
15         client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
16         client.connect(('127.0.0.1', 8080))
17         break
18     except ConnectionRefusedError:
19         print('等待3秒')
20         time.sleep(3)
21 
22 
23 while True:
24     cmd = input('请输入通信内容:').strip()
25     if not cmd:continue
26     client.send(cmd.encode('utf-8'))
27     # 获得报头长度
28     header_size = struct.unpack('i',client.recv(4))[0]
29     # 获取报头
30     header_bytes = client.recv(header_size)
31     # 解析报头
32     header_json = header_bytes.decode('utf-8')
33     header_dic = json.loads(header_json)
34 
35     total_size = header_dic['header_len']
36     recv_size = 0
37     res = b''
38 
39     while recv_size < total_size:
40         recv_data = client.recv(1024)
41         res += recv_data
42         recv_size += len(recv_data)
43 
44     print('来自服务器的消息:',res.decode('gbk'))
45 
46 client.close()
优化客户端终极版

 基于UDP协议通信的套接字

相比较于UDP,TCP之所以可靠,是因为TCP的工作原理(每发一个数据,得到回应,才会清掉系统缓存里的数据。),而非多一个链接。

场景:多用于查询场景,可靠性要求不高

注意:UDP协议发消息的时候,有效传输最大51bytes,否则就容易出现丢包

套接字常识:WEB服务端端口80,DNS端口53

进程

三个状态

正文请移步至此

 

posted @ 2018-04-18 11:33  MJay_Lee  阅读(253)  评论(0编辑  收藏  举报