UDP通信流程图(udp通信原理图)

http://www.itjxue.com  2023-02-27 12:04  来源:未知  点击次数: 

Socket 通信之 UDP 通信

前段时间,我们在 这篇文章 中谈到了多进程和进程之间的通信方式,主要谈到了本地进程之间使用队列(Queue)进程通信,如果我们要通信的进程不在同一台主机上,我们就无法使用队列进行通信了,这时就需要使用 Socket(套接字)。

Socket 是应用层和传输层之间的一层抽象协议,可以用来进行进程间通信,一般有 UDP 和 TCP 两种通信方式,前者速度稍快,稳定性不好,无法丢包重传。后者速度稍慢一点,但稳定性很好,可以丢包重传。

本文首先介绍使用 Socket 进行 UDP 通信。

使用 Socket 进行 UDP 通信的流程如下:

下面依次进行讲解。

要进行 Socket 通信,我们需要使用 socket 模块,首先需要创建一个 Socket 对象。下面是两种创建方式:

如果我们需要向别的主机发送数据,我们需要改主机的 IP 地址和相应的端口号。在使用 Socket 进行通信时,需要将两个信息写在一元组中,元组的第一项为目标主机 IP 地址,第二项为接受数据的端口号:

其中,IP 地址使用字符串类型,端口号使用数字类型。

如果不绑定端口,每次使用 Socket 时都会由操作系统动态分配一个端口,我们也可以绑定为某个固定的端口。这样做的好处是:如果我们想要接受其他主机的信息,其他主机可以直接向这个端口发送数据,如果使用动态端口的话,发送方并不知道目标端口是什么,因此无法向接收方发送数据。

绑定端口需要使用 Socket 对象的 bind 方法:

bind 方法接受一个元组作为参数,元组的第一项为绑定的 IP 地址,第二项为绑定的端口号。我们可以把第一项指定为本机上的任意一个 IP 地址,也可以设置为一个空字符串 "" ,表示本机上任意合法的 IP 地址。

使用 UDP 套接字协议时,发送数据使用 Socket 对象的 sendto 方法,接受数据使用 Socket 对象的 recvfrom 方法。这两个方法的使用方式如下:

sendto 方法接受两个参数:发送的数据和目标主机的 IP 和端口元组,在 Python3 中,发送的数据应该转为 byte 类型发送,Python2 中可以直接发送字符串。

recvfrom 接受一个参数:本次接受的最大数据尺寸。该方法是阻塞的,只有在接收到数据后才能进行后续的操作。

就像使用文件那样,在使用完套接字后,需要关闭它,调用 close 方法即可。

上面我们介绍了 Socket 的使用方式,下面我们来做一个单工通信的例子(一方负责发送信息,一方负责接收信息)。

我们这里来创建两个文件:用以发送信息的 send.py 和用以接收信息的 recv.py。该实例在虚拟机中模拟(注意将虚拟机设置为桥接模式)。

创建 send.py:

创建 recv.py:

运行结果如下:

上面实现了一个单工通信的例子:一方负责发,一方负责接收。下面我们继续实现一个双工通信的例子,使双方都能够收发消息。

由于接收和发送消息时是使用 while 循环不断轮询的,因此要实现同时发送和接受,我们需要进行多任务处理。

新建一个 msg.py:

这里我们使用 3000 端口发送数据,3001 端口接收数据,运行程序时只需填写目标主机的 IP 地址,就可以进行通信。

运行效果:

我们还可以进行局域网内的广播,只需对 Socket 加上一条设置:

同时,发送广播需要一个广播地址,以及目标主机接受广播的端口:

上面的设置只能给 0 网段的主机发送广播,要想给局域网中所有的主机发送广播,可以这样设置:

下面我们新建一个 send.py 用来发送广播:

新建一个 recv.py 用来接收广播:

运行效果如图:

完。

网络--TCP/IP(四)TCP 与 UDP 协议简介

从本节开始,我们开始学习最重要的传输层。传输层位于OSI七层模型的第四层(从下往上)。顾名思义,传输层的作用是实现应用程序间的通信。网络层的作用是保证数据在不同数据链路上传输的可达性,至于如何传输则是由传输层负责。

常见的传输层协议主要有 TCP和UDP 协议。

UDP协议最大的特点就是简单,UDP首部如图:

和UDP首部相比,TCP首部要复杂的多。解析这个首部的时间也会相应的增加,这也是TCP连接的效率低于UDP的原因之一。

TCP是面向有连接的协议,连接在每次通信前被建立,通信结束后被关闭。了解连接建立和关闭的过程通常是考察的重点。连接的建立和关闭可以用一张图来表示:

通常情况下我们认为客户端首先发起连接请求。

1.发送端发送一个SYN=1,ACK=0标志的数据包给接收端,请求进行连接,这是第一次握手;

2.接收端收到请求并且允许连接的话,就会发送一个SYN=1,ACK=1标志的数据包给发送端,告诉它,可以通讯了,并且让发送端发送一个确认数据包,这是第二次握手;

