TCPUDP编程.docx

上传人:b****5 文档编号:30702756 上传时间:2023-08-19 格式:DOCX 页数:27 大小:163.23KB
下载 相关 举报
TCPUDP编程.docx_第1页
第1页 / 共27页
TCPUDP编程.docx_第2页
第2页 / 共27页
TCPUDP编程.docx_第3页
第3页 / 共27页
TCPUDP编程.docx_第4页
第4页 / 共27页
TCPUDP编程.docx_第5页
第5页 / 共27页
点击查看更多>>
下载资源
资源描述

TCPUDP编程.docx

《TCPUDP编程.docx》由会员分享,可在线阅读,更多相关《TCPUDP编程.docx(27页珍藏版)》请在冰豆网上搜索。

TCPUDP编程.docx

TCPUDP编程

实验四TCP、UDP协议分析与通信程序设计

【实验目的】

1、理解与掌握TCP协议、UDP协议通信机制

2、熟悉使用windows操作系统所提供的网络编程接口Winsock

3、掌握基于Winsock的TCP或UDP应用程序设计方法

【预习要求】

1、复习课堂上所学习的TCP协议、UDP协议方面基本知识。

2、实验前参看相关资料,学习网络编程接口Winsock,

利用自己所熟悉的程序设计开发工具,完成本实验的程序设计。

【设计任务与要求】

设计一套基于socket接口(WinsockAPI或Winsock控件)的网络通信程序,该应用由服务器端和客户端两个程序组成,服务器端能接收客户端经网络传输的文件,并按原来的名字并存储在本地硬盘上。

要求程序使用tcp协议(面向连接)完成数据通信。

当采用tcp协议时,通信过程如下:

1、服务器端程序先运行,对指定的TCP端口进行监听。

2、给客户端指定服务器端程序所在主机IP地址、服务器端程序监听端口参数并运行后,与服务器端建立连接。

3、客户端向服务器端传输一个文件,并存储在服务器端的本地硬盘上。

(文件大小限定为10k字节左右)

4、传输完毕后,进行提示,并关闭连接。

【设计与分析环境】

(一)操作系统(选择下列自己所熟悉的):

1、WindowsXP/2003

2、Linux

(二)开发工具(选择下列自己所熟悉的):

1、Windows环境下的VisualC++(或BorlandC++)

通信接口采用WinsocketAPI,或基于MFC的套接字类(或基于Winsocket的控件)。

2、Windows环境下的VisualC#

通信接口采用Winsock控件

3、Windows环境下的其它开发工具

通信接口采用相关的通信控件。

4、Linux环境下的gcc

通信接口采用socket

(三)协议解码工具:

wireshark(或snifferpro)

【实验原理】

(一)TCP报文格式

16位源端口号

16位目的端口号

32位序号

32位确认序号

4位首部长度

保留6位

U

R

G

A

C

K

P

S

H

R

S

T

S

Y

N

F

I

N

16位窗口大小

16位检验和

16位紧急指针

选项

数据

说明:

(1)每个TCP段都包括源端和目的端的端口号,用于寻找发送端和接收端的应用进程。

这两个值加上IP首部的源端IP地址和目的端IP地址唯一确定一个TCP连接。

(2)序号用来标识从TCP发送端向接收端发送的数据字节流,它表示在这个报文段中的第一个数据字节。

如果将字节流看作在两个应用程序间的单向流动,则TCP用序号对每个字节进行计数。

(3)当建立一个新连接时,SYN标志变1。

序号字段包含由这个主机选择的该连接的初始序号ISN,该主机要发送数据的第一个字节的序号为这个ISN加1,因为SYN标志使用了一个序号。

(4)既然每个被传输的字节都被计数,确认序号包含发送确认的一端所期望收到的下一个序号。

因此,确认序号应当时上次已成功收到数据字节序号加1。

只有ACK标志为1时确认序号字段才有效。

(5)发送ACK无需任何代价,因为32位的确认序号字段和ACK标志一样,总是TCP首部的一部分。

因此一旦一个连接建立起来,这个字段总是被设置,ACK标志也总是被设置为1。

(6)TCP为应用层提供全双工的服务。

因此,连接的每一端必须保持每个方向上的传输数据序号。

(7)TCP可以表述为一个没有选择确认或否认的华东窗口协议。

因此TCP首部中的确认序号表示发送方已成功收到字节,但还不包含确认序号所指的字节。

当前还无法对数据流中选定的部分进行确认。

(8)首部长度需要设置,因为任选字段的长度是可变的。

TCP首部最多60个字节。

(9)6个标志位中的多个可同时设置为1

   ◆URG-紧急指针有效

   ◆ACK-确认序号有效

   ◆PSH-接收方应尽快将这个报文段交给应用层

   ◆RST-重建连接

   ◆SYN-同步序号用来发起一个连接

   ◆FIN-发送端完成发送任务

(10)TCP的流量控制由连接的每一端通过声明的窗口大小来提供。

窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端期望接收的字节数。

窗口大小是一个16为的字段,因而窗口大小最大为65535字节。

