【Linux网络编程】基于UDP实现多人聊天室

2023-05-16

文章目录

  • 一、UDP的概念
    • 1.1 UDP
    • 1.2 UDP特点
  • 二. 采用UDP实现多人聊天室原因
  • 三、多人聊天室项目功能
  • 四、实现多人聊天室项目流程分析
    • 4.1 前期准备
      • 4.1.1 定义结构体
      • 4.1.2 定义链表
    • 4.2 多人聊天室服务器
      • 4.2.1 接收客户端发来的消息并进行处理
      • 4.2.2 聊天室群公告功能
    • 4.3 多人聊天室客户端
      • 4.3.1 接收服务器发来的消息并进行处理
      • 4.3.2 向服务器发送消息
  • 五、多人聊天室流程图
    • 5.1 服务器流程图
    • 5.2 客户端流程图
  • 六、根据多人聊天室流程模块化进行代码实现
    • 6.1 服务器代码实现
      • 6.1.1 创建套接字
      • 6.1.2 创建服务器网络信息结构体
      • 6.1.3 将服务器网络信息结构体与套接字绑定
      • 6.1.4 创建客户端网络信息结构体
      • 6.1.5 子进程内部实现代码
      • 6.1.6 父进程内部实现代码
    • 6.2 客户端代码实现
      • 6.2.1 前两部与服务器一样
      • 6.2.2 给服务器发送登录数据包
      • 6.2.3 子进程内部实现代码
      • 6.2.4 父进程内部实现代码
      • 6.2.5 退出程序
  • 七、结果展示和总结
    • 7.1 结果展示
    • 7.2 缺点与不足
    • 7.3 总结
  • 八、UDP实现多人聊天室服务器源代码
  • 九、UDP实现多人聊天室客户端源代码

一、UDP的概念

1.1 UDP

UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。

数据报格式套接字SOCK_DGRAM

1.2 UDP特点

采用UDP只管发送数据而不去验证发送数据的正确性,不论传输是否被接收,数据流是否有丢失,都不再重新发送,特征如下:

  1. 强调快速传输而非传输顺序;
  2. 传输的数据可能丢失也可能损毁;
  3. 限制每次传输的数据大小;
  4. 数据的发送和接收是并发的。

二. 采用UDP实现多人聊天室原因

数据报套接字采用的是UDP(User Datagram Protocol)协议,本次聊天室采用UDP协议虽然可能会导致数据丢失,但是聊天并不去强调内容的正确性,而应该强调实时性和并发,并且数据丢失只是小概率事件。

三、多人聊天室项目功能

  1. 当有用户加入群聊上线时,将登录消息发给在线的所有人
  2. 当有用户在线发送消息时,将消息发给在线的所有人
  3. 当有用户退出群聊时,将用户退出消息发给在线的所有人
  4. 服务器可以发送系统公告消息

四、实现多人聊天室项目流程分析

4.1 前期准备

4.1.1 定义结构体

由于客户端给服务器发送的数据内容较多,所以需要定义结构体来发送:

typedef struct
{
    char code; //操作码 'L' 登录  'C' 群聊  'Q' 退出
    char name[32];
    char txt[128];
} msg_t;

code相当于一个协议,用来确定将要进行的操作。
code为 ‘L’ 登录 ‘C’ 群聊 ‘Q’ 退出

name[32]用来保存登录用户名;

txt[128]用来保存发送的信息

4.1.2 定义链表

服务器要给在线的所有客户端发送数据,需要将每一个客户端的信息保存,使用链表来保。

typedef struct _NODE
{
    struct sockaddr_in c_addr;//数据域
    struct _NODE *next;//指针域
} node_t;

数据域:客户端的网络信息结构体;

指针域:保存下一个结点的地址;

4.2 多人聊天室服务器

服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程
本次使用多进程实现多人聊天室服务器。

pid_t pid;
pid = fork();
if (pid == -1)
{
    //创建错误
}
else if (pid == 0)
{
    //子进程
    //接受数据并处理
}
else if (pid > 0)
{
    //父进程
    //发系统消息
}

4.2.1 接收客户端发来的消息并进行处理

