2.1.4websocket协议与服务器实现

这篇具有很好参考价值的文章主要介绍了2.1.4websocket协议与服务器实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

本文基于websocket协议,在2.1.3http/https服务器的实现中TCP服务器代码的基础上实现websocket服务器。

websocket

websocket的主要应用场景

  • 服务器主动推送数据给浏览器

websocket握手流程(在tcp三次握手之后)

  • 浏览器发送请求
  • 服务器接收请求并获取Sec-WebSocket-Key的值并进行相应的计算得到Sec-WebSocket-Accept的值,然后返回固定格式的信息给浏览器
  • 浏览器接收信息并将Sec-WebSocket-Accept值与浏览器自己计算的值进行比较,若相同则握手成功

服务器接收到的浏览器的websocket请求为:

GET / HTTP/1.1
Host: 192.168.210.132:9999
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:105.0) Gecko/20100101 Firefox/105.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Sec-WebSocket-Version: 13
Origin: null
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: GFjXgXWdcYvUfA/wMx/kig==
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

服务器返回给浏览器的信息为:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: value

其中value需通过以下计算得到:

// 请求中的Sec-WebSocket-Key
key = GFjXgXWdcYvUfA/wMx/kig==
// websocket协议定义的GUID
GUID = 258EAFA5-E914-47DA-95CA-C5AB0DC85B11  
// 将上面两段字符合并
str =  GFjXgXWdcYvUfA/wMx/kig==258EAFA5-E914-47DA-95CA-C5AB0DC85B11 
// 进行SHA-1加密算法处理
sha = SHA-1(str)
// 进行base64编码
value = base64-encoded(sha)

握手信息为字符格式,而传输的数据为二进制格式,数据的格式如下图:

2.1.4websocket协议与服务器实现

基于websocket协议的服务器

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#include <openssl/sha.h>
#include <openssl/pem.h>
#include <openssl/bio.h>
#include <openssl/evp.h>

#define BUFFER_LENGTH       1024
#define MAX_EPOLL_EVENTS    1024

#define SERVER_PORT         9999
#define PORT_COUNT          1

typedef int NCALLBACK(int, int, void*);

// --- websocket
#define GUID                "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"

#define ACCEPT_KEY_LENGTH   64

enum  // 状态机
{
    WS_END = 0,  // wsstatus初始值为WS_END,故 WS_END = 0
    WS_HANDSHARK = 1,
    WS_TRANSMISSION = 2,
    WS_COUNT
};

// websocket数据头
typedef struct ws_ophdr
{
    // 小端
    unsigned char opcode:4,
                  rsv3:1,
                  rsv2:1,
                  rsv1:1,
                  fin:1;

    unsigned char pl_len:7,
                  mask:1;
}ws_ophdr;
// ---

typedef struct ntyevent
{
    int fd;
    int events;  // EPOLLIN, EPOLLOUT等
    void* arg;
    int (*callback)(int fd, int events, void* arg);

    int status;  // 0代表fd未被epoll监听,1代表fd已被epoll监听
    char rbuffer[BUFFER_LENGTH];
    char wbuffer[BUFFER_LENGTH];

    int rlength;
    int wlength;
    // long last_active;

    char sec_accept[ACCEPT_KEY_LENGTH];

    int wsstatus;  // 0, 1, 2, 3

}ntyevent;

typedef struct eventblock
{
    struct ntyevent* events;
    struct eventblock* next;
}eventblock;

// 反应堆,监听事件,管理fd相关内容(查找fd对应的ntyevent等)
typedef struct ntyreactor
{
    int epfd;
    int blkcnt;

    struct eventblock* evblks;
}ntyreactor;

int recv_cb(int fd, int events, void* arg);
int send_cb(int fd, int events, void* arg);

// --- ntyevent相关操作
// 设置ntyevent
void nty_event_set(ntyevent* ev, int fd, NCALLBACK callback, void* arg)
{
    ev->fd = fd;
    ev->callback = callback;
    ev->events = 0;
    ev->arg = arg;
    // ev->last_active = time(NULL);

    return;
}

