2024-11-30

Socket TCP 和 UDP 编程基础(Python)

网络编程是现代计算机科学的重要组成部分,而 socket 是 Python 提供的一个模块,用于实现网络通信。本文将详细介绍 TCP 和 UDP 两种通信协议的基础知识,配合 Python 代码示例和图解,让你轻松上手。


一、TCP 和 UDP 概述

1.1 TCP 协议

  • 特点:面向连接、可靠传输、适合数据量大且顺序重要的场景。
  • 应用:网页浏览、文件传输。
  • 通信模型

    • 客户端发起连接。
    • 服务端接受连接。
    • 双方建立可靠的数据传输通道。

1.2 UDP 协议

  • 特点:无连接、快速、不保证可靠传输。
  • 应用:视频直播、在线游戏。
  • 通信模型

    • 无需建立连接。
    • 数据直接发送到目标。

二、Python Socket 基础

socket 模块是 Python 提供的网络通信接口,可以实现 TCP 和 UDP 通信。

2.1 常用方法

方法说明
socket()创建一个新的 socket 对象
bind(address)绑定到指定的 IP 和端口
listen(backlog)在 TCP 中监听连接
accept()接受客户端连接
connect(address)客户端连接到服务器
send(data)发送数据
recv(buffer_size)接收数据
sendto(data, addr)向指定地址发送数据(UDP)
recvfrom(buffer_size)接收数据(UDP)
close()关闭 socket

三、TCP 编程

3.1 TCP 服务端代码

import socket

# 创建 TCP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(("127.0.0.1", 65432))  # 绑定 IP 和端口
server_socket.listen(5)  # 最大连接数

print("服务端已启动,等待连接...")
conn, addr = server_socket.accept()  # 等待客户端连接
print(f"连接来自: {addr}")

# 接收并发送数据
data = conn.recv(1024).decode("utf-8")
print(f"收到客户端消息: {data}")
conn.send("你好,客户端!".encode("utf-8"))

# 关闭连接
conn.close()
server_socket.close()

3.2 TCP 客户端代码

import socket

# 创建 TCP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(("127.0.0.1", 65432))  # 连接服务器

# 发送数据
client_socket.send("你好,服务端!".encode("utf-8"))

# 接收数据
data = client_socket.recv(1024).decode("utf-8")
print(f"收到服务端消息: {data}")

# 关闭连接
client_socket.close()

运行结果

  • 服务端输出

    服务端已启动,等待连接...
    连接来自: ('127.0.0.1', 50000)
    收到客户端消息: 你好,服务端!
  • 客户端输出

    收到服务端消息: 你好,客户端!

TCP 通信模型图解

客户端               服务端
  |   connect()       |
  | ----------------> |
  |                   |
  |     accept()       |
  | <---------------- |
  |                   |
  |     send()         |
  | ----------------> |
  |                   |
  |     recv()         |
  | <---------------- |
  |                   |

四、UDP 编程

4.1 UDP 服务端代码

import socket

# 创建 UDP socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(("127.0.0.1", 65432))  # 绑定 IP 和端口
print("UDP 服务端已启动,等待数据...")

# 接收数据
data, addr = server_socket.recvfrom(1024)
print(f"收到来自 {addr} 的消息: {data.decode('utf-8')}")

# 发送响应
server_socket.sendto("你好,客户端!".encode("utf-8"), addr)

server_socket.close()

4.2 UDP 客户端代码

import socket

# 创建 UDP socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 发送数据
client_socket.sendto("你好,服务端!".encode("utf-8"), ("127.0.0.1", 65432))

# 接收响应
data, addr = client_socket.recvfrom(1024)
print(f"收到服务端消息: {data.decode('utf-8')}")

client_socket.close()

运行结果

  • 服务端输出

    UDP 服务端已启动,等待数据...
    收到来自 ('127.0.0.1', 50000) 的消息: 你好,服务端!
  • 客户端输出

    收到服务端消息: 你好,客户端!

