61阅读

tcp udp-网络管理之TCP/UDP篇

发布时间:2017-08-13 所属栏目:udp编程

一 : 网络管理之TCP/UDP篇

我们学习过什么是“数据包”。理解数据包,对于网络管理的网络安全具有至关重要的意义。比如,防火墙的作用本质就是检测网络中的数据包,判断其是否违反了预先设置的规则,如果违反就加以阻止。图1就是瑞星个人版防火墙软件设置规则的界面。细心的读者会发现,图1中的“协议”栏中有“TCP”、“UDP”等名词,它们是什么意思呢?现在我们就来讲讲什么是TCP和UDP。

面向连接的TCP

“面向连接”就是在正式通信前必须要与对方建立起连接。比如你给别人打电话,必须等线路接通了、对方拿起话筒才能相互通话。

图1

TCP(Transmission Control Protocol,传输控制协议)是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来,其中的过程非常复杂,我们这里只做简单、形象的介绍,你只要做到能够理解这个过程即可。我们来看看这三次对话的简单过程:主机A向主机B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;主机B向主机A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,主机A才向主机B正式发送数据。

TCP协议能为应用程序提供可靠的通信连接,使一台计算机发出的字节流无差错地发往网络上的其他计算机,对可靠性要求高的数据通信系统往往使用TCP协议传输数据。

图2

我们来做一个实验,用计算机A(安装Windows 2000 Server操作系统)从“网上邻居”上的一台计算机B拷贝大小为8,644,608字节的文件,通过状态栏右下角网卡的发送和接收指标就会发现:虽然是数据流是由计算机B流向计算机A,但是计算机A仍发送了3,456个数据包,如图2所示。这些数据包是怎样产生的呢?因为文件传输时使用了TCP/IP协议,更确切地说是使用了面向连接的TCP协议,计算机A接收数据包的时候,要向计算机B回发数据包,所以也产生了一些通信量。

图3

如果事先用网络监视器监视网络流量,就会发现由此产生的数据流量是9,478,819字节,比文件大小多出10.96%(如图3所示),原因不仅在于数据包和帧本身占用了一些空间,而且也在于TCP协议面向连接的特性导致了一些额外的通信量的产生。

面向非连接的UDP协议

“面向非连接”就是在正式通信前不必与对方先建立连接,不管对方状态就直接发送。这与现在风行的手机短信非常相似:你在发短信的时候,只需要输入对方手机号就OK了。

UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去!

图4

UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。比如,我们经常使用 “ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送UDP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。例如,在默认状态下,一次“ping”操作发送4个数据包(如图2所示)。大家可以看到,发送的数据包数量是4包,收到的也是4包(因为对方主机收到后会发回一个确认收到的数据包)。这充分说明了UDP协议是面向非连接的协议,没有建立连接的过程。正因为UDP协议没有连接的过程,所以它的通信效果高;但也正因为如此,它的可靠性不如TCP协议高。QQ就使用UDP发消息,因此有时会出现收不到消息的情况。

  附表:tcp协议和udp协议的差别

TCP协议和UDP协议各有所长、各有所短,适用于不同要求的通信环境。TCP协议和UDP协议之间的差别如附表所示。

二 : TCP&UDP测试工具应用教程

1.打开飞鱼----高级选项----端口映射----添加新规则,也就是设置虚拟服务器。

2.添加一个新规则,“阻断外网请求”为禁止。

192.168.1.254为本机的IP地址

3.添加另一个新规则----保存

192.168.1.161为另一个电脑的IP在址

4.打开周立功的《TCP&UDP测试工具》

5.点“创建服务器”设定本机端口为1008,再按确定

6.按启动服务器

7.点“创建服务器“,输入另一电脑的IP址192.168.1.161和端口1009,然后点创建。

8.远程到192.168.1.161的电脑,打开“TCP&UDP测试工具“

9.点“创建服务器“输入本机端口1009.再点确定。

10.点“启动服务器”

11.回到本机,点击“TCP&UDP测试工具”红色框中的“连接”

12.在发送区输入发送内容,勾选“自动发送”。

13.远程到192.168.1.161,如下图中的红色框中的内容,是本机192.168.1.254发出的内容。

三 : TCP/UDP编程

socket编程——TCP/UDP数据传输

socket()——生成socket句柄

#include<sys/types.h>;

#include<sys/socket.h>;

int socket(int domain, int type, int protocol);

domain : "AF_INET"

type : SOCK_STREAM(TCP), SOCK_DGRAM (UDP)

protocol:0

返回值:socket句柄(整型);

