Windows网络编程.docx
《Windows网络编程.docx》由会员分享,可在线阅读,更多相关《Windows网络编程.docx(40页珍藏版)》请在冰豆网上搜索。
Windows网络编程
Windows网络编程
课程编号
C10
课程内容
介绍计算机网络TCP\IP模型和OSI参考模型的对应层次关系、每层主要功能以及相关重要协议介绍的基本概念。
最后介绍使用API网络编程的基本步骤,MFC对Socket的封装并分别进行代码举例
编写日期
2006-04-13
作者
李伟(bingweiwei-2001@)
目录
1.OSI参考模型和TCP\IP参考模型3
1.1.每个层次功能及主要协议介绍3
2.WindowsSocket网络程序开发步骤4
2.1.WindowsAPI网络应用程序举例4
2.2.应用程序缺点及克服10
3.MFC网络应用程序开发举例12
3.1.MFC对Socket的封装12
3.2.具体实现代码工程实例13
1.OSI参考模型和TCP\IP参考模型
TCP/IP传输控制协议/网际协议是业界标准的协议组。
它是基于四层参考模型,属于TCP/IP协议组的所有协议都位于该模型的上面三层。
TCP/IP模型的每一层对应于国际标准化组织(ISO)提议的七层“开放系统互联(OSI)”参考模型的一层或者多层。
图10-1
1.1.每个层次功能及主要协议介绍
使用下表描述每一层所提供的服务以及所使用的协议:
表10-1
层
描述
协议
应用层
定义了TCP/IP应用协议以及主机程序与要使用网络的传输层服务之间的接口
HTTP,Telnet,FTP,TFTPSNMP,DNS,SMTP等
传输层
提供主机之间的通讯会话管理,定义传输数据时的服务级别以及连接状态
TCP,UDP,RTP
网络层
将数据装入IP数据包,包括用于在主机间以及经过网络转发数据包时所用的源和目标地址信息。
实现IP数据包的路由
IP,ICMP,ARP,RARP
网络接口层
指定如何通过网络物理地发送数据,包括直接与网络媒体直接接触的硬件设备如何将比特流转换为电信号
以太网\帧中继\令牌环
了解TCP/IP核心协议:
1地址解析协议(ARP):
实现IP地址到物理地址的转换;
2网际协议(IP):
是无连接的、不可靠的数据报协议,主要负责主机之间的寻址和选择数据包的路由;
3网际消息协议(ICMP):
通过ICMP,使用IP通信的主机和路由器可以报告错误并交换受限控制和状态信息;
4用户数据报协议(UDP):
在主机之间提供轻便、快捷、不可靠地传输数据;
5传输控制协议(TCP):
提供可靠的、面向连接的数据报传递服务。
UDP/TCP比较:
表10-2
UDP
TCP
无连接的服务:
在主机间不建立会话
面向连接的服务:
在主机间建立会话
UDP不能确保或承认数据传递或序列化数据
TCP通过确认和按照顺序传递数据来确保数据的传递
使用UDP的程序负责提供数据传递的可靠性
TCP确保数据的可靠传输
UDP非常快,具有低开销要求,支持点对点或者一点对多点的通信
TCP比较慢,具有更高的开销要求,只支持点对点通信。
2.WindowsSocket网络程序开发步骤
表10-3
服务器端
客户端
创建流式套接字s1=socket(…)
将本地地址与s1相连接bind(s1,…)
监听来自客户方的连接listen(s1,…)
接受连接,并且得到新的套接字s2
s2=accept(s1,…)
建立连接
建立流式套接字s=socket(…)
将套接字和服务器端主机连接
connect(s,…)
在套接字s2上读些数据,直到数据交换完
recv(s2,…)send(s2,…)
关闭套接字s2colsesocket(s2)
关闭套接字s1closesocket(s1)
传输数据
在套接字上读写数据,直到数据交换完。
Send(s1….),recv(s1,…)
关闭套接字sclosesocket().
2.1.WindowsAPI网络应用程序举例
依照如下步骤建立工程:
1.建立Win32application工程WSocketAPI,在向导中选择典型的helloworld工程:
图10-2
2.因为要使用到WindowsAPI函数,所以加入相应的库文件wsock32.lib:
图10-3
3.定义用户消息WSA_DATA_READ,用于通知对端读取数据:
图10-4
4.增加监听、连接和发送菜单项:
图10-5
5.包含头文件winsock2.hstdio.h:
图10-6
工程建好以后,接下来进行代码添加:
回忆一下API编程中,菜单、用户自定义消息这些代码都应该添加在什么地方?
尤其留意一下API编程中菜单消息,用户自定义消息响应代码添加位置。
代码中对以上网络应用程序开发的主要函数进行详尽解说。
如有不清楚请查阅相关MSDN文档。
1.添加如下全局变量:
TCHARszName[80];
TCHARszName[80];
SOCKETsock1;//监听套接字
SOCKETsock2;//服务器端接收数据套接字
SOCKETsock3;//客户端发送数据套接字
图10-7
2.添加如下局部变量:
SOCKADDR_INlocal_sin;
SOCKADDR_INacc_sin;
图10-8
//case1
caseWM_CREATE:
{
WSADATAwsaData;
if(WSAStartup(MAKEWORD(1,1),&wsaData)!
=0)
//连接应用程序与WindowsSocketsDLL的第一个函数,
//完成socket初始化工作
//也只有该函数调用成功,其它的网络函数才能调用成功
//1应用程序要求的sockets版本,高字节代表低版本,低字节代表高版本
//2指向WSADATA结构的指针
{
MessageBox(hWnd,"WinSockstartup函数初始化错误","WinSockAPI函数:
WSAStartup",MB_OK);
}
}
break;
//case2
caseIDM_LISTEN:
{
sock1=socket(AF_INET,SOCK_STREAM,0);
//1地址族一般用AF_INET,internetwork:
UDP,TCP,etc
//2套接字类型SOCK_STREAM使用TCP的流式套接字
//3协议类型
//4返回一个整形的套接字描述符
if(INVALID_SOCKET==sock1)
{
MessageBox(hWnd,"socket()失败!
","错误",MB_OK);
closesocket(sock1);
break;
}
local_sin.sin_port=288;
local_sin.sin_family=AF_INET;
local_sin.sin_addr.s_addr=INADDR_ANY;
intnsize=sizeof(szName);
gethostname(szName,nsize);
SetWindowText(hWnd,"等待客户端发起连接...");
if(bind(sock1,(structsockaddrFAR*)&local_sin,sizeof(local_sin))==SOCKET_ERROR)
//1要绑定的套接字
//2指向sockaddr结构的地址
//3sockaddr结构长度
{
sprintf(szName,"%d错误",WSAGetLastError());
MessageBox(hWnd,szName,"绑定函数出错!
",MB_OK);
break;
}
if(listen(sock1,4)<0)
//1进行监听的套接字
//2没有整整完成连接的客户端连接请求个数最大值为5,最小位1
{
sprintf(szName,"%d错误",WSAGetLastError());
MessageBox(hWnd,szName,"监听函数错误",MB_OK);
break;
}intacc_sin_len=sizeof(acc_sin);
sock2=accept(sock1,(structsockaddrFAR*)&acc_sin,(intFAR*)(&acc_sin_len));
//1处于监听状态的套接字
//2将要接受地址的sockaddr指针
//3地址长度
if(sock2<0)
{
sprintf(szName,"%d错误",WSAGetLastError());
MessageBox(hWnd,szName,"接收连接错误",MB_OK);
break;
}
MessageBox(hWnd,szName,"接收连接成功",MB_OK);
if((WSAAsyncSelect(sock,hWnd,WSA_DATA_READ,FD_READ|FD_CLOSE))>0)
//功能:
要求socket的使用者在有event事件发生时通知使用者
//1对应的Socket
//2接收网络事件的窗口句柄
//3发送给窗口的网络事件消息lParam高字节代表错误代码,
//低字节代表网络事件
//4网络事件,具体值可查阅MSDN
{
wsprintf(szName,"%d(%d(0x%x))");
MessageBox(hWnd,"WSAASyncSelect()错误",szName,MB_OK);
closesocket(sock2);
}
}
break;
//case3
caseIDM_CONNECT:
{
SOCKADDR_INdest_sin;
PHOSTENTphe;
charszTemp[200];
sock3=socket(AF_INET,SOCK_STREAM,0);
if(sock3==INVALID_SOCKET)
{
MessageBox(hWnd,"socket()失败!
","错误",MB_OK);
closesocket(sock3);
break;
}
dest_sin.sin_port=288;
dest_sin.sin_family=AF_INET;
strcpy(szName,"li");
phe=gethostbyname(szName);
if(NULL==phe)
{
sprintf(szTemp,"%d错误,请确认%s处于监听状态",WSAGetLastError(),szName);
MessageBox(hWnd,szTemp,"gethostname()函数调用失败",MB_OK);
returnFALSE;
}
memcpy((charFAR*)&(dest_sin.sin_addr),phe->h_addr,phe->h_length);
if(connect(sock3,(PSOCKADDR)&dest_sin,sizeof(dest_sin))<0)
//1将要连接的socket
//2目标socket地址
//3地址长度
{
closesocket(sock3);
MessageBox(hWnd,"connect连接失败","错误",MB_OK);
break;
}
}
break;
//case4
caseWSA_DATA_READ:
{
charszTemp[80];
intnCount;
if(WSAGETSELECTEVENT(lParam)==FD_READ)
{
if(nCount=recv((SOCKET)wParam,szTemp,80,0))
//1将要接收信息的socket
//2接收数据的缓冲区
//3缓冲区长度
//4标志位通常设为0
{
szTemp[nCount]='\0';
MessageBox(hWnd,szTemp,"WSA_DATA_READ",MB_OK);
}
else
{
MessageBox(hWnd,"连接中断","出错",MB_OK);
}
}
}
break;
编译运行上述应用程序,看看是什么结果。
2.2.应用程序缺点及克服
应用程序服务器采用了阻塞式监听方式,他正在监听的时候,应用程序不能响应用户操作。
解决办法:
另开一条线程进行监听,该工程中可以使用Win32消息处理机制巧妙解决该问题。
我们使用WSAAsyncSelect(sock,hWnd,WSA_CONNECT_ACCEPT,FD_ACCEPT).这样,当接收到请求以后,窗口将得到消息WSA_CONNECT_ACCEPT。
可以在该消息响应代码中处理Accept,具体代码如下:
将监听函数后面代码替换成WSAAsyncSelect(sock,hWnd,WSA_CONNECT_ACCEPT,FD_ACCEPT):
if(listen(sock1,4)<0)
//1进行监听的套接字
//2没有整整完成连接的客户端连接请求个数最大值为5,最小位1
{
sprintf(szName,"%d错误",WSAGetLastError());
MessageBox(hWnd,szName,"监听出错",MB_OK);
break;
}
WSAAsyncSelect(sock,hWnd,WSA_CONNECT_ACCEPT,FD_ACCEPT);
也就是将监听函数后面代码放到消息WSA_CONNECT_ACCEPT的相应部分进行处理。
添加代码如下:
//case5
caseWSA_CONNECT_ACCEPT:
{
if(WSAGETSELECTERROR(lParam)==0)
{
intacc_sin_len=sizeof(acc_sin);
sock2=accept(sock1,(structsockaddrFAR*)&acc_sin,(intFAR*)(&acc_sin_len));
//1处于监听状态的套接字
//2将要接受地址的sockaddr指针
//3地址长度
if(sock2<0)
{
sprintf(szName,"%d错误",WSAGetLastError());
MessageBox(hWnd,szName,"接收连接错误!
",MB_OK);
break;
}
MessageBox(hWnd,szName,"接收连接成功!
",MB_OK);
if((WSAAsyncSelect(sock,hWnd,WSA_DATA_READ,FD_READ|FD_CLOSE))>0)
//功能:
要求socket的使用者在有event事件发生时通知使用者,此处为通知对方读取数据
//1对应的Socket
//2接收网络事件的窗口句柄
//3发送给窗口的网络事件消息lParam高字节代表错误代码,低字节代表网络事件
//4网络事件,具体值可查阅MSDN
{
wsprintf(szName,"%d(%d(0x%x))");
MessageBox(hWnd,"WSAASyncSelect()错误",szName,MB_OK);
closesocket(sock);
}
else
{
WSAAsyncSelect(sock,hWnd,0,0);
SetWindowText(hWnd,"取消监听");
}
}
}
break;
程序运行结果如下:
图10-9
练习:
数据的发送以及接收测试(数据的发送以及接收端不做特殊要求)。
3.MFC网络应用程序开发举例
3.1.MFC对Socket的封装
MFC对Sockect的封装:
CSockect的继承关系:
图10-10
MFC对CSockect的封装后,网络应用程序开发采用如下步骤:
表10-4
服务器
客户端
//构造一个CSockect对象sockect
CSockectsockSrvr;
//构造一个CSockect对象sockect
CSockectsockClient;
//生成sockect
sockSrvr.Create(nPort);
//生成sockect
sockClient.Create();
//进行监听
sockSrvr.Listen();
//实现链接
sockClient.Connect(strAddr,nPort)
//构造新的sockect
sockSrvr.Accept(sockRecv);
//构造文件对象
CSockFilefile(&sockRecv);
//构造文件对象
CSockFilefile(&sockClient);
//构造CArchive对象
CArchive.arIn(&file,CArchive:
:
load)
//构造CArchive对象
CArchive.arIn(&file,CArchive:
:
load)
//使用CArchive传送数据
arIn>>dwValue;
//使用CArchive传送数据
arIn>>dwValue;
3.2.具体实现代码工程实例
下面以非面向连接的用户数据报协议实现MFCSocket聊天程序:
1建立基于对话框的MFC工程MFCSocket,记住选择Sockect支持;
图10-11
2.设计一个如下图所示的对话框,用来设置数据发送的目标IP地址:
图10-12
图10-13
3.添加类CSocketRecv,选择基类为CAsyncSocket,引入网络服务。
并添加方法使之与对话框关联,实现消息的自动接收。
4.设计如下主对话框界面:
图10-14
图10-15
其中按钮控件“IP地址设置”用于跳出IP地址设置对话框进行目标IP地址的设置,“数据发送”按钮用于实现数据的发送。
编辑框控件用于接受用户输入聊天数据(文本),列表框控件用于显示聊天信息,包括数据和数据的源、目的地址信息。
主要代码信息如下(蓝色部分为自己添加或者进行消息映射时生成代码):
//MFCSocketDlg.h:
headerfile
#if!
defined(AFX_MFCSOCKETDLG_H__985F963A_566B_4E7C_9E54_8A692F50D63B__INCLUDED_)
#defineAFX_MFCSOCKETDLG_H__985F963A_566B_4E7C_9E54_8A692F50D63B__INCLUDED_
#include"SocketRecv.h"
#if_MSC_VER>1000
#pragmaonce
#endif//_MSC_VER>1000
/////////////////////////////////////////////////////////////////////////////
//CMFCSocketDlgdialog
classCMFCSocketDlg:
publicCDialog
{
//Construction
public:
voidOnReceive();
CMFCSocketDlg(CWnd*pParent=NULL);//standardconstructor
CStringm_strIpInfo;
CSocketRecvm_socketRecv;
//DialogData
//{{AFX_DATA(CMFCSocketDlg)
enum{IDD=IDD_MFCSOCKET_DIALOG};
CListBoxm_ctrlListInfo;
//}}AFX_DATA
//ClassWizardgeneratedvirtualfunctionoverrides
//{{AFX_VIRTUAL(CMFCSocketDlg)
protected:
virtualvoidDoDataExchange(CDataExchange*pDX);//DDX/DDVsupport
//}}AFX_VIRTUAL
//Implementation
protected:
HICONm_hIcon;
//Generatedmessagemapfunctions
//{{AFX_MSG(CMFCSocketDlg)
virtualBOOLOnInitDialog();
afx_msgvoidOnSysCommand(UINTnID,LPARAMlParam);
afx_msgvoidOnPaint();
afx_msgHCURSOROnQueryDragIcon();
afx_msgvoidOnIpSet();
afx_msgvoidOnSendData();
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
//{{AFX_INSERT_LOCATION}}
//MicrosoftVisualC++willinsertadditionaldeclarationsimmediatelybeforethepreviousline.
#endif//!
defined(AFX_MFCSOCKETDLG