当前位置:网站首页>QQ版网络聊天室完整项目+MFC\C++\C(更改服务器IP可实现异机沟通)

QQ版网络聊天室完整项目+MFC\C++\C(更改服务器IP可实现异机沟通)

2020-12-06 09:17:21 osc_wuji6g86

资源地址1

https://github.com/Msrumo/QChatRoom

资源地址2

https://gitee.com/it-future/QChatRoom

项目简介

声明::使用服务器IP来实现不同客户端的通信,本代码可能会有些许问题,需自行调试使用!
并不说明这个源码可以直接成功实现网络通信!仅本地局域网不出错!

设计功能

1.ChatRoom模仿QQ界面实现局域网消息互通;
2.用户注册获得ID,这些将保存在mysql数据库中,可以自定义头像、昵称等;
3.登录后,选中私信模式,在好友列表中可以双击选中某位好友,来进行私信聊天;
4.群聊世界,可以跟局域网内所有在线用户交流;
5.用户主界面额外设置了Bing网页搜索功能,供用户使用。



初始化套接字:
Socket创建套接字 :af地址族 tcp/IP都是 AF_INET
流式套接字是TCP,数据包套接字是UDP
0 选择合适协议


bind 本地地址与套接字关联起来 套接字,指针地址sockaddr(包含了IP地址,端口号),长度

创建线程
Createthread 第三个是线程函数 第4个为线程传入参数 LP代表长指针(使用结构体来传入多个值)

线程函数(使用静态的) 软化这个函数就属于内本身
使用一个死循环 不断的接受recvfrom 第二个参数是存数据的 第五个参数是用来存发送消息的地址信息(sockaddr)
Postmessage 把消息发送回对话框 该消息是消息响应的消息 第二个参数就是消息 后面两个是参数

服务器端部分代码

//响应客户端的信息
LRESULT CQServerDlg::OnSocket(WPARAM wParam, LPARAM lParam)
{
   
   
	CMySocket* sock = (CMySocket*)wParam;
	CMySocket* c;

	SOCKADDR_IN sockAddr;
	int nSize = sizeof(sockAddr);
	BOOL res;

	switch (lParam)
	{
   
   
		//新的连接消息
	case ACCEPT:
		c = new CMySocket;
		c->AttachCWnd(this);

		res = sock->Accept(*c, (SOCKADDR*)&sockAddr, &nSize);
		if (res == FALSE)
		{
   
   
			MessageBox(_T("Accept Error!"));
		}
		break;
		//关闭连接消息
	case CLOSE:
		ClosePlayer(sock);
		break;
		//收到数据消息,处理消息并发送
	case RETURN:
		ParserPkt(sock);
		break;
	}
	return 1;
}

客户端响应部分代码

//Socket消息响应函数
LRESULT CQClientDlg::OnSocket(WPARAM wParam, LPARAM lParam)
{
   
   
	char	pkt[4096];
	memset(pkt, 0, 4096);

	LVFINDINFO   info;
	LVITEM lvitem;

	switch (lParam)
	{
   
   
	case RETURN:
		m_socket.Receive(pkt, 4096);

		switch (pkt[0])
		{
   
   
		case 0x11:
			//连接信息
			pName[curNum] = pkt + 2;
			curNum++;
			m_showMsg += pkt + 2;
			m_showMsg += " 进入聊室。\r\n";

			lvitem.mask = LVIF_IMAGE | LVIF_TEXT;
			lvitem.iItem = curNum;
			lvitem.pszText = pkt + 2;
			lvitem.iImage = pkt[1] - 1;
			lvitem.iSubItem = 0;

			m_list.InsertItem(&lvitem);

			break;
			//已加入用户信息
		case 0x31:
			pName[curNum] = pkt + 2;
			curNum++;

			lvitem.mask = LVIF_IMAGE | LVIF_TEXT;
			lvitem.iItem = curNum;
			lvitem.pszText = pkt + 2;
			lvitem.iImage = pkt[1] - 0x31;
			lvitem.iSubItem = 0;

			m_list.InsertItem(&lvitem);
			break;
			//退出
		case 0x41:
			//pkt + 1保存的是用户名
			m_showMsg += pkt + 1;
			m_showMsg += " 退出聊室\r\n";

			info.flags = LVFI_PARTIAL | LVFI_STRING;
			info.psz = pkt + 1;
			int item;
			item = m_list.FindItem(&info);

			if (item != -1)
			{
   
   
				m_list.DeleteItem(item);
			}
			break;
		default:
			//对于没有任何命令的消息,直接显示在消息框中
			m_showMsg += pkt + 1;
		}

		UpdateData(false);
		break;

	case CLOSE:
		MessageBox("服务器已关闭!");
		break;
	}
	return 1;
}

数据库连接

