【网络编程入门】TCP与UDP通信实战:从零构建服务器与客户端对话(附简易源码,新手友好!)

简介: 在了解他们之前我们首先要知道网络模型,它分为两种,一种是OSI,一种是TCP/IP,当然他们的模型图是不同的,如下

 目录

网络模型:

OSI

TCP/IP

区别:

但是大家是不是会好奇为什么他们是有链接和无连接的呢?

三次握手和四次挥手

概念:

流程:

三次握手(Three-Way Handshake)

四次挥手(Four-Way Handshake)

个人讲解:


网络模型:

在了解他们之前我们首先要知道网络模型,它分为两种,一种是OSI,一种是TCP/IP,当然他们的模型图是不同的,如下

OSI

image.gif 编辑

TCP/IP

image.gif 编辑

区别:

共同点:他们都是在传输层的一种通信方式

不同点:

TCP是一个可靠有链接的通信协议,一般会被运用在重要数据的传输中,例如:QQ文件的传输,如果中间出现问题则会重新开始。

UDP则是一个不可靠无连接的通信协议,也就是无论对方收没收到消息,我这边都会一直发送,例如:QQ的视频通话,我们会出现网卡的情况,这种可以理解为我发送过去了,但是对方因为网络原因收不到,从而卡顿。

但是大家是不是会好奇为什么他们是有链接和无连接的呢?

这时候可以提出三次握手四次挥手的概念,这是TCP独有的,所以它是有链接的。

三次握手和四次挥手

概念:

三次握手和四次挥手是TCP(传输控制协议)连接建立和断开过程中两个关键的步骤,它们确保了数据传输的可靠性与有序性。

流程:

三次握手(Three-Way Handshake)

三次握手是TCP连接建立的过程,目的是初始化序列号、同步通信双方的序列号以及确认双方的接收和发送能力。过程如下:

  1. 第一次握手:客户端发送一个带有SYN(同步序列编号,Synchronize)标志的TCP报文给服务器,请求建立连接。这个报文中会包含客户端选择的初始序列号(ISN,Initial Sequence Number)。
  2. 第二次握手:服务器接收到客户端的SYN报文后,会发送一个SYN报文作为应答,这个报文包含服务器的初始序列号和一个对客户端SYN报文的ACK(确认)响应,即SYN/ACK报文。这意味着服务器同意建立连接,并确认收到了客户端的SYN。
  3. 第三次握手:客户端收到服务器的SYN/ACK报文后,会发送一个ACK报文给服务器,确认收到了服务器的SYN报文。至此,TCP连接建立完成,双方可以开始数据传输。

四次挥手(Four-Way Handshake)

四次挥手是TCP连接断开的过程,确保数据传输完毕后优雅地结束连接,避免数据丢失。过程如下:

  1. 第一次挥手:客户端发送一个FIN(结束,Finish)标志的TCP报文给服务器,表示客户端没有数据要发送了,请求关闭连接。
  2. 第二次挥手:服务器接收到FIN报文后,会发送一个ACK报文给客户端,确认收到了客户端的关闭请求,但此时服务器可能还有数据要发送给客户端,所以连接并未立即关闭。
  3. 第三次挥手:服务器发送完数据后,会向客户端发送一个FIN报文,表明服务器也没有数据要发送了,请求关闭连接。
  4. 第四次挥手:客户端收到服务器的FIN报文后,发送ACK报文给服务器,确认收到了服务器的关闭请求。服务器接收到这个ACK后,会关闭连接。客户端在一段时间的等待(TIME_WAIT状态)后,如果没有数据重传,也会关闭连接。

三次握手和四次挥手是TCP协议确保数据传输可靠性和连接管理的重要机制,确保了在复杂的网络环境下数据的有序传输和连接的正确建立与终止。

个人讲解:

