一、socket代码实例
1.简单的socket通讯:
服务端代码实例:
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建socket对象 声明协议类型
sock.bind(("localhost", 9909)) # 绑定地址 端口
sock.listen(5) # 监听,5代表在允许有一个连接排队,更多的新连接连进来时就会被拒绝
conn, addr = sock.accept() # 接收客户端发来的连接请求 组成一个元祖
data = conn.recv(1024) # 接收消息
print(data)
conn.send(data.upper()) # 发送消息
conn.close() # 关闭连接
sock.close() # 关闭socket
客户端代码实例:
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 生成客户端实例
client.connect(("127.0.0.1", 9909)) # 连接地址端口
client.send("hello word".encode("utf8")) # 发送消息给服务端
data = client.recv(1024) # 接收消息
client.close() # 关闭客户端
print(data)
2.循环收发数据socket通讯:
服务端
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("localhost", 9909))
sock.listen(5)
while True:
conn, addr = sock.accept() # 有加上一个死循环(连接循环)
while True:
try: # 适应于window系统
data = conn.recv(1024) # 通讯循环
if not data: break # 适用于linux系统
print(data)
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
sock.close()
客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 9909))
while True:
msg = input(">>>:").strip()
if not msg:
continue
client.send(msg.encode("utf8"))
data = client.recv(1024)
print(data.decode('utf8'))
client.close()
3.多线程循环收发数据socket通讯:
服务端
import socket
import threading
def handler(conn):
while True: # 通讯循环
try:
data = conn.recv(1024)
if not data:
break # 适用于linux系统
print(data)
conn.send(data.upper())
except ConnectionResetError:
break
conn.close()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(("localhost", 8889))
sock.listen(5)
while True:
conn, addr = sock.accept() # 连接循环
p = threading.Thread(target=handler, args=(conn,)) # 每来一个conn连接开启一个线程,交给handler去执行
p.start()
sock.close()
服务端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(("127.0.0.1", 8889))
while True:
msg = input(">>>:").strip()
if not msg:
continue
client.send(msg.encode("utf8"))
data = client.recv(1024)
print(data.decode('utf8'))
client.close()
4.问题解决
有的同学在重启服务端时可能会遇到
这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址(如果不懂,请深入研究1.tcp三次握手,四次挥手 2.syn洪水攻击 3.服务器高并发情况下会有大量的time_wait状态的优化方法)
解决方法1:
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))
解决方法2:
发现系统存在大量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 时间
5.基于UDP协议的套接字通讯
服务端
import socket
server_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_udp.bind(("127.0.0.1", 9900))
while True:
# 循环链接,拿到的是data数据,还有addr地址
# 区别于TCP协议
data, client_addr = server_udp.recvfrom(1024)
print(data)
# 发送消息send_to 消息内容+目标addr地址
server_udp.sendto(data.upper(), client_addr)
print(data)
客户端
from socket import *
client = socket(AF_INET, SOCK_DGRAM)
while True:
msg = input(">>>>>:") # 可以发送空消息,UDP是面向消息的协议所以不存在粘包现象
# 发送消息,消息+ addr地址
client.sendto(msg.encode("utf8"), ("127.0.0.1", 9900))
data, server_addr = client.recvfrom(1024)
print(data)
client.close()
6.TCP VS UDP区别
tcp基于链接通信
基于链接,则需要listen(backlog),指定连接池的大小
基于链接,必须先运行的服务端,然后客户端发起链接请求
对于mac系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)
对于windows/linux系统:如果一端断开了链接,那另外一端的链接也跟着完蛋recv将不会阻塞,收到的是空(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)
udp无链接
无链接,因而无需listen(backlog),更加没有什么连接池之说了
无链接,udp的sendinto不用管是否有一个正在运行的服务端,可以己端一个劲的发消息,只不过数据丢失
recvfrom收的数据小于sendinto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错
只有sendinto发送数据没有recvfrom收数据,数据丢失