(注意:有很多种 domain、type,请看 socket() 的 man帮助。

另一个方式去得到 protocol。同 时请查阅 getprotobyname() 的 man 帮助。)

int clifd;

if ((clifd = socket(AF_INET,SOCK_DGRAM(或者SOCK_STREAM),0)) < 0)

{

printf("create socket error!\n");

exit(1);

}



close(clifd);

bind()——将本端sockaddr_in(赋值后)强制转换成sockaddr 类型,绑定到socket 句柄上

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

sockfd = socket(AF_INET, SOCK_STREAM, 0);

my_addr.sin_family = AF_INET;

my_addr.sin_port = htons(MYPORT);

my_addr.sin_addr.s_addr = inet_addr("132.241.5.10");

bzero(&(my_addr.sin_zero));

bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))

bind ()的作用:

作为client端发包时,bind上的是源地址和源端口

作为server端收包时,是lisen的地址和listen的端口

connect()——将对端sockaddr_in(赋值后)强制转换成sockaddr 类型,绑定到socket 句柄上

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));

connect()的作用

TCP下发起连接——在STREAM下(TCP),connect()会企图建立连接attempt to establish a connection

UDP下设对端地址和端口——在DAGRAM下(UDP),connect()的作用只是设peer address/port,而不建立连接

实验证明:TCP情况下(SOCK_STREAM),connect() bind(),二者都不能少

实验证明:UDP情况下(SOCK_DGRAM),bind()可以不要,connect()也可以被sendto()代替

例1:TCP下将bind()函数注释掉,无法建立连接

if ((clifd = socket(AF_INET,SOCK_STREAM,0)) < 0)

{

printf("create socket error!\n");

exit(1);

}

bzero(&cliaddr,sizeof(cliaddr));

cliaddr.sin_family = AF_INET;

cliaddr.sin_port = htons(0);

cliaddr.sin_addr.s_addr = htons(INADDR_ANY);

bzero(&servaddr,sizeof(servaddr));

servaddr.sin_family = AF_INET;

inet_aton(argv[1],&servaddr.sin_addr);

servaddr.sin_port = htons(SERVER_PORT);

if (connect(clifd,(struct sockaddr*)&servaddr, socklen) < 0)

{

printf("can't connect to %s!\n",argv[1]);

exit(1);

}

[root@nm socket]# ./client 10.4.3.55

can't connect to 10.4.3.55!

例2:将上例改成SOCK_DGRAM(UDP),就好了。

if ((clifd = socket(AF_INET,SOCK_DGRAM,0)) < 0)printf("create socket error!\n");

exit(1);

}

[root@nm socket]# ./client 10.4.1.105

sent 14 bytes to10.4.1.105