// 将ntyevent对应fd及事件添加/修改到epoll监听列表
int nty_event_add(int epfd, int events, ntyevent* ev)
{
    struct epoll_event ep_ev = {0, {0}};
    ep_ev.data.ptr = ev;
    ep_ev.events = ev->events = events;

    int op;
    // 判断fd是否已经被epoll监听
    // 若是,则修改该fd被监听的事件,否则,将该fd添加到epoll监听列表
    if(ev->status == 1)
    {
        op = EPOLL_CTL_MOD;
    }
    else
    {
        op = EPOLL_CTL_ADD;
        ev->status = 1;
    }

    if(epoll_ctl(epfd, op, ev->fd, &ep_ev) < 0)
    {
        printf("event add failed [fd=%d], events[%d]\n",ev->fd, events);
        return -1;
    }

    return 0;
}

// 将ntyevent对应fd及事件从epoll监听列表中删除
int nty_event_del(int epfd, ntyevent* ev)
{
    struct epoll_event ep_ev = {0, {0}};

    // 若fd未被添加到epoll监听列表
    if(ev->status != 1)
    {
        return -1;
    }

    ep_ev.data.ptr = ev;
    ev->status = 0;
    epoll_ctl(epfd, EPOLL_CTL_DEL, ev->fd, &ep_ev);

    return 0;
}
// ---

// --- ntyreactor相关操作
// 初始化ntyreactor
int ntyreactor_init(ntyreactor* reactor)
{
    if(!reactor) return -1;
    memset(reactor, 0, sizeof(ntyreactor));

    reactor->epfd = epoll_create(1);
    if(reactor->epfd <= 0)
    {
        printf("create epfd in %s err %s\n", __func__, strerror(errno));
        return -2;
    }

    ntyevent* evs = (ntyevent*)malloc(MAX_EPOLL_EVENTS*sizeof(ntyevent));
    if(!evs)
    {
        printf("create epfd in %s err %s\n", __func__, strerror(errno));
        close(reactor->epfd);
        return -3;
    }
    memset(evs, 0, (MAX_EPOLL_EVENTS)*sizeof(ntyevent));

    eventblock* block = (eventblock*)malloc(sizeof(eventblock));
    if(!block)
    {
        printf("create epfd in %s err %s\n", __func__, strerror(errno));
        free(evs);
        close(reactor->epfd);
        return -3;
    }

    block->events = evs;
    block->next = NULL;

    reactor->evblks = block;
    reactor->blkcnt = 1;

    return 0;
}

// 析构ntyreactor
int ntyreactor_destory(ntyreactor* reactor)
{
    close(reactor->epfd);

    eventblock* blk = reactor->evblks;
    eventblock* blk_next;
    while(blk)
    {
        blk_next = blk->next;

        free(blk->events);
        free(blk);

        blk = blk_next;
    }

    return 0;
}

// 为ntyreactor的链表(evblks)创建一个新结点,并分配内存
int ntyreactor_alloc(ntyreactor* reactor)
{
    if(!reactor) return -1;
    if(!reactor->evblks) return -1;

    eventblock* blk = reactor->evblks;
    while(blk->next)
        blk = blk->next;

    ntyevent* evs = (ntyevent*)malloc((MAX_EPOLL_EVENTS)*sizeof(ntyevent));
    if(!evs)
    {
        printf("%s ntyevent failed\n", __func__);
        return -2;
    }
    memset(evs, 0, (MAX_EPOLL_EVENTS)*sizeof(ntyevent));

    eventblock* block = (eventblock*)malloc(sizeof(eventblock));
    if(!block)
    {
        printf("%s eventblock failed\n", __func__);
        free(evs);
        return -3;
    }

    block->events = evs;
    block->next = NULL;

    blk->next = block;
    ++(reactor->blkcnt);

    return 0;
}

