好的,Socket网络编程是C++后端开发中最核心、最基础的内容之一。下面我将为你系统地讲解Socket网络编程的概念、流程、核心函数和一个简单的代码示例。
1. 什么是Socket?
你可以把Socket(套接字) 理解为网络通信的端点。它本质上是一个文件描述符,应用程序通过它来发送和接收网络数据,就像读写文件一样。
一个形象的比喻: 把网络比作电话系统,那么:
- IP地址 就像是你要拨打的电话号码(精确到某一栋楼,即某台主机)。
- 端口号(Port) 就像是该电话号码的分机号(精确到楼里的某一个房间,即主机上的某个进程)。
- Socket 就是你手里的电话听筒,是你进行通信的工具。
2. Socket编程的核心流程(TCP为例)
TCP协议是面向连接的、可靠的、基于字节流的传输层通信协议。它的编程流程是典型的客户端/服务器(C/S)模型。
服务器端(Server)流程
服务器端的角色是“等待并处理连接”,其核心步骤是 socket() -> bind() -> listen() -> accept() -> read()/write() -> close()。
创建Socket (
socket)- 使用
socket()函数创建一个通信端点(Socket),并指定地址族(如IPv4)、服务类型(如面向连接的TCP)和协议。 int server_fd = socket(AF_INET, SOCK_STREAM, 0);
- 使用
绑定地址和端口 (
bind)- 将创建好的Socket与一个本地的IP地址和端口号绑定起来。客户端只有知道这个地址和端口才能发起连接。
bind(server_fd, (struct sockaddr *)&address, sizeof(address));
监听连接 (
listen)- 将Socket设置为被动模式,开始监听来自客户端的连接请求。此时Socket不再用于主动连接,而是用于接收连接。
listen(server_fd, backlog);//backlog指定连接请求队列的最大长度。
接受连接 (
accept)- 这是一个阻塞调用。它会一直等待,直到有客户端发起连接。当有连接到来时,它返回一个新的Socket文件描述符,专门用于与这个新客户端通信。
int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);- 关键点:
server_fd(监听Socket)只负责接受新连接,而new_socket(已连接Socket)负责与特定客户端进行数据收发。
数据收发 (
read/writeorrecv/send)- 使用
accept返回的新Socket,通过read()和write()(或专用的recv()和send())函数与客户端进行数据交换。 valread = read(new_socket, buffer, BUFFER_SIZE);send(new_socket, hello, strlen(hello), 0);
- 使用
关闭连接 (
close)- 通信完毕后,关闭已连接的Socket。监听Socket可以继续保持开放以接受其他新连接。
close(new_socket);close(server_fd);
客户端(Client)流程
客户端的角色是“主动发起连接”,其核心步骤是 socket() -> connect() -> read()/write() -> close()。
创建Socket (
socket)- 和服务端一样,首先创建一个Socket。
int sock = socket(AF_INET, SOCK_STREAM, 0);
发起连接 (
connect)- 向指定的服务器地址和端口发起连接请求。
connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
数据收发 (
read/write)- 连接建立后,就可以像服务器端一样使用
read/write进行通信了。
- 连接建立后,就可以像服务器端一样使用
关闭连接 (
close)
流程图清晰地展示了TCP服务器和客户端在建立连接和数据传输过程中的交互步骤及对应的核心系统调用。
3. 一个简单的C++ TCP Echo Server/Client 示例
服务器端代码 (echo_server.cpp)
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 1. 创建socket文件描述符
if ((server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
std::cerr << "socket failed" << std::endl;
exit(EXIT_FAILURE);
}
// (可选)设置套接字选项,避免"Address already in use"错误
if (setsockopt(server_fd,SOL_SOCKET,
SO_REUSEADDR | SO_REUSEPORT , &opt,
sizeof(opt))) {
std::cerr << "setsockopt" << std::endl;
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET; // IPv4
address.sin_addr.s_addr = INADDR_ANY; // 绑定到本机所有IP
address.sin_port = htons(PORT); //端口转换为网络字节序
//2 .绑定socket到地址和端口
if(bind(server_fd,(struct sockaddr*)&address,sizeof(address))<0){
std::cerr<<"bind failed"<<std::endl;
exit(EXIT_FAILURE);
}
//3 .开始监听 ,最大等待队列设为3
if(listen(server_fd ,3 )<0){
std:cerr<<"listen"<<std:endl ;
exit(- EX IT_ FAIL URE );
}
std:cout<<"Server listening on port "<<PORT<<std:endl ;
while(true){//主循环 ,持续接受新链接
//4 .接受一个新链接
if((new _ socket= accept ( server _ fd , ( struct sock addr *)& address , (sock len _ t*)& addr len ))<0){
std : cerr <<" accept "<< std : endl ;
continue ;//继续等待下一个链接
}
//5 .从新链接读取数据并回显
int val read= read ( new _ socket , buffer , BUFFER _ SIZE-1);//留一位给 '\0'
if(val read>0){
buffer[val read]='\0';//添加字符串结束符
std : cout <<" Received :"<< buffer << std : endl ;
send ( new _ socket , buffer , strlen ( buffer )+l , O );//回显相同内容
memset ( buffer , O , sizeof ( buffer ));//清空缓冲区以备下次使用
}else if(val read==O){
std : cout <<" Client disconnected "<< std : endl ;
}else{
std : cerr <<" read error "<< std : endl ;
}
close ( new _ socket );//6 .关闭这个客户端的链接
}
close ( server _ fd );//关闭监听套接字 (实际不会执行到这里 )
return O;
}
####客户端代码 (echo_client.cpp)
#include<iostream>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<cstring>
#define PORT 八零八零
#define BUFFER_SIZE 一零二四
int main(){
int sock=O;
struct sock addr_in serv_addr;
char*hello="Hello from client";
char buffer[BUFFER_SIZE]={O};
if((sock=socket(AF_INET,SOCK_STREAM,O))<O){
std:cerr<<" Socket creation error "<<std:endl;
return -一;
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(PORT);
//将IP地址从文本转换为二进制形式
if(inet_pton(AF_INET,"一二七.零点零点一",&serv_addr.sin_addr)<=O){//连接到本地服务器
std:cerr<<" Invalid address / Address not supported "<<std:endl;
return -一;
}
if(connect(sock,(struct sockaddr*)&serv_addr,sizeof(serv_addr))<O){//二.发起链接
std:cerr<<" Connection Failed "<<std:endl;
return -一;
}
send(sock,hello ,strlen(hello),O);//三.发送消息
std:cout<<" Hello message sent "<<std:endl;
int valread=read(sock,buffer,BUFFER_SIZE);//四.读取回显
std:cout<<" Server echoed :"<<buffer<<std:endl;
close(sock);//五.关闭套接字
return O;}%
###4 .重要概念与进阶知识
- 阻塞 vs非阻塞I/O:上述例子是阻塞式的 。在实际高性能服务器中 ,通常使用非阻塞I/O结合I/O多路复用技术 (如 select,poll ,epoll(Linux)或kqueue(BSD)),以同时处理成千上万个并发链接 。
- UDP编程: UDP是无连接的 。其流程更简单 :服务器创建 Socket后直接 bind(),然后通过 recvfrom()和 sendto()收发数据 ;客户端创建 Socket后也可直接通过 sendto()发送数据 。没有 listen()和 accept()过程 。
- 字节序转换:如 htons(),htonl(),ntohs(),ntohl()等函数 ,用于在主机字节序和网络标准大端字节序之间转换 ,确保不同机器能正确理解数据 。
- 多线程/多进程:为了让服务器能同时服务多个客户端 ,可以在 accept一个新的链接后 ,创建一个新的线程或进程来处理这个链接的业务逻辑 。
希望这份详细的讲解能帮助你入门 Socket网络编程!这是构建任何网络服务的基石 。
内容由零声教学AI助手提供,问题来源于学员提问




