最近在做网络编程的时候遇到了亿点点困难,主要实现目标是:
- 打开浏览器,输入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#include #include // 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;
}
// 准备配置数据
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、设置服务器端口进行监听,以及设置最大连接数量
listen(servSocket, 10);
5、创建客户机SOCKET对象,等待连接该客户机对象和服务端对象
// 为客户机创建对象
sockaddr_in clntAddr;
cout << "等待连接" << endl;
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] = "wsfwq";
char head_buf[] = "HTTP/1.1 200 OKrnAccept-Ranges: bytesrn
Content-Length: 1024rn
Content-Type: text/html;charset=UTF-8rn
Connection: closernrn";
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);
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代码,如下
我是服务器
7、每次发送过去之后就关闭该客户机的连接,因为这是短连接
closesocket(clientSock);
最后附上以上内容的全部代码:
#define _CRT_SECURE_NO_WARNINGS #include#include #include // socket的头文件 using namespace std; //连接winsock2.h的静态库文件 #pragma comment(lib, "ws2_32.lib") int onFlag = 1; void closeSocket(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] = "wsfwq"; char head_buf[] = "HTTP/1.1 200 OKrnAccept-Ranges: bytesrn Content-Length: 1024rn Content-Type: text/html;charset=UTF-8rn Connection: closernrn"; 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); 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; } // 准备配置数据 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; } listen(servSocket, 10); // 为客户机创建对象 sockaddr_in clntAddr; while (1) { cout << "等待连接" << endl; 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#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;
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#include #include // socket的头文件 #include #include //#include 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) { // 释放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] = "wsfwq"; char head_buf[] = "HTTP/1.1 200 OKrnAccept-Ranges: bytesrn Content-Length: 1024rn Content-Type: text/html;charset=UTF-8rn Connection: closernrn"; 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); 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; } // 准备配置数据 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; } listen(servSocket, 10); // 为客户机创建对象 sockaddr_in clntAddr; while (1) { while (onFlag) { cout << "等待连接" << endl; 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; }
最后附上测试结果:
总结
这次网络实践作业属实搞人心态,受不了啦。编程新手写的乐色代码,大家看着玩好了,欢迎指出批评。



