当前位置: 首页 > news >正文

Linux基础 (十五):TCP 协议特点和UDP协议

        上一节,我们学习了TCP协议的服务器-客户端的编程流程以及对中间的过程进行了详细的讨论,那么,这一节,我们对于TCP协议的特点进行进一步的分析,这也是面试的重点和难点。

目录

一、TCP 协议特点

1.1  连接的建立与断开

1.1.1 面试题

1.2  TCP 状态转移(面试题)

1.3 流式服务特点

1.4 应答确认与超时重传

1.5 滑动窗口

二、多进程、多线程处理并发 

三、UDP协议

3.1 UDP协议编程流程

3.2 UDP 协议特点

3.3 应用场景

四、面试题

4.1 TCP和UDP的区别

4.2 同一个端口可不可以被一个 TCP 和一个 UDP 的应用程序同时使用?

4.3 同一个应用程序可以创建多个套接字吗?


一、TCP 协议特点

       通过前面的学习,我们知道:TCP 协议提供的是:面向连接、可靠的、字节流服务。

1.1  连接的建立与断开

        使用 TCP 协议通信的双发必须先建立连接(三次握手),然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。TCP 连接是全双工的,双方的数据可以通过一个连接进行读写。完成数据交换之后,通信双方都必须断开连接以释放系统资源(四次挥手)。 使用 tcpdump 抓包命令可以抓包观察 TCP 连接的建立与关闭。该命令需要管理员权限,格式如下(假设两个测试用的主机 IP 地址为 192.168.43.214 和 192.168.43.160 ) :

三次握手发生在客户端执行 connect()的时候,该方法返回成功,则说明三次握手已经建 立。三次握手示例图如下:

客户端执行connect()会给服务器发送一个tcp报文,此时SYN标志有效,还会发送一个序列号;服务器收到报文,会发送报文回复,此时SYN有效,发送一个序列号,还回复会一个确认号是客服端发送的序列号+1;客服端收到服务器的回复,也会再次回复服务器,此时会发送确认号是刚刚客户端发送的序列号+1  ;

四次挥手发生在客户端或服务端执行 close()关闭连接的时候,示例图如下:

当一端要进行close(),会给对方发送一个报文,此时FIN标志有效,还有一个序列号,然后对方收到报文,会回复对方已经收到了,发送一个确认号ACK,是刚刚发送的序列号+1;然后另一端也要close()关闭,也会给对方发送报文告诉对方字节要关闭了,FIN 序列号,对方收到报文了,会回复对方已经收到了,也发送一个确认号ACK,确认号是刚刚发送的序列号+1。

1.1.1 面试题

1、四次挥手的过程可以用三次完成吗? 

可以,四次挥手可以演化成三次挥手 。

        当一端close 发送报文过来,此时我也要close了,回复报文,和通知对方关闭的报文一起发送。

  1. 第一次挥手(FIN): 客户端发送一个FIN报文,表示它要关闭到服务器的数据传送。
  2. 第二次挥手(FIN): 服务器收到FIN后,直接发送一个FIN报文,表示它也要关闭到客户端的数据传送。
  3. 第三次挥手(ACK): 客户端收到FIN后,发送一个ACK报文,确认收到关闭请求,连接关闭。

2、挥手时,可能受到什么样的攻击?

FIN Flood 攻击

  • 原理:攻击者发送大量的FIN包到目标服务器。这些包会让服务器尝试关闭大量的连接,耗费资源处理这些无效的连接终止请求。
  • 影响:服务器资源被耗尽,可能导致拒绝服务(DoS攻击)。

3、为什么是三次握手,可不可以是两次为什么?

      握手只能是三次:例如客户端连接服务器后然后关闭了,服务器收到了并回复客户端,此时服务器就认为和客户端建立了链接,这个链接就一直保持着,但是客户端已经没了,所以还需要客户端第三次进行确认回复,来确保双方都保持链接。

4、三次握手时可能出现什么攻击?

  • SYN Flood 攻击

    • 原理:攻击者发送大量的SYN请求包到目标服务器,但不完成后续的握手步骤(即不发送ACK包)。目标服务器会为每个SYN请求分配资源并等待ACK回应,这样会导致服务器资源耗尽,无法处理合法用户的请求。(当有一个链接进来就会先放到未完成三次握手队列中,如果在短时间内有人连续发送链接就会把未完成三次握手队列塞满,使真正要进行链接的客户端连接不上。)
    • 影响:造成服务器拒绝服务(DoS攻击)。
  • SYN ACK Flood 攻击

    • 原理:攻击者在没有发送初始SYN包的情况下,发送大量的SYN-ACK包到目标服务器。服务器会浪费资源去回复ACK,等待建立连接,导致资源耗尽。
    • 影响:和SYN Flood类似,可能导致拒绝服务。

