socket通讯实例与TCP/UDP的区别

一、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收数据,数据丢失

原文地址:https://www.cnblogs.com/zjcode/p/8857917.html