// 在ntyreactor中查找sockfd对应的ntyevent
ntyevent* ntyreactor_idx(ntyreactor* reactor, int sockfd)
{
    if(!reactor) return NULL;
    if(!reactor->evblks) return NULL;

    int blkidx = sockfd / MAX_EPOLL_EVENTS;
    while(blkidx >= reactor->blkcnt)
    {
        ntyreactor_alloc(reactor);
    }

    int i = 0;
    eventblock* blk = reactor->evblks;
    while(i++ != blkidx && blk)
    {
        blk = blk->next;
    }

    return &blk->events[sockfd % MAX_EPOLL_EVENTS];
}

// 向ntyreactor添加listenfd
int ntyreactor_addlistener(ntyreactor* reactor, int sockfd, NCALLBACK* acceptor)
{
    if(!reactor) return -1;
    if(!reactor->evblks) return -1;

    ntyevent* event = ntyreactor_idx(reactor, sockfd);
    if(!event) return -1;

    nty_event_set(event, sockfd, acceptor, reactor);
    nty_event_add(reactor->epfd, EPOLLIN, event);

    return 0;
}

// 运行ntyreactor,即循环执行epoll_wait,对可读可写IO执行相应回调函数
int ntyreactor_run(ntyreactor* reactor)
{
    if(!reactor) return -1;
    if(reactor->epfd < 0) return -1;
    if(!reactor->evblks) return -1;

    struct epoll_event events[MAX_EPOLL_EVENTS+1];

    int checkpos = 0, i;
    
    while(1)
    {
        int nready = epoll_wait(reactor->epfd, events, MAX_EPOLL_EVENTS, 1000);
        if(nready < 0)
        {
            printf("epoll_wait error, exit\n");
            continue;
        }

        for(i = 0;i < nready; i++)
        {
            ntyevent* ev = (ntyevent*)events[i].data.ptr;

            if((events[i].events & EPOLLIN) && (ev->events & EPOLLIN))    // 若IO可输入
            {
                ev->callback(ev->fd, events[i].events, ev->arg);
            }
            if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))  // 若IO可输出
            {
                ev->callback(ev->fd, events[i].events, ev->arg);
            }
        }
    }
}
// ---

// --- websocket
// 读取一行
int readline(char* allbuf, int idx, char* linebuf)
{
    int len = strlen(allbuf);
    for(;idx < len;idx++)
    {
        if(allbuf[idx] == '\r' && allbuf[idx+1] == '\n')
            return idx + 2;
        else
            *(linebuf++) = allbuf[idx]; 
    }

    return -1;
}

// base64编码
int base64_encode(char *in_str, int in_len, char *out_str)
{    
	BIO *b64, *bio;    
	BUF_MEM *bptr = NULL;    
	size_t size = 0;    

	if (in_str == NULL || out_str == NULL)        
		return -1;    

	b64 = BIO_new(BIO_f_base64());    
	bio = BIO_new(BIO_s_mem());    
	bio = BIO_push(b64, bio);
	
	BIO_write(bio, in_str, in_len);    
	BIO_flush(bio);    

	BIO_get_mem_ptr(bio, &bptr);    
	memcpy(out_str, bptr->data, bptr->length);    
	out_str[bptr->length-1] = '\0';    
	size = bptr->length;    

	BIO_free_all(bio);    
	return size;
}

// websocket握手
int ws_handshark(ntyevent* ev)
{
    int idx = 0;
    char sec_data[128] = {0};
    char sec_accept[128] = {0};

    do
    {
        char linebuf[BUFFER_LENGTH] = {0};
        idx = readline(ev->rbuffer, idx, linebuf);

        if(strstr(linebuf, "Sec-WebSocket-Key"))  // 获取Sec-WebSocket-Key
        {
            strcat(linebuf, GUID);

            SHA1(linebuf+strlen("Sec-WebSocket-Key: "), strlen(linebuf+strlen("Sec-WebSocket-Key: ")), sec_data);

            base64_encode(sec_data, strlen(sec_data), sec_accept);

            printf("idx: %d, line: %s\n", idx, sec_accept);

            memcpy(ev->sec_accept, sec_accept, ACCEPT_KEY_LENGTH);

            return 1;
        }
    }
    while((ev->rbuffer[idx] != '\r' || ev->rbuffer[idx+1] != '\n') && idx != -1);

    return 0;
}

