Linux网络服务器编程:TCP与UDP详解

devtools/2024/12/22 14:26:55/

文章目录

    • 一、TCP与UDP概述
      • 1.1 TCP的原理
      • 1.2 UDP的原理
      • 1.3 数据流动
    • 二、Socket的使用
      • 2.1 TCP Socket示例
      • 2.2 UDP Socket示例
    • 三、数据流动时序图
      • 3.1 TCP通信详解
      • 3.2 UDP通信详解
    • 四、异常情况处理
      • 4.1 服务器ACK丢失
      • 4.2 第三次握手的ACK丢失
    • 五、总结
    • 推荐阅读

Linux网络服务器编程中,TCP和UDP是两种主要的传输层协议。本文将详细分析TCP和UDP在服务器编程中的使用、原理、代码示例、数据流动,以及一些异常情况的处理方式。

一、TCP与UDP概述

1.1 TCP的原理

TCP是一种面向连接的协议,它通过三次握手建立连接,然后在连接上进行可靠的数据传输。TCP使用序列号和确认应答(ACK)来保证数据的可靠传输,通过滑动窗口和拥塞控制算法进行流量控制和拥塞控制。

1.2 UDP的原理

相比于TCP,UDP是一种更简单的协议。UDP是无连接的,它直接在IP协议之上发送数据报,不提供数据的可靠传输、流量控制或拥塞控制。因此,UDP的延迟和开销较小,适用于对实时性要求高的应用,如语音和视频通信。

1.3 数据流动

在TCP和UDP通信中,数据是从客户端流向服务器的。客户端首先建立连接(TCP)或直接发送数据报(UDP),然后服务器接收并处理这些数据,可能会返回响应给客户端。在TCP通信中,数据的流动是双向的,客户端和服务器都可以发送数据和接收数据。在UDP通信中,数据的流动也是双向的,但是由于UDP是无连接的,客户端和服务器可以独立地发送和接收数据。

二、Socket的使用

在Linux网络服务器编程中,我们使用socket来实现TCP和UDP通信。以下是TCP和UDP的socket使用示例:

2.1 TCP Socket示例

服务器端:

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <iostream>int main() {int server_fd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = INADDR_ANY;bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));listen(server_fd, 5);while (true) {struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_addr_len);char buffer[1024];ssize_t read_len = read(client_fd, buffer, sizeof(buffer) - 1);buffer[read_len] = '\0';std::cout << "Received: " << buffer << std::endl;write(client_fd, buffer, strlen(buffer));close(client_fd);}close(server_fd);return 0;
}

客户端:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <iostream>int main() {int client_fd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));const char *message = "Hello, Server!";write(client_fd, message, strlen(message));char buffer[1024];ssize_t read_len = read(client_fd, buffer, sizeof(buffer) - 1);buffer[read_len] = '\0';std::cout << "Received: " << buffer << std::endl;close(client_fd);return 0;
}

在Linux网络编程中,socket(), sockaddr_in 结构体和相关常量都是用于创建和配置套接字的关键组件。以下是上面代码的含义和用法:

  • AF_INET:这是一个地址族(Address Family)常量,表示我们使用的是IPv4协议。在创建套接字时,需要指定地址族以确定使用哪种协议。另一个常见的地址族是AF_INET6,表示使用IPv6协议。

  • SOCK_STREAM:这是一个套接字类型(Socket Type)常量,表示我们使用的是面向连接的、可靠的字节流。在TCP协议中,我们使用SOCK_STREAM类型的套接字。另一个常见的套接字类型是SOCK_DGRAM,表示无连接的、不可靠的数据报文,通常用于UDP协议。

  • socket(AF_INET, SOCK_STREAM, 0):这是一个系统调用,用于创建一个新的套接字。它接受三个参数:地址族(如AF_INET)、套接字类型(如SOCK_STREAM)和协议(通常设置为0,让系统自动选择协议,如TCP或UDP)。此函数返回一个套接字文件描述符,用于后续的网络操作。

  • struct sockaddr_in:这是一个用于表示IPv4套接字地址的结构体。它包含了地址族、端口号和IPv4地址。在网络编程中,我们需要使用此结构体来设置服务器和客户端的地址信息。

  • server_addr.sin_family = AF_INET:设置sockaddr_in结构体中的地址族字段为AF_INET,表示使用IPv4协议。

  • server_addr.sin_port = htons(8080):设置sockaddr_in结构体中的端口号字段。htons()函数将主机字节序(Host Byte Order)转换为网络字节序(Network Byte Order)。这里我们设置端口号为8080。

  • INADDR_ANY:这是一个特殊的IPv4地址(0.0.0.0),表示服务器将监听所有可用的网络接口。当服务器有多个网络接口时,使用INADDR_ANY可以让服务器接受来自任何接口的连接请求。

  • server_addr.sin_addr.s_addr = INADDR_ANY:设置sockaddr_in结构体中的IPv4地址字段为INADDR_ANY,表示服务器将监听所有可用的网络接口。

