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


相关实践学习
深入解析Docker容器化技术
Docker是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的Linux机器上,也可以实现虚拟化,容器是完全使用沙箱机制,相互之间不会有任何接口。Docker是世界领先的软件容器平台。开发人员利用Docker可以消除协作编码时“在我的机器上可正常工作”的问题。运维人员利用Docker可以在隔离容器中并行运行和管理应用,获得更好的计算密度。企业利用Docker可以构建敏捷的软件交付管道,以更快的速度、更高的安全性和可靠的信誉为Linux和Windows Server应用发布新功能。 在本套课程中,我们将全面的讲解Docker技术栈,从环境安装到容器、镜像操作以及生产环境如何部署开发的微服务应用。本课程由黑马程序员提供。 &nbsp; &nbsp; 相关的阿里云产品:容器服务 ACK 容器服务 Kubernetes 版(简称 ACK)提供高性能可伸缩的容器应用管理能力,支持企业级容器化应用的全生命周期管理。整合阿里云虚拟化、存储、网络和安全能力,打造云端最佳容器化应用运行环境。 了解产品详情: https://www.aliyun.com/product/kubernetes
相关文章
|
存储 缓存 NoSQL
Redis 服务器全方位介绍:从入门到核心原理
Redis是一款高性能内存键值数据库,支持字符串、哈希、列表等多种数据结构,广泛用于缓存、会话存储、排行榜及消息队列。其单线程事件循环架构保障高并发与低延迟,结合RDB和AOF持久化机制兼顾性能与数据安全。通过主从复制、哨兵及集群模式实现高可用与横向扩展,适用于现代应用的多样化场景。合理配置与优化可显著提升系统性能与稳定性。
247 0
|
5月前
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
331 61
|
5月前
|
运维 网络协议 Go
Go网络编程:基于TCP的网络服务端与客户端
本文介绍了使用 Go 语言的 `net` 包开发 TCP 网络服务的基础与进阶内容。首先简述了 TCP 协议的基本概念和通信流程,接着详细讲解了服务端与客户端的开发步骤,并提供了简单回显服务的示例代码。同时,文章探讨了服务端并发处理连接的方法,以及粘包/拆包、异常检测、超时控制等进阶技巧。最后通过群聊服务端的实战案例巩固知识点,并总结了 TCP 在高可靠性场景中的优势及 Go 并发模型带来的便利性。
|
8月前
|
网络协议 物联网
VB6网络通信软件上位机开发,TCP网络通信,读写数据并处理,完整源码下载
本文介绍使用VB6开发网络通信上位机客户端程序,涵盖Winsock控件的引入与使用,包括连接服务端、发送数据(如通过`Winsock1.SendData`方法)及接收数据(利用`Winsock1_DataArrival`事件)。代码实现TCP网络通信,可读写并处理16进制数据,适用于自动化和工业控制领域。提供完整源码下载,适合学习VB6网络程序开发。 下载链接:[完整源码](http://xzios.cn:86/WJGL/DownLoadDetial?Id=20)
313 12
|
8月前
|
前端开发 Java 关系型数据库
基于ssm的网络直播带货管理系统,附源码+数据库+论文
该项目为网络直播带货网站,包含管理员和用户两个角色。管理员可进行主页、个人中心、用户管理、商品分类与信息管理、系统及订单管理;用户可浏览主页、管理个人中心、收藏和订单。系统基于Java开发,采用B/S架构,前端使用Vue、JSP等技术,后端为SSM框架,数据库为MySQL。项目运行环境为Windows,支持JDK8、Tomcat8.5。提供演示视频和详细文档截图。
254 10
|
8月前
|
JavaScript 算法 前端开发
JS数组操作方法全景图,全网最全构建完整知识网络!js数组操作方法全集(实现筛选转换、随机排序洗牌算法、复杂数据处理统计等情景详解,附大量源码和易错点解析)
这些方法提供了对数组的全面操作,包括搜索、遍历、转换和聚合等。通过分为原地操作方法、非原地操作方法和其他方法便于您理解和记忆,并熟悉他们各自的使用方法与使用范围。详细的案例与进阶使用,方便您理解数组操作的底层原理。链式调用的几个案例,让您玩转数组操作。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
9月前
|
弹性计算 云计算
阿里云认证全新发布【Apsara Clouder云计算专项技能认证:云服务器ECS入门】
阿里云认证全新发布【Apsara Clouder云计算专项技能认证:云服务器ECS入门】
|
10月前
|
监控 前端开发 应用服务中间件
小游戏源码开发搭建技术栈和服务器配置流程
近些年不同场景游戏层出不穷,现就小游戏开发技术应用及功能详细剖析!
|
PHP 数据库 数据安全/隐私保护
布谷直播源码部署服务器关于数据库配置的详细说明
布谷直播系统源码搭建部署时数据库配置明细!
|
NoSQL 应用服务中间件 PHP
布谷一对一直播源码服务器环境配置及app功能
一对一直播源码阿里云服务器环境配置及要求