// 将密文转为明文
void unmask(char* payload, int length, char* mask_key)
{
    int i = 0;

    for(i = 0;i < length;i++)
    {
        payload[i] ^= mask_key[i % 4];
    }
}

// 
int ws_transmission(ntyevent* ev)
{
    ws_ophdr* hdr = (ws_ophdr*)ev->rbuffer;

    printf("%s\n", __func__);

    if(hdr->pl_len < 126)
    {
        unsigned char* payload = NULL;
        if(hdr->mask)  // 有Masking-key(4 bytes),为密文
        {
            payload = ev->rbuffer + 6;
            unmask(payload, hdr->pl_len, ev->rbuffer + 2);
        }
        else  // 无Masking-key(0 bytes),为明文
        {
            payload = ev->rbuffer + 2;
        }

        printf("payload: %s\n", payload);

        return 1;
    }
    else if(hdr->pl_len == 126)
    {

    }
    else if(hdr->pl_len == 127)
    {

    }
    else
    {
        return 0;
        //assert(0);
    }
}

// 解析websocket请求
int ws_request(ntyevent* ev)
{
    if(ev->wsstatus == WS_HANDSHARK)
    {
        if(ws_handshark(ev))
            ev->wsstatus = WS_TRANSMISSION;
    }
    else if(ev->wsstatus == WS_TRANSMISSION)
    {
        if(ws_transmission(ev))
            ev->wsstatus = WS_END;
    }
}

// 响应websocket请求
int ws_response(ntyevent* ev)
{
    ev->wlength = sprintf(ev->wbuffer, "HTTP/1.1 101 Switching Protocols\r\n"
                                       "Upgrade: websocket\r\n"
                                       "Connection: Upgrade\r\n"
                                       "Sec-WebSocket-Accept: %s\r\n\r\n",
                                       ev->sec_accept);

    return ev->wlength;
}
// ---

// --- 回调函数
// recv回调函数
int recv_cb(int fd, int events, void* arg)
{
    ntyreactor* reactor = (ntyreactor*)arg;
    ntyevent* ev = ntyreactor_idx(reactor, fd);

    if(!ev) return -1;

    int len = recv(fd, ev->rbuffer, BUFFER_LENGTH, 0);
    nty_event_del(reactor->epfd, ev);

    if(len > 0)  // 已接收数据
    {
        ev->rlength = len;
        ev->rbuffer[len] = '\0';  // 去除脏数据

        //printf("recv [%d]:%s\n", fd, ev->rbuffer);

        // --- websocket
        printf("wsstatus: %d\n", ev->wsstatus);
        if(ev->wsstatus == WS_END)
        {
            ev->wsstatus = WS_HANDSHARK;
        }
        printf("request\n");
        ws_request(ev);  // 解析websocket请求
        // ---

        // 接收数据后,将fd设置为待发送模式
        nty_event_set(ev, fd, send_cb, reactor);
        nty_event_add(reactor->epfd, EPOLLOUT, ev);
    }
    else if(len == 0)  // 客户端断开连接
    {
        nty_event_del(reactor->epfd, ev);
        printf("recv_cb --> disconnect\n");
        close(ev->fd);
    }
    else  // 发生异常
    {
        if(errno == EAGAIN && errno == EWOULDBLOCK)
        {

        }
        else if(errno == ECONNRESET)
        {
            nty_event_del(reactor->epfd, ev);
            close(ev->fd);
        }
        printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
        // strerror() 获取指向错误消息字符串的指针
    }

    return len;
}

