【网络编程入门】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


相关实践学习
容器服务Serverless版ACK Serverless 快速入门:在线魔方应用部署和监控
通过本实验,您将了解到容器服务Serverless版ACK Serverless 的基本产品能力,即可以实现快速部署一个在线魔方应用,并借助阿里云容器服务成熟的产品生态,实现在线应用的企业级监控,提升应用稳定性。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
2天前
|
网络协议
Qt中的网络编程(Tcp和Udp)运用详解以及简单示范案例
Tcp和Udp是我们学习网络编程中经常接触到的两个通讯协议,在Qt也被Qt封装成了自己的库供我们调用,对于需要进行网络交互的项目中无疑是很重要的,希望这篇文章可以帮助到大家。 是关于Qt中TCP和UDP的基本使用和特点:
|
2天前
|
网络协议 Linux
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
|
4天前
|
网络协议 网络安全
使用NetAssist网络调试助手在单台计算机上配置TCP服务器和客户端
使用NetAssist网络调试助手在单台计算机上配置TCP服务器和客户端
13 0
|
7天前
|
弹性计算 缓存 安全
阿里云服务器ECS收费标准参考,2核4G配置ECS实例规格整理
阿里云提供多种2核4G ECS实例,如计算型c7、经济型e、u1等,价格不等,从68.0元/月到203.0元/月。ECS通用算力型u1实例采用高性能Intel处理器,网络收发包能力达30万PPS。经济型e实例基于Intel Xeon Platinum,适合入门级需求。2核4G服务器支持的并发访问人数依赖于软件效率、带宽、应用架构和用户行为等因素。更多信息请查看阿里云ECS产品页。
|
8天前
|
弹性计算 安全 Shell
阿里云ECS安全加固:从访问控制到数据保护的全方位策略
【6月更文挑战第29天】阿里云ECS安全聚焦访问控制、系统加固及数据保护。安全组限定IP和端口访问,密钥对增强SSH登录安全;定期更新补丁,使用防病毒工具;数据备份与加密确保数据安全。多维度策略保障业务安全。
37 15
|
2天前
|
弹性计算
阿里云ECS的使用心得
本文主要讲述了我是如何了解到ECS,使用ECS的一些经验,以及自己的感悟心得
|
4天前
|
弹性计算 运维 Kubernetes
阿里云ECS与混合云策略结合,提供云上云下无缝对接,提升业务灵活性和运维效率。
【7月更文挑战第3天】阿里云ECS与混合云策略结合,提供云上云下无缝对接,提升业务灵活性和运维效率。ECS支持多种计费模式和先进架构,保证低延迟计算。混合云融合公有云灵活性与私有云安全,实现资源最优配置。通过VPC互通、应用迁移、数据同步实践,确保安全合规,助力企业数字化转型。阿里云服务展示技术实力,支持企业在混合云时代抓住机遇。
30 3
|
3天前
|
弹性计算
阿里云ECS的使用心得
本文主要讲述了我是如何了解到ECS,使用ECS的一些经验,以及自己的感悟心得
|
4天前
|
弹性计算 运维 Kubernetes
阿里云ECS与混合云策略的结合,不仅为企业搭建了一个既灵活又稳定的IT基础架构,还为业务的快速发展与创新提供了坚实的技术支撑。
【7月更文挑战第3天】阿里云ECS在混合云中扮演关键角色,提供弹性计算资源和多样计费模式,确保业务连续性与灵活性。通过VPC互通、应用迁移、数据同步服务,如VPC对等连接、DTS,实现云上云下资源的高效整合。结合安全解决方案,保证在混合环境下的合规与安全。阿里云ECS助力企业数字化转型,应对市场变化。
33 1
|
4天前
|
存储 弹性计算 大数据
阿里云ECS在大数据处理中展现高效存储与计算实力,提供多样化实例规格适应不同需求
【7月更文挑战第3天】阿里云ECS在大数据处理中展现高效存储与计算实力,提供多样化实例规格适应不同需求,如大数据型实例配备高吞吐硬盘。与OSS集成实现大规模存储,通过Auto Scaling动态调整资源,确保任务高效运行。案例显示,使用ECS能提升处理速度、降低成本,为企业数据驱动创新提供有力支持。
22 1

热门文章

最新文章