Python网络编程——Socket基础(一)

一、网络的基础知识

1、OSI参考模型

  OSI是Open System Interconnect的缩写,意为开放式系统互联。一般都叫OSI参考模型,是ISO组织在1985年研究的网络互联模型。该体系结构标准定义了网络互连的七层框架,在这一框架下进一步详细规定了每一层的功能,以实现开放系统环境中的互连性、互操作性和应用的可移植性。

  OSI七层参考模型的各个层次的划分遵循下列原则:

  1、同一层中的各网络节点都有相同的层次结构,具有同样的功能。

  2、同一节点内相邻层之间通过接口(可以是逻辑接口)进行通信。

  3、七层结构中的每一层使用下一层提供的服务,并且向其上层提供服务。

  4、不同节点的同等层按照协议实现对等层之间的通信。

 

物理层
物理层是OSI的第一层,它虽然处于最底层,却是整个开放系统的基础。物理层为设备之间的数据通信提供传输媒体及互连设备,为数据传输提供可靠的环境。
1.1媒体和互连设备
规定通信设备的机械的、电气的、功能的和过程的特性,用以建立、维护和拆除物理链路连接。具体地讲,机械特性规定了网络连接时所需接插件的规格尺寸、引脚数量和排列情况等;电气特性规定了在物理连接上传输bit流时线路上信号电平的大小、阻抗匹配、传输速率 距离限制等。物理层的媒体包括架空明线、平衡电缆、光纤、无线信道等。通信用的互连设备指DTE和DCE间的互连设备。DTE即数据终端设备,又称物理设备,如计算机、终端等都包括在内。而DCE则是数据通信设备或电路连接设备,如调制解调器等。数据传输通常是经过DTE——DCE,再经过DCE——DTE的路径。互连设备指将DTE、DCE连接起来的装置,如各种插头、插座。LAN中的各种粗、细同轴电缆、T型接、插头,接收器,发送器,中继器等都属物理层的媒体和连接器。
1.2物理层的主要功能
1.2.1为数据端设备提供传送数据的通路,数据通路可以是一个物理媒体,也可以是多个物理媒体连接而成。一次完整的数据传输,包括激活物理连接,传送数据,终止物理连接.所谓激活,就是不管有多少物理媒体参与,都要在通信的两个数据终端设备间连接起来,形成一条通路。
1.2.2传输数据,物理层要形成适合数据传输需要的实体,为数据传送服务。一是要保证数据能在其上正确通过,二是要提供足够的带宽(带宽是指每秒钟内能通过的比特(BIT)数),以减少信道上的拥塞。传输数据的方式能满足点到点,一点到多点,串行或并行,半双工或全双工,同步或异步传输的需要。
 

数据链路层

数据链路可以粗略地理解为数据通道。物理层要为终端设备间的数据通信提供传输媒体及其连接.媒体是长期的,连接是有生存期的.在连接生存期内,收发两端可以进行不等的一次或多次数据通信.每次通信都要经过建立通信联络和拆除通信联络两过程.这种建立起来的数据收发关系就叫作数据链路.而在物理媒体上传输的数据难免受到各种不可靠因素的影响而产生差错,为了弥补物理层上的不足,为上层提供无差错的数据传输,就要能对数据进行检错和纠错.数据链路的建立,拆除,对数据的检错,纠错是数据链路层的基本任务。
2.1链路层的主要功能
链路层是为网络层提供数据传送服务的,这种服务要依靠本层具备的功能来实现。链路层应具备如下功能:物理地址寻址、数据的成帧、流量控制、数据的检错、重发等。在这一层,数据的单位称为帧(frame)。
2.2数据链路层的主要协议
数据链路层协议是为发对等实体间保持一致而制定的,也为了顺利完成对网络层的服务。数据链路层协议的代表包括:SDLC、HDLC、PPP、STP、帧中继等。
 

网络层

网络层的产生也是网络发展的结果.在联机系统和线路交换的环境中,网络层的功能没有太大意义.当数据终端增多时。它们之间有中继设备相连.此时会出现一台终端要求不只是与唯一的一台而是能和多台终端通信的情况,这就是产生了把任意两台数据终端设备的数据链接起来的问题,也就是路由或者叫寻径。另外,当一条物理信道建立之后,被一对用户使用,往往有许多空闲时间被浪费掉.人们自然会希望让多对用户共用一条链路,为解决这一问题就出现了逻辑信道技术和虚拟电路技术。
3.1网络层主要功能
网络层的任务就是选择合适的网间路由和交换结点,确保数据及时传送。网络层将数据链路层提供的帧组成数据包,包中封装有网络层包头,其中含有逻辑地址信息- -源站点和目的站点地址的网络地址。如果你在谈论一个IP地址,那么你是在处理第3层的问题,这是“数据包”问题,而不是第2层的“帧”。IP是第3层问题的一部分,此外还有一些路由协议和地址解析协议(ARP)。有关路由的一切事情都在这第3层处理。地址解析和路由是3层的重要目的。网络层还可以实现拥塞控制、网际互连等功能。在这一层,数据的单位称为数据包(packet)。网络层协议的代表包括:IP、IPX、RIP、OSPF等。
 

