使用winsock重叠I/O模型实现简单网络词典【C++,数据库】

使用winsock重叠I/O模型实现简单网络词典【C++,MFC,数据库】

1. 配置ODBC数据源,能够使用C访问数据库,创建数据库ZIDIAN,添加表格ZIDIAN,两列为ENG和CHI
参考https://blog.csdn.net/warrior_1/article/details/106517949?%3E
2. 创建MFC工程,通过可视化界面拖入各自的组件
服务器端
在这里插入图片描述
客户端
在这里插入图片描述
3. 服务器端对于数据库的操纵

  1. 创建全局变量
SQLHENV henv = SQL_NULL_HENV;
SQLHDBC hdbc1 = SQL_NULL_HDBC;
SQLHSTMT hstmt1 = SQL_NULL_HSTMT;
RETCODE retcode;
UCHAR   szDSN[SQL_MAX_DSN_LENGTH + 1] = "csql", szUID[MAXNAME] = "sa", szAuthStr[MAXNAME] = "sa";//ODBC数据源名,用户名,密码
  1. 创建连接数据库,断开连接方法,供其他方法调用
void db() {//开始连接
	retcode = SQLAllocHandle(SQL_HANDLE_ENV, NULL, &henv);
	retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
		(SQLPOINTER)SQL_OV_ODBC3,
		SQL_IS_INTEGER);
	retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc1);
	//1.连接数据源
	retcode = SQLConnect(hdbc1, szDSN, 4, szUID, 2, szAuthStr, 2);//其中int类型为前面字符串字段的长度
}
void dbexit() {//关闭连接
	SQLDisconnect(hdbc1);
	SQLFreeHandle(SQL_HANDLE_DBC, hdbc1);
	SQLFreeHandle(SQL_HANDLE_ENV, henv);
}
  1. 刷新按钮/读取数据
MYLIST.DeleteAllItems();	//清理下方的list control 中条目
for (int i = 0; i < 2; i++) {
	MYLIST.DeleteColumn(0);	//清理列名
}
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
DWORD dwStyle = MYLIST.GetExtendedStyle();
dwStyle |= LVS_EX_FULLROWSELECT;
dwStyle |= LVS_EX_GRIDLINES;
MYLIST.SetExtendedStyle(dwStyle);	//风格
MYLIST.InsertColumn(0, "英文单词", LVCFMT_LEFT);	//列名
MYLIST.SetColumnWidth(0, 250);	//列宽度
MYLIST.InsertColumn(1, "中文翻译", LVCFMT_LEFT);
MYLIST.SetColumnWidth(1, 250);
db();
UCHAR	sql3[21] = "select * from ZIDIAN";//语句
if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO));
else {
	retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc1, &hstmt1);//申请语句句柄
	SQLExecDirect(hstmt1, sql3, 21);	//第三个参数为SQL语句长度
	char list[15], list2[15];
	SQLBindCol(hstmt1, 1, SQL_C_CHAR, list, 15, 0);	//倒数第二参数为读取长度,建议与list长度相同
	SQLBindCol(hstmt1, 2, SQL_C_CHAR, list2, 15, 0);
	do {
		retcode = SQLFetch(hstmt1);	//下一行信息
		if (retcode == SQL_NO_DATA) {
			break;
		}
		MYLIST.InsertItem(0, "");	//插入第一列;C中不会覆盖,只是显示在最前
		MYLIST.SetItemText(0, 0 , list);
		MYLIST.SetItemText(0, 1, list2);
	} while (1);
	dbexit();
}

4)对于list control组件中信息与文本框的映射; 右击list control组件,添加变量【方便调用】为MYLIST

LPNMLISTVIEW pNMLV = reinterpret_cast<LPNMLISTVIEW>(pNMHDR);
	// TODO: 在此添加控件通知处理程序代码
	int sel = MYLIST.GetNextItem(-1, LVIS_SELECTED);	//获取选中行号
	if (sel < 0)
		return;
	else {	//选中
		CString values;
		values = MYLIST.GetItemText(sel, 0);//读取
		SetDlgItemText(IDC_EDIT1, values);//放入文本框
		values = MYLIST.GetItemText(sel, 1);
		SetDlgItemText(IDC_EDIT2, values);
	}
	*pResult = 0;

5) 删除条目(此处判断单词+翻译–也可以直接使用list control中信息作为判断依据,删除选中信息)

	CString english, chinese;
	GetDlgItemText(IDC_EDIT1, english);
	GetDlgItemText(IDC_EDIT2, chinese);
	string sqlstr = "delete from ZIDIAN where ENG='";
	sqlstr += english;
	sqlstr += "' and CHI='";
	sqlstr += chinese;
	sqlstr += "'";
	UCHAR* sql = (UCHAR*)(char*)sqlstr.c_str();	//注意字符格式的转换
	int sqlnum = strlen((char*)sql);
	db();
	retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc1, &hstmt1);//申请语句句柄
	SQLExecDirect(hstmt1, sql, sqlnum);//执行SQL语句
	dbexit();