// send回调函数
int send_cb(int fd, int events, void* arg)
{
    ntyreactor* reactor = (ntyreactor*)arg;
    ntyevent* ev = ntyreactor_idx(reactor, fd);

    if(!ev) return -1;

    // --- websocket
    ws_response(ev);  // 响应websocket请求
    // ---

    int len = send(fd, ev->wbuffer, ev->wlength, 0);
    if(len > 0)  // 已发送数据
    {
        //printf("send[fd=%d], [%d]%s\n",fd, len, ev->wbuffer);
        
        // 发送数据后,将fd设置为待接收模式
        nty_event_del(reactor->epfd, ev);
        nty_event_set(ev, fd, recv_cb, reactor);
        nty_event_add(reactor->epfd, EPOLLIN, ev);
    }
    else  // 发送失败
    {
        nty_event_del(reactor->epfd, ev);
        close(ev->fd);

        printf("send[fd=%d] error %s\n", fd, strerror(errno));
    }

    return len;
}

// accept回调函数
int accept_cb(int fd, int events, void* arg)
{
    ntyreactor* reactor = (ntyreactor*)arg;
    if(!reactor) return -1;

    struct sockaddr_in client_addr;
    socklen_t len = sizeof(client_addr);

    int clientfd = 0;

    if((clientfd = accept(fd, (struct sockaddr*)&client_addr, &len)) == -1)
    {
        if(errno != EAGAIN && errno != EINTR)
        {

        }
        printf("accept[fd=%d] error %s\n", fd, strerror(errno));
        return -1;
    }

    // 将clientfd设置为非阻塞
    int flag = 0;
    if((flag = fcntl(clientfd, F_SETFL, O_NONBLOCK)) < 0)
    {
        printf("%s: fcntl nonblocking failed, %d\n", __func__, MAX_EPOLL_EVENTS);
        return -1;
    }

    ntyevent* event = ntyreactor_idx(reactor, clientfd);

    if(!event) return -1;

    // 初始化客户端fd为接收模式
	nty_event_set(event, clientfd, recv_cb, reactor);
	nty_event_add(reactor->epfd, EPOLLIN, event);

    printf("new connect [%s:%d], pos[%d]\n",
        inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), clientfd);

    return 0;
}
// ---

// 创建服务器端口(listenfd)
int init_sock(short port)
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(fd, F_SETFL, O_NONBLOCK);

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(port);

    bind(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));

    if(listen(fd, 20) < 0)
    {
        printf("listen failed : %s\n", strerror(errno));
        return -1;
    }

    printf("listen server port : %d\n", port);
    return fd;
}

int main(int argc, char* argv[])
{
    ntyreactor* reactor = (ntyreactor*)malloc(sizeof(ntyreactor));
    ntyreactor_init(reactor);

    unsigned short port = SERVER_PORT;
    if(argc == 2)
    {
        port = atoi(argv[1]);
    }

    int i = 0;
    int sockfds[PORT_COUNT] = {0};

    for(i = 0; i < PORT_COUNT; i++)
    {
        sockfds[i] = init_sock(port + i);
        ntyreactor_addlistener(reactor, sockfds[i], accept_cb);
    }

    ntyreactor_run(reactor);

    ntyreactor_destory(reactor);

    for(i = 0; i < PORT_COUNT; i++)
    {
        close(sockfds[i]);
    }
    free(reactor);

    return 0;
}

使用SHA-1加密算法及实现base64编码需安装openssl库

sudo apt-get install libssl-dev

编译时需链接动态库ssl及crypto

gcc -o websocket websocket.c -lssl -lcrypto

参考博客:

C语言 | 位域的使用详解

弄懂大端小端含义文章来源地址https://www.toymoban.com/news/detail-431936.html

