概要
最近在做网络编程的时候遇到了亿点点困难,主要实现目标是:
- 打开浏览器,输入http://主机IP地址:8080,浏览器上显示主页(html文件)。
- 能够控制用户是否能访问http://主机IP地址:8080内容
一、使用socket来做一个小型的服务器,主要的步骤如下所示:
- 导入主要的头文件
- 创建并加载一个网络库,然后对其设置版本号
- 创建服务器端的SOCKET对象,然后配置并绑定其IP地址、端口号、填充字节(这是啥我也不懂哈哈哈哈)、协议族
- 设置服务器端口进行监听,以及设置最大连接数量
- 创建客户机SOCKET对象,等待连接该客户机对象和服务端对象
- 连接成功后,先接收网页那边发来的请求报头,然后就能把我们的网页文件上传过去显示了
- 每次发送过去之后就关闭该客户机的连接,因为这是短连接
二、使用pthread多线程对socket服务器进行控制
- 导入主要的头文件
- 定义一个全局的flag用来判断当前是否允许客户机和服务器进行连接
- 创建一个子线程用于控制这个flag的变化
- 创建一个子线程用于显示一些必要的提示信息
- 修改客户机连接服务器的条件使其受flag控制
细述
1、首先,要使用socket进行编程,需要引入基本的头文件:WinSock2.h,还有winsock2的静态库文件ws2_32.lib
#include <iostream>
#include <stdio.h>
#include <WinSock2.h> // socket的头文件
//连接winsock2.h的静态库文件
#pragma comment(lib, "ws2_32.lib")
2、在main函数里面定义WSADATA,然后设置版本号
// 加载网络库
WSADATA wsaData;
// 设置版本号
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cout << "网络库加载失败" << endl;
return 0;
}
3、创建服务器端的SOCKET对象,然后配置并绑定其IP地址、端口号、填充字节(这是啥我也不懂哈哈哈哈)、协议族
// 创建服务器端的socket,里面有三个参数,第一个是协议族的类型,第二个是STREAM类型的套接字,第三个是TCP
// 细节可以看看https://blog.csdn.net/mabin2005/article/details/120056945
SOCKET servSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (servSocket == INVALID_SOCKET) {
cout << "socket创建失败!" << endl;
return 0;
}
// 准备配置数据
/*
服务器的socket地址
包含:
1.sin_addr : IP地址
2.sin_port : 端口号
3.sin_zero : 填充字节
4.sin_family : 协议簇
*/
sockaddr_in servAddr;
// 初始化socket地址
memset(&servAddr, 0, sizeof(SOCKADDR));
// 设置使用的协议簇
servAddr.sin_family = AF_INET;
// 设置使用的端口
// htons 把本地字节序转为网络字节序
servAddr.sin_port = htons(8080);
// INADDR_ANY就是0.0.0.0的地址,表示为任意地址
servAddr.sin_addr.s_addr = INADDR_ANY;
// 绑定配置到servSocket上
if (bind(servSocket, (SOCKADDR*)&servAddr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "绑定失败" << endl;
return 0;
}
4、设置服务器端口进行监听,以及设置最大连接数量
/*
设置监听服务器端口
第二个参数指连接的最大数量
SOMAXCONN 指系统自动选择最合适的连接个数
*/
listen(servSocket, 10);
5、创建客户机SOCKET对象,等待连接该客户机对象和服务端对象
// 为客户机创建对象
sockaddr_in clntAddr;
cout << "等待连接" << endl;
/*
accept函数: `
一共三个参数:
1.SOCKET : 服务端的SOCKET对象,用于判断是否接受连接
2.sockaddr : 接受客户端的IP地址和端口号,然后判断与服务端是否一致
3.int *addrlen : 不知道,暂时
*/
int nSize = sizeof(clntAddr);
SOCKET clientSock = accept(servSocket, (SOCKADDR*)&clntAddr, &nSize);
if (!onFlag) {
closesocket(clientSock);
continue;
}
// 判断是否连接成功
if (clientSock == INVALID_SOCKET) {
cout << "连接失败" << endl;
return 0;
}
cout << "新的一个连接成功" << endl;
6、连接成功后,先接收网页那边发来的请求报头,然后就能把我们的网页文件上传过去显示了
// 处理连接请求
char recvBuf[1024] = "";
if (recv(clientSock, recvBuf, sizeof(recvBuf), 0) <= 0) {
cout << "接收出错" << endl;
break;
}
cout << "接收到数据: " << recvBuf << endl;
// 给客户端发送文本
char filePath[128] = "index.html";
// 发送文件
sendHtml(clientSock, filePath);
????????发送网页文件的代码如下:
void sendHtml(SOCKET clientSocket, const char* filePath) {
FILE* pr = fopen(filePath, "r");
if (pr == NULL) {
cout << "文件打开失败!" << endl;
exit(-1);
}
char data[1024] = "<html><body><h1>wsfwq</h1></body></html>";
char head_buf[] = "HTTP/1.1 200 OK\r\nAccept-Ranges: bytes\r\n\
Content-Length: 1024\r\n\
Content-Type: text/html;charset=UTF-8\r\n\
Connection: close\r\n\r\n";
int ret = send(clientSocket, head_buf, strlen(head_buf), 0);
if (ret == -1)
{
perror("send failed");
closesocket(clientSocket);
return;
}
//send(clientSocket, data, strlen(data), 0);
/*
发送数据index.html
send函数:
四个参数:
1.目标SOCKET
2.const* char,也就是内容
3.发送数据长度
4.flags:不知道。。。
*/
do {
fgets(data, 1024, pr);
send(clientSocket, data, strlen(data), 0);
} while (!feof(pr));
fclose(pr);
}
? ? ? ? 新手学习之路总是坎坷的,在这里我遇到了一个问题,就是这个fopen老是被提示不安全,然后就不让我编译通过,真是太可恶了,只好去找博客来解决,最后的解决方法有两步:
- 在代码文件里的第一行添加一行#define _CRT_SECURE_NO_WARNINGS,也就是添加在所有include的上面。
- 点击“选项”---“***属性”---“C/C++”---“预处理器”---“预处理器定义”,然后在里面添加上一行_CRT_SECURE_NO_WARNINGS? ? ?如图所示
? ? ? ? 这样应该就没啥问题了,还有问题就G了
? ? ? ? 还有要注意的是,在工程里要提前写好一个index.html文件,或者将上上面代码里的filePath改成自己的html文件路径也行。我用的是相对路径也就是把文件放在了工程下,如下图所示。
? ? ? ? ?里面的内容是一个非常简单的html代码,如下
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>
<div>
<h1>
我是服务器
</h1>
</div>
</body>
</html>
7、每次发送过去之后就关闭该客户机的连接,因为这是短连接
closesocket(clientSock);
最后附上以上内容的全部代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdio.h>
#include <WinSock2.h> // socket的头文件
using namespace std;
//连接winsock2.h的静态库文件
#pragma comment(lib, "ws2_32.lib")
int onFlag = 1;
void closeSocket(SOCKET socket) {
/*
关闭socket
*/
// 释放winsock
closesocket(socket);
// 关闭网络库
if (WSACleanup()) {
cout << "网络库关闭失败" << endl;
}
cout << "服务器连接已关闭。" << endl;
}
void sendHtml(SOCKET clientSocket, const char* filePath) {
FILE* pr = fopen(filePath, "r");
if (pr == NULL) {
cout << "文件打开失败!" << endl;
exit(-1);
}
char data[1024] = "<html><body><h1>wsfwq</h1></body></html>";
char head_buf[] = "HTTP/1.1 200 OK\r\nAccept-Ranges: bytes\r\n\
Content-Length: 1024\r\n\
Content-Type: text/html;charset=UTF-8\r\n\
Connection: close\r\n\r\n";
int ret = send(clientSocket, head_buf, strlen(head_buf), 0);
if (ret == -1)
{
perror("send failed");
closesocket(clientSocket);
return;
}
//send(clientSocket, data, strlen(data), 0);
/*
发送数据index.html
send函数:
四个参数:
1.目标SOCKET
2.const* char,也就是内容
3.发送数据长度
4.flags:不知道。。。
*/
do {
fgets(data, 1024, pr);
send(clientSocket, data, strlen(data), 0);
} while (!feof(pr));
fclose(pr);
}
int main()
{
// 加载网络库
WSADATA wsaData;
// 设置版本号
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cout << "网络库加载失败" << endl;
return 0;
}
// 创建服务器端的socket
SOCKET servSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (servSocket == INVALID_SOCKET) {
cout << "socket创建失败!" << endl;
return 0;
}
// 准备配置数据
/*
服务器的socket地址
包含:
1.sin_addr : IP地址
2.sin_port : 端口号
3.sin_zero : 填充字节
4.sin_family : 协议簇
*/
sockaddr_in servAddr;
// 初始化socket地址
memset(&servAddr, 0, sizeof(SOCKADDR));
// 设置使用的协议簇
servAddr.sin_family = AF_INET;
// 设置使用的端口
// htons 把本地字节序转为网络字节序
servAddr.sin_port = htons(8080);
// INADDR_ANY就是0.0.0.0的地址,表示为任意地址
servAddr.sin_addr.s_addr = INADDR_ANY;
// 绑定配置到servSocket上
if (bind(servSocket, (SOCKADDR*)&servAddr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "绑定失败" << endl;
return 0;
}
/*
设置监听服务器端口
第二个参数指连接的最大数量
SOMAXCONN 指系统自动选择最合适的连接个数
*/
listen(servSocket, 10);
// 为客户机创建对象
sockaddr_in clntAddr;
while (1) {
cout << "等待连接" << endl;
/*
accept函数: `
一共三个参数:
1.SOCKET : 服务端的SOCKET对象,用于判断是否接受连接
2.sockaddr : 接受客户端的IP地址和端口号,然后判断与服务端是否一致
3.int *addrlen : 不知道,暂时
*/
int nSize = sizeof(clntAddr);
SOCKET clientSock = accept(servSocket, (SOCKADDR*)&clntAddr, &nSize);
if (!onFlag) {
closesocket(clientSock);
continue;
}
// 判断是否连接成功
if (clientSock == INVALID_SOCKET) {
cout << "连接失败" << endl;
return 0;
}
cout << "新的一个连接成功" << endl;
// 处理连接请求
char recvBuf[1024] = "";
if (recv(clientSock, recvBuf, sizeof(recvBuf), 0) <= 0) {
cout << "接收出错" << endl;
break;
}
cout << "接收到数据: " << recvBuf << endl;
// 给客户端发送文本
char filePath[128] = "index.html";
// 发送文件
sendHtml(clientSock, filePath);
closesocket(clientSock);
}
//closesocket(clientSock);
closeSocket(servSocket);
system("pause");
return 0;
}
????????最后附上运行的结果
?
二、使用pthread多线程对socket服务器进行控制
1、导入主要的头文件,以及连接pthread的静态库文件pthreadVC2.lib
#include <pthread.h>
#pragma comment(lib, "pthreadVC2.lib")
? ? ? ? 这里我是第一次使用pthread这个库,然后发现居然没有,我尝试换成C++11的thread库,但是不知道为什么thread库和websock2.h冲突,导致我用不了,而且找了好久也找不到解决方案,最后还是回到pthread里。主要的解决方法可以参考这篇博客,我觉得写得很好:Windows下C++使用pthread错误解决方法_SeanOY的博客-CSDN博客_c++pthread.h
2、定义一个全局的flag用来判断当前是否允许客户机和服务器进行连接
? ? ? ? 这里就比较随意了,我定义了一个int型的onFlag
int onFlag = 1;
3、创建一个子线程用于控制这个flag的变化
? ? ? ? 在main函数里写以下代码
// 定义一个子线程
pthread_t thread;
// 创建子线程用于控制onFlag
pthread_create(&thread, NULL, flagControl, (void*)(0));
? ? ? ? 然后在main外面写一个函数flagControl,其实也就是通过输入字符ch并回车来判断这个字符,然后达到改变onFlag的效果。
void* flagControl(void* args) {
char ch;
while (1) {
cin >> ch;
if (ch == 'e') {
onFlag = 0;
cout << "已退出连接!" << endl;
}
else if (ch == 's') {
onFlag = 1;
cout << "你又可以连接了!" << endl;
}
}
return NULL;
}
4、创建一个子线程用于显示一些必要的提示信息
? ? ? ? 在main里面写入以下代码
// 创建子进程用于显示提示信息
pthread_t threadInfo;
pthread_create(&threadInfo, NULL, showInfo, (void*)(0));
? ? ? ? 然后在main外面写一个函数showInfo,这个函数就是根据onFlag的变化不停输出不同的信息,每次输出都与上一次间隔一秒。
void* showInfo(void* args) {
while (1) {
if (onFlag) {
cout << "enter 'e' to pause" << endl;
Sleep(1000);
}
else {
cout << "enter 's' to start" << endl;
Sleep(1000);
}
}
return NULL;
}
5、修改客户机连接服务器的条件使其受flag控制
? ? ? ?这里将onFlag用上来控制连接。
while (1) {
while (onFlag) {
cout << "等待连接" << endl;
/*
accept函数: `
一共三个参数:
1.SOCKET : 服务端的SOCKET对象,用于判断是否接受连接
2.sockaddr : 接受客户端的IP地址和端口号,然后判断与服务端是否一致
3.int *addrlen : 不知道,暂时
*/
int nSize = sizeof(clntAddr);
SOCKET clientSock = accept(servSocket, (SOCKADDR*)&clntAddr, &nSize);
if (!onFlag) {
closesocket(clientSock);
continue;
}
// 判断是否连接成功
if (clientSock == INVALID_SOCKET) {
cout << "连接失败" << endl;
return 0;
}
cout << "新的一个连接成功" << endl;
// 处理连接请求
char recvBuf[1024] = "";
if (recv(clientSock, recvBuf, sizeof(recvBuf), 0) <= 0) {
cout << "接收出错" << endl;
break;
}
cout << "接收到数据: " << recvBuf << endl;
// 给客户端发送文本
char filePath[128] = "index.html";
// 发送文件
sendHtml(clientSock, filePath);
closesocket(clientSock);
}
}
? ? ? ? 以下是所有的代码
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdio.h>
#include <WinSock2.h> // socket的头文件
#include <stdlib.h>
#include <pthread.h>
//#include <thread>
using namespace std;
//std::thread::id main_thread_id = std::this_thread::get_id();
//连接winsock2.h的静态库文件
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "pthreadVC2.lib")
int onFlag = 1;
void closeSocket(SOCKET socket) {
/*
关闭socket
*/
// 释放winsock
closesocket(socket);
// 关闭网络库
if (WSACleanup()) {
cout << "网络库关闭失败" << endl;
}
cout << "服务器连接已关闭。" << endl;
}
void sendHtml(SOCKET clientSocket, const char* filePath) {
FILE* pr = fopen(filePath, "r");
if (pr == NULL) {
cout << "文件打开失败!" << endl;
exit(-1);
}
char data[1024] = "<html><body><h1>wsfwq</h1></body></html>";
char head_buf[] = "HTTP/1.1 200 OK\r\nAccept-Ranges: bytes\r\n\
Content-Length: 1024\r\n\
Content-Type: text/html;charset=UTF-8\r\n\
Connection: close\r\n\r\n";
int ret = send(clientSocket, head_buf, strlen(head_buf), 0);
if (ret == -1)
{
perror("send failed");
closesocket(clientSocket);
return;
}
//send(clientSocket, data, strlen(data), 0);
/*
发送数据index.html
send函数:
四个参数:
1.目标SOCKET
2.const* char,也就是内容
3.发送数据长度
4.flags:不知道。。。
*/
do {
fgets(data, 1024, pr);
send(clientSocket, data, strlen(data), 0);
} while (!feof(pr));
fclose(pr);
}
void* flagControl(void* args) {
char ch;
while (1) {
cin >> ch;
if (ch == 'e') {
onFlag = 0;
cout << "已退出连接!" << endl;
}
else if (ch == 's') {
onFlag = 1;
cout << "你又可以连接了!" << endl;
}
}
return NULL;
}
void* showInfo(void* args) {
while (1) {
if (onFlag) {
cout << "enter 'e' to pause" << endl;
Sleep(1000);
}
else {
cout << "enter 's' to start" << endl;
Sleep(1000);
}
}
return NULL;
}
int main()
{
// 定义一个子线程
pthread_t thread;
// 创建子线程用于控制onFlag
pthread_create(&thread, NULL, flagControl, (void*)(0));
// 创建子进程用于显示提示信息
pthread_t threadInfo;
pthread_create(&threadInfo, NULL, showInfo, (void*)(0));
// 加载网络库
WSADATA wsaData;
// 设置版本号
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
cout << "网络库加载失败" << endl;
return 0;
}
// 创建服务器端的socket
SOCKET servSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (servSocket == INVALID_SOCKET) {
cout << "socket创建失败!" << endl;
return 0;
}
// 准备配置数据
/*
服务器的socket地址
包含:
1.sin_addr : IP地址
2.sin_port : 端口号
3.sin_zero : 填充字节
4.sin_family : 协议簇
*/
sockaddr_in servAddr;
// 初始化socket地址
memset(&servAddr, 0, sizeof(SOCKADDR));
// 设置使用的协议簇
servAddr.sin_family = AF_INET;
// 设置使用的端口
// htons 把本地字节序转为网络字节序
servAddr.sin_port = htons(8080);
// INADDR_ANY就是0.0.0.0的地址,表示为任意地址
servAddr.sin_addr.s_addr = INADDR_ANY;
// 绑定配置到servSocket上
if (bind(servSocket, (SOCKADDR*)&servAddr, sizeof(SOCKADDR)) == SOCKET_ERROR) {
cout << "绑定失败" << endl;
return 0;
}
/*
设置监听服务器端口
第二个参数指连接的最大数量
SOMAXCONN 指系统自动选择最合适的连接个数
*/
listen(servSocket, 10);
// 为客户机创建对象
sockaddr_in clntAddr;
while (1) {
while (onFlag) {
cout << "等待连接" << endl;
/*
accept函数: `
一共三个参数:
1.SOCKET : 服务端的SOCKET对象,用于判断是否接受连接
2.sockaddr : 接受客户端的IP地址和端口号,然后判断与服务端是否一致
3.int *addrlen : 不知道,暂时
*/
int nSize = sizeof(clntAddr);
SOCKET clientSock = accept(servSocket, (SOCKADDR*)&clntAddr, &nSize);
if (!onFlag) {
closesocket(clientSock);
continue;
}
// 判断是否连接成功
if (clientSock == INVALID_SOCKET) {
cout << "连接失败" << endl;
return 0;
}
cout << "新的一个连接成功" << endl;
// 处理连接请求
char recvBuf[1024] = "";
if (recv(clientSock, recvBuf, sizeof(recvBuf), 0) <= 0) {
cout << "接收出错" << endl;
break;
}
cout << "接收到数据: " << recvBuf << endl;
// 给客户端发送文本
char filePath[128] = "index.html";
// 发送文件
sendHtml(clientSock, filePath);
closesocket(clientSock);
}
}
//closesocket(clientSock);
closeSocket(servSocket);
system("pause");
return 0;
}
? ? ? ? 最后附上测试结果:
?
?
?
?
?
总结
? ? ? ? 这次网络实践作业属实搞人心态,受不了啦。编程新手写的乐色代码,大家看着玩好了,欢迎指出批评。
|