文章目录
前言
学习 socket 编程的意义在于:它让你掌握计算机之间通信的核心原理,能亲手实现聊天程序、文件传输、简易服务器等网络应用;同时这是理解 TCP/IP 协议、深入系统编程和进入后台开发、分布式系统的基础技能,也是面试和工程实践中必不可少的知识。
一,socket
socket(套接字) 是操作系统提供的一种 通信机制,最常用于网络通信,在编程时,你可以把它当作 特殊的文件描述符(FD),既能读、也能写,文件用来在本地磁盘读写数据,而 socket 用来在不同主机之间交换数据。你可以把 socket 看作网络文件,只是读写的数据不是在磁盘上,而是发送到网络上的另一个进程。
二,服务端socket
主要作用:接收客户端请求并回复,服务器端使用socket分为几个阶段:创建 socket -> 绑定地址和端口 -> 等待客户端发送数据 / 监听连接 -> 处理数据或回复 -> 关闭 socket
3-1 创建socket
socket函数原型
int socket(int domain, int type, int protocol);
domain:用于选择网络协议协议类型一般有值:AF_INET(IPv4 网络协议),AF_INET6(IPv6 网络协议),AF_UNIX/AF_LOCAL(本地进程间通信)type:指定传输方式,主要有两种:SOCK_STREAM面向连接,基于字节流(TCP),SOCK_DGRAM无连接,基于报文(UDP)protocol:通常指定具体协议:0默认协议,IPPROTO_TCP明确选择 TCP,IPPROTO_UDP明确选择 UDP
在下面的代码示例中我们会选择:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
创建一个UDP的套接字
3-2 绑定地址和端口
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY; // 任意 IP
servaddr.sin_port = htons(12345); // 端口
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
上面的struct sockaddr_in servaddr创建一个 IPv4 地址结构体 sockaddr_in,用来保存服务端的 IP 地址和端口信息结构体定义大致如下:
struct sockaddr_in {
short sin_family; // 地址族 (AF_INET)
unsigned short sin_port; // 端口号
struct in_addr sin_addr; // IP 地址
char sin_zero[8]; // 填充为与 struct sockaddr 同大小
};
解释上述代码:
servaddr.sin_family = AF_INET;
设置 地址族为 IPv4 (AF_INET)告诉内核这是一个 IPv4 的 socket
servaddr.sin_addr.s_addr = INADDR_ANY; // 任意 IP
INADDR_ANY = 0.0.0.0,表示服务端 绑定本机所有可用 IP 地址,如果服务器有多个网卡,客户端发送到任意 IP 都能被接收
servaddr.sin_port = htons(12345); // 端口
设置端口号为 12345,htons = host to network short(主机字节序 → 网络字节序),网络通信使用 大端序,保证不同平台可以正确解析端口
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
bind() 将 socket 文件描述符 与 IP + 端口 绑定
参数解释:
sockfd:之前 socket() 返回的文件描述符
(struct sockaddr*)&servaddr : 地址信息,强制类型转换为通用 sockaddr
sizeof(servaddr) : 结构体大小
3-3 接收数据
char buffer[100];
struct sockaddr_in cliaddr {};
socklen_t len = sizeof(cliaddr);
int n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr * ) & cliaddr, & len);
这段代码在一个 UDP 服务器程序中,从客户端接收数据,存储到 buffer 中,同时记录客户端的地址信息cliaddr,并返回接收的字节数n。
char buffer[100];
定义一个字符数组 buffer,大小为 100 字节,用来存储从客户端接收的数据
struct sockaddr_in cliaddr {};
定义一个 sockaddr_in 结构体变量 cliaddr,用于存储客户端的地址信息(如 IP 地址和端口号)。{} 初始化所有字段为 0
socklen_t len = sizeof(cliaddr);
定义一个 socklen_t 类型的变量 len,初始化为 cliaddr 的大小(通常 16 字节,struct sockaddr_in 的大小)。
3-4 回复数据
const char* reply = "Hello Client";sendto(sockfd, reply, strlen(reply), 0, (struct sockaddr*)&cliaddr, len); // 回复
这段代码用与服务器回复客户端信息
其它的不再过多赘述
(struct sockaddr*)&cliaddr:客户端地址结构体,告诉内核消息发送到哪里
3-5关闭socket
close(sockfd);
不关闭会造成资源泻漏
3-6 完整代码
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建 UDP socket
if (sockfd < 0) { perror("socket"); return -1; }
sockaddr_in servaddr{};
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY;
servaddr.sin_port = htons(12345);
bind(sockfd, (sockaddr*)&servaddr, sizeof(servaddr)); // 绑定端口
char buffer[100];
sockaddr_in cliaddr{};
socklen_t len = sizeof(cliaddr);
int n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (sockaddr*)&cliaddr, &len); // 接收消息
buffer[n] = '\0';
std::cout << "Server received: " << buffer << std::endl;
const char* reply = "Hi Client";
sendto(sockfd, reply, strlen(reply), 0, (sockaddr*)&cliaddr, len); // 回复客户端
close(sockfd);
return 0;
}
三,客户端socket
客户端socket代码我直接展示出来吧,我们理解了服务端之后,客户端是非常好理解的
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>
int main() {
int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建 UDP socket
if (sockfd < 0) { perror("socket"); return -1; }
sockaddr_in servaddr{};
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(12345);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本机测试
const char* msg = "Hello Server";
sendto(sockfd, msg, strlen(msg), 0, (sockaddr*)&servaddr, sizeof(servaddr)); // 发送消息
char buffer[100];
socklen_t len = sizeof(servaddr);
int n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (sockaddr*)&servaddr, &len); // 接收回复
buffer[n] = '\0';
std::cout << "Client received: " << buffer << std::endl;
close(sockfd);
return 0;
}
这里的sendto和recvfrom大家应该都懂,主要是这个客户端是如何找到服务端的重要
sockaddr_in servaddr{};
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(12345);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本机测试
在这段代码里的IP地址和端口号是找到服务端的关键
-
IP 地址(127.0.0.1):告诉客户端要把数据发送到哪个机器。127.0.0.1表示本机,也就是服务端和客户端在同一台电脑。如果服务端在另一台电脑,你就要写它的真实IP,例如192.168.1.100。 -
端口号(12345)
服务端在这个端口上监听数据。UDP/TCP都是靠端口区分不同服务的,就像房子门牌号一样。所以客户端通过IP, port就能找到服务端。
3-1 为什么客户端通常不需要手动定义 IP 和端口
客户端的职责是 主动找服务端,在调用 sendto()时:目标地址(服务端的 IP+端口) 由程序员指定,源地址(客户端的 IP+端口) 不写时由内核自动分配
IP:自动选择一块能到达服务端的本地网卡的 IP
端口:自动分配一个 临时端口,通常在 49152–65535 之间
客户端什么时候需要绑定 IP+端口:如果客户端希望 使用固定端口(比如做 P2P、游戏服务器通知端口),或者有多张网卡,必须指定 用哪张网卡的 IP 去通信,这种情况才会手动调用 bind()
1373

被折叠的 条评论
为什么被折叠?