2.2 UDP Socket示例

服务器端:

#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <iostream>int main() {int server_fd = socket(AF_INET, SOCK_DGRAM, 0);struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);server_addr.sin_addr.s_addr = INADDR_ANY;bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));while (true) {char buffer[1024];struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);ssize_t read_len = recvfrom(server_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&client_addr, &client_addr_len);buffer[read_len] = '\0';std::cout << "Received: " << buffer << std::endl;sendto(server_fd, buffer, strlen(buffer), 0, (struct sockaddr *)&client_addr, client_addr_len);}close(server_fd);return 0;
}

客户端:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <iostream>int main() {int client_fd = socket(AF_INET, SOCK_DGRAM, 0);struct sockaddr_in server_addr;server_addr.sin_family = AF_INET;server_addr.sin_port = htons(8080);inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);const char *message = "Hello, Server!";sendto(client_fd, message, strlen(message), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));char buffer[1024];struct sockaddr_in recv_addr;socklen_t recv_addr_len = sizeof(recv_addr);ssize_t read_len = recvfrom(client_fd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&recv_addr, &recv_addr_len);buffer[read_len] = '\0';std::cout << "Received: " << buffer << std::endl;close(client_fd);return 0;
}

三、数据流动时序图

以下是TCP和UDP通信的时序图,展示了客户端与服务器之间的数据流动。

3.1 TCP通信详解

在TCP通信中,我们首先需要建立一个TCP连接,然后才能在这个连接上进行数据传输。以下是TCP通信的详细步骤和时序图:

  1. 服务器执行socket()函数,创建一个新的套接字。
  2. 服务器执行bind()函数,将套接字绑定到一个指定的地址(包括IP地址和端口号)。
  3. 服务器执行listen()函数,使套接字进入监听模式,等待客户端的连接请求。
  4. 服务器执行accept()函数,阻塞并等待客户端的连接请求。当一个客户端连接请求到来时,accept()函数返回,并创建一个新的套接字与客户端进行通信。
  5. 客户端执行socket()connect()函数,向服务器发起连接请求。connect()函数会发送一个SYN(同步)数据包到服务器
  6. 服务器收到SYN数据包,在accept()函数返回后,回复一个SYN+ACK(确认应答)数据包给客户端。
  7. 客户端收到SYN+ACK数据包,回复一个ACK数据包给服务器,完成TCP连接的建立。
  8. TCP连接建立后,客户端和服务器可以通过read()write()函数进行数据传输。

以下是TCP通信的时序图:

Server                Client|                     || socket()            ||                     || bind()              ||                     || listen()            ||                     || accept()            ||                     ||--等待客户端连接请求--->||                     ||                     || socket(), connect() | |<--- SYN ------------||                     ||-- SYN + ACK ------->||                     ||<--- ACK ------------||                     ||<-- Data ------------|| read(), write()     ||						||-- Data -----------> || read(), write()     ||                     |

3.2 UDP通信详解

与TCP不同,UDP是一种无连接的协议,客户端和服务器不需要建立连接就可以直接发送数据。以下是UDP通信的详细步骤:

  1. 服务器执行socket()函数,创建一个新的套接字。
  2. 服务器执行bind()函数,将套接字绑定到一个指定的地址(包括IP地址和端口号)。
  3. 客户端执行socket()函数,创建一个新的套接字。
  4. 客户端可以直接通过sendto()函数发送数据到服务器
  5. 服务器通过recvfrom()函数接收客户端发送的数据。

以下是UDP通信的时序图:

Server                Client|                     || socket()            ||                     || bind()              ||                     ||----等待客户端数据---->||                     ||                     ||   			socket()|| 			sendto()| |<--- Data -----------|| recvfrom()          ||                     |

在这种情况下,服务器已经准备好接受客户端的数据。当客户端执行socket()sendto()函数发送数据时,服务器会通过recvfrom()函数接收这些数据。

四、异常情况处理

网络通信中,可能会遇到一些异常情况,如TCP握手过程中服务器ACK丢失、第三次握手的ACK丢失等。以下是这些异常情况的处理方式:

4.1 服务器ACK丢失

服务器发送的ACK丢失时,客户端将无法收到确认,因此会重新发送SYN。服务器在收到重复的SYN后,会再次发送ACK。这个过程会持续进行,直到客户端收到ACK或达到最大重传次数。

4.2 第三次握手的ACK丢失

当第三次握手的ACK丢失时,服务器可能仍在等待客户端的ACK。然而,客户端已经认为连接建立,可能会开始发送数据。服务器在收到客户端的数据后,会认为连接已建立,并更新连接状态。因此,即使第三次握手的ACK丢失,TCP连接仍然可以正常建立。

五、总结

本文详细讨论了Linux网络服务器编程中TCP和UDP两种方式的socket使用、原理分析、代码示例、数据流动时序图,以及一些异常情况的处理方式。理解这些概念和技巧有助于更高效地进行网络服务器编程,应对各种网络通信场景。

推荐阅读

TCP与UDP:网络协议的技术原理与要点
从HTTP到QUIC:网络协议的演进与优化
HTTPS:原理、使用方法及安全威胁


http://www.ppmy.cn/devtools/2064.html

相关文章

【机器学习】分类与预测算法评价的方式介绍

一、引言 1、机器学习分类与预测算法的重要性 在数据驱动的时代&#xff0c;机器学习已经成为了处理和分析大规模数据的关键工具。分类与预测作为机器学习的两大核心任务&#xff0c;广泛应用于各个领域&#xff0c;如金融、医疗、电商等。分类算法能够对数据进行有效归类&…

基于react native的android原生微信客服,微信支付以及判断是否安装微信

基于react native的android原生微信客服&#xff0c;微信支付以及判断是否安装微信 引入SDK&#xff08;Android Studio 环境下&#xff09;创建wxapi/WXPayEntryActivity.java&#xff08;用于接收微信响应返回信息&#xff09;CustomerServiceModule.javaCustomerServicePack…

工业级3D可视化工具HOOPS Visualize, 快速构建移动端和PC端工程应用程序!

HOOPS Visualize是一款强大的工业级3D渲染引擎&#xff0c;帮助您打造出众的工程应用程序。HOOPS Visualize的基石是图形内核&#xff0c;这是一种全功能的&#xff0c;以工程为重点的场景图技术&#xff0c;我们称为Core Graphics。Core Graphics集成到一个框架中&#xff0c;…

客服话术沟通技巧:客服怎么礼貌表达拒绝?

在客服工作中&#xff0c;面对消费者的不同需求和请求&#xff0c;如何礼貌而有效地表达“拒绝”是一项重要的沟通技巧。对于提升客户满意度和客服响应率极为重要。下面给大家分享一些客服话术沟通技巧。 一、客服话术沟通技巧 1.退差价&#xff08;售后不能退&#xff09;&am…

three.js捋文档的记录笔记(六):场景 几何体 材质 物体 相机 渲染器的简单理解

三维场景Scene const scene new THREE.Scene();物体形状&#xff1a;几何体 Geometry //创建一个长方体几何对象Geometry const geometry new THREE.BoxGeometry(100, 100, 100); 物体外观&#xff1a;材质Material //创建一个材质对象Material const material new THREE.M…

建立时间/保持时间为负是什么情况

目录 建立时间为负保持时间为负参考 在说明建立时间和保持时间为何为负的情况下&#xff0c;首先可以看看建立时间Tsu和保持时间Th的由来&#xff0c;可参考如下两篇文章&#xff1a; 建立时间和保持时间理解_为什么要满足建立时间和保持时间-CSDN博客 ic基础|时序篇&#xff…

最新在Fedora Linux安装MongoDB服务器的简单教程

本指南将帮助你在 Fedora 39/38/37/36 或您正在使用的任何其他版本上安装最新或旧版本的 MongoDB 数据库服务器。 MongoDB 是一种流行的 NoSQL 数据库服务器&#xff0c;提供社区版和企业版。它以其高性能、灵活性和可扩展性而闻名。它是一个无模式数据库&#xff0c;因此允许…

Redis深入解析:HyperLogLog、Bitmap和Geospatial的奇妙应用

本文深入探讨了Redis数据库中的三种特殊数据类型&#xff1a;HyperLogLog、Bitmap和Geospatial。 HyperLogLog用于高效估算集合基数&#xff0c;牺牲小部分准确度以节省空间Bitmap提供位操作&#xff0c;适用于二元数据的高效记录与查询Geospatial处理地理位置数据&#xff0c…