//连接MYSQL数据库
BOOL CQClientDlg::ConnectDB()
{
   
   
	//初始化数据库
	mysql_init(&m_mysql);
	//设置数据库编码格式
	mysql_options(&m_mysql, MYSQL_SET_CHARSET_NAME, "gbk");
	//连接数据库
	if (!mysql_real_connect(&m_mysql, host, user, pass, dbname, port, NULL, 0))
		return FALSE;
	return TRUE;
}
void CQClientDlg::FreeConnect() {
   
   
	mysql_free_result(m_res);
	mysql_close(&m_mysql);
}
//查询获取数据
BOOL CQClientDlg::SelectDB()
{
   
   
	UpdateData(TRUE);

	char query[150];

	  //将数据格式化输出到字符串
	sprintf(query, "select * from Client");
	//设置编码格式
	mysql_query(&m_mysql, "set names gbk");

	if (mysql_query(&m_mysql, query)) {
   
   
	/*	printf("Query failed (%s)\n", mysql_error(&m_mysql));*/
		return false;
	}
	else {
   
   
		printf("query success\n");
	}
	m_res = mysql_store_result(&m_mysql);
	if (!m_res) {
   
   
	/*	printf("Couldn't get result from %s\n", mysql_error(&m_mysql));*/
		return false;
	}
	printf("number of dataline returned: %d\n", mysql_affected_rows(&m_mysql));

	int row = 0;
	//获取结果
	while (m_row = mysql_fetch_row(m_res)) {
   
   
		count++;
		m_data[row][0] = m_row[0];
		m_data[row][1] = m_row[1];
		m_data[row++][2] = m_row[2];
	}
	return TRUE;
}

理论~

编写主要过程

初始化套接字:
Socket创建套接字 :af地址族 tcp/IP都是 AF_INET
流式套接字是TCP,数据包套接字是UDP
0 选择合适协议


bind 本地地址与套接字关联起来 套接字,指针地址sockaddr(包含了IP地址,端口号),长度

创建线程
Createthread 第三个是线程函数 第4个为线程传入参数 LP代表长指针(使用结构体来传入多个值)

线程函数(使用静态的) 软化这个函数就属于内本身
使用一个死循环 不断的接受recvfrom 第二个参数是存数据的 第五个参数是用来存发送消息的地址信息(sockaddr)
Postmessage 把消息发送回对话框 该消息是消息响应的消息 第二个参数就是消息 后面两个是参数
CSocket类:


CSocket类是从CAsyncsocket派生而来的,它继承了CAsyncsocket对WindowsSockets API的封装。与CAsyncsocket对象相比,CSocket对象代表了WindowsSockets API的更高一级的抽象化。CSocket与类CSocketFile和CArchive一起来管理对数据的发送和接收。
一个CSocket对象也支持阻塞,这对于CArchive的同步操作来说是必要的。块操作函数,比如Receive,Send,ReceiveFrom,SendTo,和Accept(都是从CAsyncsocket继承来的),都不返回一个CSocket对象中的WSAEWOULDBLOCK错误。取而代之,这些函数等待,直到操作完成。另外,当这些函数中的某一个是阻塞的时,如果调用了CancelBlockingCall,则原来的调用将因为WSAEINTR错误而终止。
要使用一个CSocket对象,调用构造函数,然后调用Create来创建基础 插槽句柄(插槽类型)。Create的缺省参数创建一个插槽,但是如果你不是用一个CArchive对象来使用这个插槽,则你可以指定一个参数来创建一个数据包 插槽来代替,或者是结合一个指定的端口来创建一个服务器插槽。在客户方使用Connect,则服务器方使用Accept来与一个客户插槽连接。然后再创建一个CSocketFile对象,并在CSocketFile的构造函数中将它连接到CSocket对象上。再接着,创建一个CArchive对象用来发送数据,一个用来接收数据(如果需要),然后在CArchive构造函数中将它们与CSocketFile对象连接。当通讯完成后,销毁CArchive,CSocketFile,CSocket对象。

Tcp的过程是

服务器端:
首先是使用WSAstartup加载套接字,以及工程设置中设置ws2_32.lib文件

在使用socket创建套接字,数据报
Bind

Recvfrom用于接受数据

Sendto

心得与体会:
客户端这边没有定义端口号,每次创建socket的时候就会随机为其添加一个空闲的端口号

实例展示

服务器端:
在这里插入图片描述

登录框:
在这里插入图片描述
主界面:
在这里插入图片描述
聊天框:
在这里插入图片描述




有条件可以试试更改服务器IP可实现异机沟通,有任何问题欢迎私信讨论!
源码自行下载使用~~

版权声明
本文为[osc_wuji6g86]所创,转载请带上原文链接,感谢
https://my.oschina.net/u/4369588/blog/4776599