到了这里,关于2.1.4websocket协议与服务器实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【colab】谷歌colab免费服务器训练自己的模型,本文以yolov5为例介绍流程

    目录 一.前言 二.准备工作 1.注册Google drive(谷歌云盘) Google Driver官网:https://drive.google.com/drive/ Colab官网:https://colab.research.google.com/ 2.上传项目文件 3.安装Colaboratory 4.colab相关操作和命令 5.项目相关操作  三.异常处理         本文介绍了在谷歌开放平台Google colab上租用免

    2023年04月08日
    浏览(51)
  • WebSocket心跳重连在微信小程序中的实现与服务器端

    WebSocket技术是一种在浏览器和服务器之间建立持久化连接的通信协议。在微信小程序中,通过WebSocket可以实现实时的双向通信。然而,由于网络等各种因素的不稳定性,WebSocket连接可能会出现断开的情况。为了保证连接的可靠性,我们可以通过心跳机制和重连机制来处理Web

    2024年03月18日
    浏览(50)
  • 【linux】挖矿病毒nanominer伪装成python占用服务器GPU!本文带你分析并杀毒!

    可以看到root用户将GPU的核心跑满了每个占用都是100%,显存吃了6G多。 不能正常显示GPU被哪些进程占用 在/tmp/.x/目录中 总结: amdmemtweak: 优化显存时序,提高挖矿效能 config.ini: 挖矿配置文件 doos.pid: 挖矿进程的pid号 logs: 挖矿病毒的输出log nanominer: 3.7.7-linux版本的挖矿病毒,这

    2024年02月14日
    浏览(41)
  • Web服务器实现|基于阻塞队列线程池的Http服务器|线程控制|Http协议

    代码地址:WebServer_GitHub_Addr 摘要 本实验通过C++语言,实现了一个基于阻塞队列线程池的多线程Web服务器。该服务器支持通过http协议发送报文,跨主机抓取服务器上特定资源。与此同时,该Web服务器后台通过C++语言,通过原生系统线程调用 pthread.h ,实现了一个 基于阻塞队列

    2024年02月07日
    浏览(62)
  • 初识http协议,简单实现浏览器和服务器通信

    平时俗称的 “网址” 其实就是说的 URL,例如在百度上搜索一个C++ 可以看到这段网址前面有个 https 那么这个就代表着使用的是https协议,现在都是使用https协议,不过还是需要认识以下http协议 像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现。

    2024年02月14日
    浏览(46)
  • 从零开始写一个RTSP服务器(二)RTSP协议的实现

    此系列只追求精简,旨在学习RTSP协议的实现过程,不追求复杂完美,所以这里要实现的RTSP服务器为了简单,实现上同一时间只能有一个客户端,下面开始介绍实现过程 在写一个RTSP服务器之前,我们必须知道一个RTSP服务器最简单的包含两部分,一部分是RTSP的交互,一部分是

    2024年04月17日
    浏览(52)
  • 【计算机网络】HTTP协议以及简单的HTTP服务器实现

    虽然我们说, 应用层协议是我们程序猿自己定的. 但实际上, 已经有大佬们定义了一些现成的, 又非常好用的应用层协议, 供我们直接参考使用. HTTP(超文本传输议)就是其中之一。 平时我们俗称的 “网址” 其实就是说的 URL 像 / ? : 等这样的字符, 已经被url当做特殊意义理解了.

    2024年01月20日
    浏览(61)
  • nodejs 实现MQTT协议的服务器端和客户端的双向交互

    公司和第三方合作开发一个传感器项目,想要通过电脑或者手机去控制项目现场的传感器控制情况。现在的最大问题在于,现场的边缘终端设备接入的公网方式是无线接入,无法获取固定IP,所以常规的HTTP协议通信就没法做,现在打算使用MQTT来实现云平台和边缘终端(传感器

    2024年02月05日
    浏览(66)
  • esp8266模块--MQTT协议连接服务器实现数据接收和发送+源码

    首先推荐中国移动的代码,我觉得中国移动的代码更为合理:(但是有一些其他的模块在里面) OneNET开发板代码、资料--2020-09-27--标准板、Mini板bug修复 - 开发板专区 - OneNET设备云论坛 (10086.cn) 以及这位b站up做的视频:(wifi模块在p9节) 【挽救小白第一季】STM32+8266+小程序智能

    2024年02月08日
    浏览(59)
  • 【网络编程】——基于TCP协议实现回显服务器及客户端

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程的一点学习心得,欢迎大家在评论区交流讨论💌 TCP提供的API主要有两个类 Socket ( 既会给服务器使用也会给客

    2024年02月03日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包