子进程循环接收客户端发来的消息,通过switch判断code所存的协议,执行特定的功能函数。

  1. 登录操作函数
  2. 群聊操作函数
  3. 退出操作函数

4.2.2 聊天室群公告功能

将父进程视为一个客户端,向子进程发送消息给所有在线的用户。

4.3 多人聊天室客户端

客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程;
本次使用多进程实现多人聊天室客户端。代码框架同上。

4.3.1 接收服务器发来的消息并进行处理

子进程循环接受服务器发来的消息,并将其打印在终端上。

4.3.2 向服务器发送消息

父进程循环向服务器发送群聊的消息,如果当消息为 “quit” 时进行用户退出群聊操作。

五、多人聊天室流程图

5.1 服务器流程图

服务器流程图

5.2 客户端流程图

客户端流程图

六、根据多人聊天室流程模块化进行代码实现

宏定义打印错误信息,进行健壮性判断。

#define PRINT_ERR(msg)                                  \
do                                                      \
{                                                       \
    printf("%s,%d,%s\n", __FILE__, __LINE__, __func__); \
    perror(msg);                                        \
    exit(-1);                                           \
} while (0)

入参合理性判断 可执行文件 ip地址 端口号

if (argc != 3)
{
    printf("age:%s ip port\n", argv[0]);
    return -1;
}

6.1 服务器代码实现

6.1.1 创建套接字

int sockfd;
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
{
    PRINT_ERR("socket error");
}

6.1.2 创建服务器网络信息结构体

struct sockaddr_in serviceaddr;
memset(&serviceaddr, 0, sizeof(serviceaddr));
serviceaddr.sin_family = AF_INET;
serviceaddr.sin_addr.s_addr = inet_addr(argv[1]);
serviceaddr.sin_port = htons(atoi(argv[2]));
socklen_t serviceaddr_len = sizeof(serviceaddr);

6.1.3 将服务器网络信息结构体与套接字绑定

if (bind(sockfd, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
{
    PRINT_ERR("bind error");
}

6.1.4 创建客户端网络信息结构体

struct sockaddr_in clientaddr;
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t clientaddr_len = sizeof(clientaddr);

6.1.5 子进程内部实现代码

  1. 使用链表头节点函数,定义链表头节点
//创建链表头节点函数
void creat_link(node_t **head)
{
    *head = (node_t *)malloc(sizeof(node_t));
}

//定义链表头节点
node_t *phead = NULL;
creat_link(&phead);
phead->next = NULL;
  1. 循环接受客户端发来的信息并通过switch进行判断执行哪个功能函数
while (1)
{
    memset(&msg, 0, sizeof(msg));//清空操作
    memset(&clientaddr, 0, sizeof(clientaddr));//清空操作
    if ((recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len)) == -1)
    {
        PRINT_ERR("recvfrom error");
    }
    printf("%8s : [%s]\n", msg.name, msg.txt);
    switch (msg.code)
    {
    case 'L':
        do_register(sockfd, msg, clientaddr, phead);
        break;
    case 'C':
        do_group_chat(sockfd, msg, clientaddr, phead);
        break;
    case 'Q':
        quit_group_chat(sockfd, msg, clientaddr, phead);
        break;
    }
}
  1. 注册操作函数

注册时,遍历链表将用户登录信息发给所以在线的用户,并将自己的客户端网络信息结构体头插进入链表中。

int do_register(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
    //遍历链表将登录信息发送给所以人
    node_t *p = phead;
    while (p->next != NULL)
    {
        p = p->next;
        if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
        {
            PRINT_ERR("recvfrom error");
        }
    }
    //将登录的客户端信息插入保存在链表
    //头插
    //定义一个新的指针保存客户端信息
    node_t *newp = NULL;
    creat_link(&newp);
    newp->c_addr = clientaddr;
    newp->next = phead->next;
    phead->next = newp;
    return 0;
}
  1. 群聊操作函数

群聊时通过遍历,将txt中的信息发送给处了自己以外的所有在线用户

int do_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
    //遍历链表,将消息发给除自己之外的所有人
    node_t *p = phead;
    while (p->next != NULL)
    {
        p = p->next;
        //判断链表客户端信息是否是自己
        //是自己就不发送
        if (memcmp(&(p->c_addr), &clientaddr, sizeof(clientaddr)))
        {
            if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
            {
                PRINT_ERR("recvfrom error");
            }
        }
    }
    return 0;
}
  1. 退出操作函数