3.最后,发送端发送一个SYN=0,ACK=1的数据包给接收端,告诉它连接已被确认,这就是第三次握手。之后,一个TCP连接建立,开始通讯。

*SYN:同步标志

同步序列编号(Synchronize Sequence Numbers)栏有效。该标志仅在三次握手建立TCP连接时有效。它提示TCP连接的服务端检查序列编号,该序列编号为TCP连接初始端(一般是客户端)的初始序列编号。在这里,可以把TCP序列编号看作是一个范围从0到4,294,967,295的32位计数器。通过TCP连接交换的数据中每一个字节都经过序列编号。在TCP报头中的序列编号栏包括了TCP分段中第一个字节的序列编号。

*ACK:确认标志

确认编号(Acknowledgement Number)栏有效。大多数情况下该标志位是置位的。TCP报头内的确认编号栏内包含的确认编号(w+1,Figure-1)为下一个预期的序列编号,同时提示远端系统已经成功接收所有数据。

*RST:复位标志

复位标志有效。用于复位相应的TCP连接。

*URG:紧急标志

紧急(The urgent pointer) 标志有效。紧急标志置位,

*PSH:推标志

该标志置位时,接收端不将该数据进行队列处理,而是尽可能快将数据转由应用处理。在处理 telnet 或 rlogin 等交互模式的连接时,该标志总是置位的。

*FIN:结束标志

带有该标志置位的数据包用来结束一个TCP回话,但对应端口仍处于开放状态,准备接收后续数据

根据一般思路,我们认为第三次是多余的,TCP协议为什么还要增加第三次的握手呢?

这是因为在网络请求的时候,我们应该时刻记住“网络是不安全的,数据包是可能丢失的”。假设没有第三次确认,客户端向服务端发送了SYN包,请求建立连接。由于网络原因,服务器没有及时收到这个包,于是客户端重新发送了SYN包。正常建立了连接。此时超时的那个确认包到达了服务端,如果是两次握手此连接就建立了,服务端就建立了一个空连接,白白浪费资源。如果是三次,客户端判断这个确认包是无效的,就丢弃了。

三次握手实际其实解决了第二步丢包问题。那么第三步的ACK包丢失了,TCP协议是如何处理的呢?

按照TCP协议处理丢包问题的一般方法,服务器会重新向客户端发送确认包,知道ACK确认为止。但实际上这种做法有可能遭到SYN泛洪攻击。所谓的泛洪攻击,是指发送方伪造多个IP地址,模拟三次握手的过程。当服务器返回ACK后,攻击方故意不确认,从而使服务器不断重发ACK。由于服务器长时间处于半连接状态,最后消耗过多的CUP和内存资源导致死机。

所以服务端采用的是这种方法,发送RST数据包,进入close状态,这个RST数据包中的TCP首部中的控制位中的RST位被置为1。这表示连接信息全部被初始化,原有的TCP通信不能继续。客户端如果还想建立TCP连接,需要从第一步握手重新开始。

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。