1.2  TCP 状态转移(面试题)

      下图是 TCP 连接从建立到关闭整个过程中通信两端状态的变化。tcp状态的改变是在建立连接和断开连接的基础上的 ,其中 CLOSED 是假想的起始点,并不是一个实际的状态。这种状态变化就好比我们打电话通话处于不同的状态,但是只要双方拨通了电话,那么就一直是通话中。只有在拨打电话和挂断电话时,状态会发生变化。

        上图中,TIME_WAIT 状态一般情况下是主动关闭的一端才会出现的状态。该状态出现后,会维持一段长为 2MSL(Maximum Segment Life)的时间,才能完全关闭。MSL 是 TCP 报文 段在网络中的最大生存时间,标准文档 RFC1122 的建议值是 2min。 在 Linux 系统上,一个 TCP 端口不能被同时打开多次(两次及以上)。当一个 TCP 连接 处于 TIME_WAIT 状态时,我们将无法立即使用该连接占用着的端口来建立一个新连接,必须要等待这两分钟过去,才能继续使用这个端口。

双方同时关闭:

     双方都执行close(),都像对方发送FIN,双发都变成FIN_WAIT_1状态,等到双方都收到各自都收到对方发出的FIN,并发出ACK之后就会变成CLOSING状态,在等到双方都收到对方的ACK之后就会变成TIME_WAIT状态。

四次挥手演化成三次挥手:

        主动关闭端执行close(),发FIN,被动关闭端收到FIN,但此时被动关闭端也要关闭了,就把ACK和FIN一起发送给主动关闭端

connect()三次握手:

      客户端执行connect()后进行第一次进行握手发出SYN状态就变成了SYN_SENT状态,这个状态非常短暂,会观察不到,瞬间就没了。

      服务器收到SYN后,又给客户端发出SYN,ACK后变成,SYN_RCVD状态。

服务器和客户端都完成三次握手状态就会变成,ESTABLISHED。

close()四次挥手:

        无论哪一方主动执行close()端,先发送FIN,然后主动关闭端就会变成FIN_WAIT_1状态,然后对方收到FIN,再发ACK就会变成CLOSE_WAIT状态,主动关闭端收到对方的回复,就变成了FIN_WAIT_2状态。此时两次挥手结束。被动关闭端执行close(),会给主动关闭端发送FIN,会变成LAST_ACK状态,主动关闭端收到FIN并发送ACK,主动关闭端状态就变成了TIME_WAIT,然后被动关闭段收到ACK,然后就消失了。

       TIME_WAIT会持续大概两分钟的时间。

如上图所示:服务器会跟很多客户端有连接,每个连接都有自己的状态。每一个连接都会有自己的接收缓冲区和发送缓冲区。

使用命令:netstat -natp可以查看连接的状态

面试题:

为什么TIME_WAIT状态要持续一段时间?

1.可靠地终止TCP的连接。

2.保证让迟来的TCP报文段有足够的时间被识别并丢弃。

  1. 被动关闭端关闭发FIN,主动关闭端收到FIN,发ACK,变成TIME_WAIT,有可能被动关闭端没收到这个ACK,这个ACK在路上丢失了,过一会被动关闭端没收到主动关闭端的ACK就会再次发FIN,如果TIME_WAIT状态不持续直接关闭,那最后假如ACK丢失,被动关闭端在发送FIN就没人管它了。
  2. 在通讯的过程中,有一些数据正在发送,但还没发送到,数据正在从A端到B端但还没到,此时断开接收端和发送端的连接,之后这个延迟的数据包到达了,但此时连接已经断开了,就会出现一些问题尤其是服务器。如果没有TIME_WAIT状态,我们就可以立刻重新启动服务端,这样延迟的数据包就会陆陆续续发到我们这个新启动的服务器里,虽然我们新启动的服务器用的是这个ip这个端口,延迟的数据包用的也是这个ip和端口,但是这些数据包是发给上个已经结束的进程的,不是发给我们这个新进程的。因此就会让TIME_WAIT状态等待大概2分钟,这俩分钟是一个报文生存期最长时间的俩倍,这样就会把我们网络中延迟的数据包耗死,我们把这些延迟的数据一收延后丢掉,俩分钟后网络中就干净了。

题目:

        一个局域网内,有一个客户端一个服务器,他们都已完成三次握手状态,没有发送数据,此时拔掉网线,服务器再close(),重新运行服务器,运行之后在插上网线,问此时客户端跟服务器的状态。

       网线拔掉之后,不进行收发送数据,双方是不知道的,由于拔掉网线,关闭服务器,服务器会发送FIN,但是客户端收不到,也不会回复,服务器就等了俩分钟后就关闭了,再重新启动服务器,此时服务器就是LISTEN状态等待连接,客户端还是完成三次握手状态。

1.3 流式服务特点

    TCP 字节流的特点,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,应用程序对数据的发送和接收是没有边界限制的。多次发送的数据会被对方一次接受,或者一次发送的数据被对方,分多次接受。

netstat -natp命令 可查看端口是否被占用 也能查看接收缓冲区和发送缓冲区有多少数据

  1. bind()会失败的原因 :端口被占用或者把这个程序运行了,又运行了一个,端口已经分给第一个运行的程序。
  2. recv()返回值为0是唯一判断对方客户端关闭链接的条件。
  3. connect()链接失败原因:没有运行服务器,客户端连接就会失败。网断了也链接不上。