在TCP中,客户端连接上服务器时会给服务器发送一条消息,此时服务器会给客户端回复一条确认消息,又叫ACK,并且同时跟着发过去的还会有一条消息,也就是确认消息和聊天消息时一起的,客户端收到后会再发送一个确认包给服务器,代表他接收到了。

四次挥手跟上面的流程大体时一样的,只不过确认包换成了挥手包,且第二次一起的聊天消息和挥手包分开了。

为什么会分开呢?

因为在断开连接时服务器可能还没有把所有的数据发送给客户端,从而导致出错,此时如果服务器的数据没有发送完就会一直发送挥手包,直到数据发送完成后才客户端才去给服务器再发一次挥手包。

如图:

image.gif 编辑

image.gif 编辑

模拟的场景有很多,大家可以自行发挥~

接下来就是TCP/UDP的通信流程,

流程大概时不会变的,所以我这里跟大家简单说一下。

服务器

TCP普通:

1.创建套接字

2.填充结构体

3.绑定IP和端口

4.监听

5.挂起等待条件 注意这里accept的函数是(sockfd,(struct sockaddr*)&caddr,&len(len = sizeof(caddr)));

6.循环接受        recv(acceptfd,buf,sizeof(buf),0)

UDP普通:

1.创建套接字

2.填充结构体

3.绑定IP和端口

4.循环接受(recvfrom)

客户端

TCP普通:

1.创建套接字

2.填充结构体

3.连接

4.收发操作

UDP普通:

1.创建套接字

2.填充结构体

4.收发操作

源码附下~:

//1.服务器
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("soclfd error\n");
        return -1;
    }
    printf("sockfd is %d\n", sockfd);
    //2.填充结构体
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    //3.绑定端口和IP
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind error\n");
        return -1;
    }
    //4.监听
    if (listen(sockfd, 5) < 0)
    {
        perror("listen error\n");
        return -1;
    }
    //5.挂起接受客户端传来的协议
    while (1)
    {
        int len = sizeof(caddr);
        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
        if (acceptfd < 0)
        {
            perror("acceptfd error\n");
            return -1;
        }
        printf("client ip = %s port = %d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
        char buf[32] = " ";
        while (1)
        {
            int recvfd = recv(acceptfd, buf, sizeof(buf), 0);
            if (recvfd < 0)
            {
                perror("recvfd error\n");
                return -1;
            }
            else if (recvfd == 0)
            {
                printf("客户端已关闭\n");
                break;
            }
            else if (strcmp(buf, "quit") == 0)
            {
                break;
            }
            else
            {
                printf("acceptfd is %s\n", buf);
            }
        }
        close(acceptfd);
    }
    close(sockfd);
    return 0;
}
//2. 客户端
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("soclfd error\n");
        return -1;
    }
    printf("sockfd is %d\n", sockfd);
    //2.填充结构体
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[2]));
    saddr.sin_addr.s_addr = inet_addr(argv[1]);
    //3.链接
    if (connect(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("connecting error\n");
        return -1;
    }
    char buf[32] = " ";
    //4.循环发送
    while (1)
    {
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        send(sockfd, buf, sizeof(buf), 0);
        if (strcmp(buf, "quit") == 0)
        {
            break;
        }
    }
    close(sockfd);
    return 0;
}

image.gif

UDP

//1.服务器
#include <stdio.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("sockfd is error");
    }
    //2.填充结构体
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    //3.??????Ip UDP?????????
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind error\n");
        return -1;
    }
    char buf[32] = " ";
    // 循环接受
    while (1)
    {
        int len = sizeof(caddr);
        int recvfromfd = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &len);//UDP的接受函数接口
        if (recvfromfd < 0)
        {
            perror("recvfromfd error\n");
            return -1;
        }
        else
        {
            printf("client is : %s\n", buf);
            printf("client IP  is %s  port is %d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
        }
        
    }
    
    close(sockfd);
    return 0;
}
//2. 客户端
#include <stdio.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("sockfd is error");
    }
    //2.填充结构体
    struct sockaddr_in caddr;
    caddr.sin_family = AF_INET;
    caddr.sin_port = htons(atoi(argv[2]));
    caddr.sin_addr.s_addr = inet_addr(argv[1]);
    // 客户端这里是非必要绑定端口和IP
    char buf[32] = " ";
    int len = sizeof(caddr);
    while (1)
    {
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        //UDP的发送
        sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, len);
    }
    close(sockfd);
    return 0;
}

