初始化套接字环境
WSADATA data;
WSAStartup(MAKEWORD(1, 1), &data);//todo: 返回值处理
这两行代码用于在Windows下初始化套接字环境。其中,WSADATA data
是用来存储套接字环境信息的结构体,WSAStartup
函数用于初始化套接字库,并将相关信息填充到 WSADATA
结构体中。
检验(容错机制)
if (WSAStartup(MAKEWORD(1, 1), &data) != 0)
{
printf("WSAStartup errorNum = %d\n", GetLastError());
return;
}
创建套接字socket
创建一个Socket对象,通过调用系统函数创建一个套接字。套接字可以是流式套接字(SOCK_STREAM)用于TCP协议通信或者数据报套接字(SOCK_DGRAM)用于UDP协议通信。
SOCKET listenSocket = socket(PF_INET, SOCK_STREAM, 0);
//todo:校验
这行代码创建了一个套接字(socket)。PF_INET 和 SOCK_STREAM 参数分别指定了套接字的协议簇和套接字类型,该示例中使用的是TCP协议和可靠的数据传输。
协议族
PF_INET
是一个宏,用于指定协议族,表示使用IPv4地址族。在IPv4地址族中,地址用32位整型表示,通常以点分十进制表示,例如 192.168.0.1。
协议族(Protocol Family)指的是一组协议的集合,其定义了一系列相关的协议和规范,用于支持网络中的通信。常见的协议族有:
● PF_INET
:表示使用IPv4地址族;
● PF_INET6
:表示使用IPv6地址族;
● PF_PACKET
:表示使用底层数据包通信。
在 C 语言和 C++ 的套接字编程中,协议族通常与套接字类型(Socket Type)和协议(Protocol)一起使用,创建一个指定类型和协议的套接字。
在示例中,使用 PF_INET
协议族创建了一个 TCP 套接字,表示使用 IPv4 地址以及可靠的数据传输方式。
套接字类型
SOCK_STREAM
[流套接字] ——TCP
面向连接、可靠的数据传输 适合传输大量的数据,不支持广播、多播SOCK_DGRAM
[数据包套接字] ——UDP
无连接 支持广播、多播
容错机制
if (INVALID_SOCKET == listenSocket)
{
printf("listenSocket errorNum = %d\n", GetLastError());
return -1;
}
bind
使用bind()函数将套接字绑定到服务器的IP地址和端口号上。指定服务器的IP地址和端口号是为了让客户端能够连接到服务器。
sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY; // 使用本地IP地址
serverAddress.sin_port = htons(8080); // 设置监听端口
bind(listenSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
//todo:校验
这段代码定义了服务器的地址结构体 serverAddress,并设置了服务器的IP地址(使用 INADDR_ANY 表示任意地址)和端口号(8080)。之后通过 bind 函数将套接字绑定到指定的地址。
sockaddr_in
sockaddr_in
是一个结构体(structure),用于表示 Internet 套接字地址。它在 netinet/in.h
头文件中定义,是一个底层的网络编程结构体,在实际编程中经常用到。
该结构体定义如下:
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};
其中,成员变量的含义如下:
sin_family
:地址簇,指定 Internet 地址族。在使用 TCP/IP 协议时,该值通常设置为AF_INET
。sin_port
:端口号,用于标识通信的应用程序。该值需要使用htons
函数进行端口号字节序的转换,以适应网络字节序(Network Byte Order)。sin_addr
:IP 地址,用于指定 Internet 上的主机地址。sin_zero
:填充,保证sin_addr
和sin_port
处于整数类型的边界上。
在使用 sockaddr_in
时,还需要进行强制类型转换为 sockaddr
类型,例如 bind
函数的参数、recvfrom
函数的第二个参数等。
示例中,使用 sockaddr_in
结构体设置服务器的地址信息,并通过 bind
函数绑定到创建的套接字上。
listen
使用listen()函数监听指定端口上的连接请求。此时服务器处于等待状态,等待客户端发起连接。
listen(listenSocket, 5);
这行代码将套接字设置为监听状态,第二个参数5为最大监听数,表示同时最多允许一个连接处于等待状态。
最大监听数目,相当于一个队列,比如最大监听5,则1 2 3 4 5 6 ,6就监听不到,等1出队,6才能入队
容错机制
if (listen(listenSocket, 5) == -1) { // 允许同时连接的最大客户端数为5
std::cerr << "Failed to listen on socket." << std::endl;
return -1;
}
accept
使用accept()函数从队列中取出一个客户端连接请求,并创建一个新的套接字用于与客户端进行通信。
// 接受客户端连接
struct sockaddr_in clientAddress;
socklen_t clientAddressLength = sizeof(clientAddress);
int clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddress, &clientAddressLength);
容错机制
if (clientSocket == -1) {
std::cerr << "Failed to accept client connection." << std::endl;
}
recv send
通过新的套接字与客户端进行通信,可以使用read()和write()等函数来读写数据。
// 处理客户端请求
char buffer[1024];
int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesRead == -1)
std::cerr << "Failed to receive data from client." << std::endl;
send(clientSocket, buffer, sizeof(buffer), 0);
可以在接收到数据后根据自己的需求进行处理,比如发送响应给客户端。此处send将接收到的buffer返回给客户端
关闭套接字
当通信完成后,关闭套接字,释放资源。
// 关闭客户端socket
close(clientSocket);
// 关闭监听socket
close(listenSocket);
清理套接字环境
WSACleanup();
这行代码用于清理套接字环境,并释放相关资源。
初始化一个简单的TCP服务器
#include <iostream>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
int main() {
//socket bind listen accept read write close
//
//套接字初始化(在windows下需要进行套接字环境的初始化,linux不需要)
WSADATA data;
WSAStartup(MAKEWORD(1, 1),&data);//todo: 返回值处理
// 创建socket
int listenSocket = socket(AF_INET, SOCK_STREAM, 0);
if (listenSocket == -1) {
std::cerr << "Failed to create socket." << std::endl;
return -1;
}
// 设置服务器地址和端口
struct sockaddr_in serverAddress;
serverAddress.sin_family = AF_INET;
serverAddress.sin_addr.s_addr = INADDR_ANY; // 使用本地IP地址
serverAddress.sin_port = htons(8080); // 设置监听端口
// 绑定socket到指定地址和端口
if (bind(listenSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
std::cerr << "Failed to bind socket." << std::endl;
return -1;
}
// 开始监听连接
if (listen(listenSocket, 5) == -1) { // 允许同时连接的最大客户端数为5
std::cerr << "Failed to listen on socket." << std::endl;
return -1;
}
std::cout << "Server started. Listening on port 8080..." << std::endl;
while (true) {
// 接受客户端连接
struct sockaddr_in clientAddress;
socklen_t clientAddressLength = sizeof(clientAddress);
int clientSocket = accept(listenSocket, (struct sockaddr*)&clientAddress, &clientAddressLength);
if (clientSocket == -1) {
std::cerr << "Failed to accept client connection." << std::endl;
continue;
}
// 处理客户端请求
char buffer[1024];
int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesRead == -1) {
std::cerr << "Failed to receive data from client." << std::endl;
continue;
}
// 在这里根据buffer的内容进行相应的处理,比如发送响应给客户端
// 关闭客户端socket
close(clientSocket);
}
// 关闭监听socket
close(listenSocket);
return 0;
}
//清理网络
WSACleanup();
以上使用了基本的socket函数来创建、绑定和监听一个服务器socket。然后使用accept函数来接受客户端连接,并使用recv函数来接收客户端发送的数据。