退出时,遍历链表将退出信息发送给除自己以外的所有在线用户,并将自己的客户端网络信息结构体在链表中删除。

int quit_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
    node_t *p = phead;

    while (p->next != NULL)
    {
        //判断链表客户端信息是否是自己
        //是自己就不发送并且将自己的客户端信息在链表内删除
        if (memcmp(&(p->next->c_addr), &clientaddr, sizeof(clientaddr)))
        {
            p = p->next;
            if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
            {
                PRINT_ERR("recvfrom error");
            }
        }
        else
        {
            node_t *pnew;
            pnew = p->next;
            p->next = pnew->next;
            pnew->next = NULL;
            free(pnew);
            pnew = NULL;
        }
    }
    return 0;
}

6.1.6 父进程内部实现代码

父进程视为一个客户端,向子进程发送消息,遍历链表给所有在线的用户。

msg.code='C';
strcpy(msg.name,"server");
while(1)
{
    fgets(msg.txt,128,stdin);
    msg.txt[strlen(msg.txt)-1]='\0';
    if(sendto(sockfd,&msg,sizeof(msg_t),0,(struct sockaddr *)&serviceaddr,serviceaddr_len)==-1)
    {
        PRINT_ERR("sendto error");
    }
}

代码结束前,在最后记得关闭套接字close(sockfd);

6.2 客户端代码实现

6.2.1 前两部与服务器一样

6.2.2 给服务器发送登录数据包

msg_t msg;
memset(&msg, 0, sizeof(msg_t));
msg.code = 'L';
printf("请输入用户名:");
fgets(msg.name, 32, stdin);
msg.name[strlen(msg.name) - 1] = '\0';

strcpy(msg.txt, "加入群聊");
if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
{
    PRINT_ERR("sendto error");
}

6.2.3 子进程内部实现代码

循环接收服务器发来的数据,并将其打印在终端上。

while (1)
{
    //每次循环前将msg置零
    memset(&msg, 0, sizeof(msg));
    //接受服务器发过来的信息并打印到终端上
    if (recvfrom(sockfd, &msg, sizeof(msg_t), 0, NULL, NULL) == -1)
    {
        PRINT_ERR("recvfrom error");
    }
    printf("%8s:[%s]\n", msg.name, msg.txt);
}

6.2.4 父进程内部实现代码

先将协议设置为群聊,将终端输入的数据发送给服务器,当终端输入"quit"时,将协议设置为退出,并将退出群聊发送给服务器。
如果退出向终端输入"quit"时,退出循环。

while (1)
{   
    //memset会把name清除
    msg.code = 'C';
    fgets(msg.txt, 128, stdin);
    msg.txt[strlen(msg.txt) - 1] = '\0';
    if (strcmp(msg.txt, "quit") == 0)
    {
        msg.code = 'Q';
        strcpy(msg.txt, "退出群聊");
    }
    if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
    {
        PRINT_ERR("sendto error");
    }
    if (strcmp(msg.txt, "退出群聊") == 0)
    {
        break;
    }
}

6.2.5 退出程序

给子进程发送杀死信号,等待回收子进程,关闭套接字。

kill(pid,SIGKILL);
wait(NULL);
close(sockfd);

七、结果展示和总结

7.1 结果展示

用户之间聊天成功
在这里插入图片描述
系统发送公告成功
在这里插入图片描述
退出群聊成功
在这里插入图片描述
成功实现基于UDP的多人聊天室。

7.2 缺点与不足

没有对客户端断连采取更为健壮的处理,应该再客户端捕获一下客户端因为ctrl+c而结束的信号,在捕获之后发送数据包给服务器,让服务器对客户端退出行为,做出删除保存客户信息结构体的行为。还有就是没有添加数据库的使用。

