资源描述
实验十七Winsock编程
———————————————————————————————— 作者:
———————————————————————————————— 日期:
12
个人收集整理 勿做商业用途
实验十七 Winsock编程
【实验目的】
(1)熟悉Visual C++的基本操作。
(2)基本了解基于对话框的windows应用程序的编写过程.
(3)对于Windows Socket编程建立初步概念。
【实验要求】
(1)应用VisualC++中MFCCSocket类,实现网络数据传输。
(2)制作实用的局域网数据传输程序。
【实验原理】
一、Windows Socket和套接口的基本概念
网际协议(Intemet Protocol,IP)是一种用于互联网的网络协议,已广为人知。它可广泛用于大多数计算机操作系统上,也可用于大多数局域网LAN(比如办公室小型网络)和广域网WAN(比如说互联网)。从它的设计看来,它是一个无连接的协议,并不能保证数据投递万无一失。两个上层协议(TCP和UDP)依赖IP协议进行数据通信.
如果希望在Microsoft Windows下通过TCP和UDP协议建立网络应用程序,则需要使用Winsock套接口编程技术。
套接口,就是一个指向传输提供者的句柄。Win32中,套接口不同于文件描述符,所以它是一个独立的类型——SOCKET。Windows Sockets描述定义了一个Microsoft Windows的网络编程界面,它是从Unix Socket的基础上发展而来的,为Windows TCP/IP提供了一个BSD型的套接字规范,除与4.3BSD Unix Sockets完全兼容外,还包括一个扩充文件,通过一组附加的API实现Windows式(即事件驱动)的编程风格;而Winsock则是在MicrosoftWindows中进行网络应用程序设计的接口。Windows在Internet支配域中的TCP/IP协议定义了Winsock网络编程规范,融入了许多新特点.使用Socket的目的是使用户在网络协议上工作而不必对该网络协议有非常深入的了解。此外,编写的程序还可被迅速地移植到任何支持Socket的网络系统中去。
Winsock提供了二种可为指定传输协议打开、计算和关闭会话的能力。在Windows下,TCP/IP上层模型在很大程度上与用户的Winsock应用有关:换言之,用户的Wimock应用控制了会话的方方面面,必要时,还会根据程序的需要格式化数据。
套接口有三种类型:流式套接口、数据报套接口及原始套接口。
流式套接口定义了一种可靠的面向连接的服务(利用TCP协议),实现了无差错无重复的顺序数据传输。数据报套接口定义了一种无连接的服务(UDP协议),数据通过相互独立的报文进行传输,是无序的,并且不保证可靠和无差错。原始套接口允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等.
面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决的,而且
往往是并发服务器。使用面向连接的套接口编程,可以通过图来表示。
无连接服务器一般都是面向事务处理的,一个请求、一个应答就完成了客户程序与服务程序之间的相互作用.若使用无连接的套接口编程,程序的流程可以用图表示.
套接口工作过程如下:服务器首先启动,通过调用socket()建立一个套接口,然后调用bind()将该套接口和本地网络地址联系在一起,再调用listen()使套接口做好侦听的准备,并规定它的请求队列的长度,之后,调用accept()来接收连接。客户在建立套接口后就可调用conllet()和服务器建立连接。连接一旦建立,客户机和服务器之间就可以通过调用read()和write()来发送和接收数据.最后,待数据传送结束后,双方调用close()关闭套接口。
在网络编程中,掌握端口的概念十分重要.
端口:基于TCP/IP协议的网络中,计算机都分配有一个IP地址,用一个32位二进制数来表示,正式的称呼是“Ipv4地址".客户机需要通过TCP或UDP和服务器通信时,必须指定服务器的IP,地址和服务端口号.另外,服务器打算侦听接入客户机请求时,也必须指定一个IP,地址和一个端口号。在选择端口时,应特别小心,因为有些可用端口号是为“已知的”(即固定的)服务保留的,如文件传输协议和超文本传输协议,即FTP(21号端口)和HTTP(一般为8080端口)。“已知的协议",即固定协议,采用的端口由“互联网编号分配认证(IANA)”控制和分配,RFC 1700中说明的编号。
从本质上说,端口号可分成3类:“已知”端口、已注册端口、动态和(或)私用端口。
(1)“已知”端口0-1023,由IANA控制,是在UNIX中为固定服务保留的。
(2)已注册的端口1024—49151,由IANA列出来的,供普通用户的普通用户进程或程序使用。
(3)动态和(或)私用端口49152-65535。
普通用户应用应选择1024-49151之间的已注册端口,从而避免端口号已被另一个应用或系统服务所用.此外,49152-65535间的端口可自由使用,因为IANA这些端口上没有注册服务。
二、MFC CSocket类的通信流程
使用CSockct类进行网络二进制数据通信的连接流程,如下表所示。
服务器端
注释
客 户 端
l
Csocket m_server
构造一个socket对象
Csocket m_client
2
m_server。create(nport)
创建socket
M_client。create(nport)
3
m_server。listen()
听等
连接
与服务器
建立连接
m_client。connectstraddr,nport)
此时阻塞。等待服务器端侦听.
4
Csocket m_receive;
m_server。accpet(m_receive)
此时阻塞,等待客户机连接.
构造新的socket对象用以接收
客户端的连接
5
CsocketFile file(&m_server);
构造一文件对象
CsocketFile file(&m_server)
6
CArchive
arin(&file,CArchive::load)
CArchive
arout(&file,CArchive::store)
构造流对象
CArchive
arin(&file,CArchive::Ioad)
CArchive
arout(&file,CArchive::store)
7
arin>>value;
arout<〈value;
用流进行数据的传输概念和
cin,cout相似
arin〉〉value;
arout<<value;
注意事项:利用cArchive类进行网络数据传输的操作固然方便直观,但是如果编写的程序是和别人的程序进行通信的话,就要注意对方的程序是否也用了CArchive类,否则会造成数据相互不能识别。
【实验步骤】
下面以一个最简单的点对点通信的聊天程序为例:
客户机/服务器模式是socket点对点网络程序典型的模式。它用到的方法也是面向连接TCP连接的套接字MFC典型方式。其工作过程是:服务器首先启动,创建套接字后等待客户的连接;客户启动以后,创建套接字,然后和服务器建立连接;连接建立后,客户机和服务器可以通过建立的套接字连接进行信息通信。
先建立一个MFC,选dialogBased,工程名为LX2,如图所示。
下一个对话框选择Window Sockets,后面的选项酌情考虑,或者连续选择默认的即可,如图所示
如果忘了添加Windows Sockets选项,可以在文件头部添加下列语句进行补救:
#include “Winsock。h”
#include “ws2tcpip。h”
#pragma comment(1ib,”Ws2_32。lib”)
注:这些语句支持winsock2.
出现Dialog以后,编辑界面,使其如图所示并且对控件点击右键,选择属性选项,把每个控件的ID改掉(控件ID就是每个控件的名字,要改成有意义的,以便将来管理)。
各个控件的ID如下表,并且在对话框视图中点击右键,选择Class Wizard选项,用该工具对控件添加变量。
控件ID
变量名
绑定变量类型
对应界面上的控件
IDC_CoNTENT
m_msg
CString
输入发送内容的文本框
IDC_CONTENT
m cuff
CEdit
输入发送内容的文本框
IDC_LISTEN
m_listen
CButton
侦听按钮
IDC_SEND
re_send
CButton
发送按钮
接着,再打开一个VC,建立客户机工程,工程名称为LX1,各个控件的ID如下表
控件ID
变量名
绑定变量类型
对应界面上的控件
IDC_CONTENT
m_msg
CStringt
输入发送内容的文本框
IDC_CONTENT
m ctrl
CEdit
输入发送内容的文本框
IDC_CONNECT
m_connect
CButton
连接按钮
IDC_SEND
m_send
CButton
发送按钮
IDC_IP
m_lp
CString
输入连接目的IP的文本框
然后,在BOOL CLx1Dig::OnlnitDialog()和BOOL CLx2DIg::OnlnitDialog()末尾添加语句,使其如下所示:
m_send.EnableWindow(FALSE);//使发送按钮变灰
return TRUE;//return TRUE unless you
//set the focus to a control
注意:这个语句作用使发送按钮失效,以免还未连接用户就点击发送,发生不可预计的错误。
为了在程序中更自由地处理CSocket得到的消息,必须新建CSocket的派生类:
在Lx2工程的工作区类视图中点击右键,添加新类: CServer,父类为CSocket。
NewClass对话框
在lx2Dlg.h里添加:
头文件: #include “Server.h”
private变量: CServer m_server;CServer m_recv
在对话框的图像上双击“侦听"按钮,添加如下代码:
void CLx2Dlg::OnListen()
{
re_server.Create(1000); //使用1000号端口
m_server.Listen(); //侦听
}
在对话框图像上再双击“发送"按钮,添加如下代码:
void CLx2Dlg::OnSend()
{
UpdateData(TRUE); //更新数据,使m_msg得到当前框中文本
m_recv.Send(m_msg,255); //发送数据
m_ctrl.SetSel(0,一1); //全选发送框文字
m_ctrl.ReplaceSel(“",TRUE); //将发送框置空
}
同样地,在Lxl工程的工作区类视图中点击右键,添加新类:
Cclient
它继承自CSocket.在CLxlDIg类里添加private变量:
CClient m_client;
双击对话框图像上的“连接”按钮,添加代码:
void CLxlDlg::OnConnect()
{
UpdateData(TURE);
m_client。Create(1001); //使用1001号端口
if(m_client.Connect(m_ip,1000)) //连接目标地址,1000端口
{
AfxMessageBox(“Client端连接成功”);
m_send.EnableWindow(TRUE); //连接成功,可以发送
m_connect.EnableWindow(FALSE); //同时禁止连接按钮
}
else
{
m_client.Close(); //如果连接失败就关闭
AfxMessageBox(”连接失败”);
}
}
}}
双击发送按钮,添加代码:
void CLx 1Dig::OnSend()
{
UlxlateData(TRUE); //更新数据,使m_msg得到当前框中文本
m_client。Send(m_msg,255); //发送数据,长度255字节
)
通过以上操作,我们建立了CSocket,主机、客户机建立连接后的消息发送代码也添加完成
了,但是还缺少使其工作的消息机制。
下面的步骤就是利用OnAccept和OnReceive函数处理socket消息。
首先,在Lx2工程的编辑界面中点击右键,选Class Wizard,在classnamc栏目里面找到CServer类,添加OnAccept和OnReceive函数并且双击下面的Member function栏目,分别为两个函数添加如下代码:
void CServer::OnAccept(int nErrorCode)
{
//TODO:Add your specialized code here and/or call the base class
CSocket::OnAccept(nErrorCode);
((CLx2Dig*)(AfxGetApp()—>m_pMainWnd))->ShowAcccpt();
//这里仅仅添加了这一句,因为CLx2DIg类是发送接收消息的主窗口,
//而且应用程序发送接收的消息也在CLx2Dlg对象实例中进行,
//所以当CServer类的对象收到客户机的Connect消息时,
//便可调用CLx2DLg对象中的ShowAccept()函数处理。
}
该步完成以后,可以为CLx2DIg类添加public成员函数ShowAccept():
void CLx2Dlg::ShowAccept()
{
m_server。Accept(m_recv);
AfxMessageBox(”Server端连接成功”);
m_send。EnableWindow(TRUE); //连接成功,可以发送
m_listen.EnableWindow(FAT SE); //同时禁止侦听按钮
}
于是,当客户机调用m_client.Connect(m_ip,1000);时,主机server端发现并调用ShowAccept函数来建立连接。执行完以后,Socket连接便被建立。
接下来的工作便是添加发送聊天信息的函数了.
注意到前面点击发送按钮的OnSend()函数已经添加好了,在Lx2工程中只要添加Server
端的接收消息和显示消息功能就可以进行消息的传送.
在CServer类中像添加OnAccept()一样添加成员函数OnReceive():
void Cserver::OnReceive(int nErrorCode)
{
// TODO:Add your specialized code here and/or call the base class
CSoeket::OnReceive(nErrorCode):
((CLx2DIg*)(AfxGetApp()-〉m_pMainWnd))->ShowMsg();
}
建立连接后,一方一旦发送数据,另一方的CSocket派生类便调用该函数.其中代码可以参
考前面OnAccept()进行理解.
在CLx2DIg里添加成员函数ShowMsg():
void CLx2Dlg::ShowMsg()
{
charbuff[255];
m_reev.Receive(buf,255); //接收消息到buf里面,长度255字节
CString msg;
Msg。Format(”%s”,buf);
AfxMessageBox(msg); //用AfxMessageBox函数显示接收到的字符串
//这里注意CString类的用法
}
同样地,逐步在Lxl工程中添加消息接收函数:
void Cclient::OnReceive(int nErrorCode)
{
//TODO:Add your specialized code here and/or call the base class
((CLx1Dlg*)(AfxGetApp()一>m_pMainWnd))-〉ShowMsg();
Csocket::OnReceive(nErrorCode):
}
void CLx1Dig::ShowMsgO
{
char buf[255];
m_client.Receive(buf,255); //接收消息到buf里面,长度255字节
CString msg;
Msg。Format("%s”,buf);
AfxMessageBox(msg); //用AfxMessageBox函数显示接收到的字符串
}
量后的收尾工作不要忘记.即在对话框销毁的时候,关闭Socket连接,释放资源.以下以Lxl工程为例,见图。
void CLxlDlg::OnDestroy()
{
Cdialog::OnDestroy0;
m_client.Close0; //关闭套接字
}
至此,代码全部添加完毕。
展开阅读全文