修改循环收发的服务器端的代码如下:
char buff[128]={0};
recv(sockfd,buff,1,0);
/*一次只接收一个字符*/

       客户端发个hello,服务器将接收字符个数改成1,出现的结果是:循环5次把hello打印完,直到把buff里的数据打印完。客服端那里会一次收到5个ok。

        这是因为服务器和客户端都有一个接收缓冲区和发送缓冲区,一端send()发送数据,先把数据写到发送缓冲区里,再通过底层协议把发送缓冲区的数据挪到对方的接受缓冲区中,然后对方再通过recv()把接收缓冲区中的数据读出来。recv()发送成功只能说明成功将数据发达发送缓冲区,对方并没有收到。有可能会多次从接收缓冲区一次读取,也有可能分多次读取,就像我们购物从菜鸟驿站取快递,我们取出的快递件也可能一次取完,也有可能还没到菜鸟驿站,我们就需要分多次取。这就是TCP 粘包:连续多次send()发送的数据,被对方recv()一次性收到。发送数据的次数跟接收数据的次数是不对应的。所以会出现粘包。如何解决呢?(面试题)

解决粘包问题的常见方法有以下几种:(面试题)

1. 使用定长消息

         通过规定每条消息的长度,接收方可以按照固定长度读取数据。例如,如果消息长度固定为100字节,接收方每次读取100字节的数据,就可以避免粘包问题。

2. 使用特殊分隔符

        在每条消息的末尾添加特定的分隔符(如换行符、特殊字符等),接收方可以通过检测分隔符来区分消息边界。

3. 使用消息头(长度前缀)

       在每条消息前添加一个消息头,用于存储消息的长度,接收方先读取消息头中的长度信息,再根据长度读取具体的消息内容。

1.4 应答确认与超时重传

       TCP 发送的报文段交给 IP 层传送的。但 IP 层只能提供尽最大努力的服务,也就是说,TCP 下面的网络所提供的是不可靠的传输。因此,TCP 必须采用适当的措施才能使两个运输层之间的通信变得可靠。TCP 的可靠传输是通过使用应答确认和超时重传来完成。下图是通过 netstat 命令抓包看到的信息:

面试题: 

tcp的可靠性体现在:应答确认、超时重传、去重、乱序重排、进行流量控制滑动窗口

  1. 应答确认:给对方send()发送一个数据,对方收到了,在底层会回复发送方表明收到数据了,A端给B端发送数据,表面只能看到俩次交互,实际有四次,另外两次我们看不到,但可以用tcpdump抓包命令看到。
  2. 超时重传:给对方发送数据收,等了一段事件后没有收到对方的回复,就认为这个数据丢失了,就会再重新发送一份数据给对方。
  3. 去重:给对方发送数据,对方收到了,回复确认收到信息,但回复这个信息丢失了,发送段没收到,就会认为发送的数据在路上丢失了,就会重新发,然后接收端就会有俩个一样的数据,重复了就会去重。
  4. 乱序重排:后发送的数据比先发送的数据先到达,这样顺序就会乱,但在接收到数据后,会对数据的顺序进行检查。
  5. 滑动窗口: 给对方发送数据,一个字节一个字节发效率不高,就会有一个窗口,窗口左边是已发送对方回复确认的数据,窗口内是有已发送未收到确认的和未发送的数据,窗口右边是超过窗口范围内外就不能发送的,窗口内比如能够发送100字节,我们20字节一个包,这样发送,发送20字节,没收到对方回复,我们还能继续发送,直到把这滑动窗口内的100字节数据全部发送完了,还没收到对方回复收到的信号,就不能再发送了,如果前面20字节,对方回复收到了,这个窗口就向后移动,确保窗口内数据有100个字节,然后新到窗口内的数据就能发送了。因为如果你光发送数据,也不知道对方收没收就到一直发;或者就是对方一次性只能接受多少数据,发太多也没用。

下图是无差错时,数据交互的流程:发送端发送数据 m1 给接收端,接收端收到数据后会给发送端一个确认信息,以表明数据已经被成功收到。在发送方未收到确认信息前,M1 应继续被保留,直到确认信息到达才能丢弃。

下图是出现差错时,数据交互的流程:

1.5 滑动窗口

       TCP 协议是利用滑动窗口实现流量控制的。一般来说,我们总是希望数据传输得更快一些,不会一次只发一个字节。但是如果发送方把数据发得过快,接受方就可能来不及接收, 这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来的及接收。在 TCP 的报头中有一个字段叫做接收通告窗口,这个字段由接收端填充,是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。所以发送端就会有一个发送窗口,这个发送窗口的大小是由接收端填充的接收通告窗口的大小决定的,并且窗口的位置会随着发送端数据的发送和接收到接收端对数据的确认而不断的向右滑动,将之称为滑动窗口。发送方的滑动窗口示意图如下:

当收到 36 的 ack,并发出 46-51 的字节后,窗口滑动的示意图如下:

二、多进程、多线程处理并发 

      如下图所示, 当一个客户端与服务器建立连接以后,服务器端 accept()返回,进而准备循环接收客户端发过来的数据。如果客户端暂时没发数据,服务端会在第 40 行的 recv()阻 塞。此时,其他客户端向服务器发起连接后,由于服务器阻塞了,无法执行 accept()接受连 接,也就是其他客户端发送的数据,服务器无法读取。服务器也就无法并发同时处理多个客户端。 

      这个问题可以通过引入多线程和多进程来解决。服务端接受一个客户端的连接后,创建 一个线程或者进程,然后在新创建的线程或进程中循环处理数据。主线程(父进程)只负责监听客户端的连接,并使用 accept()接受连接,不进行数据的处理。如下图所示:

多线程处理并发的服务器端示例代码 MultiThread.c 如下:主线程负责监听端口和接受客户端连接,每接受到一个客户端连接后,就创建一个新线程来处理该客户端的通信。每个子线程会循环接收客户端发送的数据,并回复一个确认消息"ok"。当客户端断开连接时,子线程会关闭相应的客户端套接字并退出。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>// 线程函数,用来处理单个客户端的收发数据
void* fun(void * arg)
{int c = (int)arg; // 将传入的参数转换为整数类型的客户端套接字描述符while( 1 ){char buff[128] = {0}; // 用于接收数据的缓冲区// 接收客户端发送的数据,如果接收失败或连接关闭,则退出循环if ( recv(c, buff, 127, 0) <= 0 ){break;}printf("recv(%d)=%s\n", c, buff); // 打印接收到的数据send(c, "ok", 2, 0); // 发送确认消息给客户端}printf("one client over(%d)\n", c); // 打印客户端连接结束的消息close(c); // 关闭客户端连接
}int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字assert(sockfd != -1); // 确认套接字创建成功struct sockaddr_in saddr, caddr; // 定义服务器和客户端的地址结构memset(&saddr, 0, sizeof(saddr)); // 将服务器地址结构清零saddr.sin_family = AF_INET; // 设置地址族为AF_INETsaddr.sin_port = htons(6000); // 设置端口号为6000,并转换为网络字节序saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置IP地址为127.0.0.1int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); // 绑定套接字到指定的IP地址和端口assert(res != -1); // 确认绑定成功listen(sockfd, 5); // 开始监听,最大连接数为5while( 1 ){int len = sizeof(caddr); // 客户端地址结构长度// 接受客户端连接请求,返回客户端套接字描述符int c = accept(sockfd, (struct sockaddr*)&caddr, &len);if ( c < 0 ){continue; // 如果接受失败,继续等待下一个连接}printf("accept c = %d\n", c); // 打印接受到的客户端套接字描述符pthread_t id; // 定义线程id// 创建子线程处理客户端连接,传入客户端套接字描述符作为参数pthread_create(&id, NULL, fun, (void*)c);}close(sockfd); // 关闭服务器套接字exit(0); // 退出程序
}

 多进程处理并发的服务器端示例代码 MultiProcess.c 如下:主进程负责监听端口和接受客户端连接,每接受到一个客户端连接后,创建一个子进程来处理该客户端的通信。子进程会循环接收客户端发送的数据,并回复一个确认消息"OK"。当客户端断开连接时,子进程会关闭相应的客户端套接字并退出。主进程通过捕捉SIGCHLD信号来处理子进程退出,防止产生僵尸进程。

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>// 处理客户端连接的函数
void DealClientLink(int c, struct sockaddr_in caddr)
{while (1){char buff[128] = {0}; // 用于接收数据的缓冲区int n = recv(c, buff, 127, 0); // 接收客户端发送的数据if (n <= 0) // 如果接收失败或客户端关闭连接,则退出循环{break;}// 打印客户端发送的数据,包括客户端的IP地址和端口号printf("%s:%d %s\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port), buff);send(c, "OK", 2, 0); // 发送确认消息给客户端}printf("one client unlink\n"); // 打印客户端断开连接的消息close(c); // 关闭客户端连接
}// 信号处理函数,用于处理子进程退出时的SIGCHLD信号
void sigfun(int sign)
{wait(NULL); // 等待子进程结束,防止僵尸进程
}int main()
{signal(SIGCHLD, sigfun); // 注册SIGCHLD信号处理函数,处理僵尸进程int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字assert(-1 != sockfd); // 确认套接字创建成功struct sockaddr_in saddr; // 定义服务器的地址结构memset(&saddr, 0, sizeof(saddr)); // 将服务器地址结构清零saddr.sin_family = AF_INET; // 设置地址族为AF_INETsaddr.sin_port = htons(6000); // 设置端口号为6000,并转换为网络字节序saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置IP地址为127.0.0.1int res = bind(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); // 绑定套接字到指定的IP地址和端口assert(-1 != res); // 确认绑定成功listen(sockfd, 5); // 开始监听,最大连接数为5while (1){struct sockaddr_in caddr; // 定义客户端的地址结构int len = sizeof(caddr); // 客户端地址结构长度int c = accept(sockfd, (struct sockaddr*)&caddr, &len); // 接受客户端连接请求,返回客户端套接字描述符assert(-1 != c); // 确认接受成功// 打印接受到的客户端连接成功的消息,包括客户端的IP地址和端口号printf("%s:%d Link Success\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pid_t pid = fork(); // 创建子进程assert(-1 != pid); // 确认子进程创建成功if (0 == pid){DealClientLink(c, caddr); // 子进程处理客户端连接exit(0); // 必须结束子进程,否则会有多个进程调用accept}else{close(c); // 父进程关闭客户端连接描述符}}close(sockfd); // 关闭服务器套接字exit(0); // 退出程序
}

客户端代码 TcpClient.c 如下:客户端首先创建一个套接字,然后连接到指定IP地址和端口号的服务器。连接成功后,客户端进入一个循环,从标准输入获取用户输入的数据,并将其发送到服务器。随后,客户端接收服务器的响应并打印出来。如果用户输入"end",客户端会退出循环,关闭套接字并结束程序。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建套接字assert(sockfd != -1); // 确认套接字创建成功struct sockaddr_in saddr; // 定义服务器的地址结构memset(&saddr, 0, sizeof(saddr)); // 将服务器地址结构清零saddr.sin_family = AF_INET; // 设置地址族为AF_INETsaddr.sin_port = htons(6000); // 设置端口号为6000,并转换为网络字节序saddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 设置IP地址为127.0.0.1int res = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr)); // 连接到服务器assert(res != -1); // 确认连接成功while (1){char buff[128] = {0}; // 用于存储用户输入的缓冲区printf("input:\n"); // 提示用户输入fgets(buff, 128, stdin); // 从标准输入获取用户输入if (strncmp(buff, "end", 3) == 0) // 如果用户输入"end",则退出循环{break;}send(sockfd, buff, strlen(buff), 0); // 发送用户输入的数据到服务器memset(buff, 0, 128); // 清空缓冲区recv(sockfd, buff, 127, 0); // 接收服务器的响应printf("buff=%s\n", buff); // 打印服务器的响应}close(sockfd); // 关闭套接字exit(0); // 退出程序
}

