当前位置: 首页 > article >正文

网络编程:基于TCP/UDP实现客户端和服务端通信(C语言实现简单易懂)

wx:嵌入式工程师成长日记

https://mp.weixin.qq.com/s/_eqFaiID2kzFuk3zejFptg?token=382885458&lang=zh_CNicon-default.png?t=O83Ahttps://mp.weixin.qq.com/s/_eqFaiID2kzFuk3zejFptg?token=382885458&lang=zh_CN

ddd39e6b19e14e33897aa6213919c759.png

TCP是一个面向连接的,安全的,流式传输协议,这个协议是一个传输层协议。

①面向连接:是一个双向连接,通过三次握手完成,断开连接需要通过四次挥手完成。

②安全:TCP通信过程中,会对发送的每一数据包都会进行校验, 如果发现数据丢失, 会自动重传。

③流式传输:发送端和接收端处理数据的速度,数据的量都可以不一致。

(一)TCP三次握手&四次挥手

三次握手具体过程如下:

图片

   第一次握手:客户端给服务端发一个 SYN 报文,并指明客户端的初始化序列号 ISN。此时客户端处于SYN_SENT 状态。首部的同步位SYN=1,初始序号seq=x,SYN=1的报文段不能携带数据,但要消耗掉一个序号。

    第二次握手:服务器收到客户端的 SYN 报文之后,会以自己的 SYN 报文作为应答,并且也是指定了自己的初始化序列号ISN(s)。同时会把客户端的 ISN + 1 作为ACK 的值,表示自己已经收到了客户端的 SYN,此时服务器处于 SYN_RCVD 的状态。在确认报文段中SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y。

   第三次握手:客户端收到 SYN 报文之后,会发送一个 ACK 报文,当然,也是一样把服务器的 ISN + 1 作为 ACK 的值,表示已经收到了服务端的 SYN 报文,此时客户端处于 ESTABLISHED 状态。服务器收到 ACK 报文之后,也处于 ESTABLISHED 状态,此时,双方已建立起了连接。确认报文段ACK=1,确认号ack=y+1,序号seq=x+1(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。

四次挥手具体过程如下:

图片

    第一次挥手:客户端发送一个 FIN 报文,报文中会指定一个序列号。此时客户端处于FIN_WAIT1状态。即发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN_WAIT1(终止等待1)状态,等待服务端的确认。

    第二次挥手:服务端收到 FIN 之后,会发送 ACK 报文,且把客户端的序列号值 +1 作为 ACK 报文的序列号值,表明已经收到客户端的报文了,此时服务端处于 CLOSE_WAIT 状态。即服务端收到连接释放报文段后即发出确认报文段(ACK=1,确认号ack=u+1,序号seq=v),服务端进入CLOSE_WAIT(关闭等待)状态,此时的TCP处于半关闭状态,客户端到服务端的连接释放。客户端收到服务端的确认后,进入FIN_WAIT2(终止等待2)状态,等待服务端发出的连接释放报文段。

  第三次挥手:如果服务端也想断开连接了,和客户端的第一次挥手一样,发给 FIN 报文,且指定一个序列号。此时服务端处于 LAST_ACK 的状态。即服务端没有要向客户端发出的数据,服务端发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),服务端进入LAST_ACK(最后确认)状态,等待客户端的确认。

    第四次挥手:客户端收到 FIN 之后,一样发送一个 ACK 报文作为应答,且把服务端的序列号值 +1 作为自己 ACK 报文的序列号值,此时客户端处于 TIME_WAIT 状态。需要过一阵子以确保服务端收到自己的 ACK 报文之后才会进入 CLOSED 状态,服务端收到 ACK 报文之后,就处于关闭连接了,处于 CLOSED 状态。

(二)C语言实现TCP通信

         

图片

服务端:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>
int main(){    // 1. 创建监听的套接字    int lfd = socket(AF_INET, SOCK_STREAM, 0);    if(lfd == -1)    {        perror("socket");        exit(0);    }
    // 2. 将socket()返回值和本地的IP端口绑定到一起    struct sockaddr_in addr;    addr.sin_family = AF_INET;    addr.sin_port = htons(10000);   // 大端端口    addr.sin_addr.s_addr = INADDR_ANY;  
    int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));    if(ret == -1)    {        perror("bind");        exit(0);    }
    // 3. 设置监听    ret = listen(lfd, 128);    if(ret == -1)    {        perror("listen");        exit(0);    }
    // 4. 阻塞等待并接受客户端连接    struct sockaddr_in cliaddr;    int clilen = sizeof(cliaddr);    int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &clilen);    if(cfd == -1)    {        perror("accept");        exit(0);    }    // 5. 和客户端通信    while(1)    {        // 接收数据        char buf[1024];        memset(buf, 0, sizeof(buf));        int len = read(cfd, buf, sizeof(buf));        if(len > 0)        {            printf("客户端: %s\n", buf);            write(cfd, buf, len);        }        else if(len  == 0)        {            printf("客户端断开了连接...\n");            break;        }        else        {            perror("read");            break;        }    }
    close(cfd);    close(lfd);
    return 0;}

