Python中网络编程Socket详解 如何实现客户端和服务端通信?

首页 » Python » Python中网络编程Socket详解 如何实现客户端和服务端通信?

Python网络编程Socket的介绍

socket 的原意是“插座”,在计算机通信领域,socket 被翻译为“套接字”,它是计算机之间进行通信的一种约定或一种方式。通过 socket 这种约定,一台计算机可以接收其他计算机的数据,也可以向其他计算机发送数据。网络套接字是计算机网络连接的端点。今天,大多数计算机之间的通信是基于Internet协议的;因此,大多数网络套接字都是Internet套接字。更准确地说,套接字是一个句柄(抽象引用),本地程序可以将其传递给网络应用程序编程接口(API)来使用连接,例如“在此套接字上发送此数据”。

例如,要通过TCP将“Hello, world!”发送到地址为1.2.3.4的主机的80端口,可以获得一个套接字,将其连接到远程主机,发送字符串,然后关闭套接字。

Python中网络编程Socket的使用

如何更好的理解socket网络编程,通过一些例子,你可能会知道更多。首先跟着鳄鱼君创建一个服务器端(server.py)和一个创建客户端(client.py),我们先来写一个简单的发送,接收试试。

server.py服务器端

import socket
#服务器端
server=socket.socket() #这里不写的话,默认 family = AF_INET,type = SOCK_STREAM
server.bind(('localhost',8899))  #绑定要监听的端口
server.listen()  #监听
print('我要开始等电话了')
conn,addr=server.accept()  #等电话打进来,这里会返回两个值,第一个连接的标记,第二个是对方的地址
print(conn,addr)  #conn就是客户端连接过来,然后在服务器端为其生成的一个连接实例

data=conn.recv(1024) #接收客户端发过来的消息
print('recv:',data)
conn.send(data.upper()) #转换为大写返回给客户端

server.close()

通过上面服务器端的代码,我们可以大致归结出TCP通信的基本步骤:服务器首先创建一个socket对象—>服务器端socket将自己绑定到指定端口和IP地址—>服务器端socket调用listen()监听网络—>程序可以采用循环,不断调用socket的accept()方法来接受客户端的连接

client.py客户端

import socket
#客户端
client=socket.socket() #声明socket类型,同时生成socket连接对象
client.connect(('localhost',8899))

client.send(b'Hello World') #Python3里面必须发送字节类型
data=client.recv(1024) #接收服务器端返回的消息

print('recv:',data)
client.close()

那么TCP通信客户端编程按照上面的代码,可以归纳出:客户端先创建一个socket对象—->客户端socket()调用connect()方法远程连接服务器

上面的代码就实现客户端发送,服务器端接收的功能,很显然我们只是发送了一个字节类型的Hello World,上面也提到循环发送,我一个客户端可以发送多次

server.py

import socket
server=socket.socket()
server.bind(('localhost',9999))
server.listen()
print('我要开始等电话了')
while True: #最外层的循环就是可以接受多个电话,一个关掉就换另一个客户端
    conn,addr=server.accept() #在Windows上不起作用
    print('电话来了',conn)
    while True:
        data=conn.recv(1024)
        print('recv:',data)
        if len(data)==0:  #在Windows中没有用,断开客户端和服务器端就会报错
            print('客户端已断开连接')
            break
        conn.send(data)
server.close()

client.py

import socket
client=socket.socket()
client.connect(('localhost',9999))
while True:
    input_content=input('请输入需要发送的消息:').strip()
    if len(input_content)==0:continue #为空就接着输入
    client.send(input_content.encode())
    data=client.recv(1024)
    print('recv:',data.decode())

client.close()

上面的代码在Linux中完全没有问题,我们可以打开多个客户端连接服务器端,同时跟服务器端通信的只有一个,其他的都是在阻塞着,只要当前客户端已断开,那么服务器端就会接受下一个客户端,但是在pycharm(Windows)中使用是有问题的,在server.py中,我们问什么要判断data的长度的呢?因为如果客户端如果断开的话,服务器端收到的就是空,那么在Linux上就会出现循环接受空值,在Windows上,只要客户端已断开就会报错,这里自己去尝试,看百遍不如自己敲一遍。

