【网络编程入门】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搭建和管理企业级网站应用
目录
打赏
0
1
1
0
23
分享
相关文章
新四化驱动,如何构建智能汽车的“全场景”可进化互联网络?
在智能化、电动化、网联化、共享化的时代浪潮中,汽车正从单纯的 “机械产品” 进化为先进的 “移动智能终端”。在软件定义汽车(SDV)的崭新时代,每一次 OTA 升级的顺利完成、每一秒自动驾驶的精准决策、每一帧车载娱乐交互的流畅呈现,都离不开一张实时响应、全域覆盖、安全可靠的广域网络。
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
构建云上安全共同体 | 阿里云亮相2024年(第十三届)电信和互联网行业网络安全年会
构建云上安全共同体 | 阿里云亮相2024年(第十三届)电信和互联网行业网络安全年会
Python 高级编程与实战:深入理解网络编程与异步IO
在前几篇文章中,我们探讨了 Python 的基础语法、面向对象编程、函数式编程、元编程、性能优化、调试技巧、数据科学、机器学习、Web 开发和 API 设计。本文将深入探讨 Python 在网络编程和异步IO中的应用,并通过实战项目帮助你掌握这些技术。
云栖大会 | Terraform从入门到实践:快速构建你的第一张业务网络
云栖大会 | Terraform从入门到实践:快速构建你的第一张业务网络
阿里云网络安全体系解析:如何构建数字时代的"安全盾牌"
在数字经济时代,阿里云作为亚太地区最大的云服务提供商,构建了行业领先的网络安全体系。本文解析其网络安全架构的三大核心维度:基础架构安全、核心技术防护和安全管理体系。通过技术创新与体系化防御,阿里云为企业数字化转型提供坚实的安全屏障,确保数据安全与业务连续性。案例显示,某金融客户借助阿里云成功拦截3200万次攻击,降低运维成本40%,响应时间缩短至8分钟。未来,阿里云将继续推进自适应安全架构,助力企业提升核心竞争力。
YOLOv11改进策略【模型轻量化】| ShufflenetV2,通过通道划分构建高效网络
YOLOv11改进策略【模型轻量化】| ShufflenetV2,通过通道划分构建高效网络
99 14
RT-DETR改进策略【模型轻量化】| ShufflenetV2,通过通道划分构建高效网络
RT-DETR改进策略【模型轻量化】| ShufflenetV2,通过通道划分构建高效网络
47 5
构建深度可观测、可集成的网络智能运维平台
本文介绍了构建深度可观测、可集成的网络智能运维平台(简称NIS),旨在解决云上网络运维面临的复杂挑战。内容涵盖云网络运维的三大难题、打造云原生AIOps工具集的解决思路、可观测性对业务稳定的重要性,以及产品发布的亮点,包括流量分析NPM、网络架构巡检和自动化运维OpenAPI,助力客户实现自助运维与优化。
构建超大带宽、超高性能及稳定可观测的全球互联网络
本次课程聚焦构建超大带宽、超高性能及稳定可观测的全球互联网络。首先介绍全球互联网络的功能与应用场景,涵盖云企业网、转发路由器等产品。接着探讨AI时代下全球互联网络面临的挑战,如大规模带宽需求、超低时延、极致稳定性和全面可观测性,并分享相应的解决方案,包括升级转发路由器、基于时延的流量调度和增强网络稳定性。最后宣布降价措施,降低数据与算力连接成本,助力企业全球化发展。

物联网

+关注