三、UDP协议

3.1 UDP协议编程流程

     UDP 提供的是无连接、不可靠的、数据报服务。可以通俗的将TCP理解成打电话,UDP理解成发短信。

        socket()用来创建套接字,使用 udp 协议时,选择数据报服务 SOCK_DGRAM。sendto() 用来发送数据,由于 UDP 是无连接的,每次发送数据都需要指定对端的地址(IP 和端 口)。recvfrom()接收数据,每次都需要传给该方法一个地址结构来存放发送端的地址。 recvfrom()可以接收所有客户端发送给当前应用程序的数据,并不是只能接收某一个客户端的数据。

UDP 服务端编程示例代码:

1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <string.h>
5. #include <assert.h>
6. #include <sys/socket.h>
7. #include <netinet/in.h>
8. #include <arpa/inet.h>
9.
10. int main()
11. {
12.     int sockfd = socket(AF_INET,SOCK_DGRAM,0);
13.     assert( sockfd != -1 );
14.
15.     struct sockaddr_in saddr,caddr;
16.     memset(&saddr,0,sizeof(saddr));
17.     saddr.sin_family = AF_INET;
18.     saddr.sin_port = htons(6000);
19.     saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
20.
21.     int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
22.     assert( res != -1 );
23. 
24.     while( 1 )
25.     {
26.          int len = sizeof(caddr);
27.          char buff[128] = {0};
28.          recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);
29.          printf("ip:%s,port:%d,buff=%s\n",inet_ntoa(caddr.sin_addr), ntohs(caddr.si
n_port),buff );
30.
31.         sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
32.     }
33.
34.     close(sockfd);
35. }

UDP 客户端编程示例代码:

1. #include <stdio.h>
2. #include <stdlib.h>
3. #include <unistd.h>
4. #include <string.h>
5. #include <assert.h>
6. #include <sys/socket.h>
7. #include <netinet/in.h>
8. #include <arpa/inet.h>
9.
10. int main()
11. {
12. int sockfd = socket(AF_INET,SOCK_DGRAM,0);
13. assert( sockfd != -1 );
14.
15. struct sockaddr_in saddr;
16. memset(&saddr,0,sizeof(saddr));
17. saddr.sin_family = AF_INET;
18. saddr.sin_port = htons(6000);
19. saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
20.
21. while( 1 )
22. {
23. char buff[128] = {0};
24. printf("input:\n");
25.
26. fgets(buff,128,stdin);
27.
28. if ( strncmp(buff,"end",3) == 0 )
29. {
30. break;
31. }
32.
33. sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)&saddr,sizeof(saddr));
34. memset(buff,0,128);
35.
36. int len = sizeof(saddr);
37. recvfrom(sockfd,buff,127,0,(struct sockaddr*)&saddr,&len);
38.
39. printf("buff=%s\n",buff);
40. }
41.
42. close(sockfd);
43. }

启动服务端和客户端,再关掉服务端,还能再发送数据嘛?可以 因为udp是无连接的,只要服务端启动,有人发数据就接受。关掉服务端对客户端来说,丝毫没有影响  