传输层

第4层的数据单元也称作数据包(packets)。但是,当你谈论TCP等具体的协议时又有特殊的叫法,TCP的数据单元称为段 (segments)而UDP协议的数据单元称为“数据报(datagrams)”。这个层负责获取全部信息,因此,它必须跟踪数据单元碎片、乱序到达的数据包和其它在传输过程中可能发生的危险。第4层为上层提供端到端(最终用户到最终用户)的透明的、可靠的数据传输服务。所为透明的传输是指在通信过程中 传输层对上层屏蔽了通信传输系统的具体细节。传输层协议的代表包括:TCP、UDP、SPX等。
 
 

会话层

会话层提供的服务可使应用建立和维持会话,并能使会话获得同步。在会话层及以上的高层次中,数据传送的单位不再另外命名,而是统称为报文。会话层不参与具体的传输,它提供包括访问验证和会话管理在内的建立和维护应用之间通信的机制。如服务器验证用户登录便是由会话层完成的。会话层使用校验点可使通信会话在通信失效时从校验点继续恢复通信。这种能力对于传送大的文件极为重要。会话层,表示层,应用层构成开放系统的高3层,面对应用进程提供分布处理,对话管理,信息表示,恢复最后的差错等.
 

表示层

这一层主要解决拥护信息的语法表示问题。它将欲交换的数据从适合于某一用户的抽象语法,转换为适合于OSI系统内部使用的传送语法。即提供格式化的表示和转换数据服务。数据的压缩和解压缩, 加密和解密等工作都由表示层负责。

通过前面的介绍,我们可以看出,会话层以下5层完成了端到端的数据传送,并且是可靠,无差错的传送.但是数据传送只是手段而不是目的,最终是要实现对数据的使用.由于各种系统对数据的定义并不完全相同,最易明白的例子是键盘,其上的某些键的含义在许多系统中都有差异.这自然给利用其它系统的数据造成了障碍.表示层和应用层就担负了消除这种障碍的任务.
 

应用层

应用层为操作系统或网络应用程序提供访问网络服务的接口。应用层协议的代表包括:Telnet、FTP、HTTP、SNMP等。
 
2、TCP/UDP协议

  TCP (Transmission Control Protocol)和UDP(User Datagram Protocol)协议属于传输层协议。其中TCP提供IP环境下的数据可靠传输,它提供的服务包括数据流传送、可靠性、有效流控、全双工操作和多路复 用。通过面向连接、端到端和可靠的数据包发送。通俗说,它是事先为所发送的数据开辟出连接好的通道,然后再进行数据发送;而UDP则不为IP提供可靠性、流控或差错恢复功能。一般来说,TCP对应的是可靠性要求高的应用,而UDP对应的则是可靠性要求低、传输经济的应用。TCP支持的应用协议主要 有:Telnet、FTP、SMTP等;UDP支持的应用层协议主要有:NFS(网络文件系统)、SNMP(简单网络管理协议)、DNS(主域名称系 统)、TFTP(通用文件传输协议)等.

  TCP/IP协议与低层的数据链路层和物理层无关,这也是TCP/IP的重要特点。

 

二、Socket

socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。 

建立一个socket必须至少有2端, 一个服务端,一个客户端, 服务端被动等待并接收请求,客户端主动发起请求, 连接建立之后,双方可以互发数据。 

 

 

 

socket中TCP的三次握手建立连接详解

我们知道TCP建立连接要进行“三次握手”,即交换三个分组。大致流程如下:

  • 客户端向服务器发送一个SYN J
  • 服务器向客户端响应一个SYN K,并对SYN J进行确认ACK J+1
  • 客户端再想服务器发一个确认ACK K+1

只有就完了三次握手,但是这个三次握手发生在socket的那几个函数中呢?请看下图:

image

socket中发送的TCP三次握手

从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

总结:客户端的connect在三次握手的第二个次返回,而服务器端的accept在三次握手的第三次返回。

 