上面的服务器端,我们在外层套一个while循环,就是为了接受多个客户端,就是指代多个用户打电话,经过测试,不管你打开多少个客户端连接服务器,只要一个断开,就会报ConnectionResetError的错误,呢我们可以捕获一下异常,这样就可以实现多个客户端通信了:

import socket

server = socket.socket()
server.bind(('localhost', 9999))
server.listen()
print('我要开始等电话了')
while True:  # 最外层的循环就是可以接受多个电话,一个关掉就换另一个客户端
    conn, addr = server.accept()  
    print('电话来了', conn)
    while True:
        try:
            data = conn.recv(1024)
            print('recv:', data)
            conn.send(data)
        except ConnectionResetError as e:
            print('当前客户端已经断开连接,准备接听下一个用户')
            break
server.close()

Socket常用函数方法 参数介绍

最后我们来对上面的代码做一个总结,可以看到每次都会创建下面的代码,这通常是创建一个socket对象:

socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

我们通常可以这样创建socket对象:socket.socket(),因为默认的参数已经够我们使用了,但是不妨碍我们了解其他的参数:

Socket family(地址簇)

socket.AF_UNIX unix #本机进程间通信 
socket.AF_INET #IPV4 
socket.AF_INET6  #IPV6

这些常量表示地址(和协议)族,用于socket()的第一个参数。如果没有定义AF_UNIX常量,则不支持此协议。根据系统的不同,可以使用更多的常量

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通常仅限于高级用户或管理员运行的程序使用。

这些常量表示套接字类型,用于套接字()的第二个参数。根据系统的不同,可以使用更多的常量。(只有SOCK_STREAM和SOCK_DGRAM通常有用。)前面介绍的那么多内容就是便于我们理解socket的参数。

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

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

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

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

连接到监听Internet地址的TCP服务(一个2元组(主机、端口)),并返回套接字对象。这是一个比socket.connect()更高级的函数:如果host是一个非数字的主机名,它会尝试为AF_INET和AF_INET6解析它,然后依次尝试连接到所有可能的地址,直到连接成功。这使得编写兼容IPv4和IPv6的客户端变得很容易。
传递可选的超时参数将在尝试连接之前设置套接字实例的超时。如果没有提供超时,则使用getdefaulttimeout()返回的全局缺省超时设置。
如果提供,source_address必须是一个2元组(主机、端口),以便套接字在连接之前绑定到它的源地址。如果主机或端口分别为“或0”,则将使用OS默认行为。

socket.getaddrinfo(host, port, family=0, type=0, proto=0, flags=0) #获取要连接的对端主机地址 

Socket 对象(内建)方法

函数 描述
服务器端套接字
bind() 绑定地址(host,port)到套接字, 在AF_INET下,以元组(host,port)的形式表示地址
listen() 开始TCP监听。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。
accept() 被动接受TCP客户端连接,(阻塞式)等待连接的到来
客户端套接字
connect() 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。
connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常
公共用途的套接字函数
recv() 接收TCP数据,数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。
send() 发送TCP数据,将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。
sendall() 完整发送TCP数据,完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。
recvfrom() 接收UDP数据,与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。
sendto() 发送UDP数据,将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。
close() 关闭套接字
getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
setsockopt(level,optname,value) 设置给定套接字选项的值。
getsockopt(level,optname[.buflen]) 返回套接字选项的值。
settimeout(timeout) 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())
gettimeout() 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。
fileno() 返回套接字的文件描述符。
setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。
makefile() 创建一个与该套接字相关连的文件

未经允许不得转载:作者:鳄鱼君, 转载或复制请以 超链接形式 并注明出处 鳄鱼君
原文地址:《Python中网络编程Socket详解 如何实现客户端和服务端通信?》 发布于2019-12-15

分享到:
赞(0) 赏杯咖啡

评论 抢沙发

6 + 9 =


文章对你有帮助可赏作者一杯咖啡

支付宝扫一扫打赏

微信扫一扫打赏

Vieu4.6主题
专业打造轻量级个人企业风格博客主题!专注于前端开发,全站响应式布局自适应模板。
切换注册

登录

忘记密码 ?

您也可以使用第三方帐号快捷登录

Q Q 登 录
微 博 登 录
切换登录

注册