通俗地说说socket通信
Socket 是什么?——通俗生活案例讲解
餐厅点餐模型
TCP Socket = 坐堂点餐(有座位、有服务)
餐厅场景:
┌─────────────────┐
│ 餐厅 │
│ (服务端) │
│ ──────────── │
│ 📞 收款台 │ ← listen() 等待客户
│ 🪑 座位 1 │
│ 🪑 座位 2 │
│ 🪑 座位 3 │
└─────────────────┘
↑
accept()
↑
你走进来 (客户端)
connect()
↓
获得座位
↓
点菜 send()
↓
厨房做菜
↓
上菜 recv()
流程:
顾客(客户端) 餐厅(服务端)
| |
|-- 走进餐厅 ----------> |
| socket()
| |
|-- 我要吃饭 ---------> listen()
| (等待有人进来)
| |
|<---- 欢迎光临 --------- accept()
| (分配座位和服务员)
| |
|-- 要一份红烧肉 -----> recv()
| |
|<---- 稍等片刻 -------- send()
| (厨房准备中)
| |
|<---- 红烧肉上菜 ------ send()
| |
|-- 吃完了,结账 -----> recv()
| |
|<---- 总共 68 元 -------- send()
| |
|-- 谢谢,再见 ----------> close()
| |
socket 通信除了用于Web/TCP 服务端那种 listen/accept ,还可以用作Linux 原始套接字/内核接口通信
TCP 三次握手(建立连接)
客户端 服务端
| |
|-- SYN (seq=x) ----------->| 第一次握手
| “我要连接,我的起始序号是 x”
| |
|<- SYN+ACK (seq=y, ack=x+1)-| 第二次握手
| “好的,我同意。我的起始序号是 y,我收到你的 x”
| |
|-- ACK (seq=x+1, ack=y+1)->| 第三次握手
| “确认收到,我发的下一个序号是 x+1,你发的是 y+1”
| |
|========= 连接建立 =========|
| |
| <------ 可以收发数据 ----->|
为什么需要三次?
如果只有两次:
客户端发 SYN → 服务端回复 SYN+ACK → 建立连接
问题:如果客户端的 SYN 在网络中延迟了很久才到达服务端,
服务端不知道这是一个"旧消息",可能会建立一个"幽灵连接"。
三次握手解决这个问题:
第三次 ACK 让客户端有机会确认:“这确实是我刚刚发的序号”,
从而避免创建无效连接。
TCP 四次挥手(断开连接)
客户端 服务端
| |
|-- FIN (seq=x) ----------->| 第一次挥手
| “我完了,我要关闭”
| |
|<- ACK (ack=x+1) ----------| 第二次挥手
| “我收到你的关闭信号”
| (但我可能还有数据要发)
| |
| … | 服务端可能还在发数据
| |
|<- FIN (seq=y) ------------| 第三次挥手
| “我也完了,现在可以完全关闭了”
| |
|-- ACK (ack=y+1) -------->| 第四次挥手
| “好的,我确认了”
| |
|========= 连接关闭 =========|
常用的socket函数
核心流程函数
- socket(domain, type, protocol):创建 socket,返回文件描述符(fd)。
- bind(fd, addr, addrlen):把 fd 绑定到本地 IP+端口(服务端常用)。
- listen(fd, backlog):把绑定后的 socket 变成“监听 socket”(服务端)。
- accept(listenFd, …):从监听队列取一个新连接,返回新的连接 fd(服务端)。
- connect(fd, addr, addrlen):主动连接服务端(客户端)。
- send(fd, buf, len, flags) / write(fd, buf, len):发送数据。
- recv(fd, buf, len, flags) / read(fd, buf, len):接收数据。
close(fd):关闭 socket。
地址转换函数
- htons() / htonl():主机字节序 → 网络字节序(端口/IP 整数)。
- ntohs() / ntohl():网络字节序 → 主机字节序。
- inet_addr():IPv4 字符串转整数地址(老接口,能用但不推荐新代码继续扩展)。
更推荐 inet_pton() / inet_ntop():可读性和可扩展性更好(支持 IPv6)。
常见辅助函数
- setsockopt(fd, level, optname, …):设置 socket 选项(例如 SO_REUSEADDR)。
- shutdown(fd, how):半关闭连接(只关读或只关写)。
- poll()/select()/epoll:多路复用,监听多个 fd 可读写事件(高并发服务端常用)。
socket类型
维度 A:协议族 / 地址族
AF_INET:IPv4 地址族
AF_INET6:IPv6 地址族
AF_PACKET:链路层原始报文
AF_NETLINK:用户态和内核态通信
PF_INET 和 AF_INET 在 Linux 里通常数值相同。
你可以简单理解成:
AF_* 偏“地址族”概念
PF_* 偏“协议族”概念
但实际使用里经常混用。
维度 B:socket 类型
SOCK_STREAM:流式,典型是 TCP
SOCK_DGRAM:报文式,典型是 UDP
SOCK_RAW:原始包,自己看/组包
SOCK_SEQPACKET:有序报文边界,较少见
ioctl 就是用户态程序向内核/驱动发送特殊控制命令的通用入口,不是网线上流动的报文,所以不用 recvmsg、sendto 这种报文接口,而用 ioctl。
这些地方虽然调用了 socket(…),但不是为了像 TCP/UDP 那样真正收发业务报文,而只是为了拿到一个文件描述符 fd,再把这个 fd 传给 ioctl(…),让内核执行网络配置操作
测试结果
测试步骤
终端 1(启动服务端)
./server
你会看到:
Server listening on 127.0.0.1:9000
终端 2(启动客户端)
./client
你会看到:
Connected to server 127.0.0.1:9000
Client sent: hello server
Client received: Message received
服务端会同时打印:
Client connected from 127.0.0.1:xxxxx
Received: hello server
socket通信代码(server.cpp)
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <thread>
#include <unistd.h>
namespace {
int createTcpSocket(const std::string& address, int port)
{
int listenFd = ::socket(AF_INET, SOCK_STREAM, 0);
if (listenFd < 0)
{
std::cerr << "socket failed: " << std::strerror(errno) << std::endl;
return -1;
}
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(address.c_str());
serverAddr.sin_port = htons(static_cast<uint16_t>(port));
if (::bind(listenFd, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) < 0)
{
std::cerr << "bind failed: " << std::strerror(errno) << std::endl;
::close(listenFd);
return -1;
}
if (::listen(listenFd, SOMAXCONN) < 0)
{
std::cerr << "listen failed: " << std::strerror(errno) << std::endl;
::close(listenFd);
return -1;
}
return listenFd;
}
int acceptConnection(int listenFd)
{
int connFd = ::accept(listenFd, nullptr, nullptr);
if (connFd < 0)
{
std::cerr << "accept failed: " << std::strerror(errno) << std::endl;
return -1;
}
return connFd;
}
void handleClient(int connFd)
{
char buffer[1024];
ssize_t bytesRead;
while ((bytesRead = ::read(connFd, buffer, sizeof(buffer))) > 0)
{
std::cout << "Received: " << std::string(buffer, bytesRead) << std::endl;
const char* response = "Message received";
if (::write(connFd, response, std::strlen(response)) < 0)
{
std::cerr << "write failed: " << std::strerror(errno) << std::endl;
break;
}
}
if (bytesRead < 0)
{
std::cerr << "read failed: " << std::strerror(errno) << std::endl;
}
::close(connFd);
}
void runServer(const std::string& address, int port)
{
int listenFd = -1;
listenFd = createTcpSocket(address, port);
if (listenFd < 0)
{
return;
}
std::cout << "Server listening on " << address << ":" << port << std::endl;
while (true)
{
const int connFd = acceptConnection(listenFd);
if (connFd < 0)
{
continue;
}
std::thread clientThread(handleClient, connFd);
clientThread.detach();
}
}
}
int main()
{
runServer("127.0.0.1", 9000);
return 0;
}
socket通信代码(client.cpp)
#include <arpa/inet.h>
#include <cerrno>
#include <cstring>
#include <iostream>
#include <netinet/in.h>
#include <string>
#include <sys/socket.h>
#include <unistd.h>
namespace {
int createTcpSocket()
{
int sockFd = ::socket(AF_INET, SOCK_STREAM, 0);
if (sockFd < 0)
{
std::cerr << "socket failed: " << std::strerror(errno) << std::endl;
return -1;
}
return sockFd;
}
int connectToServer(int sockFd, const std::string& address, int port)
{
sockaddr_in serverAddr{};
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = inet_addr(address.c_str());
serverAddr.sin_port = htons(static_cast<uint16_t>(port));
if (::connect(sockFd, reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)) < 0)
{
std::cerr << "connect failed: " << std::strerror(errno) << std::endl;
return -1;
}
return 0;
}
int sendMessage(int sockFd, const std::string& message)
{
if (::send(sockFd, message.c_str(), message.size(), 0) < 0)
{
std::cerr << "send failed: " << std::strerror(errno) << std::endl;
return -1;
}
return 0;
}
std::string receiveMessage(int sockFd)
{
char buffer[1024] = {0};
ssize_t bytesRead = ::recv(sockFd, buffer, sizeof(buffer) - 1, 0);
if (bytesRead < 0)
{
std::cerr << "recv failed: " << std::strerror(errno) << std::endl;
return "";
}
return std::string(buffer, buffer + bytesRead);
}
void runClient(const std::string& address, int port, const std::string& message)
{
int sockFd = -1;
try
{
sockFd = createTcpSocket();
if (sockFd < 0)
{
return;
}
if (connectToServer(sockFd, address, port) < 0)
{
::close(sockFd);
return;
}
std::cout << "Connected to server " << address << ":" << port << std::endl;
if (sendMessage(sockFd, message) < 0)
{
::close(sockFd);
return;
}
std::cout << "Client sent: " << message << std::endl;
const std::string response = receiveMessage(sockFd);
if (!response.empty())
{
std::cout << "Client received: " << response << std::endl;
}
}
catch (const std::exception& e)
{
std::cerr << "Error: " << e.what() << std::endl;
}
if (sockFd >= 0)
{
::close(sockFd);
}
}
} // namespace
int main()
{
runClient("127.0.0.1", 9000, "hello server");
return 0;
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)