3.2 UDP 协议特点

      UDP 数据报服务特点:发送端应用程序每执行一次写操作,UDP 模块就将其封装成一 个 UDP 数据报发送。接收端必须及时针对每一个 UDP 数据报执行读操作,否则就会丢包,因此它不会出现粘包现象。 并且,如果用户没有指定足够的应用程序缓冲区来读取 UDP 数据,则 UDP 数据将被截断。

3.3 应用场景

     tcp和udp应用分场景,例如下载一个文件,肯定是要完整的下载下来,数据不能丢失。如果实时通话视频时,那就用udp,因为只是要看当下的你,如果视频过程中网不好,数据没发出去,再重新发,这样慢慢的就会变成录屏,因为tcp有接收缓冲区,重新发的数据都会被对方,接收到接受缓冲区,对方要全部读完,所以这一帧数据没发送成功就不要了。

四、面试题

4.1 TCP和UDP的区别

tcp是面向连接的可靠的流式服务,udp是无连接不可靠的数据报服务 。

  1. tcp建立连接要进行三次握手,而udp不需要建立连接直接指定地址发数据就行
  2. tcp在发送数据时有应答确认,超时重传机制,而udp发送数据成功就成功,失败了也不会重发。
  3. tcp会出现粘包,udp不会出现粘包。

4.2 同一个端口可不可以被一个 TCP 和一个 UDP 的应用程序同时使用?

       是的,可以,同一个端口可以同时被一个 TCP 应用程序和一个 UDP 应用程序使用。TCP 和 UDP 是两个不同的传输层协议,它们的连接和数据传输方式不同,因此它们可以在相同的端口号上共存。操作系统和网络栈通过区分传输层协议(TCP 或 UDP)来将数据包正确地交付给对应的应用程序。例如,假设你有一个 TCP 服务在端口 8080 上运行,同时你也可以在相同的端口 8080 上运行一个 UDP 服务。这两个服务不会互相干扰,因为操作系统能够根据协议类型将到达端口 8080 的 TCP 数据包和 UDP 数据包区分开来并分别处理。

4.3 同一个应用程序可以创建多个套接字吗?

同一个应用程序可以创建多个套接字。套接字是网络通信的基础,它允许程序发送和接收数据。应用程序创建多个套接字的原因有很多,包括但不限于以下几个方面:

  1. 多协议支持:一个应用程序可能需要同时支持多种协议,例如同时使用 TCP 和 UDP,这时它需要分别为 TCP 和 UDP 创建不同的套接字。

  2. 多端口监听:一个服务器应用程序可能需要监听多个端口,以便提供不同的服务或支持不同的协议版本。例如,一个应用程序可以同时监听 80 端口(HTTP)和 443 端口(HTTPS)。

  3. 客户端连接管理:对于一个 TCP 服务器,每当一个客户端连接到服务器时,服务器通常会为每个客户端连接创建一个新的套接字。这允许服务器同时处理多个客户端连接。

  4. 多线程或多进程通信:应用程序可能使用多个套接字来实现多线程或多进程间的通信。例如,一个线程或进程负责监听网络连接,另一个线程或进程负责处理数据。

至此,已经讲解完毕!篇幅较长,慢慢消化,以上就是全部内容!请务必掌握,创作不易,欢迎大家点赞加关注评论,您的支持是我前进最大的动力!下期再见!

相关文章:

Linux基础 (十五):TCP 协议特点和UDP协议

上一节&#xff0c;我们学习了TCP协议的服务器-客户端的编程流程以及对中间的过程进行了详细的讨论&#xff0c;那么&#xff0c;这一节&#xff0c;我们对于TCP协议的特点进行进一步的分析&#xff0c;这也是面试的重点和难点。 目录 一、TCP 协议特点 1.1 连接的建立与断…...

python替换word文件中的图片

python替换word文件中的图片 模拟鼠标键盘&#xff0c;截屏 import glob import os import timeimport pyautogui import pyautogui as p from PIL import ImageGrab from pynput.keyboard import Controller# -*- coding:utf-8 -*-directory ./directory1 ./outputfor f i…...

Servlet-01

文章目录 Servlet创建Servlet探究Servlet的生命周期 HttpServletWebServlet注解详解 重定向与请求转发ServletContextServletContext中的接口 HttpServletRequestHttpServletResponse状态码解释Cookie Servlet Q&#xff1a;它能做什么呢&#xff1f; A&#xff1a;我们可以通…...

C语言:链表

链表 介绍单向链表节点结构创建节点插入节点删除节点遍历链表尾部插入查找节点链表反转示例程序程序1程序2 介绍 链表是一种常见的数据结构&#xff0c;用于存储一系列线性数据。与数组不同&#xff0c;链表中的元素在内存中不必是连续存放的&#xff0c;而是通过指针将每个元…...

【git使用二】gitee远程仓库创建与本地git命令用法