(11)检验和覆盖整个TCP报文端:

TCP首部和TCP数据。

这是一个强制性的字段,一定是由发送端计算和存储,并由接收端进行验证。

TCP检验和的计算和UDP首部检验和的计算一样,也使用伪首部。

(12)紧急指针是一个正的偏移量,黄蓉序号字段中的值相加表示紧急数据最后一个字节的序号。

TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。

(13)最常见的可选字段是最长报文大小MMS,每个连接方通常都在通信的第一个报文段中指明这个选项。

它指明本端所能接收的最大长度的报文段。

(二)用户数据报格式:

源端口号

(2字节)

目的端口号

(2字节)

总长度

(2字节)

检验和

(2字节)

数据

(三)Winsock网络编程接口简介

1.套接口网络编程原理

套接口有三种类型:

流式套接口、数据报套接口及原始套接口。

流式套接口定义了一种可靠的面向连接的服务,实现了无差错无重复的顺序数据传输.数据报套接口定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠,无差错.原始套接口允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议实现的测试等。

无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序与服务程序之间的相互作用。

若使用无连接的套接口编程,程序的流程如图:

面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决的,而且往往是并发服务器。

使用面向连接的套接口编程,其时序可以通过下图来表示:

套接口工作过程如下:

服务器首先启动,通过调用socket()建立一个套接口,然后调用bind()将该套接口和本地网络地址联系在一起,再调用listen()使套接口做好侦听的准备,并规定它的请求队列的长度,之后就调用accept()来接收连接.客户在建立套接口后就可调用connect()和服务器建立连接.连接一旦建立,客户机和服务器之间就可以通过调用send()和recv()来发送和接收数据.最后,待数据传送结束后,双方调用close()关闭套接口。

2、常用的WindowsSocketAPI函数

(1)WSAStartup函数

intWSAStartup(

WORDwVersionRequested,

LPWSADATAlpWSAData

);

使用Socket的程序在使用Socket之前必须调用WSAStartup函数。

该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。

当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。

以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。

该函数执行成功后返回0。

例:

假如一个程序要使用2.1版本的Socket,那么程序代码如下

wVersionRequested=MAKEWORD(2,1);

err=WSAStartup(wVersionRequested,&wsaData);

(2)WSACleanup函数

intWSACleanup(void);

应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。

(3)socket函数

SOCKETsocket(

intaf,

inttype,

intprotocol

);

应用程序调用socket函数来创建一个能够进行网络通信的套接字。

第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置PF_INET;第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM;第三个参数指定应用程序所使用的通信协议。

该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET。

套接字描述符是一个整数类型的值。

每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。

该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。

每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。

下面是一个创建流套接字的例子:

structprotoent*ppe;

ppe=getprotobyname("tcp");

SOCKETListenSocket=socket(PF_INET,SOCK_STREAM,ppe->p_proto);

(4)closesocket函数

intclosesocket(

SOCKETs

);

closesocket函数用来关闭一个描述符为s套接字。

由于每个进程中都有一个套接字描述符表,表中的每个套接字描述符都对应了一个位于操作系统缓冲区中的套接字数据结构,因此有可能有几个套接字描述符指向同一个套接字数据结构。

套接字数据结构中专门有一个字段存放该结构的被引用次数,即有多少个套接字描述符指向该结构。

当调用closesocket函数时,操作系统先检查套接字数据结构中的该字段的值,如果为1,就表明只有一个套接字描述符指向它,因此操作系统就先把s在套接字描述符表中对应的那条表项清除,并且释放s对应的套接字数据结构;如果该字段大于1,那么操作系统仅仅清除s在套接字描述符表中的对应表项,并且把s对应的套接字数据结构的引用次数减1。

closesocket函数如果执行成功就返回0,否则返回SOCKET_ERROR。

(5)send函数

intsend(

SOCKETs,

constcharFAR*buf,

intlen,

intflags

);

不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。

客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。

该函数的第一个参数指定发送端套接字描述符;第二个参数指明一个存放应用程序要发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数;第四个参数一般置0。

这里只描述同步Socket的send函数的执行流程。

当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。

如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。

要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。

如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。

(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)

注意:

在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

(6)recv函数

intrecv(

SOCKETs,

charFAR*buf,

intlen,

intflags

);

不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。

该函数的第一个参数指定接收端套接字描述符;第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;第三个参数指明buf的长度;第四个参数一般置0。

这里只描述同步Socket的recv函数的执行流程。

当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到协议把数据接收完毕。

当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。

recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。

如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

注意:

在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

(7)bind函数

intbind(

SOCKETs,

conststructsockaddrFAR*name,

intnamelen

);

当创建了一个Socket以后,套接字数据结构中有一个默认的IP地址和默认的端口号。

一个服务程序必须调用bind函数来给其绑定一个IP地址和一个特定的端口号。

客户程序一般不必调用bind函数来为其Socket绑定IP地址和断口号。

该函数的第一个参数指定待绑定的Socket描述符;第二个参数指定一个sockaddr结构,该结构是这样定义的:

structsockaddr{

u_shortsa_family;

charsa_data[14];

};

