计算机网络课程设计(TCPIP协议分析及实现)
(TCP/IP协议分析及实现)
班级:030634班
学号:030820339
姓名:冯炜轩
指导老师:贺刚
2009年4月28日
一、 设计目的及要求
1、初步掌握Windows socket编程的方法,并运用所学的知识实现客户端和服务器端的简单双向通信。 2、熟悉TCP或IP协议的基本原理,并能对客户/服务器编程模式有一个深入理解。 3、进一步熟悉面向对象的编程方法和封装的相关概念。
4、了解用Visual C++ 进行基于对话框的编程的方法。
5、培养一定的自学能力和独立分析问题、解决问题的能力。比如查找资料
书
关于书的成语关于读书的排比句社区图书漂流公约怎么写关于读书的小报汉书pdf
、自主学习新知识。
二、 软件、硬件环境
软件环境:Windows XP,Visual C++6.0
硬件环境:AMD Sempron(tm)Processor 3000+ , 1.61 GHz,512MB 内存物理地址扩展
三、 基本思路及所涉及的相关理论
3(1 相关理论与原理
3.1.1 Winsock
Windows Sockets接口是TCP/IP网络最为通用API,已成为Windows网络编程的事实上的
标准
excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载
。 Microsoft在Sockets API库的基础上创建了WinSock控件,专门用于Windows接口,与Sockets完全兼容。利用 WinSock 控件可以与远程计算机建立连接,Winsock包含有用户数据文报协议 (UDP)和传输
UDP 网络服务的方便途径。控制协议 (TCP)。Winsock控件对用户来说是不可见的,它提供了访问 TCP 和
Winsock控件封装了烦琐的技术细节,编写网络应用程序时,不必了解 TCP/IP的细节或调用低级的 Winsock APIs。通过设置控件的属性并调用其方法就可轻易连接到一台远程机器上去,并且还可双向交换数据。
Windows Sockets 支持两种类型的套字接,即流式套字接(SOCK——STREAM)和数据报套字接(SOCK——DGRAM)。对于要求精确传输数据的Windows Sockets通信程序,一般采用流式套接字。流式套接字提供了一个面向连接的、可靠的、数据无错的、无重复发送的及按发关顺序接收数据的报务。其内设流量控制,避免数据流超限,同时数据被看做是字节流,无长度限制。流式套接字的服务进程和客户进程在通信前必须建立各自的套接字并进行了连接,然后才能对相应的套接字进行“读”、“写”操作,实现数据的传送。会用到WindSocket的一些基本函数。关于流式套字接具体介绍如下:
1、创建套接字——socket()
功能:使用前创建一个新的套接字
格式:SOCKET PASCAL FAR socket(int af,int type,int procotol);
af: 通信发生的区域,一般取AFI_NET
type: 要建立的套接字类型流式套字接(SOCK——STREAM)数据报套字接(SOCK——DGRAM)
procotol: 使用的特定协议,一般0,即为TCP/IP协议
2、指定本地地址——bind()
功能:将套接字地址与所创建的套接字号联系起来。
格式:int PASCAL FAR bind(SOCKET s,const struct sockaddr FAR * name,int namelen);
参数:s: 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
其它:没有错误,bind()返回0,否则SOCKET_ERROR
地址结构说明:
struct sockaddr_in
{
short sin_family;//AF_INET
u_short sin_port;//16位端口号,网络字节顺序
struct in_addr sin_addr;//32位IP地址,网络字节顺序
char sin_zero[8];//保留 }
3、建立套接字连接——connect()和accept()
功能:共同完成连接工作
格式:int PASCAL FAR connect(SOCKET s,const struct sockaddr FAR * name,int namelen);
SOCKET PASCAL FAR accept(SOCKET s,struct sockaddr FAR * name,int FAR * addrlen);
参数:同上
4、监听连接——listen()
功能:用于面向连接服务器,表明它愿意接收连接。
格式:int PASCAL FAR listen(SOCKET s, int backlog);// backlog为等待队列的最大长度 5、数据传输——send()与recv()
功能:数据的发送与接收
格式:int PASCAL FAR send(SOCKET s,const char FAR * buf,int len,int flags);
int PASCAL FAR recv(SOCKET s,const char FAR * buf,int len,int flags);
参数:buf:指向存有传输数据的缓冲区的指针
6、关闭套接字——closesocket()
功能:关闭套接字s
格式:BOOL PASCAL FAR closesocket(SOCKET s); 7、字节顺序转换函数:-----inet_addr()
功能: 将点式IP地址转换为不念旧恶32位的无符号长整数。
格式:unsigned long inet_addr(const char FAR* cp) 8、WSAStartup()
功能:加载Winsock DLL相应版本。
格式:int WSAStartup() (WORD wVersionRequested,LPWSADATA lpWSAData);
9、WSAScleanup()
功能: 终止Winsock DLL的使用,并释放资源。
格式:int WSACleanup(void)
3.1.2 用winsock控件实现点对点通信
随着计算机和Internet网络的发展,原先在测量、控制、消费等领域不同设备/仪器之间,大多采用RS232/485或现场总线组建网络等进行数据传输的情况正在改变,特别在要求数据传输速度和可靠性方面的应用产品,转向基于Internet网络的远程传输和应用。网络编程大多基于TCP/IP协议,实现方法有多种,
复杂但编程适应性广的方法是调用底层的Winsock API函数或MFC基本类等;简单方便的方法可以采用如Winsock控件。在网络编程中最常用的方案便是客户机/服务器模型,客户应用程序向服务器程序请求服务。另外一种模式是点对点通信,通信双方对等,既有客户机功能又有服务器的功能,编程简单,适合传输数据流少的场合,而且采用简单的UDP协议,易于用微控制器等嵌入式系统实现,在测量控制方面有许多应用。接下将介绍一些网络编程和Winsock控件知识,用VC++实现点对点通信。 3.1.3网络通信协议基础
Winsock控件支持两种协议,TCP协议和UDP协议,都属于用TCP/IP协议。TCP(Transfer Control
Protocol)是传输控制协议的简称,是基于连接的协议,在数据传输之前必须先建立连接,通信双方是基于客户/服务器模型,必须分别建立客户应用程序和服务器应用程序。UDP(用户数据文报协议)协议是一种无连接协议,通信双方之间的传输类似于传递邮件:消息从一方发送到另一方,但是两者之间没有明确的连接,通信双方是对等的,单次传输的最大数据量取决于具体的网络。本设计是使用TCP的服务类型。
利用Winsock控件创建双方的通信过程如下(以TCP为例):
首先服务器端的应用程序先要运行,并且进行端口号的设置,然后进入等待,即等待客户端应用程序的连接请求;然后客户端的应用程序开始运行,向服务器发送信息,在服务器端会以消息对话框的形式显示这些信息;然后服务器端的应用程序会把这些信息再次地传回给客户端的应用程序,同样以消息对话框的形式显示出来。
3.1.4 二进制数据点对点通信的实现
基于网络的远程控制和测量应用中,一般数据传输采用二进制格式,双机之间的连接方式是主丛式,构建一个测控网络,从机之间如果要交换数据也得通过主机。在网络测量控制等领域,通信双方需要传输的数据流量少,下位机一般是微控制器等嵌入式系统,数据处理能力较慢,通信双方也不必保持紧密联系,因此大多采用UDP协议,基于点对点的方式,双方通信的数据可靠性可以通过定义数据表示格式来保证。为了描述如何应用,我们在这里建立一个基于对话框的简单的点对点通信例子,说明在VC++中如何用Winsock控件实现二进制数据点对点通信。
3(2 典型过程图
面向连接的流式套接字的系统调用模式图
四、方案设计
通过以上理论的学习,我设计了此双向通信的C/S程序。其设计思路基本如下:
客户机/服务器模式
客户端应用程序主要功能是:建立到服务器的连接,向服务器发送信息,接收从服务器发来的回传信息,关闭与服务器的连接。
服务器端应用程序主要功能是:创建监听Socket进行监听,在有客户进行连接请求时会创建一个新的接收的Socket处理这个客户得数据发送和接收(此部分最多可接收和处理5个客户的连接请求)。
五、 设计结果
运行前的准备:
获取主机IP地址。具体方法是在运行窗口中运行cmd命令(如图1)。
图1
接着输入ipconfig命令,即可获得所需地址(如图2)。
图2
正式运行:
先运行服务器端程序,进行网络的端口号设置,如图3
图3
接着运行客户端程序,进行连接,当正确运行时,系统会弹出连接成功的对话框,接着即可发送消息(如图4)。
图4
然后,服务器端弹出提示信息(如图5),最后,客户端弹出提示信息(如图6)。
图5 图6
以上就是一次完整的通信过程。
六、源代码(含有注释)
服务器端:
AcceptSocket.cpp
// AcceptSocket.cpp : implementation file
#include "stdafx.h"
#include "Server.h"
//自己添加的成员函数
#include "AcceptSocket.h"
#include "MainFrm.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__; #endif
/////////////////////////////////////////////////////////////////////////////
// CAcceptSocket
CAcceptSocket::CAcceptSocket(CMainFrame * pMainFrame)
{
m_pMainFrame=pMainFrame;//进程框架对象的赋值
}
CAcceptSocket::~CAcceptSocket()
{
}
// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CAcceptSocket, CSocket)
//{{AFX_MSG_MAP(CAcceptSocket)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif // 0
/////////////////////////////////////////////////////////////////////////////
// CAcceptSocket member functions
void CAcceptSocket::OnReceive(int nErrorCode)
{
char pMsg[10000],tempMsg[1000];
int ByteCount;//每次读取的字符个数
int EndFlag=0;//接受完后的标志
char AnswerMsg[10100]; //回应信息字符串
strcpy(pMsg,"");
do
{
strcpy(tempMsg,""); //每次读取时,把读取使用的缓冲区置成空
ByteCount=Receive(tempMsg,1000);//每次读取1000个字符
if (ByteCount>1000 || ByteCount<=0)
{
AfxMessageBox("接受数据出错",MB_OK);
return ;
}
else if(ByteCount<1000 && ByteCount>0)
{
EndFlag=1; //字节不足1000时,表示数据接受已经完成
}
//添加字符串的结束苻
tempMsg[ByteCount]=0;
//把每次收到的数据放在一起
strcat(pMsg,tempMsg);
} while(EndFlag==0);
//显示接收但的数据
AfxMessageBox(pMsg,MB_OK);
sprintf(AnswerMsg,"我已经收到你的消息\n\"%s\"\n 谢谢",pMsg);
//发送回传信息
Send(AnswerMsg,strlen(AnswerMsg),0);
CSocket::OnReceive(nErrorCode); }
ListenSocket.cpp
// ListenSocket.cpp : implementation file //
#include "stdafx.h"
#include "Server.h"
//自己添加的成员函数
#include "ListenSocket.h"
#include "AcceptSocket.h"
#include "MainFrm.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__; #endif
/////////////////////////////////////////////////////////////////////////////
// CListenSocket
//构造函数和析构函数的实现
CListenSocket::CListenSocket(CMainFrame * pMainFrame)
{
m_pMainFrame=pMainFrame; //进程框架对象的赋值
}
CListenSocket::~CListenSocket()
{
}
// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CListenSocket, CSocket)
//{{AFX_MSG_MAP(CListenSocket)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif // 0
/////////////////////////////////////////////////////////////////////////////
// CListenSocket member functions
void CListenSocket::OnAccept(int nErrorCode)
{
//生成接收Socket函数
CAcceptSocket * pSocket=new CAcceptSocket(m_pMainFrame);
//如果监听成功了,则通过框假类的成员函数
//把接收的socket放入接收socket队列
if(Accept(*pSocket))
{
m_pMainFrame->m_pAcceptList.AddTail(pSocket);
}
else//否则进行错误处理
delete pSocket;
CSocket::OnAccept(nErrorCode);
}
MainFrm.cpp
// MainFrm.cpp : implementation of the CMainFrame class
//
#include "stdafx.h"
#include "Server.h"
#include "MainFrm.h"
//以下是自己添加的头文件
#include "SetPortDlg.h"
#include "ListenSocket.h"
#include "AcceptSocket.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__; #endif
/////////////////////////////////////////////////////////////////////////////
// CMainFrame
IMPLEMENT_DYNCREATE(CMainFrame, CFrameWnd)
BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
//{{AFX_MSG_MAP(CMainFrame)
ON_WM_CREATE()
ON_COMMAND(ID_SET_PORT, OnSetPort)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
static UINT indicators[] =
{
ID_SEPARATOR, // status line indicator
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
/////////////////////////////////////////////////////////////////////////////
// CMainFrame construction/destruction
//构造函数和析够函数的实现
CMainFrame::CMainFrame()
{
// TODO: add member initialization code here
m_pListenSocket=NULL; //初始化请求Socket
m_pAcceptList.RemoveAll();
}
CMainFrame::~CMainFrame()
{
//释放监听空间
delete m_pListenSocket;
}
int CMainFrame::OnCreate(LPCREATESTRUCT lpCreateStruct) {
if (CFrameWnd::OnCreate(lpCreateStruct) == -1)
return -1;
if (!m_wndToolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_wndToolBar.LoadToolBar(IDR_MAINFRAME))
{
TRACE0("Failed to create toolbar\n");
return -1; // fail to create
}
if (!m_wndStatusBar.Create(this) ||
!m_wndStatusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT)))
{
TRACE0("Failed to create status bar\n");
return -1; // fail to create
}
// TODO: Delete these three lines if you don't want the toolbar to
// be dockable
m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_wndToolBar);
return 0;
}
BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs) {
if( !CFrameWnd::PreCreateWindow(cs) )
return FALSE;
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return TRUE;
}
/////////////////////////////////////////////////////////////////////////////
// CMainFrame diagnostics
#ifdef _DEBUG
void CMainFrame::AssertValid() const {
CFrameWnd::AssertValid();
}
void CMainFrame::Dump(CDumpContext& dc) const
{
CFrameWnd::Dump(dc);
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CMainFrame message handlers
//"响应端口号设置采单命令的响应处理函数"
void CMainFrame::OnSetPort() {
//弹出设置IP地址和端口号的对话框
CSetPortDlg dlg;
if(dlg.DoModal()==IDOK)
{
//创建一个socket监听
m_pListenSocket=new CListenSocket(this);
if(m_pListenSocket->Create(dlg.m_nPort))
{
//把监听的socket设置 监听状态
if(!m_pListenSocket->Listen(5))
{
MessageBox("设置监听Socket失败","错误信息",MB_OK);
//释放分配给监听socket空间
delete m_pListenSocket;
}
}
else
{
MessageBox("生成Socket错误","错误信息",MB_OK);
delete m_pListenSocket;
}
}
}
客户端:
ClientSocke.cpp
// ClientSocket.cpp : implementation file //
#include "stdafx.h"
#include "Client.h"
#include "ClientSocket.h"
#include "MainFrm.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__; #endif
/////////////////////////////////////////////////////////////////////////////
// CClientSocket
CClientSocket::CClientSocket(CMainFrame *pMainFrame)
{
m_pMainFrame=pMainFrame;
}
CClientSocket::~CClientSocket()
{
}
// Do not edit the following lines, which are needed by ClassWizard.
#if 0
BEGIN_MESSAGE_MAP(CClientSocket, CSocket)
//{{AFX_MSG_MAP(CClientSocket)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
#endif // 0
/////////////////////////////////////////////////////////////////////////////
// CClientSocket member functions void CClientSocket::OnReceive(int nErrorCode) {
char pMsg[10000],tempMsg[1000]; //用来接受数据而用的变量
int ByteCount; //每次读取的字符个数
int EndFlag=0; //接受完后的标志
//初始化接受的数据
strcpy(pMsg,"");
do
{
strcpy(tempMsg,""); //每次读取时,把读取使用的缓冲区置成空
ByteCount=Receive(tempMsg,1000);//每次读取1000个字符
if (ByteCount>1000 || ByteCount<=0)
{
AfxMessageBox("接受数据出错",MB_OK);
return ;
}
else if(ByteCount<1000 && ByteCount>0)
{
EndFlag=1;
}
tempMsg[ByteCount]=0;
strcat(pMsg,tempMsg);
} while(EndFlag==0);
AfxMessageBox(pMsg,MB_OK);
CSocket::OnReceive(nErrorCode);
}
七、体会和心得
网络通信技术的进步使信息在瞬间可以流遍全球,凭借这些技术,信息资源可以在全球充分利用。在这次网络课程设计中遇到的一些问题,最终得到了解决,在实验过程中由于大家互相学习交流,不懂的问题互相探讨,使我们在学习过程中有了很大的进步,对于我个人,我认为在这门网络与通信程序设计学习过程中学到了很多东西,我希望像贺刚老师说的那样,学习其中的内涵。知识无穷尽,是不可能学得完的。应该像老师说的那样将知识融会贯通,这才是最重要的。