UDP 通信模型图解

客户端               服务端
  |   sendto()        |
  | ----------------> |
  |                   |
  |     recvfrom()     |
  | <---------------- |

五、TCP 与 UDP 的区别

特性TCPUDP
是否连接面向连接无连接
数据可靠性高,保证数据按顺序到达不可靠,不保证顺序
速度较慢,需建立连接和确认快,无需连接
应用场景文件传输、网页浏览视频流、在线游戏

六、综合练习:多人聊天室

服务端代码

import socket
import threading

# 创建服务端
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(("127.0.0.1", 65432))
server_socket.listen(5)
clients = []

def handle_client(conn, addr):
    print(f"新客户端加入: {addr}")
    while True:
        try:
            data = conn.recv(1024).decode("utf-8")
            if not data:
                break
            print(f"{addr}: {data}")
            for client in clients:
                if client != conn:
                    client.send(f"{addr}: {data}".encode("utf-8"))
        except:
            break
    conn.close()
    clients.remove(conn)

print("聊天室启动中...")
while True:
    conn, addr = server_socket.accept()
    clients.append(conn)
    threading.Thread(target=handle_client, args=(conn, addr)).start()

客户端代码

import socket
import threading

def receive_messages(client_socket):
    while True:
        try:
            message = client_socket.recv(1024).decode("utf-8")
            print(message)
        except:
            print("连接已关闭")
            break

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(("127.0.0.1", 65432))

threading.Thread(target=receive_messages, args=(client_socket,)).start()

print("加入聊天室!输入消息开始聊天...")
while True:
    msg = input()
    client_socket.send(msg.encode("utf-8"))

七、总结

通过本文,你应该已经掌握了 Python 中基于 socket 模块进行 TCP 和 UDP 编程的基本方法。无论是构建简单的客户端-服务端模型,还是开发复杂的网络应用,都可以从这些基础知识入手。希望你通过练习能够熟练运用这些技术!

2024-09-09

解释:

这个错误通常表明Redis服务器试图绑定到端口6379时出现问题,但是没有提供具体的错误信息。在Unix-like系统中,这可能意味着端口已经被占用,或者是权限问题。

解决方法:

  1. 检查端口是否已被占用:

    执行 netstat -tulnp | grep 6379 查看端口是否被其他进程占用。

  2. 如果端口被占用,你需要找到占用进程并终止它,或者更改Redis配置文件中的端口号。
  3. 如果是权限问题,确保Redis进程有权限绑定到所需的端口。通常,低于1024的端口需要特权用户(如root)才能绑定。你可以尝试使用更高的端口号,或者以root用户运行Redis服务。
  4. 检查Redis配置文件(通常名为redis.conf),确保bind指令(如果有的话)不会阻止绑定到所需接口。
  5. 查看Redis日志文件,它可能包含更多关于错误的信息。
  6. 如果问题依然存在,可以尝试重启服务器,然后再次启动Redis服务。
  7. 如果你在云环境中,确保安全组或网络策略允许对应端口的流量。
  8. 如果上述步骤都不能解决问题,可以尝试重新安装Redis,并确保按照官方文档进行配置。
2024-09-06

在Ubuntu 22.04上安装和使用Libmodbus库的步骤如下:

  1. 安装libmodbus库:



sudo apt-update
sudo apt-get install libmodbus-dev
  1. 编写一个简单的Modbus TCP客户端示例代码:



#include <stdio.h>
#include <modbus/modbus.h>
 