客户端:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>
int main(){    // 1. 创建通信的套接字    int fd = socket(AF_INET, SOCK_STREAM, 0);    if(fd == -1)    {        perror("socket");        exit(0);    }
    // 2. 连接服务器    struct sockaddr_in addr;    addr.sin_family = AF_INET;    addr.sin_port = htons(10000);   // 大端端口    inet_pton(AF_INET, "192.168.2.3", &addr.sin_addr.s_addr);
    int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));    if(ret == -1)    {        perror("connect");        exit(0);    }
    // 3. 和服务器端通信    int number = 0;    while(1)    {        // 发送数据        char buf[1024];        sprintf(buf, "hello,server...%d\n", number++);        write(fd, buf, strlen(buf)+1);
        // 接收数据        memset(buf, 0, sizeof(buf));        int len = read(fd, buf, sizeof(buf));        if(len > 0)        {            printf("服务器: %s\n", buf);        }        else if(len  == 0)        {            printf("服务器断开了连接...\n");            break;        }        else        {            perror("read");            break;        }        sleep(1);      }    close(fd);    return 0;}

UDP是一个面向无连接的,不安全的,报式传输层协议,udp的通信过程默认也是阻塞的。

①UDP通信不需要建立连接 ,因此不需要进行connect()操作

②UDP通信过程中,每次都需要指定数据接收端的IP和端口,和发快递差不多

③UDP不对收到的数据进行排序,在UDP报文的首部中并没有关于数据顺序的信息

    UDP对接收到的数据报不回复确认信息,发送端不知道数据是否被正确接收,也不会重发数据。如果发生了数据丢失,不存在丢一半的情况,如果丢当前这个数据包就全部丢失了.

图片

(一)C语言实现UDP通信

服务端:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>
int main(){    // 1. 创建通信的套接字    int fd = socket(AF_INET, SOCK_DGRAM, 0);    if(fd == -1)    {        perror("socket");        exit(0);    }
    // 2. 通信的套接字和本地的IP与端口绑定    struct sockaddr_in addr;    addr.sin_family = AF_INET;    addr.sin_port = htons(9999);    // 大端    addr.sin_addr.s_addr = INADDR_ANY;     int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));    if(ret == -1)    {        perror("bind");        exit(0);    }
    char buf[1024];    char ipbuf[64];    struct sockaddr_in cliaddr;    int len = sizeof(cliaddr);    // 3. 通信    while(1)    {        // 接收数据        memset(buf, 0, sizeof(buf));        int rlen = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&cliaddr, &len);        printf("客户端的IP地址: %s, 端口: %d\n",               inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),               ntohs(cliaddr.sin_port));        printf("客户端: %s\n", buf);
        // 数据回复给了发送数据的客户端        sendto(fd, buf, rlen, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));    }
    close(fd);
    return 0;}

客户端:

#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <string.h>#include <arpa/inet.h>
int main(){    // 1. 创建通信的套接字    int fd = socket(AF_INET, SOCK_DGRAM, 0);    if(fd == -1)    {        perror("socket");        exit(0);    }
    // 初始化服务器地址信息    struct sockaddr_in seraddr;    seraddr.sin_family = AF_INET;    seraddr.sin_port = htons(9999);    // 大端    inet_pton(AF_INET, "192.168.2.3", &seraddr.sin_addr.s_addr);
    char buf[1024];    char ipbuf[64];    struct sockaddr_in cliaddr;    int len = sizeof(cliaddr);    int num = 0;    // 2. 通信    while(1)    {        sprintf(buf, "hello, udp %d....\n", num++);        // 发送数据, 数据发送给了服务器        sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&seraddr, sizeof(seraddr));
        // 接收数据        memset(buf, 0, sizeof(buf));        recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);        printf("服务器: %s\n", buf);        sleep(1);    }
    close(fd);    return 0;}

(二)TCP/UDP应用场景

TCP使用场景

对数据安全性要求高的时:

1.登录数据的传输 (比如用户名密码)

2.文件传输

UDP使用场景

效率高且实时性要求比较高

1.视频聊天、直播

2.通话


http://www.kler.cn/a/507553.html

相关文章:

  • 【狂热算法篇】探秘图论之 Floyd 算法:解锁最短路径的神秘密码(通俗易懂版)
  • css中的阴影详解
  • 【Web】Web API 简介
  • RabbitMQ前置概念
  • 【Git版本控制器--1】Git的基本操作--本地仓库
  • docker 部署 MantisBT
  • 如何在若依框架中自定义添加一个页面
  • 相机拍照参数:WB、FF、S、ISO、EV、焦距
  • 08、如何预防SQL注入
  • 《机器学习》——PCA降维
  • 1.快慢指针-力扣-283-移动零
  • 软件测试入门—功能需求分析:以一个旅游管理系统为例
  • LeetCode 1426 题:数元素解题全解析
  • 【机器学习实战入门项目】MNIST数字分类机器学习项目
  • 用C++实现一个基于模板的观察者设计模式
  • MySQL的不同SQL模式导致行为不同?
  • 【北京迅为】iTOP-4412全能版使用手册-第七十六章 Qt界面切换
  • Kubernetes(k8s)和Docker Compose本质区别
  • 20.<Spring图书管理系统①(登录+添加图书)>
  • 6.3、OTN 保护
  • Linux 文件权限详解
  • Unity Dots理论学习-3.ECS有关的模块(2)
  • 【FlutterDart】MVVM(Model-View-ViewModel)架构模式例子-http版本(30 /100)
  • 阿里云通义实验室自然语言处理方向负责人黄非:通义灵码2.0,迈入 Agentic AI
  • matlab函数的主要目的是对包含在 Excel 电子表格中的实验数据进行模型拟合
  • 【k8s面试题2025】3、练气中期