image.gif


相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
10天前
|
机器学习/深度学习 TensorFlow 算法框架/工具
CNN构建网络
【8月更文挑战第10天】CNN构建网络。
44 22
|
2天前
|
机器学习/深度学习 人工智能 PyTorch
【深度学习】使用PyTorch构建神经网络:深度学习实战指南
PyTorch是一个开源的Python机器学习库,特别专注于深度学习领域。它由Facebook的AI研究团队开发并维护,因其灵活的架构、动态计算图以及在科研和工业界的广泛支持而受到青睐。PyTorch提供了强大的GPU加速能力,使得在处理大规模数据集和复杂模型时效率极高。
112 59
|
1天前
|
存储 网络协议 安全
|
2天前
|
运维 安全 SDN
网络拓扑设计与优化:构建高效稳定的网络架构
【8月更文挑战第17天】网络拓扑设计与优化是一个复杂而重要的过程,需要综合考虑多方面因素。通过合理的拓扑设计,可以构建出高效稳定的网络架构,为业务的顺利开展提供坚实的支撑。同时,随着技术的不断进步和业务需求的不断变化,网络拓扑也需要不断优化和调整,以适应新的挑战和机遇。
|
4天前
|
存储 机器学习/深度学习 人工智能
迎接AI挑战:构建新一代AI网络基础设施
随着人工智能(AI)技术的飞速发展,AI模型的复杂度和数据规模急剧增加,对基础设施的需求提出了前所未有的挑战。传统的互联网基础设施已难以满足AI技术对高性能计算、大规模数据处理和低延迟网络的需求,从而催生了新一代AI基础设施的诞生。本文旨在深入探讨新一代AI基础设施的特点、优势,并介绍其在混合云环境下的应用方案。
|
11天前
|
机器学习/深度学习
DNN构建网络
【8月更文挑战第9天】DNN构建网络。
16 3
|
18天前
|
监控 安全 网络安全
如何构建安全的网络基础设施:全面指南
【8月更文挑战第2天】构建安全的网络基础设施是一个复杂而持续的过程,需要企业从规划、设计、实施到维护等各个环节都给予足够的重视和投入。通过全面的风险评估、合理的安全策略、科学的设计方案、严格的实施流程和持续的监控优化,可以为企业打造一个坚不可摧的网络安全防线。在这个过程中,企业应始终保持对新技术和新威胁的敏锐洞察力,不断优化和完善安全体系,确保网络基础设施的安全稳定运行。
|
4天前
|
机器学习/深度学习 监控 数据可视化
|
19天前
|
算法 网络协议 应用服务中间件
(五)网络编程之流量接入层设计:基于性能怪兽从零构建日均亿级吞吐量的网关架构!
在前篇关于《Nginx》的文章中曾经提到:单节点的Nginx在经过调优后,可承载5W左右的并发量,同时为确保Nginx的高可用,在文中也结合了Keepalived对其实现了程序宕机重启、主机下线从机顶替等功能。
|
20天前
|
网络协议 Java 数据处理
(一)Java网络编程之计网基础、TCP-IP协议簇、TCP、UDP协议及腾讯QQ通信原理综述
就目前而言,多数网络编程的系列的文章都在围绕着计算机网络体系进行阐述,但其中太多理论概念,对于大部分开发者而言,用途甚微。因此,在本系列中则会以实际开发者的工作为核心,从Java程序员的角度出发,详细解读Java的网络编程核心内容。