6) 增加条目

CString english, chinese;
	GetDlgItemText(IDC_EDIT1, english);
	GetDlgItemText(IDC_EDIT2, chinese);
	string sqlstr = "update  ZIDIAN set CHI='";
	sqlstr += chinese;
	sqlstr += "' where ENG='";
	sqlstr += english;
	sqlstr += "'";
	UCHAR* sql = (UCHAR*)(char*)sqlstr.c_str();
	int sqlnum = strlen((char*)sql);
	db();
	retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc1, &hstmt1);//申请语句句柄
	SQLExecDirect(hstmt1, sql, sqlnum);
	dbexit();

7)修改条目【此处以英文单词为主键以及判断依据,故之能修改翻译,想要修改英文单词可以在数据库中添加一个以此递增的行号,以此为组件和判断 依据即可】

CString english, chinese;
GetDlgItemText(IDC_EDIT1, english);
GetDlgItemText(IDC_EDIT2, chinese);
string sqlstr = "delete from ZIDIAN where ENG='";
sqlstr += english;
sqlstr += "' and CHI='";
sqlstr += chinese;
sqlstr += "'";
UCHAR* sql = (UCHAR*)(char*)sqlstr.c_str();
int sqlnum = strlen((char*)sql);
db();
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc1, &hstmt1);//申请语句句柄
SQLExecDirect(hstmt1, sql, sqlnum);
dbexit();
  1. 服务器提供的网络服务
    1) 提供访问的I/O模型,其中postsend方法如下
	// 设置I/O类型,增加套节字上的重叠I/O计数
	pBuffer->nOperation = OP_WRITE;
	pBuffer->pSocket->nOutstandingOps++;
	WSABUF buf;
	db();
	if ((retcode != SQL_SUCCESS) && (retcode != SQL_SUCCESS_WITH_INFO)) //数据库连接失败
	{
		buf.buf = (char*)"ERROR";
		buf.len = strlen("ERROR");
	}
	else {
		string  sqlstr = "select CHI from ZIDIAN where ENG ='";
		sqlstr += pBuffer->buff;
		sqlstr += "'";
		UCHAR* sql = (UCHAR*)(char*)sqlstr.c_str();
		int sqlnum = strlen((char*)sql);
		retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc1, &hstmt1);//申请语句句柄
		SQLExecDirect(hstmt1, sql, sqlnum);
		char chinese[15];
		SQLBindCol(hstmt1, 1, SQL_CHAR, chinese, 15, 0);
		retcode = SQLFetch(hstmt1);
		if (retcode != SQL_NO_DATA) {	//查到数据
			buf.buf = chinese;
			buf.len = 15;
		}
		else {	//未查到数据
			buf.buf = (char*)"NULL";
			buf.len = strlen("NULL");
		}
	}
	dbexit();
	// 投递此重叠I/O
	DWORD dwBytes;
	DWORD dwFlags = 0;
	if (::WSASend(pBuffer->pSocket->s,
		&buf, 1, &dwBytes, dwFlags, &pBuffer->ol, NULL) != NO_ERROR)
	{
		if (::WSAGetLastError() != WSA_IO_PENDING)
			return FALSE;
	}
	return TRUE;

2)创建服务线程ThreadA