sa_family指定地址族,对于TCP/IP协议族的套接字,给其置AF_INET。

当对TCP/IP协议族的套接字进行绑定时,我们通常使用另一个地址结构:

structsockaddr_in{

shortsin_family;

u_shortsin_port;

structin_addrsin_addr;

charsin_zero[8];

};

其中sin_family置AF_INET;sin_port指明端口号;sin_addr结构体中只有一个唯一的字段s_addr,表示IP地址,该字段是一个整数,一般用函数inet_addr()把字符串形式的IP地址转换成unsignedlong型的整数值后再置给s_addr。

有的服务器是多宿主机,至少有两个网卡,那么运行在这样的服务器上的服务程序在为其Socket绑定IP地址时可以把htonl(INADDR_ANY)置给s_addr,这样做的好处是不论哪个网段上的客户程序都能与该服务程序通信;如果只给运行在多宿主机上的服务程序的Socket绑定一个固定的IP地址,那么就只有与该IP地址处于同一个网段上的客户程序才能与该服务程序通信。

我们用0来填充sin_zero数组,目的是让sockaddr_in结构的大小与sockaddr结构的大小一致。

下面是一个bind函数调用的例子:

structsockaddr_insaddr;

saddr.sin_family=AF_INET;

saddr.sin_port=htons(8888);

saddr.sin_addr.s_addr=htonl(INADDR_ANY);

bind(ListenSocket,(structsockaddr*)&saddr,sizeof(saddr));

(8)listen函数

intlisten(SOCKETs,intbacklog);

服务程序可以调用listen函数使其流套接字s处于监听状态。

处于监听状态的流套接字s将维护一个客户连接请求队列,该队列最多容纳backlog个客户连接请求。

假如该函数执行成功,则返回0;如果执行失败,则返回SOCKET_ERROR。

(9)accept函数

SOCKETaccept(

SOCKETs,

structsockaddrFAR*addr,

intFAR*addrlen

);

服务程序调用accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失败就返回INVALID_SOCKET。

该函数的第一个参数指定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。

下面是一个调用accept的例子:

structsockaddr_inServerSocketAddr;

intaddrlen;

addrlen=sizeof(ServerSocketAddr);

ServerSocket=accept(ListenSocket,(structsockaddr*)&ServerSocketAddr,&addrlen);

(10)connect函数

intconnect(

SOCKETs,

conststructsockaddrFAR*name,

intnamelen

);

客户程序调用connect函数来使客户Sockets与监听于name所指定的计算机的特定端口上的服务Socket进行连接。

如果连接成功,connect返回0;如果失败则返回SOCKET_ERROR。

下面是一个例子:

structsockaddr_indaddr;

memset((void*)&daddr,0,sizeof(daddr));

daddr.sin_family=AF_INET;

daddr.sin_port=htons(8888);

daddr.sin_addr.s_addr=inet_addr("133.197.22.4");

connect(ClientSocket,(structsockaddr*)&daddr,sizeof(daddr));

【设计示例】

示例程序功能:

服务器端先运行,客户端向服务器端说”Hello!

”,服务器端向客户端说”SeeYou!

”,双方通信结束。

(一)服务器端程序:

server.cpp

//Compileandlinkwithwsock32.lib

//Example:

Server2000

//

#include

#include

#pragmacomment(lib,"wsock32.lib")

//Functionprototype

voidStreamServer(shortnPort);

//宏定义用来打印错误消息

#definePRINTERROR(s)\

fprintf(stderr,"\n%s:

%d\n",s,WSAGetLastError())

////////////////////////////////////////////////////////////

voidmain(intargc,char**argv)

{

WORDwVersionRequested=MAKEWORD(1,1);

WSADATAwsaData;

intnRet;

shortnPort;

//

//检查是否传递了参数“端口”

if(argc!

=2)

{

fprintf(stderr,"\n参数错误:

[服务器端口]\n");

return;

}

nPort=atoi(argv[1]);//将端口号由“字符串”格式转换成“短整数”格式

//

//初始化通信接口winsocket

nRet=WSAStartup(wVersionRequested,&wsaData);

if(wsaData.wVersion!

=wVersionRequested)

{

fprintf(stderr,"\nWrongversion\n");

return;

}

//

//Dothestuffastreamserverdoes

//

StreamServer(nPort);

//

//ReleaseWinSock

///去初始化通信接口winsocket

WSACleanup();

}

////////////////////////////////////////////////////////////

voidStreamServer(shortnPort)

{

//

//CreateaTCP/IPstreamsocketto"listen"with

SOCKETlistenSocket;//定义侦听套接字

listenSocket=socket(AF_INET,//Addressfamily

SOCK_STREAM,//Sockettype

IPPROTO_TCP);//Protocol

if(listenSocket==INVALID_SOCKET)

{

PRINTERROR("socket()");

return;

}

//

//Fillintheaddressstructure

SOCKADDR_INsaServer;

//初始化本地套接字地址

saServer.sin_family=AF_INET;

saServer.si

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 求职职场 > 简历

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1