资源地址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可实现异机沟通,有任何问题欢迎私信讨论!
源码自行下载使用~~