void ThreadA(PVOID pvoid) {
int nPort = 8081;	//监听端口号
SOCKET sListen =
	::WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
SOCKADDR_IN si;
si.sin_family = AF_INET;
si.sin_port = ::ntohs(nPort);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");	//IP地址设为本地
::bind(sListen, (sockaddr*)&si, sizeof(si));
::listen(sListen, 200);
// 为监听套节字创建一个SOCKET_OBJ对象
PSOCKET_OBJ pListen = GetSocketObj(sListen);
// 加载扩展函数AcceptEx
GUID GuidAcceptEx = WSAID_ACCEPTEX;
DWORD dwBytes;
WSAIoctl(pListen->s,
	SIO_GET_EXTENSION_FUNCTION_POINTER,
	&GuidAcceptEx,
	sizeof(GuidAcceptEx),
	&pListen->lpfnAcceptEx,
	sizeof(pListen->lpfnAcceptEx),
	&dwBytes,
	NULL,
	NULL);
// 创建用来重新建立g_events数组的事件对象
g_events[0] = ::WSACreateEvent();
// 在此可以投递多个接受I/O请求
for (int i = 0; i < 5; i++)
{
	PostAccept(GetBufferObj(pListen, BUFFER_SIZE));
}
//为重建事件对象数组作准备,即要先构造要在事件对象上等待受信的事件对象数组,但开始时
//事件对象数组为空,要调用RebuildArray()才能设置等待事件,而只有当g_events[0]为受信时
//才会调用RebuildArray(),这里把第一个事件设为“受信”,由于第一个事件对象并没有与任何I/O
//绑定在一起,所以要靠手动设置第一个事件的状态值,在后面的While循环中才会执行:
::WSASetEvent(g_events[0]);
while (TRUE)
{
	int nIndex =
		::WSAWaitForMultipleEvents(g_nBufferCount + 1, g_events, FALSE, WSA_INFINITE, FALSE);
	if (nIndex == WSA_WAIT_FAILED)
	{
		printf("WSAWaitForMultipleEvents() failed \n");
		break;
	}
	nIndex = nIndex - WSA_WAIT_EVENT_0;
	for (int i = 0; i <= nIndex; i++)
	{
		int nRet = ::WSAWaitForMultipleEvents(1, &g_events[i], TRUE, 0, FALSE);
		if (nRet == WSA_WAIT_TIMEOUT)
			continue;
		else
		{
			//不管该g_events[i]事件对象是否授信,都可把其状态设为“未传信”,为下次通知做准备
			::WSAResetEvent(g_events[i]);
			// 重新建立g_events数组
			if (i == 0)
			{
				RebuildArray();
				continue;
			}
			// 由受信的事件对象找到对应的缓冲区对象,进而处理这个I/O
			PBUFFER_OBJ pBuffer = FindBufferObj(g_events[i]);
			if (pBuffer != NULL)
			{
				if (!HandleIO(pBuffer))
					RebuildArray();
			}
		}
	}
}
}

3)启动线程

_beginthread(ThreadA, 0, NULL);

修改按钮是否可见GetDlgItem(按钮名)->ShowWindow(参数);参数SW_HIDE为隐藏,SW_SHOW显示;关闭线程使用_endthread();或其他方法关闭,
5. 客户端程序
1) 打开连接【先开服务器】;给出全局变量

SOCKET s;
WSADATA wd;//获取socket相关信息

创建与服务器端连接socket【此处没有相关的异常处理,可自行添加连接失败方法】:

WSAStartup(MAKEWORD(2, 2), &wd);
//1.创建TCP Socket,流式套接字
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in addr;//不建议使用sockaddr,建议使用sockaddr_in
addr.sin_port = htons(8081);//网络字节序
addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//网络字节序
addr.sin_family = AF_INET;//地址族
int len = sizeof(sockaddr_in);
connect(s, (sockaddr*)&addr, len);

修改按钮是否可见同上
2)断开连接

closesocket(s);
//清理winsock环境
WSACleanup();

修改按钮是否可见
3)查询过程

CString english;
GetDlgItemText(IDC_EDIT1, english);//从文本框读
string str(english.GetBuffer());//类型转换
int ret;
char* eng = (char*)str.c_str();
int lensent = sizeof(eng);//发送字节个数
send(s, eng, lensent, 0);//发送给服务器端
char recvbuf[20];
ret = ::recv(s, recvbuf, 20, 0);	//接受回应消息
recvbuf[ret] = '\0';
CString result;
result.Format("%s", recvbuf);	//字符类型转换
SetDlgItemText(IDC_EDIT2, result);	//结果填入文本框

注:
1. 对于char*到CString转换报错或其他问题
打开工程属性设置,在高级中将字符集改成未设置;如果有3类警告可以忽略,在常规中将警告等级改成1级或二级即可;关闭SDL检查
2. 对于文本行按下ENTER键,退出界面的问题
右击文本框–>类向导–>虚函数–>在左侧选中PreTranslateMessage–>添加函数–>编辑函数,添加如下代码

BOOL CMFCAppSQLCLIENTDlg::PreTranslateMessage(MSG* pMsg)
{
	// TODO: 在此添加专用代码和/或调用基类
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_ESCAPE)
	{
		// 如果消息是键盘按下事件,且是Esc键,执行以下代码(什么都不做,你可以自己添加想要的代码)
		return TRUE;
	}
	if (pMsg->message == WM_KEYDOWN && pMsg->wParam == VK_RETURN)
	{
		// 如果消息是键盘按下事件,且是Entert键,执行以下代码(什么都不做,你可以自己添加想要的代码)
		return TRUE;
	}
	return CDialogEx::PreTranslateMessage(pMsg);
}

ps:此代码中基本没有异常处理,可以自行添加;同时代码较为简单,C++初学者可以看看

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值