IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 一个基于C++的简单多线程socket服务器实例,将自己的网页上传到IP+端口上(局域网)VS2017 -> 正文阅读

[网络协议]一个基于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 <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老是被提示不安全,然后就不让我编译通过,真是太可恶了,只好去找博客来解决,最后的解决方法有两步:

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

? ? ? ? 最后附上测试结果:

?

?

?

?

?

总结

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

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-05-21 19:17:01  更:2022-05-21 19:17:32 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 11:13:25-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码