{

UDP不需要BIND (其实连CONNECT也可省略,就剩SENDTO即可),TCP则BIND,CONNECT缺一不可

UDP(SOCK_DGRAM)下,sendto()函数和connect()函数冲突,用sendto,就不能用connect()

发UDP包100个,可是每发1个,就停止了,出错。

if ((clifd = socket(AF_INET,SOCK_DGRAM,0)) < 0)

{

printf("create socket error!\n");

exit(1);

}

bzero(&cliaddr,sizeof(cliaddr));

cliaddr.sin_family = AF_INET;

cliaddr.sin_port = htons(0);

cliaddr.sin_addr.s_addr = htons(INADDR_ANY);

bzero(&servaddr,sizeof(servaddr));

servaddr.sin_family = AF_INET;

inet_aton(argv[1],&servaddr.sin_addr);

servaddr.sin_port = htons(SERVER_PORT);

if (connect(clifd,(struct sockaddr*)&servaddr, socklen) < 0)

{

printf("can't connect to %s!\n",argv[1]);

exit(1);

}

strcpy(message,"this is a test");

for(i=0;i<100;i++){

if ((numbytes=sendto(clifd, message, strlen(message), 0, (struct sockaddr *)&servaddr, sizeof(struct sockaddr))) == -1) {

printf("error send message %s!",message);

exit(1);

}

printf("sent %d bytes %s to%s\n",numbytes,message,inet_ntoa(servaddr.sin_addr));

}

[root@nm socket]# gcc -o client client.c

[root@nm socket]# ./client 10.4.1.105

sent 14 bytes this is a test to10.4.1.105

为什么不继续传了?

解决:删掉connect(),直接用sendto();

结果:

[root@nm socket]# ./client 10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

sent 14 bytes this is a test to10.4.1.105

UDP sendto发包,尽管配了connect()系统也不报错,但connect函数会使UDP包无法连续发

UDP到底怎么发包?

或者直接sendto();

或者conncet() + send() 代替 sendto()

总结TCP,UDP发包的调用函数过程

TCP发包UDP发包

socket()

bind()

connect()

send()

close(clifd)socket()

sendto()

close(clifd)

socket()

connect()

send()

close(clifd)

sizeof(struct sockaddr) 的含义:connect() 将不会将多余的字节给 socket

关于发包的随机绑定源端口,TCP和UDP做法不同

TCP下,bind(),可以通过 cliaddr.sin_port = htons(0);实现TCP自动绑定源端口

UDP下,sendto自动绑定源端口,或connect(),自动绑定,再send()

int shutdown(int sockfd, int how);关闭通讯

how 的值是下面的其中之 一:

0 – 不允许接受

1 – 不允许发送

2 – 不允许发送和接受(和 close() 一样)

server端 的处理过程

1。bind() 捆绑本地listen 端口号

2。listen()开始听

3。accept()处理

listen()

int listen(int sockfd, int backlog);

int backlog: 队列长度,队列中允许的连接数目

进入的连接在队列中会一直等待直到被接受 accept()

accept()

int accept(int sockfd,sockaddr_in*addr, int*sin_size);

注意,是sockaddr_in,所以不需要强制转化成sockaddr类型了

int *sin_size:是sizeof(struct sockaddr_in)的指针化

返回值:新句柄

以后send()和 recv()中应该使用accept()产生的新的socket 句柄 new_fd,

原有句柄sockfd继续用于listen

accept 全过程

远端client通过一个server端的listen端口连接进来

它的connection将加入到等待接受的队列中。

accept() 告诉它你有空闲的连接。

accept()将返回一个新的socket句柄!

这样你就有两个socket了:

原来的一个还在listen

新的一个在准备发送 (send()) 和接收 ( recv()) 数据

bind 的地址和端口

发包时,是源地址和源端口

收包时,是lisen的地址和listen的端口(也是源地址和端口)

总之,bind的都是本端的地址和端口

一个完整的server 端bind+listen+accept

#include <string.h>;

#include<sys/socket.h>;

#include<sys/types.h>;

#define MYPORT 3490

#define BACKLOG 10

main()

{

int sockfd, new_fd;

struct sockaddr_in my_addr,their_addr;

int sin_size;

sockfd = socket(AF_INET, SOCK_STREAM, 0);

my_addr.sin_family = AF_INET;

my_addr.sin_port = htons(MYPORT);

my_addr.sin_addr.s_addr =INADDR_ANY;

任意地址,即listen本机所有接口

bzero(&(my_addr.sin_zero));

bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));

listen(sockfd,BACKLOG); 开始listen

in_size = sizeof(struct sockaddr_in);

需要单独一个sizeof变量,因为accept需要传的是指针

new_fd= accept(sockfd, &their_addr,&sin_size);

注意这里accept的参数their_addr没经过任何赋值,是空的

以后send()和 recv()中你应该使用accept()产生的新的socket 句柄 new_fd

只想让一个连接进来——accpet()后,使用 close() 去关闭原来的socket句柄

int sockfd, new_fd;

sockfd = socket(AF_INET, SOCK_STREAM, 0);

new_fd = accept(sockfd, &their_addr, &sin_size);

close(sockdf);

send()

通常用于tcp的stream,必须与bind和connect配合

int send(int sockfd, const void *msg, int len, int flags);

const void *msg:要发的信息的指针

msg可以是任何类型的指针,比如一个字符串指针,或者一个struct指针(一般packet都是一个struct)

int len :要发的信息的长度

flags: 设置为0。

返回值是实际发送的数据的字节数--它可能小于你要求发送的数目

char *msg = "hello world";

int len, bytes_sent;

len = strlen(msg);

bytes_sent = send(sockfd,msg, len, 0);

recv()

int recv(int sockfd, void *buf, int len, unsigned int flags);

void *buf :存接收的信息的缓冲区的地址(指针)

int len: 缓冲区的最大max长度

(如果buffer是一个数组char s[30],则len就是30)

flags: 设置为0

char pack[100];

recv(sockfd,&pack,sizeof(pack),0);

sendto()

用于udp的DGRAM不用bind()和connect()函数的情况下

sendto,recvfrom与send,recv不同之处,要加上struct sockaddr参数,即替代了bind()的作用

int sendto(int sockfd, const void *msg, int len, unsigned int flags,const struct sockaddr *to, int tolen);

const void *msg:要发的信息的指针