7.3 总结

整体项目并不复杂,只需要想清楚service和client分别需要做什么事。画好流程图,就会很清晰的将思路缕顺。从而只需要模块化的完成相应代码即可。

server为了保存每次连入进来的client信息,使用了链表,对链表的操作就是简单的插入,删除操作。

这算是最近学习中实现的一个稍微综合一点的项目,虽然不多,却也有所收获,便发布一篇帖子去记录,代码中可能有许多不规范的地方,逻辑可能并不严谨,还请各位不吝赐教。

八、UDP实现多人聊天室服务器源代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
//宏定义打印错误信息
#define PRINT_ERR(msg)                                      \
    do                                                      \
    {                                                       \
        printf("%s,%d,%s\n", __FILE__, __LINE__, __func__); \
        perror(msg);                                        \
        exit(-1);                                           \
    } while (0)

typedef struct
{
    char code; //操作码 'L' 登录  'C' 群聊  'Q' 退出
    char name[32];
    char txt[128];
} msg_t;
//链表结构体
typedef struct _NODE
{
    struct sockaddr_in c_addr;
    struct _NODE *next;
} node_t;

void creat_link(node_t **head);
int do_register(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead);
int do_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead);
int quit_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead);

int main(int argc, const char *argv[])
{
    //入参合理性判断
    if (argc != 3)
    {
        printf("age:%s ip port\n", argv[0]);
        return -1;
    }
    //创建套接字
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        PRINT_ERR("socket error");
    }
    //创建服务器网络信息结构体
    struct sockaddr_in serviceaddr;
    memset(&serviceaddr, 0, sizeof(serviceaddr));
    serviceaddr.sin_family = AF_INET;
    serviceaddr.sin_addr.s_addr = inet_addr(argv[1]);
    serviceaddr.sin_port = htons(atoi(argv[2]));
    socklen_t serviceaddr_len = sizeof(serviceaddr);
    //将服务器网络信息结构体与套接字绑定
    if (bind(sockfd, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
    {
        PRINT_ERR("bind error");
    }
    //创建客户端网络信息结构体
    struct sockaddr_in clientaddr;
    memset(&clientaddr, 0, sizeof(clientaddr));
    socklen_t clientaddr_len = sizeof(clientaddr);
    msg_t msg;
    //创建父子进程
    pid_t pid;
    pid = fork();
    if (pid == -1)
    {
        PRINT_ERR("fork error");
    }
    else if (pid == 0)
    {
        //子进程
        //接受数据并处理

        //定义链表头节点
        node_t *phead = NULL;
        creat_link(&phead);
        phead->next = NULL;

        while (1)
        {
            memset(&msg, 0, sizeof(msg));
            memset(&clientaddr, 0, sizeof(clientaddr));
            if ((recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &clientaddr_len)) == -1)
            {
                PRINT_ERR("recvfrom error");
            }
            printf("%8s : [%s]\n", msg.name, msg.txt);
            switch (msg.code)
            {
            case 'L':
                do_register(sockfd, msg, clientaddr, phead);
                break;
            case 'C':
                do_group_chat(sockfd, msg, clientaddr, phead);
                break;
            case 'Q':
                quit_group_chat(sockfd, msg, clientaddr, phead);
                break;
            }
        }
    }
    else if (pid > 0)
    {
        //父进程
        //发系统消息
        msg.code='C';
        strcpy(msg.name,"server");
        while(1)
        {
            fgets(msg.txt,128,stdin);
            msg.txt[strlen(msg.txt)-1]='\0';
            if(sendto(sockfd,&msg,sizeof(msg_t),0,(struct sockaddr *)&serviceaddr,serviceaddr_len)==-1)
            {
                PRINT_ERR("sendto error");
            }
        }
    }
    close(sockfd);
    return 0;
}
//创建链表头节点函数
void creat_link(node_t **head)
{
    *head = (node_t *)malloc(sizeof(node_t));
}
//登录操作
int do_register(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
    //遍历链表将登录信息发送给所以人
    node_t *p = phead;
    while (p->next != NULL)
    {
        p = p->next;
        if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
        {
            PRINT_ERR("recvfrom error");
        }
    }
    //将登录的客户端信息插入保存在链表
    //头插
    //定义一个新的指针保存客户端信息
    node_t *newp = NULL;
    creat_link(&newp);
    newp->c_addr = clientaddr;
    newp->next = phead->next;
    phead->next = newp;
    return 0;
}

