socket模块

python中的socket

需明确:关于网络协议 和socket相关概念,对于所有编程语言都是一致的,区别仅仅是各编程语言的函数名称不同

# 1.导入socket模块
import socket
# 2.创建socket对象 函数定义如下
socket.socket(socket_family,socket_type,protocal=0)
#socket_family 可以是 AF_UNIX 或 AF_INET。
#socket_type 可以是 SOCK_STREAM表示TCP协议 或 SOCK_DGRAM表示UDP协议。
#protocol 一般不填,默认值为 0。
   
# 2.1获取TCP 套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 或者 后面的参数都有默认值,可以不写,默认创建的是TCP协议socket
tcpSock = socket.socket()

# 2.2获取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)

要明确一点:无论是客户端服务器端都使用的都是socket对象

服务端套接字函数
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()      得到阻塞套接字操作的超时时间

一.基于TCP的socket

前言:已经明确socket是别人封装好的接口,使用接口就变的简单了

按照通讯流程编写代码即可

1.TCP通讯流程

重点是要先接受通话请求 才能建立连接进行通话

TCP的通讯流程与打电话的过程非常相似

买手机 == socket() 装进手机卡 == bind() 待机 == listen() 电话来了 接受通话 == accept() 听 == read() 说 == write() 挂电话 == close()

2.TCP服务端

import socket
ip_port=('127.0.0.1',9000)  #电话卡
BUFSIZE=1024                #收发消息的尺寸
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
s.bind(ip_port) #手机插卡
s.listen(5)     #手机待机

conn,addr=s.accept()            #手机接电话

print('接到来自%s的电话' %addr[0])

msg=conn.recv(BUFSIZE)             #听消息,听话
print(msg,type(msg))

conn.send(msg.upper())          #发消息,说话

conn.close()                    #挂电话

s.close()                       #手机关机

服务端

3.TCP客户端

import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

s.connect(ip_port)           #拨电话

s.send('linhaifeng nb'.encode('utf-8'))         #发消息,说话(只能发送字节类型)

feedback=s.recv(BUFSIZE)                           #收消息,听话
print(feedback.decode('utf-8'))

s.close()                                       #挂电话

注意TCP中必须先启动服务器再启动客户端,否则客户端由于无法链接服务器,直接报错!

如上就完成了一个最基本的TCP通讯,但是建立是为了传输数据,二传输数据很多时候并不是一次性就传输完成了,需要多次收发过程,所以需要给代码加上循环

4.改进版服务器端

import socket
ip_port=('127.0.0.1',8081)#电话卡
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机
s.bind(ip_port) #手机插卡
s.listen(5)     #手机待机

while True:                         #新增接收链接循环,可以不停的接电话
   conn,addr=s.accept()            #手机接电话
   # print(conn)
   # print(addr)
   print('接到来自%s的电话' %addr[0])
   while True:                         #新增通信循环,可以不断的通信,收发消息
       msg=conn.recv(BUFSIZE)             #听消息,听话
       print(msg,type(msg))
       conn.send(msg.upper())          #发消息,说话
   conn.close()                    #挂电话
s.close()                       #手机关机

5.改进版客户端

import socket
ip_port=('127.0.0.1',8081)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect_ex(ip_port)           #拨电话

while True:                             #新增通信循环,客户端可以不断发收消息
   msg=input('>>: ').strip()
   if len(msg) == 0:continue
   s.send(msg.encode('utf-8'))         #发消息,说话(只能发送字节类型)
   
   feedback=s.recv(BUFSIZE)                           #收消息,听话
   print(feedback.decode('utf-8'))
s.close()                                       #挂电话

常见错误:

1.端口占用

在调试过程中,可能会遇见以下错误:

问题发生原因:

1.可能是由于你已经启动了服务器程序,却又再次启动了服务器程序,同一个端口不能被多个进程使用导致!

2.三次握手或四次挥手时,发生了异常导致对方程序已经结束而服务器任然处于time_wait状态导致!

3.在高并发的场景下,由于链接的客户端太多,也会产生大量处于time_wait状态的链接

解决的方案:

第1种原因,很简单关闭之前运行的服务器即可

第2,3中原因导致的问题,有两种解决方案:

1.设置服务器重用端口

#加入一条socket配置,重用ip和端口
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,必须在bind前加
phone.bind(('127.0.0.1',8081))

2.通过调整linux内核参数解决(了解)

