C++实现的基于多线程的TCP聊天智能机器人

文章目录

  • 1、功能需求
  • 2、功能分析
  • 3、知识梳理
    • 3.1 Socket解释
    • 3.2 TCP与UDP的区别
    • 3.3 Client/Server结构
  • 4、服务端与客户端代码代码设计思路
    • 4.1 TCP编程的客户端一般步骤
    • 4.2 TCP服务端程序编写的一般步骤
    • 4.3 服务端与客户端交互逻辑
    • 4.4 智能功能的实现
    • 4.5 多用户接入服务端的实现
  • 5、附属代码
    • 5.1 服务端代码
    • 5.2 客户端代码

1、功能需求

1、客户端连接聊天机器人服务器。
2、消息发送:客户端发送消息给机器人服务器。
3、消息接收:客户端接收到机器人服务器发送给他的消息。
4、可以有多个客户端同时连接。
5、智能回复功能:根据用户发送的消息内容,稍微有点智能回复。

2、功能分析

1、客户机需要连接到服务器后,才能发送消息给服务器,所以需要使用connect到服务器的ip地址;服务器需先listen到客户的请求,然后,然后在accept到客户机的ip地址,最后在相互收发信息。
2、客户机与服务器之间的消息发送采用一问一答形式,客户机询问一句,服务器打一句;关于智能回复功能使用的是C++文件读取,读取本地的智能语言库,进而实现智能回答。
3、采用listen对多个用户进行接入,限制最大连接客户数为5,采用多线程实现多个客户与服务器进行通信。
4、开发环境与开发工具的选择:
开发环境:Windows10
开发工具:Visual Studio 2019

3、知识梳理

3.1 Socket解释

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
C++实现的基于多线程的TCP聊天智能机器人_第1张图片

建立网络通信连接至少要一对端口号(socket)。socket本质是编程接口(API),对TCP/IP的封装,TCP/IP也要提供可供程序员做网络开发所用的接口,这就是Socket编程接口;HTTP是轿车,提供了封装或者显示数据的具体形式;Socket是发动机,提供了网络通信的能力。
C++实现的基于多线程的TCP聊天智能机器人_第2张图片
Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。

3.2 TCP与UDP的区别

TCP和UDP是OSI模型中的运输层中的协议。TCP提供可靠的通信传输,而UDP则常被用于让广播和细节控制交给应用的通信传输。
TCP:
TCP充分实现了数据传输时各种控制功能,可以进行丢包的重发控制,还可以对次序乱掉的分包进行顺序控制。而这些在UDP中都没有。此外,TCP作为一种面向有连接的协议,只有在确认通信对端存在时才会发送数据,从而可以控制通信流量的浪费。TCP通过检验和、序列号、确认应答、重发控制、连接管理以及窗口控制等机制实现可靠性传输。
UDP:
UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收到的那一刻,立刻按照原样发送到网络上的一种机制。即使是出现网络拥堵的情况下,UDP也无法进行流量控制等避免网络拥塞的行为。此外,传输途中如果出现了丢包,UDO也不负责重发。甚至当出现包的到达顺序乱掉时也没有纠正的功能。如果需要这些细节控制,那么不得不交给由采用UDO的应用程序去处理。换句话说,UDP将部分控制转移到应用程序去处理,自己却只提供作为传输层协议的最基本功能。UDP有点类似于用户说什么听什么的机制,但是需要用户充分考虑好上层协议类型并制作相应的应用程序。

3.3 Client/Server结构

Client/Server结构(C/S结构)是大家熟知的客户机和服务器结构。它是软件系统体系结构,通过它可以充分利用两端硬件环境的优势,将任务合理分配到Client端和Server端来实现,降低了系统的通讯开销。目前大多数应用软件系统都是Client/Server形式的两层结构,由于现在的软件应用系统正在向分布式的Web应用发展,Web和Client/Server 应用都可以进行同样的业务处理,应用不同的模块共享逻辑组件;因此,内部的和外部的用户都可以访问新的和现有的应用系统,通过现有应用系统中的逻辑可以扩展出新的应用系统。这也就是目前应用系统的发展方向。C++实现的基于多线程的TCP聊天智能机器人_第3张图片

4、服务端与客户端代码代码设计思路

4.1 TCP编程的客户端一般步骤

1、WinSock头文件以及Socket库的引入#include//WindowsSocket编程头文件
#pragma comment(lib,“ws2_32.lib”)//链接ws2_32.lib库文件到此项目中
2、Socket库的初始化,创建客服端和服务端Socket,以及客户端和服务端的地址包的初始化
3、绑定IP地址、端口等信息到socket上,用函数bind();* 可选。
4、设置要连接的对方的IP地址和端口等属性;
5、连接服务器,用函数connect();
6、收发数据,用函数send()和recv(),或者read()和write();
7、关闭网络连接,使用函数closesocket();

4.2 TCP服务端程序编写的一般步骤

