服务器端的准备工作
- 1 网络通信初始化
- (突然忘记了#pragma是做什么的了)
- 1. #pragma comment(lib, "库名") 的作用
- 2. 其他常见 #pragma 指令
- (1) #pragma once
- (2) #pragma warning
- (3) #pragma pack
- (突然忘记了#pragma是做什么的了)
- 2 创建套接字(Socket)
- 3 设置套接字属性 :端口可复用(可省略)
- 端口复用(SO_REUSEADDR)的作用是什么?
- setsockopt() 是套接字编程中用于配置套接字选项的核心函数:
- 4 绑定套接字和网络地址
- 5 动态分配端口(可略)
- 1. 什么是动态分配端口?
- 6 监听
- 服务器端的准备工作
在接受浏览器前端的网页请求之前,服务器端需要做一些准备工作,流程如下:
1 网络通信初始化
//网络通信需要包含头文件,需要加载的库文件
#include<WinSock2.h>
#pragma comment(lib,"WS2_32.lib")
-
WinSock2.h
:Windows Sockets 2(Winsock)的头文件,定义了网络编程所需的函数、结构体和常量。 -
#pragma comment(lib, "WS2_32.lib")
:指示编译器链接到WS2_32.lib
库,确保程序能正确调用Winsock函数。
//1,网络通信初始化(windows)
WSADATA wsaData; // 网络通信相关的版本等信息// 在windows系统使用网络通信,必须先进行网络协议初始化(Linux系统不需要)
int ret = WSAStartup( // WSAStartup 网络通信初始化,MAKEWORD(1, 1), // 指定使用Windows Sockets规范的1.1版本&wsaData); // 存储初始化后的版本等信息
if (ret != 0) {return -1;//初始化失败
}
(突然忘记了#pragma是做什么的了)
#pragma
是 C/C++ 中的一种预处理指令,用于向编译器传递特定的控制命令。
1. #pragma comment(lib, "库名")
的作用
#pragma comment(lib, "WS2_32.lib")
-
功能:告诉编译器在链接阶段需要将
WS2_32.lib
库文件链接到最终的可执行文件中。 -
为什么需要它:Windows 的 Winsock API 函数(如
WSAStartup
、socket
)的实现代码位于WS2_32.lib
库中。如果不链接此库,编译时会报“未解析的外部符号”错误。
2. 其他常见 #pragma
指令
(1) #pragma once
#pragma once
- 功能:确保头文件只被包含一次,避免重复定义(类似
#ifndef HEADER_H
+#define HEADER_H
的传统方式)。
(2) #pragma warning
#pragma warning(disable: 4996) // 禁用警告 C4996
#pragma warning(push, 3) // 保存当前警告级别,并设置为级别3
#pragma warning(pop) // 恢复之前的警告级别
- 功能:控制编译器的警告信息输出。
(3) #pragma pack
#pragma pack(push, 1) // 按1字节对齐结构体
struct MyStruct {char a;int b;
};
#pragma pack(pop) // 恢复默认对齐方式
- 功能:控制结构体的内存对齐方式,常用于网络传输或硬件交互时精确控制数据布局。
2 创建套接字(Socket)
socket()
是网络编程中用于**创建套接字(Socket)**的核心函数,它是操作系统提供的底层接口,用于实现不同设备之间的网络通信(如客户端与服务器)
它是网络通信的端点。套接字是网络编程的基础,用于实现 TCP/UDP 通信、监听连接、发送和接收数据等操作。
int socket(int domain, int type, int protocol);
-
返回值:
-
成功:返回一个文件描述符(非负整数),代表创建的套接字。
-
失败:返回
-1
,并通过errno
表示错误类型(如权限不足、协议不支持等)。
-
1. domain
(协议族/地址族)
指定套接字使用的网络协议族,决定套接字可处理的地址类型。
2. type
(套接字类型)
指定套接字的数据传输方式,决定通信的可靠性和行为。
3. protocol
(协议类型)
明确指定要使用的传输层协议。通常设为 0
,表示根据 domain
和 type
自动选择协议。
3 设置套接字属性 :端口可复用(可省略)
端口复用(SO_REUSEADDR)的作用是什么?
当服务器程序崩溃或主动关闭后,绑定的端口可能处于 TIME_WAIT
状态(TCP 连接的正常关闭阶段)。此时立即重启服务器会因端口被占用而失败,并提示 Address already in use
。设置 SO_REUSEADDR
允许套接字绑定到处于 TIME_WAIT
状态的端口,从而快速重启服务。
setsockopt()
是套接字编程中用于配置套接字选项的核心函数:
int setsockopt(int sockfd, // 套接字描述符int level, // 选项的协议层int optname, // 选项名const void *optval, // 指向选项值的指针socklen_t optlen // 选项值的长度
);
返回值:
-
成功:返回
0
-
失败:返回
-1
1. sockfd
(套接字描述符)
- 作用:要设置选项的套接字
2. level
(协议层)
指定选项所属的协议层,常见值:
3. optname
(选项名)
具体要设置的选项名称,常见选项:
4. optval
(选项值)
- 作用:指向选项值的指针,具体类型由
optname
决定。 -
-
常见类型:
-
int
:如SO_REUSEADDR
启用时设为1
。 -
struct timeval
:如超时设置。 -
linger
:控制关闭时的延迟行为。
-
5. optlen
(选项值长度)
- 作用:指定
optval
数据的大小(字节数)。
4 绑定套接字和网络地址
服务器 客户端+-------------------+ +-------------------+| 1. 创建 Socket | | || (socket()) | | |+-------------------+ +-------------------+| |+-------------------+ +-------------------+| 2. 绑定地址和端口 | | || (bind()) | | |+-------------------+ +-------------------+| |+-------------------+ +-------------------+| 3. 监听连接 | | 4. 发起连接 || (listen()) | <-------- | (connect()) |+-------------------+ +-------------------+| |+-------------------+ +-------------------+| 5. 接受连接 | | || (accept()) | <-------- | |+-------------------+ +-------------------+| |+-------------------+ +-------------------+| 6. 收发数据 | <-------> | 6. 收发数据 || (send/recv) | | (send/recv) |+-------------------+ +-------------------+| |+-------------------+ +-------------------+| 7. 关闭连接 | | 7. 关闭连接 || (close()) | | (close()) |+-------------------+ +-------------------+
socket就像电话,bind()相当于给电话分配号码,listen()和accept()是等待来电,connect()是拨号,send()和recv()是通话过程。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
5 动态分配端口(可略)
1. 什么是动态分配端口?
当服务器将端口号设为 0 时,操作系统会自动选择一个空闲端口分配给套接字。这个过程称为 动态端口分配。
getsockname
是一个网络编程函数,用于 获取套接字(Socket)绑定的本地地址信息(IP 地址和端口号)。它的作用类似于 查看电话机的实际分机号。
. 使用场景
(1) 动态分配端口
当服务器将端口号设为 0
时,操作系统会自动分配一个可用端口。此时需要通过 getsockname
获取实际分配的端口号。
(2) 绑定到通配地址(INADDR_ANY
)
如果服务器绑定到所有网络接口(如 Wi-Fi、以太网),可以通过 getsockname
获取实际使用的 IP 地址。
int getsockname(int sockfd, // 套接字描述符struct sockaddr *addr, // 存储获取的地址信息socklen_t *addrlen // 地址结构体的长度(输入时为缓冲区大小,输出时为实际大小)
);
返回值:
-
成功:返回
0
-
失败:返回
-1
6 监听
int listen(int sockfd, int backlog);
服务器端的准备工作
#include<stdio.h>//网络通信需要包含头文件,需要加载的库文件
#include<WinSock2.h>
#pragma comment(lib,"WS2_32.lib")void error_die(const char* str)
{perror(str);// 打印错误原因exit(1);
}//实现网络的初始化
//返回值:套接字(服务器端套接字)
//端口
//参数:port 表示端口
// 如果*port的值是0,那么就自动分配一个可用的端口
int startup(unsigned short* port)
{//1,网络通信初始化(windows)WSADATA wsaData; // 网络通信相关的版本等信息// 在windows系统使用网络通信,必须先进行网络协议初始化(Linux系统不需要)int ret = WSAStartup( // WSAStartup 网络通信初始化,MAKEWORD(1, 1), // 指定使用Windows Sockets规范的1.1版本&wsaData); // 存储初始化后的版本等信息if (ret != 0) {error_die("wsaData");//初始化失败}//2,创建套接字int server_socket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);if (server_socket == -1) {//打印错误提示,并结束程序error_die("socket");}//3 设置套接字属性 端口可复用(可省略)// 端口复用int opt = 1;ret = setsockopt(server_socket, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));if (ret == -1) {error_die("setsockopt");}//4 绑定套接字和网络地址//配置服务器端的网络地址struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));//清零server_addr.sin_family = AF_INET;//网络地址的类型server_addr.sin_port = htons(*port);//转换为网络字节序 // htons():将端口号从 主机字节序 转换为 网络字节序(大端模式)。//为什么需要转换?//不同计算机的字节序可能不同(如 x86 是小端,网络传输统一用大端),转换保证数据正确解析。server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//绑定到本机所有可用的 IP 地址。if (bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {error_die("[bind]");}// 5 动态分配端口if (*port == 0) {int namelen = sizeof(server_addr);if (getsockname(server_socket, (struct sockaddr*)&server_addr, &namelen) == -1) {error_die("getsockname");}*port = ntohs(server_addr.sin_port); 转换端口号为主机字节序}//6 创建监听队列if (listen(server_socket, 5) < 0) {error_die("listen");}return(server_socket);}