发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决,
vi /etc/sysctl.conf
编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
然后执行 /sbin/sysctl -p 让参数生效。
net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间

2.强行关闭链接

(发生错误演示,运行上面的改进版的服务器与客户端,链接成功后直接停止客户端程序)

当客服端与服务器链接成功后,如果一方没有执行close,而是直接强行终止程序(或是遇到异常被迫终止),都会导致另一方发送问题,

在windows下,接收数据的一方在recv函数处将抛出异常

Traceback (most recent call last):
 File "C:/Users/jerry/PycharmProjects/untitled/TCP/server.py", line 9, in <module>
   conn.recv(1024)
ConnectionResetError: [WinError 10054] 远程主机强迫关闭了一个现有的连接。

linux下,不会抛出异常会导致接收数据的一方,recv方法不断的收到空消息,造成死循环

要使应用程序能够在不同平台正常工作,那需要分别处理这两个问题

解决方案如下:

import socket
ip_port=('127.0.0.1',8081)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind(ip_port)
s.listen(5)    
while True:                        
   conn,addr=s.accept()          
   while True:                        
       try:
           msg=conn.recv(BUFSIZE)            
           #linux不会抛出异常,会接收到空消息,这里加以判断
           if not msg:
               conn.close()
               break
           print(msg,type(msg))
           conn.send(msg.upper())        
      except ConnectionResetError:
      #只要异常发生则意味着对方以及关闭了,服务器也相应的关闭该链接
           conn.close()
           break
   conn.close()              
s.close()                      

至此TCP通讯模板程序就完成了,可以不断的接收新的链接,不断的收发消息,并且不会因为客户端强制关闭而异常退出!

二.基于UDP的socket

1.UDP 通讯流程

UDP通讯流程与对讲机非常类似

买传呼机 == socket() 固定对讲频道 == bind() 收信号 == recvfrom() 发信号 == sendto() 由于不需要建立 连接所以省去 TCP的listen()和accept()这两步

2.UDP服务器端

import socket
ip_port=('127.0.0.1',9000) # 固定通讯频道
BUFSIZE=1024
#在TCP中socket的初始化参数可以省略, 因为默认创建的就是TCP协议的socket
#而UDP则必须手动指定相关参数
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 买对讲机
udp_server_client.bind(ip_port)
while True:
   msg,addr=udp_server_client.recvfrom(BUFSIZE) #收信息
   print(msg,addr)
   udp_server_client.sendto(msg.upper(),addr) # 发信息

3.UDP客户端

import socket
ip_port=('127.0.0.1',9000) #确定通讯频道
BUFSIZE=1024
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 买对讲机
while True:
   msg=input('>>: ').strip()
   if not msg:continue
   udp_server_client.sendto(msg.encode('utf-8'),ip_port) # 发消息
   back_msg,addr=udp_server_client.recvfrom(BUFSIZE) #收消息

udp是无链接的,先启动哪一端都不会报错,即使对方地址根本不存在也不会报错,强制关闭任何一方也没有任何问题

另外,由于无连接的特点,服务器不需要针对摸个客户端进行循环,只要循环的接收即可,谁发来的消息都可以被处理,基于这个特点我们可以编写一个UDP程序,实现多个客户端同时与服务器通讯

4.UDP聊天服务器

import socket
ip_port=('127.0.0.1',8081)
udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #买手机
udp_server_sock.bind(ip_port)

while True:
   qq_msg,addr=udp_server_sock.recvfrom(1024)
   print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
   back_msg=input('回复消息: ').strip()
   udp_server_sock.sendto(back_msg.encode('utf-8'),addr)

5.UDP聊天客户端

import socket
BUFSIZE=1024
udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

qq_name_dic={
   '狗哥':('127.0.0.1',8081),
   '天线宝宝':('127.0.0.1',8081),
   '巴拉巴拉小魔女':('127.0.0.1',8081),
   '王尼玛':('127.0.0.1',8081),
}

while True:
   qq_name=input('请选择聊天对象: ').strip()
   while True:
       msg=input('请输入消息,回车发送: ').strip()
       if msg == 'quit':break
       if not msg or not qq_name or qq_name not in qq_name_dic:continue
       udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
       back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
       print('来自[%s:%s]的一条消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
udp_client_socket.close()

客户端1

客户端2

服务器:

posted @ 2019-03-01 16:27  -Rye-  阅读(210)  评论(0)    收藏  举报