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

news/2025/1/20 2:23:06/

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.ppmy.cn/news/1564553.html

相关文章

minio https配置

minio启动时候指定数据目录,配置文件&#xff0c;密钥文件目录&#xff0c;环境文件 1.创建minio用户,专门用于服务启动的 groupadd -r minio-user useradd -M -r -g minio-user minio-user 2.在当前用户目录下创建minio目录&#xff0c;存储minio相关文件 mkdir minio 在mini…

el-dialog弹窗的@open方法中,第一次引用ref发现undefined问题,第二次后面又正常了

解决方法 直接不用这个open方法&#xff0c;转而用opened&#xff0c;代码例子&#xff1a; <el-dialog title"单个新增" :visible.sync"PlacardShowSingle" opened"openpbSingle()" width"1100px" top"1%" :close-on-c…

【Leetcode 热题 100】45. 跳跃游戏 II

问题背景 给定一个长度为 n n n 的 0 0 0 索引 整数数组 n u m s nums nums。初始位置为 n u m s [ 0 ] nums[0] nums[0]。 每个元素 n u m s [ i ] nums[i] nums[i] 表示从索引 i i i 向前跳转的最大长度。换句话说&#xff0c;如果你在 n u m s [ i ] nums[i] nums[i…

EAMM: 通过基于音频的情感感知运动模型实现的一次性情感对话人脸合成

EAMM: 通过基于音频的情感感知运动模型实现的一次性情感对话人脸合成 1所有的材料都可以在EAMM: One-Shot Emotional Talking Face via Audio-Based Emotion-Aware Motion Model网站上找到。 摘要 尽管音频驱动的对话人脸生成技术已取得显著进展&#xff0c;但现有方法要么忽…

JAVA实现五子棋小游戏(附源码)

文章目录 一、设计来源捡金币闯关小游戏讲解1.1 主界面1.2 黑棋胜利界面1.3 白棋胜利界面 二、效果和源码2.1 动态效果2.2 源代码 源码下载更多优质源码分享 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/145161039 JA…

IT程序设计文档,软件需求设计文档,详细设计模板(Word原件)

1引言 1.1编写目的 1.2项目背景 1.3参考材料 2系统总体设计 2.1整体架构 2.2整体功能架构 2.3整体技术架构 2.4设计目标 2.5.1总体原则 2.5.2实用性和先进性 2.5.3标准化、开放性、兼容性 2.5.4高可靠性、稳定性 2.5.5易用性 2.5.6灵活性和可扩展性 2.5.7经济性…

vue v-if和key值的注意的地方

v-if的使用 v-if 用来判断元素的显示与隐藏&#xff0c; 与v-show的相同和区别&#xff1a; v-if和v-show 为true 都占据位置&#xff0c;为false都不占有位置 控制手段&#xff1a;v-if 通过删除和添加dom结构进行显示和隐藏&#xff0c;v-show通过css的display&#xff1…

openssl s_server源码剥离

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…