1.网络编程基础
1.什么是网络编程
网络编程是编写程序使网络上的两个或者多个设备之间进行数据传输。
2.网络编程的实现
socket网络编程
主要通过套接字(ip+端口号)实现两个主机(服务端和客户端)之间的通信
3.TCP通信过程
TCP三次握手和四次挥手
socket编程分为TCP和UDP两个模块,其中TCP是可靠的、安全的,常用于发送文件等,而UDP是不可靠的、不安全的,常用作视频通话等
TCP建立连接:客户端发送请求-->服务器响应并确认请求-->客户端确认连接
断开连接:客户端发起请求 -->服务器收到关闭请求,并且确认数据全部接收-->服务器发起关闭-->客户端确认关闭
服务器端server:
-
创建套接字socket()
头文件
#include<sys/socket.h> #include<netinet/in.h> //操作ip地址的api #include<arpa/inet.h> //操作ip地址的api #include<unistd.h> //系统操作资源的头文件
创建一个具有网络属性的文件描述符
int socket(int domain, int type, int protocol);
int listen_sock = socket(AF_INET,SOCK_STREAM,0);
参数: domain:协议族 AF_UNIX,AF_LOCAL 本地连接 AF_INET IPv4 AF_INET6 IPv6 type: SOCK_STREAM 流式套接字 TCP SOCK_DGRAM 数据报套接字 UDP protocol:0
-
分配套接字bind():分配IP地址和端口号
//定义结构体 //服务器端饿结构体 struct sockaddr_in server_addr; //这个结构体定义了IP地址和端口号 memset(&server_addr,0,sizeof(server_addr));//把IP地址置空 //指定IP地址和端口号 server_addr.sin_famliy=AF_INET;//指定ipv4 server_addr.sin_addr.s_addr = INADDR_ANY;//监听所有的ip地址 server_addr.sin_port = htons(9999); //端口号 /**htons()函数 htons()函数用于将主机字节序转换为网络字节序 **/ //绑定 bind(listen_sock,(struct sockaddr*)&server_addr,sizeof(server_addr)); //socket和IP地址进行绑定
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen); /**功能: 将IP地址和端口号绑定在sockfd上,难点在于第二参结构体赋值 参数: sockfd:这个是socket创建出来的具有网络属性的文件描述符 my_addr:结构体指针,用来赋值IP地址和端口号 addrlen:结构体的长度 返回值: 成功返回0 失败,返回-1并设置错误码**/
-
等待连接请求listen():调用listen()函数转为可接收请求状态,服务器端就在接收客户端的连接了
//监听套接字 listen(listen_sock,5); /** 参数: sockfd:这个是socket创建出来的具有网络属性的文件描述符 backlog:最大的客户端连接数量**/
-
允许连接accept():调用accept()函数受理连接请求。
//接收客户端的连接请求 //客户端的结构体 struct sockaddr_in client_addr; socklen_t client_addr_len = sizeof(client_addr); //接收客户端请求,返回客户端的socket int client_sock = accept(listen_sock,(struct sockaddr*)&client_addr,&client_addr_len); /** 功能: 等待客户端连接,第二参能保存对方的IP地址和端口号,不需要保存对方设置NULL 参数: sockfd:这个是socket创建出来的具有网络属性的文件描述符 addr:用来保存客户端信息的结构体 addrlen:结构体长度。 **/
-
数据交互read()/write():读取数据和传输数据
//接收客户端的消息 char buffer[1024]; //用buffer存 int read_size = read(client_sock,buffer,sizeof(buffer),0); //接收到客户端的消息存到buffer中,返回消息大小 /** 功能: 数据接收 参数: sockfd:套接字文件描述符,client_sock:接收的是哪个客户端 buf:接收缓冲区 len:接收缓冲区长度 flags:默认为0,表示阻塞 **/ //打印一下 std::cout << buffer << std::endl; //给客户端发送消息 //发送内容 std::string res_msg = "Hello Client"; //发送 write(client_sock,res_msg.c_str(),res_msg.length(),0);//给客户端client_sock发
-
断开连接close()
//关闭客户端的socket close(client_sock); //关闭服务器端的socket close(listen_sock);
客户端client
-
创建套接字socket()
int client_sock = socket(AF_INET,SOCK_STREAM,0);
-
请求连接connect()
//连接服务器 struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; inet_pton(AF_INET,"127.0.0.1",&server_addr.sin_addr.s.addr);//将127.0.0.1赋值给erver_addr.sin_addr.s.addr /** inet_pton():十进制格式的ip转换为二进制的ip地址 inet_ntoa():将ip地址从二进制转换为十进制 **/ //连接 connect(client_sock,(struct sockaddr*)&server_addr,sizeof(server_addr));
-
数据交互read()/write()
//发送消息给服务器 std::string res_msg = "Hello Client"; //发送 write(client_sock,res_msg.c_str,res_msg.length());//给服务器client_sock发 //接收服务器端的消息 char buffer[1024]; //用buffer存 int read_size = read(client_sock,buffer,sizeof(buffer),0); //接收服务器client_sock的消息
-
断开连接close()
close(client_sock);//关闭客户端
4.UDP通信过程
UDP:不要连接,只管发送,数据因此不稳定,容易丢包
UDP通信没有accept和connect
发送端(服务器)
-
创建socket套接字
int listen_sock = socket(AF_INET,SOCK_STREAM,0);
-
bind()绑定:绑定自己的IP地址和端口号
//定义结构体 //服务器端结构体 struct sockaddr_in server_addr; //这个结构体定义了IP地址和端口号 memset(&server_addr,0,sizeof(server_addr));//把IP地址置空 //指定IP地址和端口号 server_addr.sin_famliy=AF_INET;//指定ipv4 server_addr.sin_addr.s_addr = inet_addr(argv[1]); server_addr.sin_port = htons(9999); //端口号 //绑定 bind(listen_sock,(struct sockaddr*)&server_addr,sizeof(server_addr)); //socket和IP地址进行绑定
-
声明别人的IP地址和端口号
//定义客户端结构体 struct sockaddr_in client_addr; //这个结构体定义了IP地址和端口号 memset(&client_addr,0,sizeof(client_addr));//把IP地址置空 //指定IP地址和端口号 client_addr.sin_famliy=AF_INET;//指定ipv4 client_addr.sin_addr.s_addr = inet_addr(argv[2]); client_addr.sin_port = htons(8888); //端口号
-
发送数据
char buf[50]; while(1){ char buf[50] = {'\0'}; cout << buf <<endl; cin>> buf; sendto(listen_sock,buf,strlen(buf),0,(struct sockaddr*)&client_addr,sizeof(client_addr)); } close(listen_sock)
接收端(客户端)
-
创建socket套接字
int client_sock = socket(AF_INET,SOCK_STREAM,0);
-
绑定自己的IP地址和端口号
//定义客户端结构体 struct sockaddr_in client_addr; //这个结构体定义了IP地址和端口号 memset(&client_addr,0,sizeof(client_addr));//把IP地址置空 //指定IP地址和端口号 client_addr.sin_famliy=AF_INET;//指定ipv4 client_addr.sin_addr.s_addr = inet_addr(argv[2]); client_addr.sin_port = htons(8888); //端口号 //绑定 bind(client_sock,(struct sockaddr*)&client_addr,sizeof(client_addr));
-
声明别人的IP地址和端口号
struct sockaddr_in server_addr; //这个结构体定义了IP地址和端口号 memset(&server_addr,0,sizeof(server_addr));//把IP地址置空 //指定IP地址和端口号 server_addr.sin_famliy=AF_INET;//指定ipv4 server_addr.sin_addr.s_addr = inet_addr(argv[1]); server_addr.sin_port = htons(9999); //端口号
-
接收数据
char buf[50]; while(1){ char buf[50] = {'\0'}; recvfrom(client_sock,buf,sizeof(buf),0,(struct sockaddr*)&client_addr,sizeof(client_addr)) cout << buf <<endl; }
2.Epoll-I/O多路复用
1.阻塞与非阻塞
阻塞IO:这种模式下下一个用户进程在发起一个IO操作后,CPU只有在收到响应或者超时后才可以处理其他事情,否则就会一直阻塞。
非阻塞IO:这种模式下下一个用户进程在发起一个IO操作后,如果数据没有就绪,则会立刻返回,同时标记数据资源不可用,此时CPU时间片可以做其他的事情。
2.同步与异步IO
发生在使用资源阶段,根据实际IO操作来判断
同步IO:应用发送或者接收数据后,如果不返回,那么就阻塞等待,直到数据成功或者失败返回
异步IO:应用发送或或者接收数据后立刻返回,数据写入OS缓存,由OS完成数据发送或者接收,等OS处理完成后在通知应用
3.多路IO复用
多路IO复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序执行相应的读写操作,没有文件句柄就绪时,监视线程会被阻塞,交出CPU,直到有文件/连接就绪。
多路指的是网络连接,复用指的是同一个线程可以被复用来服务多个文件描述符
常见的IO多路复用模型有:select模型,poll模型,epoll模型
1)select模型
select模型是最古老的IO多路复用机制之一,使用fd_set数据结构来保存文件描述符集合,并提供了select()函数来等待文件描述符的就绪状态。它有一个限制,即所监视的文件描述符数量有一个上限,通常是1024。
#include <sys/select.h> //功能: 监听多个文件描述符的属性变化(读,写,异常) int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
//参数: // nfds : 最大文件描述符+1 // readfds : 需要监听的读的文件描述符存放集合 // writefds :需要监听的写的文件描述符存放集合 NULL // exceptfds : 需要监听的异常的文件描述符存放集合 NULL // timeout: 多长时间监听一次 固定的时间,限时等待 NULL 永久监听 // struct timeval { // long tv_sec; /* seconds */ 秒 // long tv_usec; /* microseconds */微妙 // }; //返回值: 返回的是变化的文件描述符的个数
变化的文件描述符会存在监听的集合中,未变化的文件描述符会从集合中删除 监视的文件描述符有三类,readfds,writefds,exceptfds 调用后函数会阻塞,直到有描述符就绪(有数据读、写、或者有except),或者超时(timeout指定时间,如果立即返回设置null),函数返回 当select函数返回后,可以通过遍历fdset,来找到就绪的描述符。
优点:良好的跨平台性
缺点:单个进程能够监视的文件描述符的数量存在最大限制 在Linux上为1024可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但这样会造成效率的降低 每次调用 select,都需要把 fd 集合从用户态拷贝到内核态,这个开销在 fd 很多时会很大 对 socket 扫描时是线性扫描,采用轮询的方法,效率较低(高并发)
2)poll模型
#include <poll.h> //功能: 监听多个文件描述符的属性变化 int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/** 参数: fds : 监听的数组的首元素地址 nfds: 数组有效元素的最大下标+1 timeout : 超时时间 -1是永久监听 >=0 限时等待 **/ //数组元素: struct pollfd { int fd; /* file descriptor */ 需要监听的文件描述符 short events; /* requested events */需要监听文件描述符什么事件 EPOLLIN 读事件 EPOLLOUT写事件 short revents; /* returned events */ 返回监听到的事件 EPOLLIN 读事件 EPOLLOUT写事 };
相比于select的优点就是没有文件描述符1024的限制,不过缺点一样:每次都需要将需要监听的文件描述符从应用层拷贝到内核每次都需要将数组中的元素遍历一遍才知道那个变化了
3)epoll模型
Linux特有的IO多路复用机制,自从2.5.44内核版本引入后成为主流。它使用基于事件的方式来管理文件描述符,使用一个事件表(event table)来保存文件描述符和事件信息,并提供了epoll_create()、epoll_ctl()和epoll_wait()等函数来操作事件表。
epoll本质是系统在内核维护了一颗红黑树,监听的文件描述符会作为新的节点插入红黑树,epoll会等待有状态变化的节点记录在链表里,然后拷贝到用户所给的数组里面返回出来。
-
创建红黑树
int epoll_create(int size);//size:监听的文件描述符的上限 返回树的句柄
-
上树,下树,修改节点
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event); /** epfd:树的句柄 op: EPOLL_CTL_ADD 上树 EPOLL_CTL_DEL 下树 EPOLL_CTL_MOD 修改 fd:上树,下树的文件描述符 event:上树的节点 **/
上树的节点的结构体
struct epoll_event{ uint32_t events;//需要监听的事件 epoll_data_t data; //需要监听的文件描述符 } //epoll_data_t union typedef union epoll_data{ void *ptr; int fd; uint32_t u32; uint64_t u64; }epoll_data_t;
例如,将cfd(文件描述符)上树
int epfd = epoll_create(1); struct epoll_event ev; ev.data.fd = cfd;//需要监听的文件描述符 ev.events = EPOLLIN;//监听读事件,其他事件包括EPOLLOUT,EPOLLERR,EPOLLHUP,各自的含义是监听写事件、错误事件、挂起事件 epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
-
监听树上文件描述符的变化
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);//返回的是变化的文件描述符个数 /** epfd:树的句柄 events:接收变化的节点的数组的首地址 maxevents:数组元素的个数 timeout:-1 永久监听 大于等于0 限时等待 **/
当调用epoll_wait检查是否有事件发生时,只需要检查eventpoll对象中的双链表中是否有元素即可,如果链表不为空,则把发生的事件复制到用户态,同时将事件数量返回给用户。
4.epoll服务端代码
//创建套接字 int server_fd = socket(AF_INET,SOCK_STREAM,0); if(server_fd==-1){ std::error << "Failed to create socket\n"; exit(1); } //设置服务器地址 struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; //0.0.0.0 server_addr.sin_port = htons(8888); //绑定套接字 if(bind(server_fd,(sockaddr*)&server_addr,sizeof(server_addr))==-1){ std::error << "Failed to bind socket\n"; exit(1); } //监听套接字 if(listen(server_fd,16)==-1){ std::error << "Failed to listen socket\n"; exit(1); } //创建epoll实例 epoll_fd = epoll_create(1); //将服务器套接字添加到epoll实例中 struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; ev.data.fd = server_fd; epoll_ctl(epoll_fd,EPOLL_CTL_ADD,server_fd,&ev); std::cout <<"服务端开始监听" <<std::endl;
void run(){ while(true){ //使用epoll等待事件 int num_ready = epoll_wait(epoll_fd,events,8,-1); for(int i = 0;i<num_ready;i++){ if(ev[i].data.fd = server_fd){ //有新的连接请求 int client_fd = accept(server_fd,(sockaddr*)&client_addr,sizeof(client_addr)); if(client_fd==-1){ std::error << "Failed to accept socket\n"; exit(1); } std::cout << "new connection from" << inet_toa(client_addr.sin_addr) << ":" << ntohs(client_addr.sin_port) <<std::endl; //将新的客户端套接字添加到epoll实例中 ev.events = EPOLLIN | EPOLLET; ev.data.fd = client_fd; epoll_ctl(epoll_fd,EPOLL_CTL_ADD,client_fd,&ev); }else{ //有数据到达现有的客户端套接字 char buffer[200]; //接收 int size = recv(ev[i].data.fd,buffer,200,0); if(size <=0){ //关闭客户端套接字,从epoll实例中移除 close(ev[i].data.fd ); epoll_ctl(epoll_fd,EPOLL_CTL_DEL,ev[i].data.fd ,NULL); }else{ //业务代码,比如将接受到的数据,再原样发送给客户端 send(ev[i].data.fd,buffer,buffer.length(),0); } } } } }
3.http服务器
1.HTTP
1)http
超文本传输协议,主要是用在网页传输上,网站访问上使用
传输的数据是明文的,可以被截获,查看里面的内容-->不够安全
http端口号 80
2)HTTP工作原理
-
客户端与服务器建立TCP连接。
-
客户端发送HTTP请求:包含请求行、请求头和可选的响应体。
-
服务器处理请求并返回HTTP相应:包含状态行、响应头和可选的相应体。
-
关闭连接或保持连接。
3)HTTP请求和相应格式
请求格式:
/**请求行:方法URL版本:[请求方法] + [URI] + [HTTP版本] 请求头:请求的属性,头部字段名:值 空行:遇到空行表示请求报头结束。 请求正文(可选):请求正文允许为空字符串,如果请求正文存在,则在请求报头中会有一个Content-Length属性来标识请求正文的长度。 GET /index.html HTTP/1.1 Host: www.example.com User-Agent: Mozilla/5.0 **/
相应格式:
/** 状态行:[HTTP版本] + [状态码] + [状态码描述]。 响应报头:响应的属性,这些属性都是以key: value的形式按行陈列的。 空行:遇到空行表示响应报头结束。 响应正文:响应正文允许为空字符串,如果响应正文存在,则在响应报头中会有一个Content-Length属性来标识响应正文的长度。 HTTP/1.1 200 OK Content-Type: text/html Content-Length: 123 <html>...</html> **/
4)HTTP状态码
HTTP 状态码表示服务器对请求的处理结果,常见的状态码包括:
-
1xx (信息):请求已接收,继续处理。
-
2xx (成功):请求成功,常见如 200 OK。
-
3xx (重定向):需要进一步操作以完成请求,常见如 301 Moved Permanently。
-
4xx (客户端错误):请求包含语法错误或无法完成,常见如 404 Not Found。
-
5xx (服务器错误):服务器在处理请求时发生错误,常见如 500 Internal Server Error。
2.HTTPS
1)https
是http协议的加密版本,https端口是 443 数据传输是经过加密的,比较安全
身份认证:验证服务器身份,防止中间人攻击。
数据完整性:确保数据在传输过程中不被篡改。
传输过程的安全性,在http基础上有证书: 也就是拥有 ssh 服务: 有公钥和私钥
2)HTTPS工作原理
-
客户端发起 HTTPS 连接请求。
-
服务器返回 SSL/TLS 证书,包含服务器的公钥。
-
客户端验证证书的合法性,生成对称密钥,并使用服务器公钥加密该密钥。
-
服务器使用私钥解密密钥,建立加密通信通道。
-
客户端与服务器进行加密通信
3.基于 Windows 系统的 C++ 实现HTTP网络通信
1.客户端
-
初始化 Winsock。
-
创建 Socket。
-
连接服务器
-
发送 HTTP 请求。
-
接收 HTTP 响应。
-
关闭 Socket 并清理 Winsock。
#include <winsock2.h> #include <iostream> #include <string> #pragma comment(lib, "ws2_32.lib") // Winsock 库 int main() { // 初始化 Winsock WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { std::cerr << "WSAStartup failed: " << WSAGetLastError() << std::endl; return 1; } // 创建 Socket SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (clientSocket == INVALID_SOCKET) { std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl; WSACleanup(); return 1; } // 服务器地址配置 sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8888); // HTTP 端口号 8888 serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 连接服务器 if (connect(clientSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { std::cerr << "Connect failed: " << WSAGetLastError() << std::endl; closesocket(clientSocket); WSACleanup(); return 1; } // 构造 HTTP GET 请求 std::string request = "GET / HTTP/1.1\r\n"; request += "Host: example.com\r\n"; request += "Connection: close\r\n"; request += "\r\n"; // 发送 HTTP 请求 if (send(clientSocket, request.c_str(), request.length(), 0) == SOCKET_ERROR) { std::cerr << "Send failed: " << WSAGetLastError() << std::endl; closesocket(clientSocket); WSACleanup(); return 1; } // 接收服务器响应 char buffer[4096]; int bytesReceived; while ((bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0)) > 0) { buffer[bytesReceived] = '\0'; // Null-terminate the buffer std::cout << buffer; // 输出服务器响应 } if (bytesReceived == SOCKET_ERROR) { std::cerr << "Recv failed: " << WSAGetLastError() << std::endl; } // 关闭 Socket closesocket(clientSocket); // 清理 Winsock WSACleanup(); return 0; }
2.服务器端
-
初始化 Winsock。
-
创建服务器端的监听 Socket。
-
绑定 Socket 到指定端口。
-
监听客户端连接请求。
-
接收客户端请求,解析 HTTP 请求头。
-
构造并发送 HTTP 响应。
-
关闭连接和清理资源。
#include <winsock2.h> #include <iostream> #include <string> #pragma comment(lib, "ws2_32.lib") // 链接 Winsock 库 // 处理客户端请求 void handleClient(SOCKET clientSocket) { char buffer[4096]; int bytesReceived = recv(clientSocket, buffer, sizeof(buffer), 0); if (bytesReceived > 0) { buffer[bytesReceived] = '\0'; // 将缓冲区内容设为以 '\0' 结尾 // 输出收到的 HTTP 请求 std::cout << "HTTP Request Received:\n" << buffer << std::endl; // 构造 HTTP 响应 std::string httpResponse = "HTTP/1.1 200 OK\r\n" "Content-Type: text/html\r\n" "Connection: close\r\n" "\r\n" "<html><body><h1>Hello, World!</h1></body></html>"; // 发送响应 send(clientSocket, httpResponse.c_str(), httpResponse.length(), 0); } // 关闭连接 closesocket(clientSocket); } int main() { // 初始化 Winsock WSADATA wsaData; if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { std::cerr << "WSAStartup failed: " << WSAGetLastError() << std::endl; return 1; } // 创建监听 Socket SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (serverSocket == INVALID_SOCKET) { std::cerr << "Socket creation failed: " << WSAGetLastError() << std::endl; WSACleanup(); return 1; } // 配置服务器地址 sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(8080); // 监听端口 8080 serverAddr.sin_addr.s_addr = INADDR_ANY; // 绑定 Socket 到端口 if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { std::cerr << "Bind failed: " << WSAGetLastError() << std::endl; closesocket(serverSocket); WSACleanup(); return 1; } // 监听连接请求 if (listen(serverSocket, SOMAXCONN) == SOCKET_ERROR) { std::cerr << "Listen failed: " << WSAGetLastError() << std::endl; closesocket(serverSocket); WSACleanup(); return 1; } std::cout << "Server listening on port 8080..." << std::endl; // 接受客户端连接 while (true) { sockaddr_in clientAddr; int clientSize = sizeof(clientAddr); SOCKET clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &clientSize); if (clientSocket == INVALID_SOCKET) { std::cerr << "Accept failed: " << WSAGetLastError() << std::endl; continue; } // 处理客户端连接 handleClient(clientSocket); } // 关闭服务器 Socket closesocket(serverSocket); // 清理 Winsock WSACleanup(); return 0; }
4.基于 Windows 系统的 C++ 实现HTTPS网络通信
-
初始化 Winsock:使用
WSAStartup
和WSACleanup
管理 Windows 套接字 API。 -
创建 SSL 上下文:通过 OpenSSL 提供的
SSL_CTX
来管理 SSL/TLS 上下文。服务器端需要加载证书和私钥。 -
创建并连接套接字:客户端通过标准套接字 API 连接到服务器,服务器则需要监听特定的端口。
-
SSL 握手:使用
SSL_connect
(客户端)或SSL_accept
(服务器)进行 TLS 握手,建立加密连接。 -
发送和接收数据:通过
SSL_write
和SSL_read
进行加密的数据传输。
1.客户端:
#include <iostream> #include <winsock2.h> #include <openssl/ssl.h> #include <openssl/err.h> #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "libssl.lib") #pragma comment(lib, "libcrypto.lib") void InitializeSockets() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); } void CleanupSockets() { WSACleanup(); } SSL_CTX* InitSSLContext() { SSL_library_init(); SSL_load_error_strings(); const SSL_METHOD* method = TLS_client_method(); return SSL_CTX_new(method); } int main() { // 初始化 Winsock InitializeSockets(); // 创建套接字 SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == INVALID_SOCKET) { std::cerr << "Socket creation failed!" << std::endl; CleanupSockets(); return -1; } // 连接到服务器 sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(443); // HTTPS 默认端口 serverAddr.sin_addr.s_addr = inet_addr("93.184.216.34"); // 示例IP地址,改为你的服务器IP if (connect(sock, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) { std::cerr << "Connection failed!" << std::endl; closesocket(sock); CleanupSockets(); return -1; } // 初始化 OpenSSL SSL_CTX* ctx = InitSSLContext(); SSL* ssl = SSL_new(ctx); SSL_set_fd(ssl, sock); // 建立 SSL 连接 if (SSL_connect(ssl) <= 0) { std::cerr << "SSL connection failed!" << std::endl; ERR_print_errors_fp(stderr); closesocket(sock); SSL_CTX_free(ctx); CleanupSockets(); return -1; } // 发送 HTTPS 请求 const char* request = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n"; SSL_write(ssl, request, strlen(request)); // 接收响应 char buffer[4096]; int bytesReceived; while ((bytesReceived = SSL_read(ssl, buffer, sizeof(buffer))) > 0) { std::cout.write(buffer, bytesReceived); } // 清理 SSL_shutdown(ssl); closesocket(sock); SSL_free(ssl); SSL_CTX_free(ctx); CleanupSockets(); return 0; }
2.服务器端
#include <iostream> #include <winsock2.h> #include <openssl/ssl.h> #include <openssl/err.h> #pragma comment(lib, "ws2_32.lib") #pragma comment(lib, "libssl.lib") #pragma comment(lib, "libcrypto.lib") void InitializeSockets() { WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData); } void CleanupSockets() { WSACleanup(); } SSL_CTX* InitSSLContext() { SSL_library_init(); SSL_load_error_strings(); const SSL_METHOD* method = TLS_server_method(); SSL_CTX* ctx = SSL_CTX_new(method); // 加载服务器证书 if (SSL_CTX_use_certificate_file(ctx, "server.crt", SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } // 加载服务器私钥 if (SSL_CTX_use_PrivateKey_file(ctx, "server.key", SSL_FILETYPE_PEM) <= 0) { ERR_print_errors_fp(stderr); exit(EXIT_FAILURE); } return ctx; } int main() { // 初始化 Winsock InitializeSockets(); // 创建监听套接字 SOCKET listenSock = socket(AF_INET, SOCK_STREAM, 0); if (listenSock == INVALID_SOCKET) { std::cerr << "Socket creation failed!" << std::endl; CleanupSockets(); return -1; } // 绑定端口 sockaddr_in serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(443); serverAddr.sin_addr.s_addr = INADDR_ANY; bind(listenSock, (sockaddr*)&serverAddr, sizeof(serverAddr)); listen(listenSock, 1); // 初始化 OpenSSL SSL_CTX* ctx = InitSSLContext(); while (true) { // 接受客户端连接 SOCKET clientSock = accept(listenSock, nullptr, nullptr); if (clientSock == INVALID_SOCKET) { std::cerr << "Accept failed!" << std::endl; continue; } // 创建 SSL 会话 SSL* ssl = SSL_new(ctx); SSL_set_fd(ssl, clientSock); // 进行 SSL 握手 if (SSL_accept(ssl) <= 0) { std::cerr << "SSL handshake failed!" << std::endl; ERR_print_errors_fp(stderr); closesocket(clientSock); SSL_free(ssl); continue; } // 接收客户端请求 char buffer[4096]; int bytesReceived = SSL_read(ssl, buffer, sizeof(buffer)); if (bytesReceived > 0) { buffer[bytesReceived] = '\0'; std::cout << "Received request: " << buffer << std::endl; // 发送响应 const char* response = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!"; SSL_write(ssl, response, strlen(response)); } // 关闭连接 SSL_shutdown(ssl); closesocket(clientSock); SSL_free(ssl); } // 清理 closesocket(listenSock); SSL_CTX_free(ctx); CleanupSockets(); return 0; }