Python攻克之路-网络编程(socket)
1.网络编程
(1).概述
自从互联网诞生以来,现在基本上所有的程序都是网络程序,很少有单机版的程序了。
计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信.
举个例子,当你使用浏览器访问新浪网时,你的计算机就和新浪的某台服务器通过互联网连接起来了,然后,新浪的服务器把网页内容作为数据通过互联网传输到你的电脑上.
由于你的电脑上可能不止浏览器,还有QQ、Skype、Dropbox、邮件客户端等,不同的程序连接的别的计算机也会不同,所以,更确切地说,网络通信是两台计算机上的两个进程之间的通信。比如,浏览器进程和新浪服务器上的某个Web服务进程在通信,而QQ进程是和腾讯的某个服务器上的某个进程在通信.
网络编程对所有开发语言都是一样的,Python也不例外。用Python进行网络编程,就是在Python程序本身这个进程内,连接别的服务器进程的通信端口进行通信.
(2).网络通信三要素
描述:实际是进程之间的通信,要给某个进程发信息,首先要找到目标设备需要ip地址,以及这个进程的端口号,另外还要知道在那个协议下通信
a. ip
- 用来标识网络上一台独立的主机
- IP地址 = 网络地址 + 主机地址(网络号:用于识别主机所在的网络/网段,主机呈:用于识别网络中的主机)
- 特殊的IP地址:127.0.0.1(本地回环地址、保留地址,点分十进制)可用于简单的测试网卡是否故障,表示本南
b. port
- 用于标识进程的逻辑地址,不同的进程都有不同的端口标识
- 端口:要将数据发送到对方指定的应用程序上,为了标识应用程序,所以给这些网络应用程序数字进行标识,为了方便称呼这些数字,则将这些数字称为端口(此端口是一个逻辑端口)
c. protocol
- 传输协议:通讯的规则,例如:TCP,UDP(相像两个人聊天使用的语言)
(3).传输协议分析:
UDP: uer datagram protocol 用户数据协议
特点:
- 面向无连接:传输数据之前源端口和目的端口不需要建立连接
- 每个数据包的大小都限制在64K(8个字节)以内
- 是不可靠的协议(即:发送出去的数据不确定能否收到)
- 传输速率快,效率高
- 现实生活实例:邮局寄件、实时在线聊天、视频会议等
(4).TCP: transmission control protocol 传输控制协议
特点:
- 面向连接:传输数据之前需要建立连接
- 在连接过程中进行大量数据传输
- 通过"三次握手"的方式完成连接,是安全可靠协议
- 传输速度慢,效率低
(5).网络通讯步骤
确定对端ip地址 --》 确定应用程序端口 --》 确定通讯协议
summary: 网络通讯的过程实际是一个(source)不断封装数据包和(destination)不断解封装数据包的过程
简单说:sender利用应用软件将上层应用程序产生的数据前后加上相应的层标识不断的往下层传输(封包过程),最终到达物理层通过可见的物理设备,如:网线、光纤等将数据包传输到数据接收方,然后接收方则通过完全相反的操作不断的读取和去除每一层的标识信息(拆包过),最终将数据传递到最高层的指定的应用程序端口,并进行处理
2.socket
(1).socket概述
网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电,有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务
(2).socket通讯流程
例子描述:去京东购物,作为用户是client,京东是server,购物的过程也是一个通讯的过程,前提京东是server保持打开的状态,它打开后,任何user都可以与它建立连接,需要有ip,port,protocol
流程描述:
1. 服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
2. 服务器为sokcet绑定ip地址和端口号
3. 服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器socket并没有被打开
4. 客户端创建socket
5. 客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
6. 服务器socket接收请求客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息,这时socket进入阻塞状态,所谓即socket()方法
一直等到客户端返回连接信息后才返回,开始接收下一个客户端连接请求
7. 客户端连接成功,向服务器发送连接状态信息
8. 服务器accpet方法返回,连接成功
9. 客户端向socket写入信息(或服务端向socket写入信息)
10. 服务器读取信息(客户端读取信息)
11. 客户端关闭
(3).服务端客户端配置使用的方法
一般情况: 默认设置好的两个参数,基于TCP连接,服务器之间的通信
family: 决定网络协议,AF_INET(服务之间通信),AF_UNIX(unix系统不同进程间通信)
type: 决定使用tcp还是udp连接
SOCK_STREAM: TCP
SOCK_DGRAM: UDP
a.server使用的方法
bind()
将套接字绑定到地址.address地址的格式取决于地址族.在AF_INET下,以元组(host,port)的形式表示地址
listen(backing)
开始监听传入连接.backlog指定在拒绝连接之前,可以挂起最大连接数量
- backlog等于5,表示内核已经接到连接请求,但服务连接请求,但服务器还没调用accept进行处理连接个数最大5
- 这个值不能无限大,因为要在内核中维护连接队列
accept()
- 接受并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据,address是连接客户端的地址
- 接收TCP客户的连接(阻塞式)等待连接的到来,
recv(bufsize[,flag])
- 接受套接字的数据,数据以字符串形式返回,bufsize指定最多可以接收的数量.flag提供有关消息的其他消息,通常可以忽略
send(string) #可能一次发不完,python3中,无论是发送还是接收都要是bytes,py2中不区分
- 将string中的数据发送到连接的套接字,返回值是要发送的字节数量,该数量可能小于string的字节大小.即:可能未将指定内容全部发送
sendall() #把send写到一个while true下不断发送
- 将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有的数据,成功返回close,失败抛出异常
- 内部通过递归调用send,将所有内容发送出去
close() 关闭套接字
b.client方法
connect(address)
- 连接到address处的套接字,一般,address的格式为元组(hostname,port),如果连接出错,返回socket,error错误
recv()
send(string)
sendall()
close()
c.其他方法
setblocking(bool)
- 是否阻塞(默认True),如果设置False,那么accept和recv时,会处于阻塞状态,一旦无数据,则报错,继续走下去
connect_ex(address)
- 与connect相似,但是有返回值,连接成功时返回0,连接失败时返回编码,例如:10061
recvfrom(bufsize[,flag])
- 与recv()类似,但是返回值是(data,address),其中data是包含接收数据的字符串,address是发送数据套接字地址
sendto(string[,flag],address)
- 将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址.返回值是发送的字节数. 该函数主要用于UDP协议
settimeout(timeout)
- 一般建立连接后处于等待状态,设置套接字操作的超时期(连接可以维持多久),timeout是一个浮点数,单位是秒,值为None表示没有超时期,一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如:client连接最多等待5s)
gettimeout
- 取出来后,多少秒过时
getpeername
- 返回连接套接字的远程地址,返回值通常是元组(ipaddr,port)
getsockename()
- 返回套接字自身的地址,通常是一个元组(ipaddr,port)
fileno()
- 套接字的文件描述符
(4).配置
a.服务端建立连接
[root@node2 socket]# vim server.py #!/usr/local/python3/bin/python3 import socket family type sk = socket.socket() #创建一个socket对象,所有的操作都是基于它来实现 address=('127.0.0.1',8888) #用一个元组定义ip和port sk.bind(address) #使用sk的bind方法绑定ip和port sk.listen(3) #数字是决定数列的个数(能排队的数量),server运行起来,其他client可以连接,默认只能处理单个请求 conn=sk.accept() #使用accept()时,server就阻塞,等待请求 print(conn)
b.客户端建立连接
描述: 客户端做的只需要去连接 [root@node2 socket]# cat client.py #!/usr/local/python3/bin/python3 import socket sk=socket.socket() address=('127.0.0.1',8888) sk.connect(address)
c.启动程序(先启server,再使用client连接)
[root@node2 socket]# python3 server.py #当client运行时,就会打印它的连接信息 (<socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8888), raddr=('127.0.0.1', 37436)>, ('127.0.0.1', 37436)) # socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0是sk=socket.socket() # raddr=('127.0.0.1', 37436) 是client的ip和随机端口 [root@node2 socket]# python3 client.py
d.发送和接收配置
[root@node2 socket]# cat server.py #!/usr/local/python3/bin/python3 import socket # family type sk = socket.socket() address=('127.0.0.1',8888) sk.bind(address) sk.listen(3) conn,addr=sk.accept() #sk.accept()是元组有两部分socket对象和建立连接的client地址和Port,使用两个变量来接收 #conn是client端socket的对象 inp=input('>>> ') conn.send(bytes(inp,'utf8')) #接收还是发送都要bytpes类型 #server和client连接,server的sk是等待其他的连接,sk只是绑定一个端口号和ip,真正来连接的是client的socket连接(conn), server的accept有两部分参数conn和addr,得到conn才是真正建立通道,server自身的sk只是绑定端口和等待连接,原因是client 可能有多个,多个client与server进行绑定,client都是使用各自的conn 但是在client是使用sk来接收的,实际client的sk.recv()与server的conn.send()是同一个通道socket对象,accept时, 已经把sk传到server,server是通过conn命名 [root@node2 socket]# cat client.py #!/usr/local/python3/bin/python3 import socket sk=socket.socket() address=('127.0.0.1',8888) sk.connect(address) data=sk.recv(1024) #最大量是8k,默认使用1024,指定一次最大收的量,同时处于阻塞,直到有数据进入 print(str(data,'utf8')) #得到是byptes,转换回字符串,如果是汉字,会显示汉字的编码 [root@node2 socket]# python3 server.py >>> hello #字符串,字母对照的ASCII与utf-8是一致的 [root@node2 socket]# python3 client.py b'hello' [root@node2 socket]# python3 server.py >>> 走 [root@node2 socket]# python3 client.py 走
e.关闭连接
描述: 实际是关闭通道 server: conn.close()是关闭单个通道,sk.close是关闭整个server端的socket,相当于无法接收所有请求
f.使用while True实现持久通讯
[root@node2 socket]# cat server.py #!/usr/local/python3/bin/python3 import socket # family type sk = socket.socket() address=('127.0.0.1',8888) sk.bind(address) sk.listen(3) conn,addr=sk.accept() while True: data=conn.recv(1024) print(str(data,'utf8')) inp=input('>>> ') conn.send(bytes(inp,'utf8')) conn.close() [root@node2 socket]# cat client.py #!/usr/local/python3/bin/python3 import socket sk=socket.socket() address=('127.0.0.1',8888) sk.connect(address) while True: inp=input('>>> ') sk.send(bytes(inp,'utf8')) data=sk.recv(1024) print(str(data,'utf-8')) sk.close() [root@node2 socket]# python3 client.py >>> hi hi,reid >>> hi, lin how are you doing? >>> fine [root@node2 socket]# python3 server.py hi >>> hi,reid hi, lin >>> how are you doing? fine
g.退出处理:实现client先退出
[root@node2 socket]# cat client.py #!/usr/local/python3/bin/python3 import socket sk=socket.socket() address=('127.0.0.1',8888) sk.connect(address) while True: inp=input('>>> ') if inp == 'exit': #当client输入是exit,就跳出循环 break sk.send(bytes(inp,'utf8')) data=sk.recv(1024) print(str(data,'utf-8')) sk.close() [root@node2 socket]# cat server.py #!/usr/local/python3/bin/python3 import socket # family type sk = socket.socket() address=('127.0.0.1',8888) sk.bind(address) sk.listen(3) conn,addr=sk.accept() while True: data=conn.recv(1024) #如果client发送空格就会直接处于阻塞状态,是不允许发送空的,直接发送新的值才继续往下走 if not data:break #client退出后,如果接收到的数据是空的,这里就跳出循环 print(str(data,'utf8')) inp=input('>>> ') conn.send(bytes(inp,'utf8')) conn.close()
不间断聊天方法一:
[root@node2 socket]# cat server.py #!/usr/local/python3/bin/python3 import socket # family type sk = socket.socket() address=('127.0.0.1',8888) sk.bind(address) sk.listen(3) conn,addr=sk.accept() while True: data=conn.recv(1024) if not data: conn.close() #关闭之前的连接 conn, addr = sk.accept() #conn新的客户端 print(addr) # continue #不向下走,到循环下的conn.recv,新的用户进来可以通信 inp=input('>>> ') conn.send(bytes(inp,'utf8')) sk.close() [root@node2 socket]# python3 server.py ('127.0.0.1', 37448) >>> find [root@node2 socket]# python3 client.py >>> hek find
不间断聊天方法二:
[root@node2 socket]# cat server.py #!/usr/local/python3/bin/python3 import socket # family type sk = socket.socket() address=('127.0.0.1',8888) sk.bind(address) sk.listen(3) while True: conn, addr = sk.accept() #正常程序到这,会继续向下运行 while True: data=conn.recv(1024) if not data:break #break后,退出是内部的循环,还在大的循环中还可以重新接收 inp=input('>>> ') conn.send(bytes(inp,'utf8')) sk.close() [root@node2 socket]# python3 server.py >>> hello client [root@node2 socket]# python3 client.py >>> exit [root@node2 socket]# python3 client.py >>> hello server hello client
h.listen(3):设置排列三个用户测试
[root@node2 socket]# python3 server.py client1 >>> server client2 [root@node2 socket]# python3 client.py #当正在通信的关闭后才能到下一个 >>> client1 server >>> exit [root@node2 socket]# python3 client.py >>> client2
i.退出处理(windows下出现状态,linux正常)
描述:server与client连接时,client如果强迫断开,server会发生报错,主要的原因是data=conn.recv(1024)处于阻塞状态,它是在等待conn的连接信息,但是通过硬处理导致conn出现问题了,使用异常处理解决
[root@node2 socket]# cat server.py #!/usr/local/python3/bin/python3 import socket # family type sk = socket.socket() address=('127.0.0.1',8888) sk.bind(address) sk.listen(3) while True: conn, addr = sk.accept() while True: try: ## data=conn.recv(1024) ##linux下会认为这个是空的 except Exception: ## break ## print(str(data,'utf-8')) if not data:break inp=input('>>> ') conn.send(bytes(inp,'utf8')) sk.close()