int main() {
    int rc;
    modbus_t *ctx = modbus_new_tcp("127.0.0.1", 502);
    if (ctx == NULL) {
        fprintf(stderr, "Unable to allocate libmodbus context\n");
        return -1;
    }
 
    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Unable to connect: %s\n", modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }
 
    uint16_t tab_reg[3];
 
    rc = modbus_read_registers(ctx, 0, 3, tab_reg);
    if (rc == -1) {
        fprintf(stderr, "%s\n", modbus_strerror(errno));
        return -1;
    }
 
    printf("Register 0: %d\nRegister 1: %d\n", tab_reg[0], tab_reg[1]);
 
    modbus_close(ctx);
    modbus_free(ctx);
 
    return 0;
}
  1. 编译示例代码:



gcc -o modbus-tcp-client modbus-tcp-client.c -lmodbus
  1. 运行客户端程序:



./modbus-tcp-client

确保Modbus服务器端在运行,并且客户端的IP地址和端口与服务器端的信息匹配。上述代码示例展示了如何创建一个简单的Modbus TCP客户端,连接到服务器,并读取3个寄存器的值。

2024-09-04

以下是一个简单的Spring Boot TCP服务器和客户端的示例。

TCP服务器端 (Spring Boot):

  1. 添加依赖 spring-boot-starter-integrationspring-boot-starter-webpom.xml
  2. 配置服务器端口和TCP连接工厂。
  3. 创建TCP连接接收器。
  4. 启动Spring Boot应用程序。



import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.ip.tcp.TcpInboundGateway;
import org.springframework.integration.ip.tcp.TcpNetServerConnectionFactory;
import org.springframework.integration.ip.tcp.serializer.ByteArraySerializer;
 
@Configuration
public class TcpServerConfig {
 
    @Bean
    public TcpNetServerConnectionFactory serverConnectionFactory() {
        TcpNetServerConnectionFactory factory = new TcpNetServerConnectionFactory(12345);
        factory.setSerializer(new ByteArraySerializer());
        factory.setDeserializer(new ByteArraySerializer());
        return factory;
    }
 
    @Bean
    public TcpInboundGateway tcpInboundGateway() {
        TcpInboundGateway gateway = new TcpInboundGateway();
        gateway.setConnectionFactory(serverConnectionFactory());
        return gateway;
    }
 
    @ServiceActivator
    public void clientMessageHandler() {
        // 处理接收到的消息
    }
}

TCP客户端 (Spring Boot):

  1. 添加依赖 spring-boot-starter-integrationspring-boot-starter-webpom.xml
  2. 配置服务器地址和端口。
  3. 发送消息。
  4. 启动Spring Boot应用程序。



import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.ip.tcp.TcpOutboundGateway;
import org.springframework.integration.ip.tcp.TcpNetClientConnectionFactory;
import org.springframework.integration.ip.tcp.serializer.ByteArraySerializer;
 
@Configuration
public class TcpClientConfig {
 
    @Bean
    public TcpNetClientConnectionFactory clientConnectionFactory() {
        TcpNetClientConnectionFactory factory = new TcpNetClientConnectionFactory("localhost", 12345);
        factory.setSerializer(new ByteArraySerializer());
        factory.setDeserializer(new ByteArraySerializer());
        return factory;
    }
 
    @Bean
    @ServiceActivator
    public TcpOutboundGateway tcpOutboundGateway() {
        Tc
2024-09-04



import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
 
public class TCPServer {
 
    private int port;
 
    public TCPServer(int port) {
        this.port = port;
    }
 
    public void run() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
             .channel(NioServerSocketChannel.class)
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 public void initChannel(SocketChannel ch) throws Exception {
                     ChannelPipeline pipeline = ch.pipeline();
                     pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                     pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                     pipeline.addLast("decoder", new StringDecoder());
                     pipeline.addLast("encoder", new StringEncoder());
                     pipeline.addLast("handler", new TCPServerHandler());
                 }
             });
 
            ChannelFuture f = b.bind(port).sync();
            System.out.println("服务器启动,监听端口:" + port);
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
 
    public static void main(String[] args) throws Exception {
        TCPServer server = new TCPServer(8080);
        server.run();
    }
}

