客户端服务器端通信-Socket网络编程

实现客户端服务器端通信

实验目的

  1. 了解相关网络协议的基本原理和工作流程;
  2. 掌握使用 Socket 进行网络通信的方法;
  3. 体会客户机、服务器交互模式。

实验任务

1.通过调用 Socket 相关函数实现网络通信;
2. 实现界面系统和后台通信系统的协同配合;
3. 回顾常用控件和 GDI 对象的使用方法;
4. 感受 MFC 下多线程的基本用法。

实验设备

个人 PC,Windows 操作系统,VS2013开发环境。

实验内容

  1. 分别建立服务器和客户机,以单线程/多线程方式实现客户机和服务器的连
    接和通信,从客户机向服务器传输你的个人信息和绘图信息,在服务器上显示
    你发送的信息并按照传输来的信息绘制图形。服务器和客户端的界面布局和功
    能效果分别如图 1 和图 2 所示。
    1.1 功能要求
  2. 服务器端首先运行并在某一端口(如 9990)监听客户端连接请求;
  3. 在客户端填写服务器端的 IP 地址和端口号,连接并发送消息;
  4. 填写自己的姓名、学号、专业和绘图相关信息向服务器发送,服务器收到消息
    后,显示出你的全部消息,并按照所发送消息的内容在绘图区绘制指定的图形。
    客户端样图
    服务端的样图

客户端

一、首先打开VS2013选择创建一个MFC项目,我们先设计客户端,按照对应的界面要求设计好后我们就可以添加对应的功能,我们知道,在客户端我们需要经历这样几个步骤,wsastartup,socket,connect,send。因此我们双击发送连接按钮,在这里我们做的是只到连接的工作,具体我们看代码如下:

void CMFCApplication5Dlg::OnBnClickedButton1()
{
	UpdateData(TRUE);
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		s1= "WSAstartup failed!";
		AfxMessageBox(s1);
		return ;
	}

	sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == sHost)
	{
		s2= "socket error!";
		AfxMessageBox(s2);
		WSACleanup();
		return ;
	}
	SOCKADDR_IN addrsServ;
	int i = 0;
	str.Format(_T("%d"), i);
	addrsServ.sin_family = AF_INET;
	addrsServ.sin_port = htons(9990);
	addrsServ.sin_addr.S_un.S_addr = inet_addr("192.168.191.1");
	int sSreveraddlen = sizeof(addrsServ);
	UpdateData(false);
	retval = connect(sHost, (LPSOCKADDR)&addrsServ, sizeof(addrsServ));
	if (SOCKET_ERROR == retval)
	{
		s3= "connect failed!";
		AfxMessageBox(s3);
		closesocket(sHost);
		WSACleanup();
		return ;
	}

	// TODO:  在此添加控件通知处理程序代码
}

二、接下来我们要实现向服务端发送信息,而这个就需要我们双击发送按钮,当我们点击时,实现我们的内容向服务端的发送,因此我们就需要获取每一个编辑框的内容。
代码如下:

void CMFCApplication5Dlg::OnBnClickedButton2()
{

	int bufsize;
	CString str;
	CString str1;
	CString str2;
	CString str3;
	CString str4;
	CString str5;
	CString str6;
	CString str7;
	CString str8;
	CString str9;
	CString str10;
	CString str11;
	CString str12;
	if (((CButton*)GetDlgItem(IDC_RADIO1))->GetCheck() == 1)
	{
		str12 = "R";
	}
	if (((CButton*)GetDlgItem(IDC_RADIO2))->GetCheck() == 1)
	{
		str12 = "E";
	}
	str11 = ";";
	GetDlgItemText(IDC_EDIT3, str1);
	GetDlgItemText(IDC_EDIT4, str2);
	GetDlgItemText(IDC_EDIT5, str3);
	GetDlgItemText(IDC_EDIT6, str4);
	GetDlgItemText(IDC_EDIT7, str5);
	GetDlgItemText(IDC_EDIT8, str6);
	GetDlgItemText(IDC_EDIT9, str7);
	GetDlgItemText(IDC_EDIT10, str8);
	GetDlgItemText(IDC_EDIT11, str9);
	GetDlgItemText(IDC_EDIT11, str10);
	str = str1 + str11 + str2 + str11 + str3 + str11 + str12 + str11 + str4 + str11 + str5 + str11 + str6 + str11 + str7 + str11 + str8 + str11 + str9 + str11 + str10;
	USES_CONVERSION;
	char *a = T2A(str.GetBuffer(0));
	str.ReleaseBuffer();
	sprintf_s(buf, "%s", a);
	bufsize = send(sHost, buf, strlen(buf), 0);
	if (SOCKET_ERROR == bufsize)
	{
		s5 = "recv failed!";
		AfxMessageBox(s5);
		closesocket(sHost);
		WSACleanup();
		return;
	}

	// TODO:  在此添加控件通知处理程序代码
}

这样我们就完成了客户端的实现。

服务端

一、我们首先也是设计页面,其次按照书上的步骤进行操作,一些必备的函数,
调用 WSAStartup()加载 Socket 库进行初始化;
调用 Socket()函数创建基于 TCP 的流式套接字用于主线程的请求监听;
设置服务器端的本机地址:
你的 sockaddr_in 结构.sin_family = AF_INET;
你的 sockaddr_in 结构.sin_port = htons(你的监听端口,比如 9990);
你的 sockaddr_in 结构.s_addr = htonl(INADDR_ANY);
调用 bind()函数将 Socket 与地址进行绑定;
调用 listen()函数设置套接字为监听工作状态。
代码如下:

void CMFCApplication6Dlg::OnBnClickedButton1()
{
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		s1 = "WSAstartup failed!";
		return ;
	}
	s1 = "WSAstartup successed!";
	sServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == sServer)
	{
		s2 = "socket error!";
		WSACleanup();
		return ;
	}
	s2 = "socket success!";
	SOCKADDR_IN addrsServ;
	int i = 0;
	str.Format(_T("%d"), i);
	addrsServ.sin_family = AF_INET;
	addrsServ.sin_port = htons(9990);
	addrsServ.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	int sSreveraddlen = sizeof(addrsServ);
	retval = bind(sServer, (const struct sockaddr *)&addrsServ, sizeof(SOCKADDR_IN));
	if (SOCKET_ERROR == retval)
	{
		s3 = "bind failed!";
		closesocket(sServer);
		WSACleanup();
		return ;
	}
	s3 = "bind successed!";
	retval = listen(sServer, 1);
	if (SOCKET_ERROR == retval)
	{
		s4 = "listen failed!";
		closesocket(sServer);
		WSACleanup();
		return ;
	}
	s4 = "listen successed!";
	editt.SetWindowText(s1+"\r\n"+s2+"\r\n"+s3+"\r\n"+s4 );
	hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)this, 0, NULL);
	if (hThread == NULL)
	{
		CString ss;
		ss="Create Thread Failed!! !";
		AfxMessageBox(ss);
		closesocket(sServer);
		WSACleanup();
		return;
	}
	// TODO:  在此添加控件通知处理程序代码
}

二、这里最重要的是添加一个线程,这样当我们执行accept时我们即使产生阻塞也不会影响我们,因为我们将这个进程放在线程里,这样会提高我们的用户体验。
具体我们看代码:

DWORD WINAPI ClientThread(LPVOID lpParameter)
{
	CMFCApplication6Dlg * dlg = (CMFCApplication6Dlg*)lpParameter;
	SOCKADDR_IN addrcClient;
	int cClientaddlen = sizeof(addrcClient);
	sClient = accept(sServer, (struct sockaddr *)&addrcClient, &cClientaddlen);
	if (INVALID_SOCKET == sClient)
	{
		s5 = "accept failed!";
		AfxMessageBox(s5);
		closesocket(sServer);
		closesocket(sClient);
		WSACleanup();
		return 0;
	}
	s5 = "accept successed!";
	AfxMessageBox(s5);
	while (1)
	{
		memset(buf, 0x00, 100);
		int bufsize;
		retval = recv(sClient,buf,100,0);
		if (SOCKET_ERROR == retval)
		{
			s5 = "recv failed!";
			AfxMessageBox(s5);
			closesocket(sServer);
			closesocket(sClient);
			WSACleanup();
			return 0;
		}
		rec = CString(buf);
		SYSTEMTIME tm;
		GetLocalTime(&tm);
		char sDateTime[30];
		sprintf(sDateTime, "%4d-%2d-%2d  %2d:%2d:%2d", tm.wYear, tm.wMonth, tm.wDay, tm.wHour, tm.wMinute, tm.wSecond);
		CString year(sDateTime);
		dlg->editstr = dlg->editstr + s1 + "\r\n" + s2 + "\r\n" + s3 + "\r\n" + s4 + "\r\n" + s5+"\r\n\r\n" +year+ "\r\n\r\n" + rec;
		dlg->editt.SetWindowText(dlg->editstr);
		int pos;
		CString rr;
		for (int i = 0; i < 11; i++)
		{
			pos = rec.Find(';');
			rr = rec.Mid(pos + 1, rec.GetLength());
			re[i] = rec.Left(pos);
			rec = rr;
		}
		CWnd * pWnd = dlg->GetDlgItem(IDC_STATIC1);
		CDC * dc = pWnd->GetDC();//注意这里获取了新的 dc
		pWnd->Invalidate();
		pWnd->UpdateWindow();
		CRect rectView;
		CRgn rgn;
		dlg->GetDlgItem(IDC_STATIC1)->GetClientRect(&rectView);
		rgn.CreateRectRgn(rectView.left, rectView.top, rectView.right, rectView.bottom);
		dc->SelectClipRgn(&rgn);
		CPen pNewPen(PS_SOLID, 2, RGB(0, 0, 0));
		CPen *pOldPen;
		pOldPen = dc->SelectObject(&pNewPen);
		CBrush pNewBrush1(RGB(255, 255, 0));
		CBrush *pOldBrush;
		if (re[3] == 'R')
		{
			CRect rect1(_ttoi(re[4]), _ttoi(re[5]), _ttoi(re[6]), _ttoi(re[7]));
			CBrush pNewBrush1(RGB(_ttoi(re[8]), _ttoi(re[9]), _ttoi(re[10])));
			dc->Rectangle(&rect1);
			pOldBrush = dc->SelectObject(&pNewBrush1);
			dc->Rectangle(&rect1);
			dc->SelectObject(pOldPen);//恢复原有的笔
			dc->SelectObject(pOldBrush);
			dc->DeleteDC();
		}
		if (re[3] == 'E')
		{
			CRect rect1(_ttoi(re[4]), _ttoi(re[5]), _ttoi(re[6]), _ttoi(re[7]));
			CBrush pNewBrush1(RGB(_ttoi(re[8]), _ttoi(re[9]), _ttoi(re[10])));
			dc->Ellipse(&rect1);
			pOldBrush = dc->SelectObject(&pNewBrush1);
			dc->Ellipse(&rect1);
			dc->SelectObject(pOldPen);//恢复原有的笔
			dc->SelectObject(pOldBrush);
			dc->DeleteDC();
		}
		break;
	}
	return 0;
}

这里我们不仅实现了从客户端接受信息还可以将其显示在我们的对应的编辑框中,其此,我们还可以根据客户端的请求进行画图,这里请参考本博主的其他文章有详细介绍关于画图及其移动的相关介绍,这里我们要注意的是我们需要用“;”来当做分隔符,你也可以选择别的进行这取决你了。

注:本博客纯原创,如需转载请告诉博主哦!

猜你喜欢

转载自blog.csdn.net/weixin_42189863/article/details/86512337