一、套接字的概念

​ 套接字是一种用于网络通信的编程接口,它提供了一种在不同主机上的进程之间进行通信的机制。套接字允许应用程序在网络上发送和接收数据,就像文件操作允许程序在本地存储上读写数据一样。它是网络通信的端点,每个套接字都由一个 IP 地址和一个端口号组成,通过这两个元素可以唯一地标识网络中的一个通信进程。

img

点击并拖拽以移动编辑

二、套接字的类型

  1. 流式套接字(Stream Sockets)
    • 基于 TCP(Transmission Control Protocol)协议,提供面向连接、可靠的字节流服务。
    • 数据传输是顺序的、无差错的、无重复的,并且保证数据的完整性和可靠性。
    • 适用于需要可靠传输的应用,如文件传输、HTTP、SMTP、FTP 等。
    • 在 C++ 中,使用 SOCK_STREAM 常量表示。
  2. 数据报套接字(Datagram Sockets)
    • 基于 UDP(User Datagram Protocol)协议,提供无连接、不可靠的数据报服务。
    • 每个数据报是独立的消息,不保证顺序,也不保证接收方一定能收到数据报。
    • 适合对传输速度要求较高但对数据完整性要求相对较低的应用,如音频、视频流、DNS 查询等。
    • 在 C++ 中,使用 SOCK_DGRAM 常量表示。

三、套接字的地址结构
在 C++ 中,使用 sockaddr_in 结构来表示 IPv4 地址,主要成员包括:

1
2
3
4
5
6
struct sockaddr_in {
short sin_family; // 地址族,一般为 AF_INET(IPv4)
unsigned short sin_port; // 端口号,需要使用 htons 转换为网络字节序
struct in_addr sin_addr; // IP 地址结构体
char sin_zero[8]; // 填充字节,一般不用
};

点击并拖拽以移动

in_addr 结构包含一个 s_addr 成员,存储 32 位的 IPv4 地址,可以使用 inet_addr 函数将点分十进制的 IP 地址转换为网络字节序的 32 位整数。

四、套接字编程的基本步骤

服务器端

1.创建套接字

1
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);

点击并拖拽以移动

  • AF_INET:使用 IPv4 地址族。
  • SOCK_STREAM:创建一个流式套接字(TCP)。
  • 0:使用默认协议。
  • 函数返回一个套接字描述符,若失败则返回 -1。

2.绑定地址和端口

1
2
3
4
5
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(8080);
serverAddress.sin_addr.s_addr = INADDR_ANY;
bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));

点击并拖拽以移动

  • 将服务器地址和端口绑定到创建的套接字上。
  • htons 函数将端口号从主机字节序转换为网络字节序。
  • INADDR_ANY 表示接受来自任何网络接口的连接。
  • bind 函数将套接字与地址结构绑定,失败返回 -1。

3.监听连接请求

1
listen(serverSocket, 5);

点击并拖拽以移动

  • 开始监听客户端的连接请求。
  • 5 表示等待队列的最大长度,即最多允许 5 个未处理的连接请求。
  • 失败返回 -1。

4.接受连接请求

1
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddress, &clientAddressLength);

点击并拖拽以移动

  • 阻塞函数,等待客户端的连接请求,一旦有请求就接受并创建一个新的套接字 clientSocket 用于与客户端通信。
  • clientAddress 是一个 sockaddr_in 结构,存储客户端的地址信息。
  • 失败返回 -1。

5.数据传输

1
2
3
char buffer[1024];
recv(clientSocket, buffer, sizeof(buffer), 0);
send(clientSocket, message, strlen(message), 0);

点击并拖拽以移动

  • recv 函数从客户端接收数据。
  • send 函数向客户端发送数据。
  • 可以使用 sendall 函数确保完整发送数据,处理可能的部分发送情况。

6.关闭套接字

1
2
close(clientSocket);
close(serverSocket);

点击并拖拽以移动

  • 关闭客户端和服务器的套接字,释放资源。

客户端

1.创建套接字

1
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);

点击并拖拽以移动

  • 与服务器创建套接字的过程相同。

2.指定服务器地址和端口

1
2
3
4
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(8080);
serverAddress.sin_addr.s_addr = inet_addr("127.0.0.1");

点击并拖拽以移动

  • 设置服务器的 IP 地址和端口号。
  • inet_addr 函数将点分十进制的 IP 地址转换为网络字节序。

3.连接服务器

1
connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));

点击并拖拽以移动

  • 向服务器发起连接请求,失败返回 -1。

4.数据传输

1
2
send(clientSocket, message, strlen(message), 0);
recv(clientSocket, buffer, sizeof(buffer), 0);

点击并拖拽以移动

  • 发送和接收数据。

5.关闭套接字

1
close(clientSocket);

点击并拖拽以移动

五、重要函数详解

1. socket 函数

1
int socket(int domain, int type, int protocol);

点击并拖拽以移动

  • domain:地址族,如 AF_INET(IPv4)或 AF_INET6(IPv6)。
  • type:套接字类型,如 SOCK_STREAM(TCP)或 SOCK_DGRAM(UDP)。
  • protocol:协议,通常为 0 表示使用默认协议。

2. bind 函数

1
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

点击并拖拽以移动

  • sockfd:套接字描述符。
  • addr:指向包含地址和端口信息的 sockaddr 结构的指针。
  • addrlenaddr 结构的长度。

3. listen 函数

1
int listen(int sockfd, int backlog);

点击并拖拽以移动

  • sockfd:套接字描述符。
  • backlog:等待队列的最大长度。

4. accept 函数

1
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

点击并拖拽以移动

  • sockfd:监听套接字的描述符。
  • addr:存储客户端地址信息的 sockaddr 结构指针。
  • addrlenaddr 结构的长度指针,存储客户端地址长度。

5. connect 函数

1
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

点击并拖拽以移动

  • sockfd:客户端套接字描述符。
  • addr:服务器的地址信息。
  • addrlen:地址信息的长度。

6. send 和 recv 函数

1
2
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

点击并拖拽以移动

  • sockfd:套接字描述符。
  • buf:发送或接收数据的缓冲区。
  • len:缓冲区的长度。
  • flags:通常为 0,可设置为 MSG_OOB 等特殊标志