Socket 网络通信基础
什么是 Socket?
Socket(套接字)是网络编程中最基本的概念。它可以看作是应用程序与网络之间的一个接口,通过 Socket 可以实现不同计算机之间的数据交换。
Socket 的比喻
可以把 Socket 比作是打电话的过程:
- IP 地址 - 电话号码
- 端口 - 分机号
- Socket - 电话机本身
- 发送数据 - 拨号和说话
- 接收数据 - 接听和听话
Socket 的分类
根据通信协议,Socket 分为两种主要类型:
TCP Socket(面向连接)
- 可靠的连接
- 数据按顺序到达
- 适合需要可靠性的应用(如文件传输、远程登录)
- 建立连接需要三次握手
UDP Socket(无连接)
- 无连接,直接发送数据包
- 数据可能丢失或乱序
- 低延迟,高效率
- 适合实时通信(如视频直播、在线游戏)
LeBot 中的 Socket 应用
在 LeBot 中,Socket 用于:
- 与远程控制端进行实时通信
- 与多个传感器节点交换数据
- 实现分布式系统的节点间通信
- 上传和下载数据
TCP/IP 协议栈
为了理解 Socket 编程,需要了解 TCP/IP 协议栈的基本结构。
协议层次
应用层 - HTTP, FTP, SMTP, SSH, ROS, 自定义协议
┌────────────────────────────────────┐
│ 传输层 - TCP, UDP
├────────────────────────────────────┤
│ 网络层 - IP(IPv4, IPv6)
├────────────────────────────────────┤
│ 链路层 - 以太网, WiFi
└────────────────────────────────────┘
硬件 - 网卡、路由器等重要的网络概念
IP 地址:唯一标识网络上的一台计算机
- IPv4:192.168.1.100(32 位)
- IPv6:2001:db8::1(128 位)
端口:运行在同一计算机上的程序之间的区分
- 范围:0-65535
- 0-1023:保留端口(需要 root 权限)
- 1024-49151:注册端口
- 49152-65535:动态端口
协议:定义通信规则
- TCP:可靠、有序
- UDP:快速、可能丢失
TCP 通信原理
TCP 连接建立(三次握手)
建立 TCP 连接需要三次握手:
客户端 服务器
| |
|------------ SYN (seq=x) -------->| 1. 客户端发送 SYN 包
| |
|<------- SYN+ACK (seq=y, ack=x+1) | 2. 服务器响应 SYN+ACK
| |
|------- ACK (seq=x+1, ack=y+1) -->| 3. 客户端发送 ACK
| |
|========== 连接建立 =============|TCP 连接断开(四次挥手)
关闭连接需要四次挥手:
客户端 服务器
| |
|------------ FIN (seq=x) -------->| 1. 客户端发送 FIN
| |
|<---------- ACK (ack=x+1) --------| 2. 服务器确认
| |
|<---------- FIN (seq=y) ---------| 3. 服务器发送 FIN
| |
|----------- ACK (ack=y+1) ------->| 4. 客户端确认
| |UDP 通信原理
与 TCP 不同,UDP 是无连接的协议,直接发送数据包:
发送端 接收端
| |
|-------- 数据包 (目标 IP:端口) ----->|
| |
|-------- 数据包 (目标 IP:端口) ----->| (可能延迟或丢失)
| |
|-------- 数据包 (目标 IP:端口) ----->|Python Socket 编程
TCP 服务器
Python 提供了 socket 模块来进行网络编程。以下是一个简单的 TCP 服务器示例:
python
#!/usr/bin/env python3
import socket
import threading
def handle_client(conn, addr):
"""处理客户端连接"""
print(f"客户端连接: {addr}")
try:
while True:
# 接收数据(最多 1024 字节)
data = conn.recv(1024)
if not data:
print(f"客户端 {addr} 断开连接")
break
# 打印接收到的数据
message = data.decode('utf-8')
print(f"从 {addr} 收到: {message}")
# 发送响应
response = f"服务器已收到: {message}"
conn.send(response.encode('utf-8'))
finally:
conn.close()
def start_server(host='0.0.0.0', port=5000):
"""启动 TCP 服务器"""
# 创建 Socket 对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 解决 "Address already in use" 错误
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 绑定地址和端口
server_socket.bind((host, port))
# 监听连接(最多 5 个等待连接)
server_socket.listen(5)
print(f"服务器启动,监听 {host}:{port}")
try:
while True:
# 接受客户端连接
conn, addr = server_socket.accept()
# 为每个客户端创建新线程
client_thread = threading.Thread(
target=handle_client,
args=(conn, addr)
)
client_thread.daemon = True
client_thread.start()
except KeyboardInterrupt:
print("\n服务器关闭")
finally:
server_socket.close()
if __name__ == '__main__':
start_server()TCP 客户端
python
#!/usr/bin/env python3
import socket
import sys
def connect_to_server(host='127.0.0.1', port=5000):
"""连接到 TCP 服务器"""
# 创建 Socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
# 连接到服务器
print(f"正在连接到 {host}:{port}")
client_socket.connect((host, port))
print("已连接到服务器")
# 交互式通信
while True:
# 获取用户输入
message = input("输入要发送的消息 (输入 'exit' 退出): ")
if message.lower() == 'exit':
break
# 发送数据
client_socket.send(message.encode('utf-8'))
# 接收响应
response = client_socket.recv(1024)
print(f"服务器回复: {response.decode('utf-8')}")
except ConnectionRefusedError:
print(f"错误:无法连接到 {host}:{port}")
except Exception as e:
print(f"错误: {e}")
finally:
client_socket.close()
print("已断开连接")
if __name__ == '__main__':
host = sys.argv[1] if len(sys.argv) > 1 else '127.0.0.1'
port = int(sys.argv[2]) if len(sys.argv) > 2 else 5000
connect_to_server(host, port)UDP 服务器
python
#!/usr/bin/env python3
import socket
def start_udp_server(host='0.0.0.0', port=5001):
"""启动 UDP 服务器"""
# 创建 UDP Socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定地址和端口
server_socket.bind((host, port))
print(f"UDP 服务器启动,监听 {host}:{port}")
try:
while True:
# 接收数据(缓冲区大小 1024 字节)
data, addr = server_socket.recvfrom(1024)
message = data.decode('utf-8')
print(f"从 {addr} 收到: {message}")
# 发送响应
response = f"UDP 服务器已收到: {message}"
server_socket.sendto(response.encode('utf-8'), addr)
except KeyboardInterrupt:
print("\n服务器关闭")
finally:
server_socket.close()
if __name__ == '__main__':
start_udp_server()UDP 客户端
python
#!/usr/bin/env python3
import socket
def send_udp_message(host='127.0.0.1', port=5001, message="Hello UDP"):
"""发送 UDP 消息"""
# 创建 UDP Socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
try:
# 发送数据(不需要建立连接)
print(f"发送到 {host}:{port}: {message}")
client_socket.sendto(message.encode('utf-8'), (host, port))
# 接收响应
response, _ = client_socket.recvfrom(1024)
print(f"服务器回复: {response.decode('utf-8')}")
except Exception as e:
print(f"错误: {e}")
finally:
client_socket.close()
if __name__ == '__main__':
send_udp_message()C/C++ Socket 编程
TCP 服务器(C 语言)
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#define PORT 5000
#define BUFFER_SIZE 1024
void* handle_client(void* arg) {
int client_fd = *(int*)arg;
char buffer[BUFFER_SIZE];
printf("客户端连接\n");
while (1) {
// 接收数据
int n = recv(client_fd, buffer, BUFFER_SIZE - 1, 0);
if (n <= 0) {
printf("客户端断开连接\n");
break;
}
buffer[n] = '\0';
printf("收到消息: %s\n", buffer);
// 发送响应
char response[BUFFER_SIZE];
snprintf(response, BUFFER_SIZE, "服务器已收到: %s", buffer);
send(client_fd, response, strlen(response), 0);
}
close(client_fd);
free(arg);
pthread_exit(NULL);
}
int main() {
int server_fd;
struct sockaddr_in server_addr;
// 创建 Socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
perror("socket");
return 1;
}
// 设置 Socket 选项
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// 绑定地址和端口
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("bind");
return 1;
}
// 监听连接
listen(server_fd, 5);
printf("服务器启动,监听端口 %d\n", PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
// 接受客户端连接
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) {
perror("accept");
continue;
}
// 创建线程处理客户端
pthread_t thread_id;
int* arg = malloc(sizeof(int));
*arg = client_fd;
pthread_create(&thread_id, NULL, handle_client, arg);
pthread_detach(thread_id);
}
close(server_fd);
return 0;
}TCP 客户端(C 语言)
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BUFFER_SIZE 1024
int main(int argc, char* argv[]) {
const char* host = argc > 1 ? argv[1] : "127.0.0.1";
int port = argc > 2 ? atoi(argv[2]) : 5000;
int sock_fd;
struct sockaddr_in server_addr;
char buffer[BUFFER_SIZE];
// 创建 Socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd < 0) {
perror("socket");
return 1;
}
// 设置服务器地址
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
inet_pton(AF_INET, host, &server_addr.sin_addr);
// 连接到服务器
if (connect(sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
perror("connect");
return 1;
}
printf("已连接到服务器 %s:%d\n", host, port);
// 交互式通信
while (1) {
printf("输入消息 (输入 'exit' 退出): ");
fgets(buffer, BUFFER_SIZE, stdin);
// 移除换行符
buffer[strcspn(buffer, "\n")] = 0;
if (strcmp(buffer, "exit") == 0) {
break;
}
// 发送数据
send(sock_fd, buffer, strlen(buffer), 0);
// 接收响应
int n = recv(sock_fd, buffer, BUFFER_SIZE - 1, 0);
if (n > 0) {
buffer[n] = '\0';
printf("服务器回复: %s\n", buffer);
}
}
close(sock_fd);
return 0;
}Socket 编程的关键概念
阻塞 vs 非阻塞
阻塞 Socket(默认):
- 在等待数据时会一直阻塞
- 简单易用,但可能降低效率
非阻塞 Socket:
- 立即返回,不管是否有数据
- 效率高,但编程复杂
python
import socket
# 设置为非阻塞
sock = socket.socket()
sock.setblocking(False)
# 或使用 settimeout
sock.settimeout(1.0) # 1 秒超时缓冲区管理
接收数据时需要考虑缓冲区大小:
python
# 一次接收 1024 字节
data = sock.recv(1024)
# 一次接收全部数据(需要知道数据长度)
def recv_all(sock, length):
data = b''
while len(data) < length:
chunk = sock.recv(min(1024, length - len(data)))
if not chunk:
break
data += chunk
return data错误处理
Socket 编程中的错误处理至关重要:
python
import socket
import errno
try:
sock.connect((host, port))
except socket.timeout:
print("连接超时")
except ConnectionRefusedError:
print("连接被拒绝")
except OSError as e:
if e.errno == errno.ECONNRESET:
print("连接被重置")
else:
print(f"Socket 错误: {e}")LeBot 中的 Socket 应用示例
实时传感器数据接收
python
#!/usr/bin/env python3
import socket
import json
import threading
import time
class SensorDataReceiver:
def __init__(self, port=5002):
self.port = port
self.running = False
self.sensor_data = {}
def start_server(self):
"""启动传感器数据接收服务器"""
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(('0.0.0.0', self.port))
server_socket.listen(5)
print(f"传感器数据服务器启动,监听端口 {self.port}")
self.running = True
try:
while self.running:
conn, addr = server_socket.accept()
thread = threading.Thread(
target=self.handle_sensor_data,
args=(conn, addr)
)
thread.daemon = True
thread.start()
except KeyboardInterrupt:
print("服务器关闭")
finally:
server_socket.close()
def handle_sensor_data(self, conn, addr):
"""处理传感器数据"""
print(f"传感器节点连接: {addr}")
while self.running:
try:
data = conn.recv(1024)
if not data:
break
# 解析 JSON 格式的传感器数据
sensor_info = json.loads(data.decode('utf-8'))
sensor_id = sensor_info.get('id')
self.sensor_data[sensor_id] = sensor_info
print(f"传感器 {sensor_id}: {sensor_info}")
# 发送确认
ack = json.dumps({'status': 'ok'})
conn.send(ack.encode('utf-8'))
except json.JSONDecodeError:
print(f"无法解析数据: {data}")
except Exception as e:
print(f"错误: {e}")
break
conn.close()
def get_sensor_data(self, sensor_id):
"""获取特定传感器的数据"""
return self.sensor_data.get(sensor_id)
if __name__ == '__main__':
receiver = SensorDataReceiver()
receiver.start_server()远程控制命令发送
python
#!/usr/bin/env python3
import socket
import json
class RemoteController:
def __init__(self, host='192.168.1.100', port=5003):
self.host = host
self.port = port
self.sock = None
def connect(self):
"""连接到机器人"""
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
self.sock.connect((self.host, self.port))
print(f"已连接到机器人 {self.host}:{self.port}")
return True
except Exception as e:
print(f"连接失败: {e}")
return False
def send_command(self, command, **kwargs):
"""发送控制命令"""
if not self.sock:
print("未连接到机器人")
return False
msg = {
'command': command,
'params': kwargs
}
try:
self.sock.send(json.dumps(msg).encode('utf-8'))
# 等待响应
response = self.sock.recv(1024)
result = json.loads(response.decode('utf-8'))
return result.get('status') == 'success'
except Exception as e:
print(f"发送命令失败: {e}")
return False
def move_forward(self, speed=0.5, duration=1.0):
"""前进命令"""
return self.send_command('move', direction='forward', speed=speed, duration=duration)
def rotate(self, angle=90, speed=1.0):
"""旋转命令"""
return self.send_command('rotate', angle=angle, speed=speed)
def stop(self):
"""停止命令"""
return self.send_command('stop')
def close(self):
"""断开连接"""
if self.sock:
self.sock.close()
print("已断开连接")
if __name__ == '__main__':
controller = RemoteController()
if controller.connect():
# 发送一系列控制命令
controller.move_forward(speed=0.5, duration=2.0)
time.sleep(2)
controller.rotate(angle=90, speed=1.0)
time.sleep(1)
controller.stop()
controller.close()Socket 编程的最佳实践
- 始终检查返回值 - 确保每个 Socket 操作都成功
- 适当的错误处理 - 捕获并处理所有可能的异常
- 资源清理 - 使用 try-finally 确保 Socket 正确关闭
- 缓冲区管理 - 处理粘包和缓冲区溢出问题
- 心跳包 - 定期发送心跳包保持连接
- 超时设置 - 防止长时间挂起
- 多线程同步 - 正确处理并发访问
总结
Socket 是网络编程的基础,理解 TCP/UDP 的工作原理和 Socket 编程的基本方法对于构建分布式机器人系统至关重要。本章介绍了:
- Socket 的基本概念和分类
- TCP 和 UDP 的工作原理
- Python 和 C 中的 Socket 编程
- LeBot 中的实际应用
掌握 Socket 编程后,你可以构建高效、可靠的机器人通信系统。在接下来的章节中,我们将探讨蓝牙通信,这是 LeBot 与移动设备交互的重要方式。