int do_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
    //遍历链表,将消息发给除自己之外的所有人
    node_t *p = phead;
    while (p->next != NULL)
    {
        p = p->next;
        //判断链表客户端信息是否是自己
        //是自己就不发送
        if (memcmp(&(p->c_addr), &clientaddr, sizeof(clientaddr)))
        {
            if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
            {
                PRINT_ERR("recvfrom error");
            }
        }
    }
    return 0;
}
//退出群聊操作
int quit_group_chat(int sockfd, msg_t msg, struct sockaddr_in clientaddr, node_t *phead)
{
    node_t *p = phead;

    while (p->next != NULL)
    {
        //判断链表客户端信息是否是自己
        //是自己就不发送并且将自己的客户端信息在链表内删除
        if (memcmp(&(p->next->c_addr), &clientaddr, sizeof(clientaddr)))
        {
            p = p->next;
            if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(p->c_addr), sizeof(p->c_addr)) == -1)
            {
                PRINT_ERR("recvfrom error");
            }
        }
        else
        {
            node_t *pnew;
            pnew = p->next;
            p->next = pnew->next;
            pnew->next = NULL;
            free(pnew);
            pnew = NULL;
        }
    }
    return 0;
}

九、UDP实现多人聊天室客户端源代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
//宏定义打印错误信息
#define PRINT_ERR(msg)                                      \
    do                                                      \
    {                                                       \
        printf("%s,%d,%s\n", __FILE__, __LINE__, __func__); \
        perror(msg);                                        \
        exit(-1);                                           \
    } while (0)

typedef struct
{
    char code; //操作码 'L' 登录  'C' 群聊  'Q' 退出
    char name[32];
    char txt[128];
} msg_t;