network socket is an endpoint of a connection across a computer network. Today, most communication between computers is based on the Internet Protocol; therefore most network sockets are Internet sockets. More precisely, a socket is a handle (abstract reference) that a local program can pass to the networking application programming interface (API) to use the connection, for example "send this data on this socket". Sockets are internally often simply integers, which identify which connection to use.

网络套接字是跨越计算机网络的连接的端点。 今天,计算机之间的大部分通信都基于互联网协议; 因此大多数网络套接字都是Internet套接字。 更确切地说,套接字是本地程序可以传递给网络应用程序编程接口(API)以使用连接的句柄(抽象引用),例如“在此套接字上发送此数据”。 套接字在内部通常只是整数,用于标识要使用的连接。

For example, to send "Hello, world!" via TCP to port 80 of the host with address 1.2.3.4, one might get a socket, connect it to the remote host, send the string, then close the socket:

1
2
3
4
Socket socket = getSocket(type = "TCP")
connect(socket, address = "1.2.3.4", port = "80")
send(socket, "Hello, world!")
close(socket)

socket API is an application programming interface (API), usually provided by the operating system, that allows application programs to control and use network sockets. Internet socket APIs are usually based on the Berkeley sockets standard. In the Berkeley sockets standard, sockets are a form of file descriptor (a file handle), due to the Unix philosophy that "everything is a file", and the analogies between sockets and files: you can read, write, open, and close both. In practice the differences mean the analogy is strained, and one instead use different interfaces (send and receive) on a socket. In inter-process communication, each end will generally have its own socket, but these may use different APIs: they are abstracted by the network protocol.

套接字API是应用程序编程接口(API),通常由操作系统提供,允许应用程序控制和使用网络套接字。 Internet套接字API通常基于Berkeley套接字标准。 在伯克利套接字标准中,套接字是一种文件描述符(文件句柄)的形式,由于Unix的哲学是“一切都是文件”,以及套接字和文件之间的类比:你可以读,写,打开和关闭 都。 在实践中,差异意味着类比紧张,而在插座上使用不同的接口(发送和接收)。 在进程间通信中,每一端通常都有自己的套接字,但这些套接字可能使用不同的API:它们被网络协议抽象出来。

socket address is the combination of an IP address and a port number, much like one end of a telephone connection is the combination of a phone number and a particular extension. Sockets need not have an address (for example for only sending data), but if a program binds a socket to an address, the socket can be used to receive data sent to that address. Based on this address, internet sockets deliver incoming data packets to the appropriate application process or thread.

套接字地址是IP地址和端口号的组合,就像电话连接的一端是电话号码和特定分机的组合。 套接字不必具有地址(例如仅用于发送数据),但是如果程序将套接字绑定到地址,套接字可用于接收发送到该地址的数据。 基于这个地址,互联网套接字将传入的数据包传递给适当的应用程序进程或线程。

 

socket()

Socket Families(地址簇) 

socket.AF_UNIX unix 本机进程间通信 

socket.AF_INET IPV4 默认为IPV4

socket.AF_INET6  IPV6

 These constants represent the address (and protocol) families, used for the first argument to socket(). If the AF_UNIX constant is not defined then this protocol is unsupported. More constants may be available depending on the system.

 这些常量表示地址(和协议)族,用于socket()的第一个参数。 如果未定义AF_UNIX常量,则此协议不受支持。 取决于系统,可能有更多的常量。 

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.SOCK_SEQPACKET #废弃了

These constants represent the socket types, used for the second argument to socket(). More constants may be available depending on the system. (Only SOCK_STREAM and SOCK_DGRAM appear to be generally useful.)

Socket 方法

socket.socket(family=AF_INETtype=SOCK_STREAMproto=0fileno=None)

Create a new socket using the given address family, socket type and protocol number. The address family should be AF_INET (the default), AF_INET6AF_UNIXAF_CAN or AF_RDS. The socket type should beSOCK_STREAM (the default), SOCK_DGRAMSOCK_RAW or perhaps one of the other SOCK_ constants. The protocol number is usually zero and may be omitted or in the case where the address family is AF_CAN the protocol should be one of CAN_RAW or CAN_BCM. If fileno is specified, the other arguments are ignored, causing the socket with the specified file descriptor to return. Unlike socket.fromfd()fileno will return the same socket and not a duplicate. This may help close a detached socket using socket.close().

