wordpress全站采集,芜湖市建设工程质监站网站,河北建设工程信息网站,网站统计代码添加上一节#xff0c;我们学习了TCP协议的服务器-客户端的编程流程以及对中间的过程进行了详细的讨论#xff0c;那么#xff0c;这一节#xff0c;我们对于TCP协议的特点进行进一步的分析#xff0c;这也是面试的重点和难点。
目录
一、TCP 协议特点
1.1 连接的建立与断… 上一节我们学习了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了回复报文和通知对方关闭的报文一起发送。 第一次挥手FIN 客户端发送一个FIN报文表示它要关闭到服务器的数据传送。第二次挥手FIN 服务器收到FIN后直接发送一个FIN报文表示它也要关闭到客户端的数据传送。第三次挥手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报文段有足够的时间被识别并丢弃。 被动关闭端关闭发FIN主动关闭端收到FIN发ACK变成TIME_WAIT有可能被动关闭端没收到这个ACK这个ACK在路上丢失了过一会被动关闭端没收到主动关闭端的ACK就会再次发FIN如果TIME_WAIT状态不持续直接关闭那最后假如ACK丢失被动关闭端在发送FIN就没人管它了。在通讯的过程中有一些数据正在发送但还没发送到数据正在从A端到B端但还没到此时断开接收端和发送端的连接之后这个延迟的数据包到达了但此时连接已经断开了就会出现一些问题尤其是服务器。如果没有TIME_WAIT状态我们就可以立刻重新启动服务端这样延迟的数据包就会陆陆续续发到我们这个新启动的服务器里虽然我们新启动的服务器用的是这个ip这个端口延迟的数据包用的也是这个ip和端口但是这些数据包是发给上个已经结束的进程的不是发给我们这个新进程的。因此就会让TIME_WAIT状态等待大概2分钟这俩分钟是一个报文生存期最长时间的俩倍这样就会把我们网络中延迟的数据包耗死我们把这些延迟的数据一收延后丢掉俩分钟后网络中就干净了。 题目 一个局域网内有一个客户端一个服务器他们都已完成三次握手状态没有发送数据此时拔掉网线服务器再close重新运行服务器运行之后在插上网线问此时客户端跟服务器的状态。 网线拔掉之后不进行收发送数据双方是不知道的由于拔掉网线关闭服务器服务器会发送FIN但是客户端收不到也不会回复服务器就等了俩分钟后就关闭了再重新启动服务器此时服务器就是LISTEN状态等待连接客户端还是完成三次握手状态。 1.3 流式服务特点 TCP 字节流的特点发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系应用程序对数据的发送和接收是没有边界限制的。多次发送的数据会被对方一次接受或者一次发送的数据被对方分多次接受。 netstat -natp命令 可查看端口是否被占用 也能查看接收缓冲区和发送缓冲区有多少数据 bind会失败的原因 端口被占用或者把这个程序运行了又运行了一个端口已经分给第一个运行的程序。recv返回值为0是唯一判断对方客户端关闭链接的条件。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的可靠性体现在应答确认、超时重传、去重、乱序重排、进行流量控制滑动窗口 应答确认给对方send发送一个数据对方收到了在底层会回复发送方表明收到数据了A端给B端发送数据表面只能看到俩次交互实际有四次另外两次我们看不到但可以用tcpdump抓包命令看到。超时重传给对方发送数据收等了一段事件后没有收到对方的回复就认为这个数据丢失了就会再重新发送一份数据给对方。去重给对方发送数据对方收到了回复确认收到信息但回复这个信息丢失了发送段没收到就会认为发送的数据在路上丢失了就会重新发然后接收端就会有俩个一样的数据重复了就会去重。乱序重排后发送的数据比先发送的数据先到达这样顺序就会乱但在接收到数据后会对数据的顺序进行检查。滑动窗口 给对方发送数据一个字节一个字节发效率不高就会有一个窗口窗口左边是已发送对方回复确认的数据窗口内是有已发送未收到确认的和未发送的数据窗口右边是超过窗口范围内外就不能发送的窗口内比如能够发送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.hint 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是无连接不可靠的数据报服务 。 tcp建立连接要进行三次握手而udp不需要建立连接直接指定地址发数据就行tcp在发送数据时有应答确认超时重传机制而udp发送数据成功就成功失败了也不会重发。tcp会出现粘包udp不会出现粘包。 4.2 同一个端口可不可以被一个 TCP 和一个 UDP 的应用程序同时使用 是的可以同一个端口可以同时被一个 TCP 应用程序和一个 UDP 应用程序使用。TCP 和 UDP 是两个不同的传输层协议它们的连接和数据传输方式不同因此它们可以在相同的端口号上共存。操作系统和网络栈通过区分传输层协议TCP 或 UDP来将数据包正确地交付给对应的应用程序。例如假设你有一个 TCP 服务在端口 8080 上运行同时你也可以在相同的端口 8080 上运行一个 UDP 服务。这两个服务不会互相干扰因为操作系统能够根据协议类型将到达端口 8080 的 TCP 数据包和 UDP 数据包区分开来并分别处理。 4.3 同一个应用程序可以创建多个套接字吗 同一个应用程序可以创建多个套接字。套接字是网络通信的基础它允许程序发送和接收数据。应用程序创建多个套接字的原因有很多包括但不限于以下几个方面 多协议支持一个应用程序可能需要同时支持多种协议例如同时使用 TCP 和 UDP这时它需要分别为 TCP 和 UDP 创建不同的套接字。 多端口监听一个服务器应用程序可能需要监听多个端口以便提供不同的服务或支持不同的协议版本。例如一个应用程序可以同时监听 80 端口HTTP和 443 端口HTTPS。 客户端连接管理对于一个 TCP 服务器每当一个客户端连接到服务器时服务器通常会为每个客户端连接创建一个新的套接字。这允许服务器同时处理多个客户端连接。 多线程或多进程通信应用程序可能使用多个套接字来实现多线程或多进程间的通信。例如一个线程或进程负责监听网络连接另一个线程或进程负责处理数据。 至此已经讲解完毕篇幅较长慢慢消化以上就是全部内容请务必掌握创作不易欢迎大家点赞加关注评论您的支持是我前进最大的动力下期再见