在学习socket编程前,需要先了解下struct模块。使用struct模块可以在不改变传输数据类型的前提下,实现python跨平台使用;
Struct 模块
作用:
Python是一门非常简洁的语言,对于数据类型的表示,不像其他语言预定义了许多类型(如:在C#中,光整型就定义了8种),它只定义了六种基本类型:字符串,整数,浮点数,元组,列表,字典。通过这六种数据类型,我们可以完成大部分工作。但当Python需要通过网络与其他的平台进行交互的时候,必须考虑到将这些数据类型与其他平台或语言之间的类型进行互相转换问题。打个比方:C++写的客户端发送一个int型(4字节)变量的数据到Python写的服务器,Python接收到表示这个整数的4个字节数据,怎么解析成Python认识的整数呢? Python的标准模块struct就用来解决这个问题。
将打包的数据类型,发给服务器端;服务器端解包时,直接还原回来,这样传输之后数据类型不会被改变;
struct.pack
其函数原型为:struct.pack(fmt, v1, v2, ...),参数fmt是格式字符串,v1, v2, ...表示要转换的python值。
# -*- coding: UTF-8 -*-
import struct
a = 20
b = 400
s = struct.pack("ii", a, b) #转换后的s是bytes类型
print ('length:', len(s))
print (s)
print (type(s))
s1 = struct.pack("2i", a, b)
print ('length:', len(s1))
print (s1)
print (type(s1))
** “ii”和”2i”表示的都是将两个参数转化为int类型,网络间传输的是bytes类型
格式符"i"表示转换为int,'ii'表示有两个int变量。进行转换后的结果长度为8个字节(int类型占用4个字节,两个int为8个字节),可以看到输出的结果是乱码,因为结果是二进制数据。
struct.unpack
struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型;
它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组
import struct
s=struct.pack("ii",20,400)
a1,a2=struct.unpack("ii",s)
#print (struct.unpack("ii",s))
print('a1:',a1) #输出20
print('a2:',a2) #输出400
struct.calcsize
struct.calcsize用于计算格式字符串所对应的结果的长度,如:struct.calcsize('ii'),返回8。因为两个int类型所占用的长度是8个字节。
在互联网上是通过二进制进行传输,所以就需要将str通过encode()编码成bytes进行传输,而在接收中通过decode()解码成我们需要的编码进行处理数据,这样不管对方是什么编码都不会出现乱码的现象!清楚这一原理之后,我们来看下socket编程
Socket 编程
Socket通讯原理描述:
套接字是为特定网络协议(例如TCP/IP,ICMP/IP,UDP/IP等)套件对上的网络应用程序提供者提供当前可移植标准的对象。它们允许程序接受并进行连接,如发送和接受数据。为了建立通信通道,网络通信的每个端点拥有一个套接字对象极为重要。和大多数语言一样,Python 支持面向连接和无连接,实现接口功能与步骤也大致相同。
面向连接即需要先连接然后通讯, 面向连接主要协议就是传输控制协议(tcp),要创建tcp套接字时需要指定套接字类型为 SOCK_STRAM,表达了他作为流套接字的特点。
无连接,顾名思义无需建立连接就可以进行通讯,这时数据到达顺序、可靠性就无法保证了。实现这种连接的协议就是用户数据包协议(udp)。创建UDP时需要指定套接字类型为 SOCK_DGRAM。
服务器端:
1. 第一步是创建socket对象,调用socket构造函数。如:
socket = socket.socket( family, type )
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
family参数代表地址家族,可为AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用于同一台机器上的进程间通信。
type参数代表套接字类型,可为SOCK_STREAM(TCP/流套接字)和SOCK_DGRAM(UDP/数据报套接字)。
2. 第二步是将socket绑定到指定地址。这是通过socket对象的bind方法来实现的:
socket.bind( address )
由AF_INET所创建的套接字,address地址必须是一个双元素元组,格式是(host,port)。host代表主机,port代表端口号。如果端口号正在使用、主机名不正确或端口已被保留,bind方法将引发socket.error异常。
3. 第三步是使用socket套接字的listen方法接收连接请求。
socket.listen( backlog )
backlog指定最多允许多少个客户连接到服务器。它的值至少为1。收到连接请求后,这些请求需要排队,如果队列满,就拒绝请求。
4. 第四步是服务器套接字通过socket的accept方法等待客户请求一个连接。
connection, address = socket.accept()
调用accept方法时,socket会时入“waiting”状态。客户请求连接时,方法建立连接并返回服务器。accept方法返回一个含有两个元素的元组(connection,address)。第一个元素connection是新的socket对象,服务器必须通过它与客户通信;第二个元素 address是客户的Internet地址。
5. 第五步是处理阶段,服务器和客户端通过send和recv方法通信(传输数据)。服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接收信息。调用recv 时,服务器必须指定一个整数,它对应于可通过本次方法调用来接收的最大数据量。recv方法在接收数据时会进入“blocked”状态,最后返回一个字符串,用它表示收到的数据。如果发送的数据量超过了recv所允许的,数据会被截短。多余的数据将缓冲于接收端。以后调用recv时,多余的数据会从缓冲区删除(以及自上次调用recv以来,客户可能发送的其它任何数据)。
6. 传输结束,服务器调用socket的close方法关闭连接
客户端:
1. 第一步是创建一个socket以连接服务器:socket = socket.socket( family, type )
2. 第二步是使用socket的connect方法连接服务器。对于AF_INET家族,连接格式如下:
socket.connect( (host,port) )
host代表服务器主机名或IP,port代表服务器进程所绑定的端口号。如连接成功,客户就可通过套接字与服务器通信,如果连接失败,会引发socket.error异常。
3. 第三步是处理阶段,客户和服务器将通过send方法和recv方法通信。
4. 传输结束,客户通过调用socket的close方法关闭连接。
TCP简单的server 端和客户端实例
server.py 服务端内容
#encoding=utf-8
import sys
import socket
ip_port = ('127.0.0.1',9999) #开启ip和端口
sk = socket.socket() #生成一个句柄
sk.bind(ip_port) #绑定ip和端口
sk.listen(5) #最多连接数
print ('进入监听状态...')
conn,addr = sk.accept() #等待链接,阻塞,直到渠道链接conn打开一个新的对象专门给当前链接的客户端;addr是ip地址;获取客户端请求数据
print(addr) #打印结果('127.0.0.1', 26568)
client_data = conn.recv(1024) #1024数据限制,conn为socket对象
print (client_data.decode("utf-8")) #打印客户端发送的数据
conn.send('服务端回复内容'.encode("utf-8")) #向客户端发送数据
conn.close() #关闭服务端和客户端的链接
client.py 客户端内容
#coding:utf-8
import socket
ip_port = ('127.0.0.1',9999) #连接服务端ip和端口
sk = socket.socket() #生成一个句柄
sk.connect(ip_port) #请求连接服务端
sk.send('客户端发送数据'.encode("utf-8")) #向服务端发送数据
server_reply = sk.recv(1024) #接收服务端返回信息
print("get data:",server_reply.decode("utf-8")) #打印接收的信息
sk.close() #关闭客户端与服务端的连接
** 可通过打开两个cmd窗口分别执行两个文件;执行顺序:先执行server端 再执行client端
UDP简单的server 端和客户端
server.py 服务端
#encoding:utf-8
from socket import *
from time import ctime
HOST = ''
PORT = 1200
BUFSIZ = 128
ADDR = (HOST, PORT)
udpServer = socket(AF_INET, SOCK_DGRAM) # 创建一个服务器端UDP套接字
udpServer.bind(ADDR) # 绑定服务器套接字
print('已经进入监听状态...')
data, addr = udpServer.recvfrom(BUFSIZ) # 接收来自客户端的数据
print(u"得到客户端数据:",data.decode("utf-8"))
# 向客户端发送数据
udpServer.sendto(b'%s %s[%s]' % ("服务器发送消息:".encode("utf-8"),\ctime().encode("utf-8"),data),addr) #addr为客户端的地址
print('向客户端发送的数据为:', data)
udpServer.close()
Client.py 客户端
#encoding=utf-8
from socket import *
HOST = 'localhost'
PORT = 1200
BUFSIZ = 128
ADDR = (HOST, PORT)
udpClient = socket(AF_INET, SOCK_DGRAM) # 创建客户端UDP套接字
data = input('>')
udpClient.sendto(data.encode("utf-8"), ADDR) # 向服务器端发送数据
data, ADDR = udpClient.recvfrom(BUFSIZ) # 接收来自服务器端的数据
print(data.decode("utf-8")) # 打印服务端传输的数据
udpClient.close() # 关闭连接