【网络编程入门】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搭建和管理企业级网站应用
相关文章
|
2月前
|
缓存 网络协议 Java
【JavaEE】——TCP回显服务器(万字长文超详细)
ServerSocket类,Socket类,PrintWriter缓冲区问题,Socket文件释放问题,多线程问题
|
4月前
|
网络协议 Java API
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
【网络】TCP回显服务器和客户端的构造,以及相关bug解决方法
86 2
|
4月前
|
存储 网络协议 Java
【网络】UDP和TCP之间的差别和回显服务器
【网络】UDP和TCP之间的差别和回显服务器
98 1
|
弹性计算 NoSQL JavaScript
ECS服务器入门体验
SpringBoot项目部署、Vue项目部署、Redis安装
ECS服务器入门体验
|
21天前
|
弹性计算 数据挖掘 应用服务中间件
阿里云轻量应用服务器68元与云服务器99元和199元区别及选择参考
目前阿里云有三款特惠云服务器,第一款轻量云服务器2核2G68元一年,第二款经济型云服务器2核2G3M带宽99元1年,第三款通用算力型2核4G5M带宽199元一年。有的新手用户并不是很清楚他们之间的区别,因此不知道如何选择。本文来介绍一下它们之间的区别以及选择参考。
329 87
|
14天前
|
存储 弹性计算 应用服务中间件
阿里云轻量应用服务器出新品通用型实例了,全球26个地域可选
近日,阿里云再度发力,推出了首款全新升级的轻量应用服务器——通用型实例。这款服务器实例不仅标配了200Mbps峰值公网带宽,更在计算、存储、网络等基础资源上进行了全面优化,旨在为中小企业和开发者提供更加轻量、易用、普惠的云计算服务,满足其对于通用计算小算力的迫切需求。目前,这款新品已在全球26个地域正式上线,为全球用户提供了更加便捷、高效的上云选择。
115 27
|
5天前
|
机器学习/深度学习 人工智能 弹性计算
阿里云AI服务器价格表_GPU服务器租赁费用_AI人工智能高性能计算推理
阿里云AI服务器提供多种配置,包括CPU+GPU、FPGA等,适用于人工智能、机器学习和深度学习等计算密集型任务。本文整理了阿里云GPU服务器的优惠价格,涵盖NVIDIA A10、V100、T4等型号,提供1个月、1年和1小时的收费明细。具体规格如A10卡GN7i、V100-16G卡GN6v等,适用于不同业务场景,详情见官方页面。
50 11
|
6天前
|
存储 弹性计算 数据挖掘
阿里云服务器ECS通用算力型u1和ECS经济型e实例性能特点、使用及常见问题解答FAQ
阿里云ECS云服务器的经济型e实例和通用算力型u1实例深受开发者和中小企业青睐。e实例适合中小型网站、开发测试等轻量级应用,采用共享CPU调度模式,性价比高;u1实例则适用于中小型企业级应用,提供更高的性能保障和稳定性,支持固定CPU调度模式,计算性能更稳定。同等配置下,u1实例在网络带宽、IOPS等方面表现更优,价格也相对较高。个人用户可选择e实例,中小企业建议选择u1实例以确保业务稳定性。
|
2月前
|
机器学习/深度学习 人工智能 PyTorch
阿里云GPU云服务器怎么样?产品优势、应用场景介绍与最新活动价格参考
阿里云GPU云服务器怎么样?阿里云GPU结合了GPU计算力与CPU计算力,主要应用于于深度学习、科学计算、图形可视化、视频处理多种应用场景,本文为您详细介绍阿里云GPU云服务器产品优势、应用场景以及最新活动价格。
阿里云GPU云服务器怎么样?产品优势、应用场景介绍与最新活动价格参考
|
30天前
|
存储 运维 安全
阿里云弹性裸金属服务器是什么?产品规格及适用场景介绍
阿里云服务器ECS包括众多产品,其中弹性裸金属服务器(ECS Bare Metal Server)是一种可弹性伸缩的高性能计算服务,计算性能与传统物理机无差别,具有安全物理隔离的特点。分钟级的交付周期将提供给您实时的业务响应能力,助力您的核心业务飞速成长。本文为大家详细介绍弹性裸金属服务器的特点、优势以及与云服务器的对比等内容。
119 23