这个示例代码展示了如何使用Netty框架来创建一个基本的TCP长连接服务器。服务器监听8080端口,并使用LengthFieldBasedFrameDecoderLengthFieldPrepender处理帧解码和编码,确保消息边界被正确识别。同时,它展示了如何添加StringDecoderStringEncoder来处理字符串消息,以及如何自定义处理器TCPServerHandler来处理具体的业务逻辑。这个代码提供了一个简单的模板,可以根据具体需求进行扩展和修改。

2024-09-04



import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
 
public class MyMessageEncoder extends MessageToByteEncoder<MyMessage> {
 
    @Override
    protected void encode(ChannelHandlerContext ctx, MyMessage msg, ByteBuf out) throws Exception {
        // 将消息转换为字节,并写入ByteBuf
        out.writeInt(msg.getLength()); // 写入消息长度
        out.writeBytes(msg.getData()); // 写入消息数据
    }
}
 
class MyMessage {
    private int length;
    private byte[] data;
 
    // 构造函数、getter和setter省略
 
    public int getLength() {
        return length;
    }
 
    public byte[] getData() {
        return data;
    }
}

这个简单的示例展示了如何使用MessageToByteEncoder来编码自定义的消息对象。在这个例子中,我们假设MyMessage有一个整数字段表示数据的长度和一个字节数组表示实际的数据内容。在encode方法中,我们将消息的长度和数据写入ByteBuf中,以便它可以被发送。这个编码器可以被用在Netty的编码器链中,与LengthFieldPrepender一起用于解决TCP粘包和拆包问题。

2024-09-04

在Oracle数据库网络配置中,DCD(Dynamic Connections Directory)是一种动态监听器注册机制,可以使数据库客户端在连接时间较长时,监听器能够自动识别到数据库实例的存在。Linux TCP KeepAlive是一种TCP/IP的网络配置选项,可以在网络连接空闲时发送保活包,以防止连接超时。而硬件防火墙通常用于保护网络边界免受未授权访问。

以下是这三者可能的配置方式和相关的代码示例:

  1. DCD配置:

    listener.ora文件中配置DCD,如下所示:




LISTENER =
  (DESCRIPTION_LIST =
    (DESCRIPTION =
      (ADDRESS = (PROTOCOL = TCP)(HOST = myhost.example.com)(PORT = 1521))
      (ADDRESS = (PROTOCOL = IPC)(KEY = EXTPROC1521))
    )
  )
 
SID_LIST_LISTENER =
  (SID_LIST =
    (SID_DESC =
      (GLOBAL_DBNAME = mydb.example.com)
      (ORACLE_HOME = /u01/app/oracle/product/11.2.0/db_1)
      (SID_NAME = mydb)
    )
    (SID_DESC =
      (SID_NAME = plsextproc)
      (ORACLE_HOME = /u01/app/oracle/product/11.2.0/db_1)
      (PROGRAM = extproc)
    )
  )
  1. Linux TCP KeepAlive配置:

    /etc/sysctl.conf文件中配置TCP KeepAlive参数,如下所示:




# 启用TCP KeepAlive
net.ipv4.tcp_keepalive_time = 30
# 每个包的发送间隔
net.ipv4.tcp_keepalive_intvl = 10
# 在认为连接失效之前发送的包数
net.ipv4.tcp_keepalive_probes = 5

然后执行sysctl -p使配置生效。

  1. 硬件防火墙配置:

    硬件防火墙通常通过其管理界面进行配置,这通常涉及定义允许通过的网络流量规则,以及可能的异常规则。具体配置取决于硬件防火墙的品牌和型号。

这些配置通常由网络管理员或IT专家进行,因此不需要详细的代码示例。然而,对于TCP KeepAlive,可以使用sysctl命令来配置Linux系统参数,对于DCD和监听器注册,需要在Oracle的监听器配置文件中进行相应设置。硬件防火墙的配置通常在防火墙的管理界面上进行。

