10.python网络编程(startin part 1)
一.什么是socket?
socket就是为了实现C/S架构而生的,socket位于应用层和传输层之间,是传输层和应用层之间的一组接口,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议,所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。
也有人将socket说成ip+port,ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序,ip地址是配置到网卡上的,而port是应用程序开启的,ip与port的绑定就标识了互联网中独一无二的一个应用程序。
补充:有人会把socket和程序的pid弄混,程序的pid是同一台机器上不同进程或者线程的标识。
二.socket种类。
基于文件型套接字。
AF_UNIX:基于文件型的套接字,就是通过底层的文件系统来取数据,两个套接字进程运行在同一台机器,可以通过访问同一个文件系统来实现两个程序之间的通信。
基于网络型套接字。
三.socket工作流程。
tcp服务端建立连接流程:
socket()实例化出一个套接字对象→bind()绑定ip地址和端口→listen()开始监听之前绑定的ip地址和端口→accept()开始被动接收tcp客户端发来的连接,会一直等连接的到来(如果没有连接,就会一直等,直到有客户端连过来。
tcp客户端建立连接流程:
socket()客户端也初始化一个套接字对象→connect()主动去连接服务端(主动初始化与tcp服务器的连接)如果连接成功,客户端和服务器之间就可以互相收发数据了。(其实connect就相当于去回应服务端的accept,服务端的accept一直在等待客户端去connect)
客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
四.socket模块的用法。
import socket
socket.socket(socket_family,socket_type,protocal=0)
socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默认值为 0。
获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
例如tcpSock = socket(AF_INET, SOCK_STREAM)
服务端常用专属套接字方法:
s.bind():绑定(主机,端口号)到套接字。
s.listen():开始进行tcp监听。#listen方法中可以指定一个backlog参数,这个参数用于设置tcp连接池的大小(关于tcp连接池和三次握手的知识后面会做补充。)
s.accept():被动接受TCP客户的连接,(阻塞式)等待连接的到来。
客户端常用专属套接字方法:
s.connect():主动初始化TCP服务器连接
s.connect_ex() : connect()函数的扩展版本,出错时返回出错码,而不是抛出异常。
客户端和服务端共同具有的常用套接字方法:
s.recv():接收tcp数据。#需要指定每次收多少字节。
s.send(): 发送tcp数据,#发送TCP数据(send在待发送数据量大于对端缓存区剩余空间时,数据丢失,不会发完)
s.sendall(): 发送tcp数据,发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于对端缓存区剩余空间时,数据不丢失,循环调用send直到发完)(个人分析,本质上是调用了循环)
s.recvfrom():接收UDP数据
s.sendto() : 发送UDP数据
s.getpeername() : 连接到当前套接字的远端的地址
s.getsockname() : 当前套接字的地址
s.getsockopt() :返回指定套接字的参数
s.setsockopt() : 设置指定套接字的参数
s.close() :关闭套接字
五.socket用法示例。
服务端:
#!/usr/bin/python2.7
# -*- coding:utf-8 -*-
import socket
address_and_port = ('127.0.0.1',8888)
s1 = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
s1.bind(address_and_port)
s1.listen(3)
conn,addr = s1.accept() #执行了socket的accept方法后,开始阻塞,等待客户端的连接.
#执行了accept方法后,会返回一个元祖,这个元祖里面包含了一个客户端的连接对象,还有客户端的ip地址和端口.
while True:
data = conn.recv(1024) #用于接收tcp数据,后面的1024表示,recv一次最多可以接收1024字节.(执行到recv,如果对端没有发来数据,或者内容为空,程序会阻塞住,不继续向下执行。)
print data
conn.send(data.upper())
conn.close() #关闭与客户端的连接
s1.close() #关闭服务端套接字连接
客户端:
#!/usr/bin/python2.7
# -*- coding:utf-8 -*-
import socket
s1 = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s1.connect(('127.0.0.1',8888))
while True:
msg = raw_input(">>>").strip()
s1.send(msg.encode(('utf-8')))
print "客户端已发送数据!"
data = s1.recv(1024)
print data
#上面两个代码,可以简单理解下socket的工作流程。
六.解决sockt 通信客户端断开服务端崩溃等问题的解决方法(连接循环,通信循环,以及异常捕捉)
服务端:
#!/usr/bin/python2.7
# -*- coding:utf-8 -*-
import socket
address_and_port = ('127.0.0.1',8888)
s1 = socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM)
s1.bind(address_and_port)
s1.listen(3)
while True: #此处问连接循环,会循环等连接。
conn,addr = s1.accept() #执行了socket的accept方法后,开始阻塞,等待客户端的连接.
#执行了accept方法后,会返回一个元祖,这个元祖里面包含了一个客户端的连接对象,还有客户端的ip地址和端口.
while True: #数据传输循环
try: #防止客户端断开导致的服务断抛异常。
data = conn.recv(1024) #用于接收tcp数据,后面的1024表示,recv一次最多可以接收1024字节.
if not data:
break
print data
conn.send(data.upper())
except Exception:
break
conn.close() #关闭与客户端的连接
s1.close() #关闭服务端套接字连接
客户端:
#!/usr/bin/python2.7
# -*- coding:utf-8 -*-
import socket
s1 = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s1.connect(('127.0.0.1',8888))
while True:
msg = raw_input(">>>").strip()
if not msg: #防止客户端发送空数据
continue
s1.send(msg.encode(('utf-8')))
print "客户端已发送数据!"
data = s1.recv(1024)
print data
s1.close()
#以上是防止服务端崩溃的一些解决方法。
七.关于udp套接字。
服务端大概操作流程:
ss = socket() #创建一个服务器的套接字
ss.bind() #绑定服务器套接字
inf_loop: #服务器无限循环
cs = ss.recvfrom()/ss.sendto() # 对话(接收与发送)
ss.close() # 关闭服务器套接字
客户端大概的操作流程:
cs = socket() # 创建客户套接字
comm_loop: # 通讯循环
cs.sendto()/cs.recvfrom() # 对话(发送/接收)
cs.close() # 关闭客户套接字
下面是udp套接字的一个简单的示例:
示例1:
服务端:
import socket
udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
udp_server.bind(('127.0.0.1',8888))
while True:
msg,addr = udp_server.recvfrom(1024)
print msg ,addr
udp_server.sendto(msg.upper(),addr)
客户端:
import socket
udp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
msg = raw_input(">>:").strip()
if not msg:
continue
udp_client.sendto(msg.encode('utf-8'),("127.0.0.1",8888))
back_msg,addr = udp_client.recvfrom(1024)
print back_msg.decode('utf-8') ,addr
八.关于tcp套接字和udp套接字你需要知道的。
首先有几点必须明确!
1.1 所有的收发消息,都是在操作自己的tcp/udp缓冲区,发消息是将数据发送到自己的缓冲区中,收消息同样也是从缓冲区中收取。
1.2 tcp 使用send发消息,使用recv收消息
1.3udp 使用sendto发消息,使用recvfrom收消息
tcp与udp
2.1 tcp是基于流的,udp是基于报的
2.2 使用send发送数据流的时候,若数据流为空,那么tcp buffer(缓冲区)也为空,操作系统不会控制tcp发包。
sendinto(bytes_data,ip_port):发送数据报,bytes_data为空,还有ip_port,所有即便是发送空的bytes_data,数据报其实也不是空的,自己这端的缓冲区收到内容,操作系统就会控制udp协议发包。
3.关于tcp的补充
tcp基于链接通信:
基于链接,则需要listen(backlog),指定半连接池的大小
基于链接,必须先运行的服务端,然后客户端发起链接请求
对于mac系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解 决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)
对于windows/linux系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到 的是空(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)
udp通信。
无链接,因而无需listen(backlog),更加没有什么连接池之说了
无链接,udp的sendinto不用管是否有一个正在运行的服务端,可以己端一个劲的发消息,只不过数据丢失
recvfrom收的数据小于sendinto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错
只有sendinto发送数据没有recvfrom收数据,数据丢失。
注意:
1.你单独运行上面的udp的客户端,你发现并不会报错,相反tcp却会报错,因为udp协议只负责把包发出去,对方收不收,我根本不管,而tcp是基于链接的,必须有一个服务端先运行着,客户端去跟服务端建立链接然后依托于链接才能传递消息,任何一方试图把链接摧毁都会导致对方程序的崩溃。
2.上面的udp程序,你注释任何一条客户端的sendinto,服务端都会卡住,为什么?因为服务端有几个recvfrom就要对应几个sendinto,哪怕是sendinto(b'')那也要有。