1、WinSock头文件以及Socket库的引入#include//WindowsSocket编程头文件
#pragma comment(lib,“ws2_32.lib”)//链接ws2_32.lib库文件到此项目中
2、Socket库的初始化,创建客户端和服务端Socket,以及客户端和服务端的地址包的初始化
3、创建一个socket,用函数socket();
4、设置socket属性,用函数setsockopt(); * 可选
5、绑定IP地址、端口等信息到socket上,用函数bind();
6、开启监听,用函数listen();
7、接收客户端上来的连接,用函数accept();
8、收发数据,用函数send()和recv(),或者read()和write();
9、关闭网络连接,使用函数closesocket();
10、关闭监听;

4.3 服务端与客户端交互逻辑

C++实现的基于多线程的TCP聊天智能机器人_第4张图片

4.4 智能功能的实现

1、建立一个info.txt文件作为智能回复语言库。
2、当服务器接收到客户机发来的消息时,先与智能语言库中的语言进行匹配,若匹配,则发送txt文件中已写好的回复消息给客户机;若不匹配,则进行服务端实行人工回复。
注:每次匹配到一个智能语言或读取到文件末尾时,文件流指针需返回文件首,这样方便下一次匹配。
3、通话结束,关闭info.txt文件。
4、效果展示:
C++实现的基于多线程的TCP聊天智能机器人_第5张图片

4.5 多用户接入服务端的实现

1、编写线程函数DWORD WINAPI ServerThread(LPVOID lpParameter)/服务器线程/,实现单个客户的智能回复
2、在主函中调用CreateThread(NULL, 0, &ServerThread,sockCli, 0, NULL);这个多线程函数实现多个用户接入到服务端实现通信聊天
3、效果展示:
C++实现的基于多线程的TCP聊天智能机器人_第6张图片

5、附属代码

5.1 服务端代码

#include
#include
#include
#include
using namespace std;
#include				//WinSocket库
#pragma comment(lib,"ws2_32.lib")	//链接ws2_32.lib库文件

/*===================全局变量==================*/
const int BUF_SIZE = 2048;
SOCKADDR_IN addrSer, addrCli;				//address
int naddr = sizeof(SOCKADDR_IN);			//服务器长度
char sendbuf[BUF_SIZE] = {0};				//发送数据
char recvbuf[BUF_SIZE] = {0};				//接送数据
/*=============================================*/

/* 
LPVOIP的类型是void * 
DWOERD类型是unsigned long
*/
DWORD WINAPI ServerThread(LPVOID lpParameter)/*服务器线程*/
{
	SOCKET *sockCli = (SOCKET *)lpParameter;
	ifstream in("info.txt", ifstream::in);//读取文件
	//ofstream out("info.txt", ifstream::out | ifstream::app);//写入文件
	if (!in)
	{
		cout << "智能回复语言库打开失败!" << endl;
		system("pause");
		return -1;
	}
	while (1) 
	{
		int receByt = recv(*sockCli, recvbuf, sizeof(recvbuf), 0);
		if (receByt > 0)
			cout << "客户:" << recvbuf << "\t端口号为:" << *sockCli << endl;
		else
		{
			cout << "接收消息结束!" << endl;
			break;
		}
		if (recvbuf[0] == 'n' || recvbuf[0] == 'N')
		{
			cout << "客户" << *socket << "已下线!" << endl;
			MessageBox(NULL, "客户端程序关闭",LPCSTR(*socket), MB_OK);
			break;
		}
		//cout << inet_ntoa(addrCli.sin_addr) << ":" << recvbuf << endl;
		string line;
		while (getline(in, line))
		{
			const char *temp = line.c_str();
			if (temp[0] == 'Q')
			{
				char szTemp[BUF_SIZE] = { 0 };
				strcpy(szTemp, temp + 2);
				if (strcmp(szTemp, recvbuf) == 0)
				{
					getline(in, line);
					temp = line.c_str();
					//send(sockCli, temp + 2, strlen(temp)-1, 0);
					strcpy_s(sendbuf, temp + 2);
					//in.set_rdbuf(0);
					in.clear();
					streampos sp = in.tellg();//文件的大小
					in.seekg(-sp, ios::cur);//文件指针返回第一行
					sp = in.tellg();
					break;
				}
			}
			if (temp[0] == '#')
			{
				cout << "您说的东西我听不懂,我请主人为您解答!" << endl;
				//in.set_rdbuf(0);
				in.clear();
				streampos sp = in.tellg();
				in.seekg(-sp, ios::cur);

				break;
			}
			if (temp[0] == 'A')
				continue;

		}
		/*清空字符缓存*/
		memset(recvbuf, 0, sizeof(recvbuf));
		/*服务器回复消息*/
		if (strlen(sendbuf) != 0)
		{
			cout << "服务器:" << sendbuf << endl;
		}
		else
		{
			cout << "服务器:";
			cin >> sendbuf;
			cin.clear();
			/*写入智能语言库*/
			//out << "Q:" << recvbuf << endl;
			//out << "A:" << sendbuf << endl;
		}
		int k=send(*sockCli, sendbuf, sizeof(sendbuf), 0);
		if (k < 0) {
			cout << "发送失败" << endl;
		}
		memset(sendbuf, '\0', sizeof(sendbuf));
	}//while
	/*关闭智能语言库 */
	//out.close();
	in.close();
	closesocket(*sockCli);
	free(sockCli);
	return 0;
}

