栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

一个基于C++的简单多线程socket服务器实例,将自己的网页上传到IP+端口上(局域网)VS2017

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

一个基于C++的简单多线程socket服务器实例,将自己的网页上传到IP+端口上(局域网)VS2017

概要

最近在做网络编程的时候遇到了亿点点困难,主要实现目标是:

  1. 打开浏览器,输入http://主机IP地址:8080,浏览器上显示主页(html文件)。
  2. 能够控制用户是否能访问http://主机IP地址:8080内容

一、使用socket来做一个小型的服务器,主要的步骤如下所示:

  1. 导入主要的头文件
  2. 创建并加载一个网络库,然后对其设置版本号
  3. 创建服务器端的SOCKET对象,然后配置并绑定其IP地址、端口号、填充字节(这是啥我也不懂哈哈哈哈)、协议族
  4. 设置服务器端口进行监听,以及设置最大连接数量
  5. 创建客户机SOCKET对象,等待连接该客户机对象和服务端对象
  6. 连接成功后,先接收网页那边发来的请求报头,然后就能把我们的网页文件上传过去显示了
  7. 每次发送过去之后就关闭该客户机的连接,因为这是短连接

二、使用pthread多线程对socket服务器进行控制

  1. 导入主要的头文件
  2. 定义一个全局的flag用来判断当前是否允许客户机和服务器进行连接
  3. 创建一个子线程用于控制这个flag的变化
  4. 创建一个子线程用于显示一些必要的提示信息
  5. 修改客户机连接服务器的条件使其受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老是被提示不安全,然后就不让我编译通过,真是太可恶了,只好去找博客来解决,最后的解决方法有两步:

  1. 在代码文件里的第一行添加一行#define _CRT_SECURE_NO_WARNINGS,也就是添加在所有include的上面。
  2. 点击“选项”---“***属性”---“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;
}

        最后附上测试结果:

 

 

 

 

 

总结

        这次网络实践作业属实搞人心态,受不了啦。编程新手写的乐色代码,大家看着玩好了,欢迎指出批评。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/913284.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号