2024-09-04

解释:

这个错误表明Redis服务器在尝试监听TCP连接时无法在本地地址127.0.0.1的6380端口创建服务器套接字。这通常是因为端口已被占用或权限问题。

解决方法:

  1. 检查端口是否被其他应用占用:

    • 在Linux上,可以使用lsof -i :6380netstat -tulnp | grep 6380
    • 在Windows上,可以使用netstat -ano | findstr :6380
  2. 如果端口被占用,请找到占用端口的应用程序并停止它,或者更改Redis配置文件中的端口号。
  3. 如果端口未被占用,检查Redis配置文件中的bind指令是否包含127.0.0.1,如果不是,添加它或者移除bind指令以允许任意地址绑定。
  4. 确保当前用户有权限在该端口上创建套接字。
  5. 如果更改配置后问题依旧,请检查防火墙设置,确保没有阻止Redis的端口。
  6. 重启Redis服务。

如果以上步骤无法解决问题,请查看Redis日志文件以获取更多信息。

2024-09-02

在C#中使用ModeBus TCP读取汇川Easy521 PLC的数据,你需要一个支持ModeBus协议的库,比如LibUA (开源的ModeBus库) 或者使用第三方库,如EasyModbus。

以下是使用EasyModbus库进行ModeBus TCP通信的简单示例:

首先,安装EasyModbus库:




Install-Package EasyModbus

然后,使用以下代码读取PLC的寄存器:




using EasyModbus;
using System;
using System.Net;
 
namespace ModbusTCPExample
{
    class Program
    {
        static void Main(string[] args)
        {
            // PLC的IP地址
            string plcIp = "192.168.1.10";
            // PLC的端口,ModeBus TCP通常是502
            int port = 502;
            // 创建Modbus TCP客户端
            ModbusClient modbusClient = new ModbusClient(IPAddress.Parse(plcIp), port);
            try
            {
                // 连接PLC
                modbusClient.Connect();
                // 读取起始地址为0的寄存器,长度为10的数据
                int[] registers = modbusClient.ReadHoldingRegisters(0, 10);
                for (int i = 0; i < registers.Length; i++)
                {
                    Console.WriteLine($"Register {i}: {registers[i]}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
            finally
            {
                // 断开连接
                if (modbusClient.Connected)
                {
                    modbusClient.Disconnect();
                }
            }
        }
    }
}

确保PLC的IP地址、端口和寄存器地址是正确的,并且你有足够的权限去访问PLC的资源。上述代码中的ReadHoldingRegisters方法用于读取保持寄存器,如果你需要读取输入寄存器或者线圈状态,可以使用ReadInputRegistersReadCoils方法。

2024-09-02

解释:

这个错误表明Harbor在尝试连接PostgreSQL数据库时遇到了权限问题。具体来说,是因为pgsql.conf配置文件的权限设置不正确,导致Harbor无法读取或写入数据库配置。

解决方法:

  1. 确认pgsql.conf文件的位置,通常在Harbor安装目录下的data文件夹中。
  2. 检查该文件的权限。你需要确保Harbor服务的运行用户有足够的权限去读取和写入这个文件。
  3. 如果权限不正确,可以使用chmod命令来修改文件权限。例如,如果你想要让Harbor的运行用户有权限读写文件,可以使用以下命令:

    
    
    
    chmod 644 /path/to/pgsql.conf

    确保将/path/to/pgsql.conf替换为实际的文件路径。

  4. 如果修改权限后问题依旧,检查是否是SELinux或其他安全模块导致的权限问题,并根据需要调整相应的安全策略。
  5. 确认Harbor服务是否以正确的用户身份运行,并确保该用户有权访问配置文件。
  6. 重启Harbor服务,检查问题是否已解决。

如果以上步骤无法解决问题,请查看Harbor和PostgreSQL的日志文件,以获取更多关于错误的信息,进一步诊断问题。