(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。

(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。

(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。

UDP和Socket通信步骤是什么?

UDP Server程序

1、编写UDP Server程序的步骤

(1)使用socket()来建立一个UDP socket,第二个参数为SOCK_DGRAM。

(2)初始化sockaddr_in结构的变量,并赋值。sockaddr_in结构定义:

struct sockaddr_in {

uint8_t sin_len;

sa_family_t sin_family;

in_port_t sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

这里使用“08”作为服务程序的端口,使用“INADDR_ANY”作为绑定的IP地址即任何主机上的地址。

(3)使用bind()把上面的socket和定义的IP地址和端口绑定。这里检查bind()是否执行成功,如果有错误就退出。这样可以防止服务程序重复运行的问题。

(4)进入无限循环程序,使用recvfrom()进入等待状态,直到接收到客户程序发送的数据,就处理收到的数据,并向客户程序发送反馈。这里是直接把收到的数据发回给客户程序。

2、udpserv.c程序内容:

#include sys/types.h

#include sys/socket.h

#include string.h

#include netinet/in.h

#include stdio.h

#include stdlib.h

#define MAXLINE 80

#define SERV_PORT 8888

void do_echo(int sockfd, struct sockaddr *pcliaddr, socklen_t clilen)

{

int n;

socklen_t len;

char mesg[MAXLINE];

for(;;)

{

len = clilen;

/* waiting for receive data */

n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, len);

/* sent data back to client */

sendto(sockfd, mesg, n, 0, pcliaddr, len);

}

}

int main(void)

{

int sockfd;

struct sockaddr_in servaddr, cliaddr;

sockfd = socket(AF_INET, SOCK_DGRAM, 0); /* create a socket */

/* init servaddr */

bzero(servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

servaddr.sin_port = htons(SERV_PORT);

/* bind address and port to socket */

if(bind(sockfd, (struct sockaddr *)servaddr, sizeof(servaddr)) == -1)

{

perror("bind error");

exit(1);

}

do_echo(sockfd, (struct sockaddr *)cliaddr, sizeof(cliaddr));

return 0;

}

UDP Client程序

1、编写UDP Client程序的步骤

(1)初始化sockaddr_in结构的变量,并赋值。这里使用“8888”作为连接的服务程序的端口,从命令行参数读取IP地址,并且判断IP地址是否符合要求。

(2)使用socket()来建立一个UDP socket,第二个参数为SOCK_DGRAM。

(3)使用connect()来建立与服务程序的连接。与TCP协议不同,UDP的connect()并没有与服务程序三次握手。上面我们说了UDP是非连接的,实际上也可以是连接的。使用连接的UDP,kernel可以直接返回错误信息给用户程序,从而避免由于没有接收到数据而导致调用recvfrom()一直等待下去,看上去好像客户程序没有反应一样。

(4)向服务程序发送数据,因为使用连接的UDP,所以使用write()来替代sendto()。这里的数据直接从标准输入读取用户输入。

(5)接收服务程序发回的数据,同样使用read()来替代recvfrom()。

(6)处理接收到的数据,这里是直接输出到标准输出上。

2、udpclient.c程序内容:

#include sys/types.h

#include sys/socket.h

#include string.h

#include netinet/in.h

#include stdio.h

#include stdlib.h

#include arpa/inet.h

#include unistd.h

#define MAXLINE 80

#define SERV_PORT 8888

void do_cli(FILE *fp, int sockfd, struct sockaddr *pservaddr, socklen_t servlen)

{

int n;

char sendline[MAXLINE], recvline[MAXLINE + 1];

/* connect to server */

if(connect(sockfd, (struct sockaddr *)pservaddr, servlen) == -1)

{

perror("connect error");

exit(1);

}

while(fgets(sendline, MAXLINE, fp) != NULL)

{

/* read a line and send to server */

write(sockfd, sendline, strlen(sendline));

/* receive data from server */

n = read(sockfd, recvline, MAXLINE);

if(n == -1)

{

perror("read error");

exit(1);

}

recvline[n] = 0; /* terminate string */

fputs(recvline, stdout);

}

}

int main(int argc, char **argv)

{

int sockfd;

struct sockaddr_in srvaddr;

/* check args */

if(argc != 2)

{

printf("usage: udpclient IPaddress\n");

exit(1);

}

/* init servaddr */

bzero(servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(SERV_PORT);

if(inet_pton(AF_INET, argv[1], servaddr.sin_addr) = 0)

{

printf("[%s] is not a valid IPaddress\n", argv[1]);

exit(1);

}

sockfd = socket(AF_INET, SOCK_DGRAM, 0);

do_cli(stdin, sockfd, (struct sockaddr *)servaddr, sizeof(servaddr));

return 0;

}

运行例子程序

1、编译例子程序

使用如下命令来编译例子程序:

gcc -Wall -o udpserv udpserv.c

gcc -Wall -o udpclient udpclient.c

编译完成生成了udpserv和udpclient两个可执行程序。

2、运行UDP Server程序

执行./udpserv 命令来启动服务程序。我们可以使用netstat -ln命令来观察服务程序绑定的IP地址和端口,部分输出信息如下:

Active Internet connections (only servers)

Proto Recv-Q Send-Q Local Address Foreign Address State

tcp 0 0 0.0.0.0:32768 0.0.0.0:* LISTEN

tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN

tcp 0 0 0.0.0.0:6000 0.0.0.0:* LISTEN

tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN

udp 0 0 0.0.0.0:32768 0.0.0.0:*

udp 0 0 0.0.0.0:8888 0.0.0.0:*

udp 0 0 0.0.0.0:111 0.0.0.0:*

udp 0 0 0.0.0.0:882 0.0.0.0:*

可以看到udp处有“0.0.0.0:8888”的内容,说明服务程序已经正常运行,可以接收主机上任何IP地址且端口为8888的数据。

如果这时再执行./udpserv 命令,就会看到如下信息:

bind error: Address already in use

说明已经有一个服务程序在运行了。

3、运行UDP Client程序

执行./udpclient 127.0.0.1命令来启动客户程序,使用127.0.0.1来连接服务程序,执行效果如下:

Hello, World!

Hello, World!

this is a test

this is a test

^d

输入的数据都正确从服务程序返回了,按ctrl+d可以结束输入,退出程序。

如果服务程序没有启动,而执行客户程序,就会看到如下信息:

$ ./udpclient 127.0.0.1

test

read error: Connection refused

说明指定的IP地址和端口没有服务程序绑定,客户程序就退出了。这就是使用connect()的好处,注意,这里错误信息是在向服务程序发送数据后收到的,而不是在调用connect()时。如果你使用tcpdump程序来抓包,会发现收到的是ICMP的错误信息。

visio怎么画TCP UDP通信协议?

1.这个图无须选择特别的类型和模板,形状也只有“直线”。

2.在“常规”中选择任何一个模板即可。看样图应该是选择了“常规--框图”模板.

4.最下面的箭头形状在“框图”模板中的“方块”模具中。

补充:

1.线条上的文字,直接双击线条即可输入。

2.其它文字就用“文本框”吧。

----若想用的好,去学一下“文本块”的操作。

(责任编辑:IT教学网)

更多

推荐Frontpage教程文章