int main()
{
	system("color 05");

	/*初始化socket库*/
	WSADATA wsadata;
	if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata))
	{
		cout << "socket库载入失败!" << endl;
		system("pause");
		return -1;
	}
	else cout << "socket库载入成功!" << endl;
	
	/*创建套接字socket*/
	SOCKET sockSer = socket(AF_INET, SOCK_STREAM, 0);//服务器套接字
	//SOCKET sockCli;									 //客服端套接字

	/*套接字的初始化*/
	//addrSer.sin_addr.S_un.S_addr = INADDR_ANY;	//所有ip地址
	addrSer.sin_addr.s_addr = inet_addr("192.168.43.65");
	addrSer.sin_family = AF_INET;					//IP地址族
	addrSer.sin_port = htons(1111);					//端口号
	
	/*套接字绑定ip地址*/
	bind(sockSer, (SOCKADDR *)& addrSer, sizeof(SOCKADDR));
	/*开始监听来自客户的服务请求,最多连接5个客户*/
	listen(sockSer, 5);
	/*进入监听状态,接收客服机发送请求*/
	cout << "服务器开启成功,等待客户连接..." << endl;
	while (1)
	{	
		SOCKET *sockCli = new SOCKET;
		*sockCli= accept(sockSer, (SOCKADDR *)& addrCli, &naddr);
		cout << "一个服务器已连接客户端,端口号为:" << *sockCli << endl;
		CreateThread(NULL, 0, &ServerThread,sockCli, 0, NULL);

	}
	/*关闭套接字接口*/
	closesocket(sockSer);
	//closesocket(sockCli);
	WSACleanup();
}

5.2 客户端代码

#include
#include
#include  //WindowsSocket编程头文件
#include
#include
#pragma comment(lib,"ws2_32.lib")//链接ws2_32.lib库文件到此项目中
using namespace std;

//================全局常量==================
//创建缓冲区
const int BUF_SIZE = 2048;
//================全局变量==================
SOCKET sockSer, sockCli;			//套接字
SOCKADDR_IN addrSer, addrCli;		//address client,service
int naddr = sizeof(SOCKADDR_IN);	//
char zh[] = "好的,结束通话!";
char sendbuf[BUF_SIZE];				//发送数据
char recvbuf[BUF_SIZE];				//接收数据


int main() {
	//加载socket库
	system("color 03");
	cout << "客户端启动" << endl;
	WSADATA wsadata;
	if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
	{
		//输出出错信息
		cout << "载入socket库失败!" << endl;
		system("pause");
		return 0;
	}
	else {
		cout << "载入socket库成功!" << endl;
	}
	//创建Soucket;
	/*
	函数原型:SOCKET socket(int af,int type,int protocol);
	af:int类型,代表套接字使用的那种网络地址,对于IP地址族,该参数一般
	*/
	sockCli = socket(AF_INET, SOCK_STREAM, 0);
	//描述协议族,INET属于ipv4;
	//sock_stream创建套接字类型:tcp;
	//0不指定协议,常用的协议有tcp、udp等

	//初始化客户端地址包
	addrCli.sin_addr.s_addr = inet_addr("127.0.0.1");
	addrCli.sin_family = AF_INET;
	addrCli.sin_port = htons(1111);

	//初始化服务器地址
	addrSer.sin_addr.s_addr = inet_addr("192.168.43.65");
	addrSer.sin_family = AF_INET;
	addrSer.sin_port = htons(1111);

	while (true)
	{
		cout << "开始连接..." << endl;
		/*connect到服务端*/
		if (connect(sockCli, (SOCKADDR *)& addrSer, sizeof(addrSer)) != SOCKET_ERROR)
		{
			cout << "客户端连接成功" << endl;
			cout << "欢迎来到聊天室,输入n结束聊天!" << endl;
			while (true)
			{
				//发送给服务器信息
				cout << "客户:";
				cin >> sendbuf;
				cout << "等待服务器响应..." << endl;
				/*客户端发送信息*/
				send(sockCli, sendbuf, sizeof(sendbuf), 0);

			    /*接收服务器发来的消息*/
				recv(sockCli, recvbuf, sizeof(recvbuf), 0);
				if (recvbuf == zh)/*结束通话*/
				{
					MessageBox(NULL, "客户端程序关闭", "客户端", MB_OK);
					return 0;
				}
				cout << "服务器:" << recvbuf << endl;
			}

		}
		else
		{
			cout << "客户端连接失败" << endl;
		}
	}
	closesocket(sockSer);
	closesocket(sockCli);
	WSACleanup();
	return 0;
}

你可能感兴趣的:(计算机网络)