Python入门自学进阶——8-网络编程

网络编程,就是在两台或多台计算机之间通信,网络通信的三个要素:IP地址、端口号、协议。

socket所在层次示意图:

我们写的程序运行起来就是用户进程,我们的程序进行在运行时,如果要进行网络通信,只需要与socket进行交互就可以,socket封装了底层的协议与逻辑,使我们不必关心底层的实现,简化网络通信编程。

 SOCKET编程:

涉及两方:服务器端和客户端。

 

 服务器与客户端都需要创建socket。

python中创建socket,需要引入socket模块,然后使用socket方法创建:

import socket
sk = socket.socket(family,type,proto)

socket(family,type[,proto]) 使用给定的地址族、套接字类型、协议编号(默认为0)来创建套接字。其中,family参数可选值如下:

名称目的
AF_INETIPv4网络通信
AF_INET6IPv6网络通信
AF_PACKET链路层通信
AF_UNIX, AF_LOCAL本地通信

type参数

protocol参数:

  0  (默认)与特定的地址家族相关的协议,如果是 0 ,则系统就会根据地址格式和套接类别,自动选择一个合适的协议

IPPROTO_TCPIPPTOTO_UDPIPPROTO_SCTPIPPROTO_TIPCTCP
TCP传输协议UDP传输协议STCP传输协议TIPC传输协议

 也就是说,默认不输入参数时,socket()创建的是IPv4网络协议簇,流式socket,即TCP协议数据的一个进程。同socket.socket(family=socket.AF_INET,type=socket.SOCK_STREAM,proto=0)   语句等价。

创建好socket后,下一步要进行分叉了,就是要确定到低是作为服务器还是客户端,作为服务器,我们知道,服务器要对外提供服务,需要有确定的地址和端口,这个socket就要与作为服务器的地址和端口进行绑定,相当于变身为具体的服务器了,这个服务器的地址就是绑定的IP和端口号。客户端就可以通过这个地址进行链接。

服务端套接字函数:
s.bind()    绑定(主机,端口号)到套接字
s.listen()  开始TCP监听,半连接池可以指定等待数量
s.accept()  被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数:
s.connect()     主动初始化TCP服务器连接
s.connect_ex()  connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数:
s.recv()        接收TCP数据
s.send()       发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)
s.sendall()    发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)
s.recvfrom()     接收UDP数据
s.sendto()        发送UDP数据
s.getpeername()     连接到当前套接字的远端的地址
s.getsockname()     当前套接字的地址
s.getsockopt()      返回指定套接字的参数
s.setsockopt()      设置指定套接字的参数
s.close()           关闭套接字

面向锁的套接字方法:
s.setblocking()     设置套接字的阻塞与非阻塞模式
s.settimeout()      设置阻塞套接字操作的超时时间
s.gettimeout()      得到阻塞套接字操作的超时时间

面向文件的套接字的函数:
s.fileno()          套接字的文件描述符
s.makefile()        创建一个与该套接字相关的文件

服务器端:

 运行后,在accept()处阻塞,即停止在此处,等待客户端连接的到来。运行如下客户端:

服务器端则接着执行,打印clisock:

 打印的clisock如下:

(<socket.socket fd=300, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.1.117', 8888), raddr=('192.168.1.117', 63077)>, ('192.168.1.117', 63077))

server_tcpsock如下:

<socket.socket fd=296, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0> 没有绑定地址前
<socket.socket fd=296, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('192.168.1.117', 8888)>,绑定本地地址后

 可以看到这是同一个sock,fd都是296,绑定地址与没绑定之前的区别就是多了laddr,即本地地址。

而接收客户端连接后,生成了新的sock,新的sock的fd是300,多了raddr,即客户端的地址,返回的clisock分成两部分,一部分是新的sock,一部分是客户端地址(IP+端口号),新的sock是以服务器的原sock为模板,通过增加raddr来生成的。这样服务器就有了客户端地址,就可以向客户端发送信息。

客户端是主动连接服务器,客户端是知道服务器的地址的,服务器是通过客户端的连接获得了客户端的地址。

通过程序,可以看到,只要客户端连接服务器,就在服务器端生成新的socket,不必发送信息。服务器与这个客户端的通信就使用这个socket,如果有其他客户端连接,会生成其他socket,相互的连接也是使用各自的socket,不会发生混乱。

clisock并不是socket,它是一个元组,包含了socket,所以,一般使用:

conn,addr = server_tcpsock.accept()

来分别获得socket和raddr。

看一下结果:

 连接后,服务器和客户端都可以主动发送信息,但要做到一发一收。

客户端发送数据:

使用socket,应用TCP进行通信,数据必须是bytes类型的,所以需要进行转码:

服务器端接收:

 服务器端的运行结果:

看到接收后数据也是bytes型,显示时需要再次转换为字符串:

 运行结果:

 服务器端发送数据:

客户端接收:

客户端结果:

 注意服务器的发送数据和接收数据,使用的socket都是conn,而不是server_tcpsock。

程序运行完毕后,连接是要关闭的,这里是Python自动为我们关闭的连接,一般我们需要在程序中主动关闭连接。对于服务器来说,关闭连接不是关闭所有的socket,而是关闭连接进来的socket,这里就是conn,因为服务器启动后是要不间断为所有客户端服务的,哪个客户端不使用了,就关闭哪个客户端的连接,也就是关系哪个客户端的socket。客户端只有一个socket,直接关闭这个socket就行了。

使用conn.close()关闭socket。

 对于发送信息,有send()和sendall(),区别是,send()发送数据是有大小限制的,最大为发送缓冲区大小的数据,多出来的不发送,sendall()则反复循环使用send(),将所有数据全部发送。

recv()接收数据,参数用于指定接收缓冲区的大小,即一次最大接收多少数据。

 recv()也有阻塞的作用,即一方启动后,可以不等到对方发送数据,先启动接收,这时因为对方还没发送数据,所以这时接收方就阻塞,一直在接收等待状态。

持续连接:

 

 

 

 上述程序的BUG:

1、当客户端断开连接后,服务器端就应该跳出第二层while循环,否则服务器停在input处,输入信息发送,会使用已经断开的socket,发生错误。

2、客户端一开始就输入quit,直接终止时,服务器端也进行了接收,运行到input处,然后就跟第1条错误类似。

3、客户端连接以后,如果直接又异常终止了,服务器端会直接错误,接收语句使用了异常的socket。

那么,程序修改就针对上面的逻辑进行修改:当客户端主动结束时,服务器端接收的是空,可以此为判定条件,跳出内层循环;如果异常终止,在接收时就会出错,可以使用try语句捕获错误。

修改一:在这种交互中,不能发空,也就是不能直接回车,直接回车后,发送方因为是空,没有发送,又无法回到输入,卡死,接收端则一直处于接收阻塞,卡死

 双方都增加一个判断输入为空的判断。

修改二:双方有一方正常结束时,另一方收到的是空,判断后结束:

 

 修改三:客户端异常结束,服务器端要能判断并正常结束,同时服务器端在一个客户端终止后,循环连接其他客户端:

 对于listen()方法,参数是连接池的大小,这里是2,就是可以有2个连接排队,即一个有3个连接:

 关于多次运行同一程序的问题:在pycharm高版本中,需要如下操作:

右键点击程序,选择edit 'client'...,出现如下界面:

 

 将Allow parallel run勾选上,就可以同时运行多个,即允许并行运行。

 

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值