目录 gitee介绍 管理者注册gitee账号 管理者在gitee网站上创建远程仓库 每个开发者安装git与基本配置 1.git的下载和安装 2.配置SSH公钥 3.开发者信息配置 git命令用法 gitee介绍 Gitee&#xff08;又称码云&#xff09;是一个基于Git的代码托管服务&#xff0c;由开源…...

明星百科大全PHP网站源码

源码介绍 明星百科大全网站源码&#xff0c;国内外明星娱乐音乐、新闻八卦、写真照片、相关影视作品等等的明星百科网站源码。 源码截图 源码下载 明星百科大全PHP网站源码...

白酒:茅台镇白酒的品鉴会与文化交流活动

茅台镇&#xff0c;这个位于中国贵州省的小镇&#xff0c;因其与众不同的自然环境和杰出的酿酒工艺而成为世界著名的白酒产区。云仓酒庄豪迈白酒作为茅台镇的品牌&#xff0c;积极参与各种品鉴会和文化交流活动&#xff0c;向世界展示了中国白酒的魅力和文化底蕴。 近年来&…...

python中列表结构在点云数据处理中用法

1、前言 Python中的列表&#xff08;list&#xff09;是一种可变的序列类型&#xff0c;用于存储集合数据。列表用途非常广泛&#xff0c;包括但不限于以下几个方面&#xff1a; 存储集合数据&#xff1a;列表用于存储一系列有序的元素&#xff0c;这些元素可以是任何数据类型&…...

土耳其(小亚细亚)历史上的各个阶段

一个国家的历史书写方式有两种&#xff0c;其一是按本国主体民族的渊源&#xff0c;其二是本国国土内发生的都属于本国史。一般来说&#xff0c;这两种方式相当程度上是重合的&#xff0c;但也有例外&#xff0c;比如本文要讲述的土耳其。 土耳其的国土并不辽阔&#xff0c;其…...

Windows下基于Frida查看内存基址和修改寄存器

使用Frida能够方便地获取到DLL基址&#xff0c;还能修改寄存器值。首先要通过任务管理器获得进程的PID&#xff0c;然后写Python脚本把Frida附加到这个PID进程&#xff0c;根据IDA分析出来的函数地址&#xff0c;HOOK到目标函数&#xff0c;修改寄存器的值&#xff0c;最终实现…...

2024中国网络安全产品用户调查报告(发布版)

自2020年始&#xff0c;人类进入了21世纪的第二个十年&#xff0c;全球进入了百年未有之大变局&#xff0c;新十年的开始即被新冠疫情逆转了全球化发展的历程&#xff0c;而至2022年3月俄乌战争又突然爆发&#xff0c;紧接着2023年7月“巴以冲突"皱起&#xff0c;世界快速…...

手写图片懒加载

参考来自前辈 Aidan路修远i 的文章面试官&#xff1a;请你手写一下&#xff01;懒加载 - 掘金 (juejin.cn) Hello.vue <template><div><!-- src里面为空&#xff0c;data-original里面写图片真正的url(此处省略) --><img src"" data-origina…...

大型语言模型(LLMs)的后门攻击和防御技术

大型语言模型&#xff08;LLMs&#xff09;通过训练在大量文本语料库上&#xff0c;展示了在多种自然语言处理&#xff08;NLP&#xff09;应用中取得最先进性能的能力。与基础语言模型相比&#xff0c;LLMs在少样本学习和零样本学习场景中取得了显著的性能提升&#xff0c;这得…...

力扣2594.修车的最少时间