msg可以是任何类型的指针,比如一个字符串指针,或者一个struct指针(一般packet都是一个struct)

int len:要发的信息的长度

flags: 设置为00

const struct sockaddr *to:要强制地址转换

int tolen:sizeof(struct sockaddr)

返回值:实际发出的字节数

例1:发一串字符的UDP包

if ((numbytes=sendto(clifd, message, strlen(message), 0, (struct sockaddr *)&servaddr,sizeof(struct sockaddr))) == -1) {

printf("error send message %s!",message);

exit(1);

}

例2:发一个正经的包(一个cisco netflow包)

if ((numbytes=sendto(clifd,&pkts,24+48*FLOWCOUNT, 0, (struct sockaddr *)&servaddr, socklen)) == -1) {

printf("error send !\n");

printf("Wait Error:%s\n",strerror(errno));

exit(1);

}

recvfrom()

intrecvfrom(int sockfd, void *buf, int len, unsigned int flags, struct sockaddr *from, int*fromlen);

void *buf :存接收的信息的缓冲区的地址(指针)

int len: 缓冲区的最大max长度

(如果buffer是一个数组char s[30],则len就是30)

flags: 设置为0

const struct sockaddr *from:它的内容是接收到的包源IP 地址和源端口信息

要强制地址转换

int fromlen:sizeof(struct sockaddr) 的指针

UDP不需要listen(),必须直接recvfrom(),有listen会报错

[root@nm server]# ./server

call listen failure!

-1

改成TCP

if ((servfd = socket(AF_INET,SOCK_STREAM,0)) < 0)

[root@nm server]# ./server

收发会不会存在“对不齐”的问题?

1。对齐问题,sizeof会自动解决

2。只要收端和发端struct定义相同,就不会有问题。

errno和strerror(errno)

系统函数返回值是-1,这时一个系统全局变量errno就会被赋值

立刻执行strerror(errno),会返回一个错误描述字符串

if ((numbytes=sendto(clifd, (char *)&pkts,24+48*FLOWCOUNT, 0, (struct sockaddr *)&servaddr, socklen)) == -1) {

printf("Wait Error:%s\n",strerror(errno))

}

errno

errno在C程序中是一个全局变量,这个变量由C运行时库函数设置,用户程序需要在程序发生异常时检测之它。

主要在math.h和stdio.h头文件声明的函数中使用了errno,前者用于检测数学运算的合法性,后者用于检测I/O操作中(主要是文件)的错误

#include<errno.h>

#include<math.h>

#include<stdio.h>

int main(void)

{

errno = 0;使用errno之前,我们最好将其设置为0(正常值)

if (NULL == fopen("d:\\1.txt", "rb"))

{

printf("%d", errno);

}

perrorprint a system error message

if (serverSocket = socket(PF_INET, SOCK_STREAM,IPPROTO_TCP);

)

{

perror("socket()");

exit(1);

}

四 : TCP与UDP的区别

TCP(传输控制协议):
1)提供IP环境下的数据可靠传输(一台计算机发出的字节流会无差错的发往网络上的其他计算机,而且计算机A接收数据包的时候,也会向计算机B回发数据包,这也会产生部分通信量),有效流控,全双工操作(数据在两个方向上能同时传递),多路复用服务,是面向连接,端到端的传输;
2)面向连接:正式通信前必须要与对方建立连接。事先为所发送的数据开辟出连接好的通道,然后再进行数据发送,像打电话。
3)TCP支持的应用协议:Telnet(远程登录)、FTP(文件传输协议)、SMTP(简单邮件传输协议)。TCP用于传输数据量大,可靠性要求高的应用。
UDP(用户数据报协议,User Data Protocol)
1)面向非连接的(正式通信前不必与对方建立连接,不管对方状态就直接发送,像短信,QQ),不能提供可靠性、流控、差错恢复功能。UDP用于一次只传送少量数据,可靠性要求低、传输经济等应用。
2) UDP支持的应用协议:NFS(网络文件系统)、SNMP(简单网络管理系统)、DNS(主域名称系统)、TFTP(通用文件传输协议)等。
总结:
TCP:面向连接、传输可靠(保证数据正确性,保证数据顺序)、用于传输大量数据(流模式)、速度慢,建立连接需要开销较多(时间,系统资源)。
UDP:面向非连接、传输不可靠、用于传输少量数据(数据包模式)、速度快。
本文标题:tcp udp-网络管理之TCP/UDP篇
本文地址: http://www.61k.com/1082549.html

61阅读| 精彩专题| 最新文章| 热门文章| 苏ICP备13036349号-1