Socket套接字方法
family(socket家族)
socket.AF_UNIX: # 用于本机进程间通讯,为了保证程序安全,两个独立的程序(进程)间是不能互相访问彼此的内存的,但为了实现进程间的通讯,可以通过创建一个本地的socket来完成
socket.AF_INET: # (还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过,他们要么是只用于某个平台,要么就是已经被废弃,或者是很少被使用,或者是根本没有实现,所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET)
socket type类型
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通常仅限于高级用户或管理员运行的程序使用
服务端套接字函数
s.bind() # 绑定(主机,端口号)到套接字 s.listen() # 开始TCP监听 s.accept() # 被动接受TCP客户的连接,(阻塞式)等待连接的到来
客户端套接字函数
s.connect() # 主动初始化TCP服务器连接 s.connect_ex() connect() # 函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
s.recv() # 接收数据 s.send() # 发送数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完,可后面通过实例解释) s.sendall() # 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完) s.recvfrom() # 从套接字接收数据. 返回值是一对(字节,地址) s.getpeername() # 连接到当前套接字的远端的地址 s.close() # 关闭套接字 socket.setblocking(flag) # True or False,设置socket为非阻塞模式,以后讲io异步时会用 socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) # 返回远程主机的地址信息,例子 socket.getaddrinfo('luffycity.com',80) socket.getfqdn() # 拿到本机的主机名 socket.gethostbyname() # 通过域名解析ip地址
基本的套接字例子
服务端:
和客户端编程相比,服务器编程就要复杂一些。
服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。
所以,服务器会打开固定端口监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket。
首先,创建一个基于IPv4和TCP协议的Socket
然后,我们要绑定监听的地址和端口。服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0
绑定到所有的网络地址,还可以用127.0.0.1
绑定到本机地址。127.0.0.1
是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。
端口号需要预先指定。因为我们写的这个服务不是标准服务,所以用8083
这个端口号。请注意,小于1024
的端口号必须要有管理员权限才能绑定
紧接着,调用listen()
方法开始监听端口,传入的参数指定等待连接的最大数量
接下来,服务器程序通过一个永久循环来接受来自客户端的连接,accept()
会等待并返回一个客户端的连接
连接建立后,客户端发来"hello",服务端返回大写的"HELLO"
import socket # phone和conn两个套接字 # 1.买手机 基于网络通信,基于TCP协议 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # print(phone) # 2.绑定手机 真实情况下,服务端的地址 phone.bind(('127.0.0.1', 8083)) # 0-65535:0-1024个操作系统使用,1024以后随便用 # 3.开机 监听listen 最大挂起5个 phone.listen(5) # 4.等电话链接 print('staring...') conn, client_addr = phone.accept() # 接收链接对象 等于客户端的connect,底层完成了套接字的三次握手 # 5.收 发消息 while True: # 通信循环 data = conn.recv(1024) # 1.单位:bytes 2.1024代表最大接受1024个bytes print('客户端的数据', data) conn.send(data.upper()) # 6.挂电话 conn.close() # 7.关机 phone.close()
客户端:
创建Socket
时,AF_INET
指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6
。SOCK_STREAM
指定使用面向流的TCP协议,这样,一个Socket
对象就创建成功,但是还没有建立连接。
客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。
服务器,提供什么样的服务,端口号就必须固定下来。由于我们想要访问网页,因此新浪提供网页服务的服务器必须把端口号固定在80
端口,因为80
端口是Web服务的标准端口。其他服务都有对应的标准端口号,例如SMTP服务是25
端口,FTP服务是21
端口,等等。端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用。
注意phone.connect
参数是一个tuple
,包含地址和端口号。
建立TCP连接后,我们就可以向服务器发送请求。
接收数据时,调用recv(max)
方法,一次最多接收指定的字节数,因此,在一个while循环中反复接收,直到recv()
返回空数据,表示接收完毕,退出循环。
当我们接收完数据后,调用close()
方法关闭Socket,这样,一次完整的网络通信就结束了.
import socket # 1.买手机 基于网络通信,基于TCP协议 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # print(phone) # 2.拨号 phone.connect(('127.0.0.1', 8083)) # 3.发 收消息 phone.send('hello'.encode('utf-8')) data = phone.recv(1024) print(data) # 4.关闭 phone.close()
Server与Client循环收发数据
服务端:
import socket # phone和conn两个套接字 # 1.买手机 基于网络通信,基于TCP协议 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # print(phone) # 2.绑定手机 真实情况下,服务端的地址 phone.bind(('127.0.0.1', 8083)) # 0-65535:0-1024个操作系统使用,1024以后随便用 # 3.开机 监听listen 最大挂起5个 phone.listen(5) # 4.等电话链接 print('staring...') conn, client_addr = phone.accept() # 接收链接对象 print(client_addr) # 5.收 发消息 while True: data = conn.recv(1024) # 1.单位:bytes 2.1024代表最大接受1024个bytes print('客户端的数据', data) conn.send(data.upper()) # 6.挂电话 conn.close() # 7.关机 phone.close()
客户端:
import socket # 1.买手机 基于网络通信,基于TCP协议 phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # print(phone) # 2.拨号 phone.connect(('127.0.0.1', 8083)) # 3.发 收消息 while True: msg = input('>>>:').strip() phone.send(msg.encode('utf-8')) data = phone.recv(1024) print(data) # 4.关闭 phone.close()
Server与Client实现简单的聊天功能
服务端:
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', 8083)) phone.listen(5) conn, client_addr = phone.accept() while True: try: data = conn.recv(1024) print('Client recv:', data) if not data: break response = input('>>>:').strip() conn.send(response.encode()) print('Server send:', response) except ConnectionResetError: print('客户端强制关闭') break conn.close() phone.close()
客户端:
import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8083)) while True: msg = input('>>>:').strip() if not msg:continue phone.send(msg.encode('utf-8')) data = phone.recv(1024) print(data) phone.close()
简单的的聊天功能存在以下问题:
1.多个客户端与服务端交互的时候,处于一个派对的状态,只有第一个客户端与服务端断开后,下一个客户端与服务端才可以交互.
2.实际上断开第一个客户端,服务端也跟着断了,原因在于服务端收不到数据,直接就断开了
下面是优化后的聊天功能代码:
服务端:
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', 8083)) phone.listen(5) while True: conn, client_addr = phone.accept() while True: try: data = conn.recv(1024) print('Client recv:', data) if not data: # 服务端收不到数据,就直接break了 break response = input('>>>:').strip() conn.send(response.encode()) print('Server send:', response) except ConnectionResetError: print('客户端强制关闭') break conn.close() phone.close()
客户端:
import socket phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) phone.connect(('127.0.0.1', 8083)) while True: msg = input('>>>:').strip() if not msg:continue phone.send(msg.encode('utf-8')) data = phone.recv(1024) print(data) phone.close()
重启服务端时可能会遇到端口被占用的问题:
解决方法:
sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 一行代码搞定,写在bind之前 sock_server.bind((HOST, PORT))
或者服务端和客户端修改端口.