使用给定的地址系列,套接字类型和协议号创建一个新套接字。 地址系列应该是AF_INET(默认),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 套接字类型应该是SOCK_STREAM(默认),SOCK_DGRAM,SOCK_RAW或者其他SOCK_常量之一。 协议编号通常为零,可以省略,或者在地址族为AF_CAN的情况下,协议应该是CAN_RAW或CAN_BCM之一。 如果指定了fileno,则其他参数将被忽略,从而导致带有指定文件描述符的套接字返回。 与socket.fromfd()不同,fileno将返回相同的套接字,而不是重复的。 这可能有助于使用socket.close()关闭分离的套接字。

socket.socketpair([family[, type[, proto]]])

Build a pair of connected socket objects using the given address family, socket type, and protocol number. Address family, socket type, and protocol number are as for the socket() function above. The default family is AF_UNIX if defined on the platform; otherwise, the default is AF_INET.

使用给定的地址系列,套接字类型和协议编号构建一对连接的套接字对象。 地址族,套接字类型和协议号与上面的socket()函数一样。 如果在平台上定义,则默认系列是AF_UNIX; 否则,默认是AF_INET。

socket.create_connection(address[, timeout[, source_address]])

Connect to a TCP service listening on the Internet address (a 2-tuple (host, port)), and return the socket object. This is a higher-level function than socket.connect(): if host is a non-numeric hostname, it will try to resolve it for both AF_INET and AF_INET6, and then try to connect to all possible addresses in turn until a connection succeeds. This makes it easy to write clients that are compatible to both IPv4 and IPv6.

Passing the optional timeout parameter will set the timeout on the socket instance before attempting to connect. If no timeout is supplied, the global default timeout setting returned by getdefaulttimeout() is used.

If supplied, source_address must be a 2-tuple (host, port) for the socket to bind to as its source address before connecting. If host or port are ‘’ or 0 respectively the OS default behavior will be used.

连接到监听Internet地址的TCP服务(2元组(主机,端口)),然后返回套接字对象。 这是比socket.connect()更高级别的函数:如果主机是非数字主机名,它将尝试为AF_INET和AF_INET6解析它,然后尝试依次连接到所有可能的地址,直到连接成功。 这使得编写兼容IPv4和IPv6的客户端变得容易。

尝试连接之前,传递可选的timeout参数将在套接字实例上设置超时。 如果未提供超时,则使用由getdefaulttimeout()返回的全局默认超时设置。

如果提供,则在连接之前,source_address必须是套接字绑定到的源地址的2元组(主机,端口)。 如果主机或端口分别为“0”或0,则将使用操作系统默认行为。

socket.getaddrinfo(hostportfamily=0type=0proto=0flags=0)

#获取要连接的对端主机地址

sk.bind(address)

  s.bind(address) 将套接字绑定到地址。address地址的格式取决于地址族。在AF_INET下,以元组(host,port)的形式表示地址。

sk.listen(backlog)

  开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数量。

      backlog等于5,表示内核已经接到了连接请求,但服务器还没有调用accept进行处理的连接个数最大为5,这个值不能无限大,因为要在内核中维护连接队列

sk.setblocking(bool)

  是否阻塞(默认True),如果设置False,那么accept和recv时一旦无数据,则报错。

sk.accept()

  接受连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址。

  接收TCP 客户的连接(阻塞式)等待连接的到来

sk.connect(address)

  连接到address处的套接字。一般,address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

sk.connect_ex(address)

  同上,只不过会有返回值,连接成功时返回 0 ,连接失败时候返回编码,例如:10061

sk.close()

  关闭套接字

sk.recv(bufsize[,flag])

  接受套接字的数据。数据以字符串形式返回,bufsize指定最多可以接收的数量。flag提供有关消息的其他信息,通常可以忽略。

sk.recvfrom(bufsize[.flag])

  与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

sk.send(string[,flag])

  将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。即:可能未将指定内容全部发送。

sk.sendall(string[,flag])

  将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

      内部通过递归调用send,将所有内容发送出去。

sk.sendto(string[,flag],address)

  将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。该函数主要用于UDP协议。

sk.settimeout(timeout)

  设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如 client 连接最多等待5s )

sk.getpeername()

  返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

sk.getsockname()

  返回套接字自己的地址。通常是一个元组(ipaddr,port)

sk.fileno()

  套接字的文件描述符

socket.sendfile(fileoffset=0count=None)

     发送文件 ,但目前多数情况下并无什么卵用。

 

例子1:基础实现,只能一对一收发