int main(int argc, const char *argv[])
{
    //入参合理性判断
    if (argc != 3)
    {
        printf("age:%s ip port\n", argv[0]);
        return -1;
    }
    //创建套接字
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
    {
        PRINT_ERR("socket error");
    }
    //创建服务器网络信息结构体
    struct sockaddr_in serviceaddr;
    memset(&serviceaddr, 0, sizeof(serviceaddr));
    serviceaddr.sin_family = AF_INET;
    serviceaddr.sin_addr.s_addr = inet_addr(argv[1]);
    serviceaddr.sin_port = htons(atoi(argv[2]));
    socklen_t serviceaddr_len = sizeof(serviceaddr);
    //给服务器发送登录数据包
    msg_t msg;
    memset(&msg, 0, sizeof(msg_t));
    msg.code = 'L';
    printf("请输入用户名:");
    fgets(msg.name, 32, stdin);
    msg.name[strlen(msg.name) - 1] = '\0';

    strcpy(msg.txt, "加入群聊");
    if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
    {
        PRINT_ERR("sendto error");
    }
    //创建父子进程
    pid_t pid;
    pid = fork();
    if (pid == -1)
    {
        PRINT_ERR("fork error");
    }
    else if (pid == 0)
    {
        //子进程
        //接受数据并处理
        while (1)
        {
            //每次循环前将msg置零
            memset(&msg, 0, sizeof(msg));
            //接受服务器发过来的信息并打印到终端上
            if (recvfrom(sockfd, &msg, sizeof(msg_t), 0, NULL, NULL) == -1)
            {
                PRINT_ERR("recvfrom error");
            }
            printf("%8s:[%s]\n", msg.name, msg.txt);
        }
    }
    else if (pid > 0)
    {
        //父进程
        //发送消息
        while (1)
        {   
            //memset会把name清除
            msg.code = 'C';
            fgets(msg.txt, 128, stdin);
            msg.txt[strlen(msg.txt) - 1] = '\0';
            if (strcmp(msg.txt, "quit") == 0)
            {
                msg.code = 'Q';
                strcpy(msg.txt, "退出群聊");
            }
            if (sendto(sockfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
            {
                PRINT_ERR("sendto error");
            }
            if (strcmp(msg.txt, "退出群聊") == 0)
            {
                break;
            }
        }
        kill(pid,SIGKILL);
        wait(NULL);
        close(sockfd);
    }
    return 0;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Linux网络编程】基于UDP实现多人聊天室 的相关文章

  • char类型与int类型的转换

    在c语言中 xff0c char类型与int类型可以转换 xff0c 如何转换我在此做一个粗略的总结 首先是char转换为int include lt stdio h gt int main char a a 61 110 int c 61
  • C语言中字符串大小与长度的区分

    字符串大小和字符串长度不同 xff1a 字符串大小指该字符串占用多少空间 字符串长度指该字符串的字符个数
  • 【字符串函数】strcat的使用及原理

    1 strcat函数的函数声明 char strcat char Destination const char Source 2 strcat函数的头文件 include lt string h gt 3 strcat函数的使用 strca
  • 【DRM】DRM Display Driver Guide

    https zhuanlan zhihu com p 534267979 目录 收起
  • TCP协议

    1 TCP协议的可靠传输 我们知道TCP协议的特点有 有连接 可靠传输 面向字节流 全双工 其中连接 面向字节流和全双工在 网络编程 该文章中详细说明了 而要深入了解TCP协议 了解可靠传输也是重中之重 可靠传输也是TCP协议的一大特点 那
  • c/c++程序运行不出结果?

    1 xff0c 条件语句未执行 在运行过程中 xff0c 用到if或while等条件语句 xff0c 条件之后的语句没有执行就结束了 xff0c 且输出变量也未初始化 xff0c 当然就输出不了任何东西了 xff1b 2 xff0c 变量未
  • 【解救ROS】关于ros机器人(小车)动态调试PID参数

    1 打开终端 xff0c 连接树莓派 ssh clbrobot 64 clbrobt 2 打开底盘节点 roslaunch clbrobot bringup launch 3 再打开一个终端 ssh clbrobot 64 clbrobt
  • 十进制转十六进制(C语言)

    首先要注意输入非负整数的范围 xff1a 这里用long表示 xff1b long 取值 2147483648 2147483647 include lt stdio h gt int main long n int i 61 0 j ch
  • C语言之数组的定义及其使用方法

    作者 xff1a 从未止步 博客主页 xff1a 从未止步的博客 专栏 xff1a 和我一起学C 语录 xff1a Every day is a second chance 行动是理想最高贵的表达 xff0c 给大家介绍一款超牛的斩获大厂o
  • python之常量的定义

    常量 xff1a 与C语言不同的是 xff0c python中并没有用来修饰常量的修饰符 xff0c 在python中可以通过自定义实现常量 xff0c 要求常量的标识符必须全是大写字母 xff0c 且该值不能被修改 举例 xff1a PI
  • C语言之根据摄氏温度求华氏温度

    求摄氏温度26 C对应的华氏温度 计算公式 xff1a f 61 9 c 5 43 32 xff0c 式中 xff1a c表示摄氏温度 xff0c f表示华氏温度 输入输出示例 xff1a celsius 61 26 fahr 61 78
  • python文件的操作和异常之异常

    异常 xff1a python使用称为异常的特殊类对象来管理程序执行期间发生的错误 xff0c 每当发生让python不知所措的错误时 xff0c 他都会创建一个异常的对象 如果你编写了处理该异常的代码 xff0c 程序将继续进行 xff0
  • Java---抽象类和接口

    抽象类 xff1a 抽象类的基本概念 xff1a 在面向对象的概念中 xff0c 所有的对象都是通过类来描述并创建的 xff0c 但是有一种特殊的类 xff0c 并不能用完整的信息来描述一个具体的对象 xff0c 这样的类就是抽象类 xff
  • 通过基于注解的声明式事务实现事务功能~

    编程式事务 xff1a 事务功能的相关操作全部通过自己编写代码来实现 xff1a span class token class name Connection span conn span class token operator 61 s
  • 《DRM 专栏》| 彻底入门 DRM 驱动

    https cloud tencent com developer article 2021477 前面的 DRM 应用程序系列文章中 xff0c 我们学习了如何使用 libdrm 接口编写 DRM 应用程序 本篇我们将进入一个全新的世界
  • 声明式事务的属性~

    说明 xff1a 本篇文章的代码是基于上篇文章之上的 xff0c 如有需要 xff0c 请移至这篇文章 声明式事务的属性 xff1a 只读 xff0c 超时 xff0c 回滚策略 xff1a 事务属性 xff1a 只读 对一个查询操作来说
  • 配置SpringMVC的前端控制器DispatcherServlet时,<url-pattern>中“/“和“/*“的区别

    span class token comment lt 表示所匹配的请求可以是login或 html或 js或 css方式的请求路径 xff0c 但是 不能匹配 jsp请求路径的请求 xff0c 因此就可以避免在访问jsp页面时 xff0c
  • 解决在idea中构建项目出现不能创建java类,只能创建文件的问题~

    注意 xff1a 一定要根据箭头的指向顺序进行 修改完成 xff0c 如下所示 xff1a
  • 解决在idea中构建项目时,出现的java:错误:不支持发行版本 5

    错误描述 xff0c 如下所示 xff1a 错误原因 xff1a idea中默认配置中有几个地方的jdk版本与实际不一致 解决办法 xff1a 第一步 xff1a 第二步 xff1a 第三步 xff1a 第四步 xff1a 第五步 xff1
  • 【C++】搭建HTTP服务器

    目录 项目介绍 网络协议栈介绍 协议分层 数据的封装与分用 HTTP相关知识介绍 HTTP的特点 URL格式 URI URL URN HTTP的协议格式 HTTP的请求方法 HTTP的状态码 HTTP常见的Header CGI机制介绍 CG

随机推荐

  • vscode 出现“未定义标识符cout”

    1 检查是否有 xff1a include lt iostream gt using namespace std 2 若有 xff0c 可以修改一下vscode的设置 xff1a 将C Cpp intelli Sense Engine的 D
  • FreeRtos.STM32f103c8t6调用vTaskDelay时间不准问题

    1 选择晶振要选择正确 2 内核选择的主频要和实际一致 define configCPU CLOCK HZ unsigned long 72000000 系统主频 define configTICK RATE HZ TickType t 1
  • FreeRtos外部中断使用

    首先是config文件里的宏 ARM Cortex M 架构自身允许最多 256 个不同的优先级 xff08 最多有 8 个 优先级位 xff0c 因此从 0 到 0xff 的优先级都是可能的 xff09 xff0c 但绝大多数使用 ARM
  • MPU-6050资源介绍

    介绍 作用 xff1a 测量三轴加速度和三轴角速度 测三轴加速度 xff1a 加速度计 测三轴角速度 xff1a 陀螺仪 加速度计具有静态稳定性 xff0c 动态不稳定性 陀螺仪具有静态不稳定性 xff0c 动态稳定性 需要互补滤波 xff
  • STM32硬件I2C

    以I2C2为例子 硬件I2C不需要我们手动去翻转电平 首先配置I2C xff1a 配置GPIO xff0c 配置复用功能I2C 配置复用开漏 xff0c 使能I2C I2C起始条件函数 64 param I2Cx where x can b
  • 最简单的 DRM 应用 & drm设备不工作

    https zhuanlan zhihu com p 341895843 https zhuanlan zhihu com p 75321245 编写最简单的 DRM 应用 主程序 xff1a int main int argc char
  • spi通信

    特点 常规四线通信方式 一根片选线 xff0c 一根时钟线 xff0c 两根数据线 xff1a 一根是主进从出线 xff0c 一根主出从入线 同步 xff0c 全双工 xff0c 通信方式 和谁通信就将谁的片选线拉低 xff0c 每增加一个
  • FreeRtos任务通信之消息队列

    入队阻塞 xff1a 往队列写数据时 xff0c 队列满时的阻塞 当阻塞结束时 xff0c 优先级最高的任务优先进入就绪态 xff0c 同 优先级时 xff0c 阻塞时间最长的进入就绪态 出队阻塞 xff1a 读队列时 xff0c 队列为空
  • STM32F103C8T6 ADC功能

    12位逐次逼近型ADC 1us转换时间 输入电压0 3 3v和转化范围0 2 12 1 xff08 0 4095 xff09 成线性关系 2个ADC资源 xff0c ADC1和ADC2 xff0c 10个外部通道 有两种转换单元组 xff1
  • stm32串口实验

    目录 xff08 一 xff09 STM32 串口简介 二 软件设计 xff08 三 xff09 效果 xff1a 1 实现功能 xff1a STM32 通过串口和上位机的对话 xff0c STM32 在收到上位机发过来的字符串后 xff0
  • 基于stm32的GPS解析数据

    目录 1 GPS模块 2 GPS发送的数据格式 3 软件设计 3 1配置好串口 3 2然后写串口中断函数 效果 1 GPS模块 2 GPS发送的数据格式 GPRMC lt 1 gt lt 2 gt lt 3 gt lt 4 gt lt 5
  • 大疆遥控控制M3508电机二(基于HAL库)

    接上一篇文章 xff0c 话不多说直接开始 一 打开我们创建的工程文件 xff0c 先就建立一个文件夹用来存放我们写的子文件 xff08 不建立也行 xff09 xff0c 然后建立pid h xff0c pid c存入我们建立的文件夹中
  • aruco_ros 在相机图像中显示的坐标轴姿态与TF发布的姿态不一致 解决方法

    aruco ros 在相机图像中显示的坐标轴姿态与TF发布的姿态不一致 解决方法 运行环境问题描述解决方案 运行环境 系统版本 xff1a Ubuntu 16 04 ROS版本 xff1a Kinetic 问题描述 在进行手眼标定过程中 x
  • 安装vscode的时候没有勾选快捷方式用vscode打开文件,如何用vscode快速打开文件或者文件夹

    1 需要在注册表里面添加内容可以实现 xff0c 下面有一种简单的方式来完成这个操作 2 需要在电脑桌面新建一个Open File With VS code reg 文件然后用打开文件 xff0c 将以下代码复制 Windows Regis
  • C语言学习笔记->const和define区别

    1 define 是预编译指令 xff0c 而const 是普通变量的定义 define定义的宏是在预处理阶段展开的 xff0c 而const定义的只读变量在编译运行中使用 2 const定义的是变量 xff0c 而define定义的是常量
  • 基于栈、寄存器的优缺点

    基于栈的优点 xff1a 对硬件的依赖不高 xff0c 跨平台性 缺点 xff1a 使用的指令集高基于寄存器的优点 xff1a 相对于栈来说 xff0c 寄存器的性能高 xff0c 使用的指令集少 Java代码执行流程 xff1a java
  • Makefile文件的编写(实例详解)

    1 什么是Makefile xff1f 一个工程中的源文件不计其数 xff0c 其按类型 功能 模块分别放在若干个目录中 xff0c Makefile定义了一系列的规则来指定哪些文件需要先编译 xff0c 哪些文件需要后编译 xff0c 哪
  • 用C语言实现万年历的代码及思路(详细教程)

    万年历程序要求 xff1a 1 当选择1的时候 xff0c 输入年 xff0c 打印输入的这一年12月的日历 2 当选择2的时候 xff0c 输入年 月 xff0c 打印输入这一年这一月的日历 实现效果 xff1a 选择1时 span cl
  • 有符号数和无符号数参与运算时的问题

    陷阱题目 xff1a 下面的代码输出是 xff08 xff09 span class token macro property span class token directive hash span span class token di
  • 【Linux网络编程】基于UDP实现多人聊天室

    文章目录 一 UDP的概念1 1 UDP1 2 UDP特点 二 采用UDP实现多人聊天室原因三 多人聊天室项目功能四 实现多人聊天室项目流程分析4 1 前期准备4 1 1 定义结构体4 1 2 定义链表 4 2 多人聊天室服务器4 2 1