力扣2594.修车的最少时间 二分答案 class Solution {public:long long repairCars(vector<int>& ranks, int cars) {ranges::sort(ranks);auto check [&](long long x) -> bool{long long res 0;for(auto v : ranks){long long k sqrt(x/v);res k;if(r…...

攻防演练之-成功的钓鱼邮件溯源

书接上文&#xff0c;《网络安全攻防演练风云》专栏之攻防演练之-网络安全产品大巡礼二&#xff0c;这里。 演练第一天并没有太大的波澜&#xff0c;白天的时间过得很快。夜色降临&#xff0c;攻防演练中心内的灯光依旧明亮。对于网络安全团队来说&#xff0c;夜晚和白天并没有…...

Gi标签管理

文章目录 前言理解标签创建标签操作标签总结 前言 理解标签 标签&#xff0c;可以理解为对某次commit的一次标识&#xff0c;相当于起起了一个别名。 例如&#xff0c;在项目发布某个版本时候&#xff0c;针对最后一次commit起一个v1.0这样的标签来标识里程碑的意义。 这有什…...

2024福建等保测评公司有哪些?分别叫做什么名字?

2024福建等保测评公司有哪些&#xff1f;分别叫做什么名字&#xff1f; 【回答】&#xff1a;2024年具有资质的福建等保测评公司有6家&#xff0c;其名称以及地址如下&#xff1a; 1、福建省网络与信息安全测评中心&#xff0c;福州市鼓楼区东街8号利达大厦A座8层&#xff1b…...

王先宏老师厉害了,活页笔记版古琴曲谱拆箱图

王先宏老师走心了&#xff0c;活页笔记版古琴曲谱拆箱图&#xff0c;简直是史上最好的古琴学习利器&#xff01;送的防滑垫还带铝合金夹层的&#xff0c;养弦膏都是市面上没有的的。 这些古琴谱上的笔记就是老师课堂上用的&#xff0c;直接拿来就可以跟着弹&#xff0c;不用您…...

TalkingData 是一家专注于提供数据统计和分析解决方案的独立第三方数据智能服务平台

TalkingData 是一家专注于提供数据统计和分析解决方案的独立第三方数据智能服务平台。通过搜索结果&#xff0c;我们可以了解到 TalkingData 的一些关键特性和市场情况&#xff0c;并将其与同类型产品进行比较。 TalkingData 产品特性 数据统计与分析&#xff1a;提供专业的数…...

Springboot的小型超市商品展销系统-计算机毕业设计源码01635

摘 要 科技进步的飞速发展引起人们日常生活的巨大变化&#xff0c;电子信息技术的飞速发展使得电子信息技术的各个领域的应用水平得到普及和应用。信息时代的到来已成为不可阻挡的时尚潮流&#xff0c;人类发展的历史正进入一个新时代。在现实运用中&#xff0c;应用软件的工作…...

UV胶开裂主要因素有哪些?如何避免?

UV胶开裂主要因素有哪些&#xff1f;如何避免&#xff1f; UV胶开裂的原因可能包括多个方面&#xff1a; 固化不足&#xff1a;UV胶的固化需要足够的紫外线照射。如果照射时间不够&#xff0c;或者紫外线光源的强度不足&#xff0c;胶水可能没有完全固化&#xff0c;从而导致开…...

LogicFlow 学习笔记——3. LogicFlow 基础 节点 Node

节点 Node LogicFlow 内置了一些基础节点&#xff0c;开发者在实际应用场景中&#xff0c;可以基于这些基础节点&#xff0c;定义符合自己业务逻辑的节点。 认识基础节点 LogicFlow是基于svg做的流程图编辑框架&#xff0c;所以我们的节点和连线都是svg基本形状&#xff0c;…...

VMware清理拖拽缓存

磁盘空间越用越小&#xff0c;如何快速解决磁盘空间的问题&#xff0c;甩掉烦恼 安装VM tools之后可以通过拖拽的方式把文件拉入虚拟机之中。但每一次拖拽&#xff0c;其实都是现在cache文件夹里面生成一个同样的文件&#xff0c;并使用cp拷贝的方式将其拷贝到拖拽放置的目录中…...

跨语言系统中的功能通信:Rust、Java、Go和C++的最佳实践

在现代软件开发中&#xff0c;使用多种编程语言构建复杂系统已成为一种常见的做法。每种编程语言都有其独特的优势和适用场景&#xff0c;这使得在同一个系统中使用多种语言变得合理且高效。然而&#xff0c;这也带来了一个重要的挑战&#xff1a;如何在这些不同语言之间实现高…...

4. Revit API UI 之 Ribbon(界面)

4. Revit API UI 之 Ribbon&#xff08;界面&#xff09; 第二篇中&#xff0c;我们提到了IExternalApplication&#xff0c;该接口需要实现两个方法&#xff1a;Revit启动时调用的OnStartup 方法&#xff0c;和Revit关闭时调研的OnShutdown 方法。文中还给了个例子&#xff0…...

js数组方法

改变原始数组返回一个新数组添加元素push&#xff0c;unshiftconcat&#xff0c;[…arr] 展开语法删除元素pop&#xff0c;shift&#xff0c;splicefilter&#xff0c;slice替换元素splice&#xff0c;arr[i] … 赋值map排序reverse&#xff0c;sort先将数组复制一份...

PyTorch -- 最常见损失函数 LOSS 的选择

损失函数&#xff1a;度量模型的预测结果与真实值之间的差异&#xff1b;通过最小化 loss -> 最大化模型表现代码实现框架&#xff1a;设有 模型预测值 f (x), 真实值 y 方法一&#xff1a; 步骤 1. criterion torch.nn.某个Loss()&#xff1b;步骤 2. loss criterion(f(x…...

Prometheus 监控系统

一、Prometheus概述 是一个开源的服务监控系统和时序数据库&#xff0c;其提供了通用的数据模型和快捷数据采集、存储和査询接口。它的核心组件. 1.1 Prometheus server 会定期从静态配置的监控目标或者基于服务发现自动配置的目标中进行拉取数据&#xff0c;新拉取到的数据会…...

Spring Boot中使用logback出现LOG_PATH_IS_UNDEFINED文件夹

1.首先查看&#xff0c;application.properties 文件是否按格式编写 logging.pathmylogs logging.configclasspath:logback-spring.xml2.查看 logback-spring.xml <springProperty scope"context" name"LOG_HOME" source"logging.path"/> …...

代码随想录——组合总数Ⅲ(Leetcode216)

题目链接 回溯 class Solution {List<List<Integer>> res new ArrayList<List<Integer>>();List<Integer> list new ArrayList<Integer>();public List<List<Integer>> combinationSum3(int k, int n) {backtracking(k, …...