server端

 1 import socket
 2 
 3 sever = socket.socket()  #声明socket类型,生成socket连接对象
 4 sever.bind(("localhost",4545))  #绑定socket服务的地址和端口
 5 sever.listen()   #监听
 6 print("我要开始等电话了")
 7 
 8 while True:
 9     conn,sddr = sever.accept()  #等待电话,返回链接的标记位和链接地址
10     #conn是服务端生成的客户端链接实例
11     print("电话来了")
12     while True:
13         data = conn.recv(1024)  #通过标记来接受
14         if not data:
15             break
16         print("recv:",data.decode())
17         conn.send(data.upper()) #通过标记来发送
18 
19 sever.close()
View Code

 client端

 1 import socket
 2 
 3 client = socket.socket()  #声明socket类型,生成socket连接对象
 4 
 5 client.connect(("localhost",4545))  #设置socket的地址和端口
 6 while True:
 7     mas = input(">>").strip()
 8     client.send(mas.encode("utf-8"))  #发送的内容
 9 
10     data = client.recv(1024)  #接受的内容
11     print(data.decode())
12 
13 client.close()  #关闭socket
View Code

 

例子2:基本ssh

server端

 1 import socket,os
 2 
 3 server = socket.socket()
 4 server.bind(("localhost",4000))
 5 server.listen()
 6 
 7 while True:
 8     conn,addr = server.accept()
 9     print("链接客户机",addr)
10     while True:
11         data = conn.recv(1024)
12         if not data:
13             print("客户机断开...")
14             break
15         print("接收指令:",data)
16         msg = os.popen(data.decode()).read()
17         if len(msg) == 0:
18             msg = "Have no message"
19         conn.send(str(len(msg.encode())).encode("utf-8"))
20         data = conn.recv(1024)  #防止粘包,进行交互
21         print(addr,data.decode())
22         conn.send(msg.encode("utf-8"))
23         print("信息发送成功")
24 
25 server.close()
View Code

 client端

 1 import socket
 2 
 3 client = socket.socket()
 4 client.connect(("localhost",4000))
 5 
 6 while True:
 7     cmd = input(">>:").strip()
 8     if len(cmd) == 0:
 9         continue
10     client.send(cmd.encode("utf-8"))
11     msg_size = client.recv(1024)
12     client.send("准备好接受数据了".encode("utf-8"))
13     print(int(msg_size))
14     msg_recv_size = 0
15     msg = b""
16     while msg_recv_size < int(msg_size.decode()):
17         data = client.recv(1024)
18         msg_recv_size += len(data)
19         print(msg_recv_size)
20         # print(data.decode()) #报错原因:中文编码时断开发送,造成信息不足
21         msg += data
22     else:
23         print(msg_recv_size)
24         print(msg.decode("utf-8"))
25 
26 client.close()
View Code

 

 例子3:基本ftp

server端

 1 import socket,os
 2 
 3 server = socket.socket()
 4 server.bind(("0.0.0.0",9999))
 5 server.listen()
 6 
 7 while True:
 8     conn,addr = server.accept()
 9     print("链接客户机",addr)
10     while True:
11         print("等待新指令")
12         data = conn.recv(1024)
13         if not data:
14             print("客户机已断开")
15             break
16 
17         cmd,filename = data.decode().split()
18         print("file name:",filename)
19         if os.path.isfile(filename):
20             f = open(filename,"rb")
21             file_size = os.stat(filename).st_size
22             conn.send(str(file_size).encode("utf-8"))
23             conn.recv(1024)
24             for line in f:
25                 conn.send(line)
26             f.close()
27             print("send done")
28 
29         else:
30             print("err :don't have this file")
31 
32 server.close()
View Code

client端

 1 import socket
 2 
 3 client = socket.socket()
 4 client.connect(("localhost",9999))
 5 
 6 while True:
 7     cmd = input(">>:").strip()
 8     if len(cmd)==0:continue
 9     if cmd.startswith("get"):
10         client.send(cmd.encode("utf-8"))
11         server_fsize = client.recv(1024)
12         print("file size:",server_fsize.decode())
13         client.send(b"ready to recv file")
14         file_totle_size = int(server_fsize)
15         receive_size = 0
16         filename = cmd.split()[1]
17         f = open(filename+"副本","wb")
18         while receive_size<file_totle_size:
19             data = client.recv(1024)
20             receive_size += len(data)
21             f.write(data)
22             print(file_totle_size,receive_size)
23         else:
24             print("file done")
25             f.close()
26 
27 client.close()
View Code

 

 

 

 

 

 
 

 

posted @ 2018-03-05 19:01  有点黑的小白  阅读(268)  评论(0编辑  收藏  举报