C++socket
- socket 中文直译是
插座
- 在计算机硬件中,“Socket” 指的是插座或插槽,比如处理器插槽、插头插座等。这个术语借用了电气工程中插头和插座的概念,代表两个相互配合的部分,通过一个接口进行连接和通信。
- 同样地,在网络编程中,“Socket” 是应用程序与网络协议栈之间的接口。应用程序通过创建一个 socket 连接到网络,就像插头插入插座一样,以便进行数据通信
- 在网络中传输数据就像寄快递一样,拿 TCP 来说,寄快递需要源地址(主机 IP)和寄件人(端口对应的程序),收件地址(目标 IP)和收件人(目标计算机端口对应的应用程序)
创建一个 socket
int socket (int __domain, int __type, int __protocol);
socket参数详解
-
__domain
- AF_INET:IPv4 协议,使用 IPv4 地址进行通信
- AF_INET6:IPv6 协议,使用 IPv6 地址进行通信
- AF_UNIX 或 AF_LOCAL:用于本地通信(进程间通信,IPC),在 Unix 系统中使用
- AF_PACKET:用于底层网络接口访问,通常在 Linux 下用于直接访问网络设备
-
__type
- SOCK_STREAM:提供面向连接的、可靠的字节流服务(TCP 协议)
- SOCK_DGRAM:提供无连接、不可靠的数据报服务(UDP 协议)
- SOCK_RAW:提供原始套接字访问,可以处理网络层数据包
- SOCK_SEQPACKET:提供有序的、可靠的双向连接的传输
-
__protocol
- IPPROTO_TCP:TCP 协议,通常与 SOCK_STREAM 一起使用
- IPPROTO_UDP:UDP 协议,通常与 SOCK_DGRAM 一起使用
- IPPROTO_ICMP:ICMP 协议,通常与原始套接字 SOCK_RAW 一起使用
- IPPROTO_IP:IP 协议的基协议,通常与 SOCK_RAW 一起使用
- 0:自适应协议,自动选择
socket_in结构体
struct sockaddr_in { sa_family_t sin_family; // 地址族 (Address family) in_port_t sin_port; // 16位端口号 (Port number) struct in_addr sin_addr; // 32位IPv4地址 (IPv4 address) char sin_zero[8]; // 填充 (Padding) };
大端和小端
-
大端和小端的区别在于大端的字节按照顺序排序,小段按照逆序排序
int n=1; //大端字节序:0x00000001 //小端字节序:0x01000000
网络字节序统一为大端字节序,所以在网络传输的情况下需要将本地字节序转到网络字节序
-
字节序函数和对应全称
htons() - Host to Network Short • 功能: 将 16 位短整数(short)从主机字节序转换为网络字节序。 • 原型: uint16_t htons(uint16_t hostshort); htonl() - Host to Network Long • 功能: 将 32 位长整数(long)从主机字节序转换为网络字节序。 • 原型: uint32_t htonl(uint32_t hostlong); ntohs() - Network to Host Short • 功能: 将 16 位短整数从网络字节序转换为主机字节序。 • 原型: uint16_t ntohs(uint16_t netshort); ntohl() - Network to Host Long • 功能: 将 32 位长整数从网络字节序转换为主机字节序。 • 原型: uint32_t ntohl(uint32_t netlong);
将信息绑定到 socket
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind函数参数详解
- sockfd:这是由 socket() 函数返回的套接字文件描述符,表示要绑定的套接字
- addr:指向 sockaddr 结构体的指针,包含了套接字需要绑定的本地地址。通常,这个结构体会被强制转换为特定协议族的地址结构体(如 sockaddr_in 用于 IPv4,sockaddr_in6 用于 IPv6)
- addrlen:这个参数指定了 addr 结构体的长度(字节数)。对于 sockaddr_in 结构体
监听对应端口等待连接
int listen(int sockfd, int backlog);
listen函数参数详解
- sockfd:套接字文件描述符,即通过 socket() 函数创建的套接字的文件描述符
- backlog:待处理队列的最大长度
- 带处理队列最大长度:意思是完成了 TCP 三次握手后但是还没有被accept函数取走的连接
取出连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
accept函数参数详解
- socked:监听套接字的文件描述符,即之前通过 socket() 创建并通过 bind() 和 listen() 设置的套接字
- addr:指向 sockaddr 结构体的指针,用于存储客户端的地址信息。如果不需要获取客户端地址,可以将此参数设为 nullptr
- addrlen:指向一个 socklen_t 类型的变量的指针,初始时这个变量应该包含 addr 指向的缓冲区的长度。在函数返回时,它将被设置为实际存储的地址长度。如果 addr 为空,此参数也可以为空
通信操作
- 可以凭借 accept 返回的 fd 使用 read/wired、send/recv进行读写操作,新手建议直接用read/wired
代码示例
- 忽略报错,新手更容易读懂一点,但自己写代码要加上,socket 系列函数出错都是一致返回-1
-
socket()创建套接字时,如果出错(例如由于资源不足或参数错误),会返回 -1
-
bind()将套接字绑定到地址时,如果出错(例如地址和端口号已经被使用、权限问题等),会返回 -1
-
listen()将套接字设置为监听状态时,如果出错(例如套接字未绑定、参数无效等),会返回 -1
-
accept()接受连接时,如果出错(例如没有连接、套接字未监听等),会返回 -1
-
read()从套接字读取数据时,如果出错(例如连接已关闭、文件描述符无效等),会返回 -1
- 返回值若是正数那就是读取到的字节数、若等于 0 那么是客户端关闭连接,也有可能是多次读取到了末尾
-
write()向套接字写入数据时,如果出错(例如连接已关闭、文件描述符无效等),会返回 -1
-
#include <iostream>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <fcntl.h>
#include<cstring>
using namespace std;
int main() {
char buff[1024];
// 获取当前进程的 PID
pid_t pid = getpid();
std::cout << "Current Process ID: " << pid << std::endl;
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in server_addr{};
//指定为IPv4
server_addr.sin_family = AF_INET;
//INADDR_ANY的宏定义为0x00000000,代表监听本地所有网卡的端口
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//设置监听端口为 9999
server_addr.sin_port = htons(9999);
bind(listenfd, (sockaddr *) &server_addr, sizeof(server_addr));
listen(listenfd, 10);
sockaddr_in cli{};
socklen_t len = sizeof(cli);
int cli_fd = accept(listenfd, (sockaddr *) &cli, &len);
int n = 0;
while (true) {
//从网络中读取 1024字节
n = read(cli_fd, buff, 1024);
//因为如果客户端关闭连接,服务器会有一个读事件,但是大小为 0
if (n == 0) { break; }
cout << buff << endl;
//将读取到的数据写回客户端
write(cli_fd, buff, 1024);
}
close(listenfd);
close(cli_fd);
}