C/C++服务器基础(网络、协议、数据库)
Socket
Socket是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。它可以看成是两个网络应用程序进行通信时,各自通信连接中的端点。Socket上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口
App通过Socket发送和接收数据,主要提供了TCP Socket和UDP Socket来收发数据,基于Socket对象操作系统提供了一系列接口来收发数据。
下面提供客户端和服务器代码及其讲解:
客户端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#ifdef WIN32 // WIN32 宏, Linux宏不存在
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endifint main(int argc, char** argv) {int ret;// 配置一下windows socket 版本// 一定要加上这个,否者低版本的socket会出很多莫名的问题;
#ifdef WIN32WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(2, 2);ret = WSAStartup(wVersionRequested, &wsaData);if (ret != 0) {printf("WSAStart up failed\n");system("pause");return -1;}
#endifint s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (s == INVALID_SOCKET) {goto failed;}// 配置一下要连接服务器的socket// 127.0.0.1 本机IP地址;struct sockaddr_in sockaddr;sockaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");sockaddr.sin_family = AF_INET;sockaddr.sin_port = htons(6080); // 连接信息要发送给监听socket;// 发送连接请求到我们服务端的监听socket;ret = connect(s, &sockaddr, sizeof(sockaddr));if (ret != 0) {goto failed;}// 连接成功, s与服务器对应的socket就会建立连接;// 客户端在连接的时候他也需要一个IP地址+端口;// 端口是服务器端口。不是,客户端一个没有使用的端口就可以了;// 客户端自己也会分配一个IP + 端口(只要是没有使用的就可以了);// char buf[11];memset(buf, 0, 11);send(s, "Hello", 5, 0);recv(s, buf, 5, 0);printf("%s\n", buf);failed:if (s != INVALID_SOCKET) {closesocket(s);s = INVALID_SOCKET;}#ifdef WIN32WSACleanup();
#endifsystem("pause");return 0;
}
服务器
#include <stdio.h>
#include <string.h>
#include <stdlib.h>// 配置windows socket环境
#ifdef WIN32 // WIN32 宏, Linux宏不存在
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib, "WSOCK32.LIB")
#endif
// end int main(int argc, char** argv) {int ret;// 配置一下windows socket 版本// 一定要加上这个,否者低版本的socket会出很多莫名的问题;
#ifdef WIN32WORD wVersionRequested;WSADATA wsaData;wVersionRequested = MAKEWORD(2, 2);ret = WSAStartup(wVersionRequested, &wsaData);if (ret != 0) {printf("WSAStart up failed\n");system("pause");return -1;}
#endif// step1 创建一个监听的socket;int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // TCPif (s == INVALID_SOCKET) { // 创建goto failed;}// end // ip地址 + 端口,监听到哪个IP地址和端口上;struct sockaddr_in sockaddr;sockaddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");sockaddr.sin_family = AF_INET;sockaddr.sin_port = htons(6080); // 127.0.0.1: 6080端口上;ret = bind(s, (const struct sockaddr*)&sockaddr, sizeof(sockaddr));if (ret != 0) {goto failed;}// 开启监听ret = listen(s, 1); while (1) {// 等待客户介入进来;struct sockaddr_in c_address; // 客户端的IP地址;int address_len = sizeof(c_address);// cs 是我们服务端为客户端创建的配对的socket;// c_address 就是我们客户端的IP地址和端口;printf("waiting....!!!!\n");int cs = accept(s, (struct sockaddr*)&c_address, &address_len); // 在这里像卡住了一样的;OSprintf("new client %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));// 收数据;char buf[11];memset(buf, 0, 11);recv(cs, buf, 5, 0);printf("recv: %s\n", buf);// end // 发数据给客户端send(cs, buf, 5, 0);// end closesocket(cs);}failed:if (s != INVALID_SOCKET) {closesocket(s);}
// 结束的时候也要清理
#ifdef WIN32WSACleanup();
#endif
// end system("pause");return 0;
}
说明
这两个部分的代码实现了客户端和服务器的最简单通信,先运行服务器程序监听端口,再打开客户端程序,客户端程序会与服务器建立TCP连接,并发送“Hello”字符串,服务器收到后会回复一个“Hello”给客户端
Select管理模型
用于监听所有Socket,在服务器监听连接端口的同时,能够接收所有客户端传过来的数据。
步骤:
- 准备一个句柄集合
- 将Socket句柄加入到这个集合
- 调用Select函数等待在这个集合上
- 当其中一个句柄有时间发生的时候,OS唤醒任务从Select返回
- 处理事件,继续Select
根据上一小节服务器代码修改:
static int client_fd[4096];static int socket_count = 0;#define MAX_BUF_LEN 4096static unsigned char recv_buf[MAX_BUF_LEN];ret = listen(s, 1); fd_set set;while (1) {FD_ZERO(&set);FD_SET(s,&set); // 监听句柄加入到等待集合// 客户端介入进来的socket,加入到句柄集合for(int j = 0; j < socket_count; j++){if(clint_fd[j] != INVALID_SOCKET){FD_SET(client_fd[j],&set);}}ret = select(0,&set, NULL,NULL,NULL); // 这里监听事件的发生if(ret < 0){printf("select error\n");continue;}else if(ret == 0){printf("select timeout\n");continue;}if(FD_ISSET(s, &set)){ // 发送过来连接请求struct sockaddr_in c_address; // 客户端的IP地址;int address_len = sizeof(c_address);printf("waiting....!!!!\n");int cs = accept(s, (struct sockaddr*)&c_address, &address_len);printf("new client %s:%d\n", inet_ntoa(c_address.sin_addr), ntohs(c_address.sin_port));client_fd[socket_count] = cs;socket_count++;continue;}for(int j = 0; j < socket_count; j++){if(client_fd[j] != INVALID_SOCKET && FD_ISSET(client_fd[j], &set)){int len = recv(client_fd[j], recv_buf, MAX_BUF_LEN, 0);if(len <= 0){closesocket(client_fd[j]);client_fd[j] = INVALID_SOCKET;}else{recv_buf[len] = 0;printf("recv: %s\n", recv_buf);send(client_fd[j], recv_buf, len, 0);}}}}
缺点:
- 每次有事件都需要遍历所有句柄,性能不好
- 能够管理句柄的数目有限
- 读写仍然是同步的
IOCP管理模型
1: IOCP: 是windows针对高性能服务器做的IO的管理模式,又叫完成端口;Linux平台有类似的机制叫epoll
2: IOCP的核心模式:
1>提交请求;
2>等待结果;
3>继续提交请求;
3: 监听:
1>提交一个监听请求,使用完成端口来等待这个请求到来;
2>请求来了后,处理,继续提交请求;
4: 读取数据:
1>提交一个读取数据的请求。
2>请求完成后,处理完后继续提交;
5: 发送数据的请求:
1>提交一个发送数据的请求;
2>请求完成后,继续处理;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/*
IOCP 只支持windows平台 Linux epoll
*/#include <WinSock2.h>
#include <mswsock.h>
#include <windows.h>#pragma comment(lib, "WSOCK32.lib ")
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "odbc32.lib")
#pragma comment(lib, "odbccp32.lib")// 非常重要的数据结构;
enum {IOCP_ACCPET = 0, // 监听socket,接入请求;IOCP_RECV, // 读请求;IOCP_WRITE, // 写请求;
};// 接收数据的时候最大的buf大小;
#define MAX_RECV_SIZE 8192struct io_package {// 自己定义的, 一定要在第一个就要用WSAOVERLAPPED 结构体;?// 所有的请求的等待,都是等在这个结构对象上的,必须是第一个;WSAOVERLAPPED overlapped;// 操作类型, 监听,读,写, IOCP_ACCPET, IOCP_RECV, IOCP_WRITEint opt;// 句柄,就是我们提交请求的句柄, accept的句柄或你读写请求的句柄int accpet_sock;// 结构体,配合读写数据的时候用的bufffer;WSABUF wsabuffer; // wsabuffer.buf = 内存;, wsabuffer.len = MAX_RECV_SIZE;// 定义了一个buf,这个buf就是整正的内存;char pkg[MAX_RECV_SIZE];
};// 投递一个用户的请求;
static void
post_accept(SOCKET l_sock) {// step1: 分配一个io_package 数据结构;struct io_package* pkg = malloc(sizeof(struct io_package));memset(pkg, 0, sizeof(struct io_package));// 初始化好了接受数据的buf --> WSABUFpkg->wsabuffer.buf = pkg->pkg;pkg->wsabuffer.len = MAX_RECV_SIZE - 1;pkg->opt = IOCP_ACCPET; // 请求类型;DWORD dwBytes = 0;SOCKET client = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);int addr_size = (sizeof(struct sockaddr_in) + 16);pkg->accpet_sock = client; // 创建一个socket,然后客户端连接进来后,就用这个socket和我们的客户端通讯;// 发送一个异步的请求,来接入客户端的连接;AcceptEx(l_sock, client, pkg->wsabuffer.buf, 0/*pkg->wsabuffer.len - addr_size* 2*/,addr_size, addr_size, &dwBytes, &pkg->overlapped);
}static void
post_recv(SOCKET client_fd) {struct io_package* io_data = malloc(sizeof(struct io_package));memset(io_data, 0, sizeof(struct io_package));io_data->opt = IOCP_RECV;io_data->wsabuffer.buf = io_data->pkg;io_data->wsabuffer.len = MAX_RECV_SIZE - 1;io_data->accpet_sock = client_fd;DWORD dwRecv = 0;DWORD dwFlags = 0;int ret = WSARecv(client_fd, &(io_data->wsabuffer),1, &dwRecv, &dwFlags,&(io_data->overlapped), NULL);
}int main(int argc, char** argv) {// 如果你做socket那么必须要加上;WSADATA data;WSAStartup(MAKEWORD(2, 2), &data);// step1:创建一个完成端口;HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);if (iocp == INVALID_HANDLE_VALUE) {goto failed;}// end // 创建我们的监听socket,开始监听SOCKET l_sock = INVALID_SOCKET;l_sock = socket(AF_INET, SOCK_STREAM, 0);if (l_sock == INVALID_SOCKET) {goto failed;}struct sockaddr_in s_address;memset(&s_address, 0, sizeof(s_address));s_address.sin_family = AF_INET;s_address.sin_addr.s_addr = inet_addr("127.0.0.1");s_address.sin_port = htons(6080);if (bind(l_sock, (struct sockaddr *) &s_address, sizeof(s_address)) != 0) {goto failed;}if (listen(l_sock, SOMAXCONN) != 0) {goto failed;}// end// 让IOCP来管理我们的l_sock// 第三个参数,是用户传的自定义数据(一个指针带入进去),后面讲解;CreateIoCompletionPort((HANDLE)l_sock, iocp, (DWORD)0, 0);// 发送一个监听用户进来的请求;post_accept(l_sock); // 投递一个accpet接入请求;while (1) {DWORD dwTrans;DWORD udata;struct io_package* io_data;// 通过完成端口,来获得这个请求的结果;// 调用操作系统的API函数,查看,那个请求完成了;// 如果没有状态完成了,那么任务会挂起,等待;int ret = GetQueuedCompletionStatus(iocp, &dwTrans, &udata, (LPOVERLAPPED*)&io_data, WSA_INFINITE);if (ret == 0) { // 意外;if (io_data) {if (io_data->opt == IOCP_RECV) {closesocket(io_data->accpet_sock);free(io_data);}else if (io_data->opt == IOCP_ACCPET) {free(io_data);post_accept(l_sock);}}continue;}if (dwTrans == 0 && io_data->opt == IOCP_RECV) { // // 关闭socket发生了;closesocket(io_data->accpet_sock);free(io_data);continue;// end}switch (io_data->opt) {case IOCP_ACCPET: // 接入一个socket;{// 接入的client_socketint client_fd = io_data->accpet_sock;int addr_size = (sizeof(struct sockaddr_in) + 16);struct sockaddr_in* l_addr = NULL;int l_len = sizeof(struct sockaddr_in);struct sockaddr_in* r_addr = NULL;int r_len = sizeof(struct sockaddr_in);GetAcceptExSockaddrs(io_data->wsabuffer.buf,0, /*io_data->wsabuffer.len - addr_size * 2, */addr_size, addr_size,(struct sockaddr**)&l_addr, &l_len,(struct sockaddr**)&r_addr, &r_len);// 将新进来的client_fd,也加入完成端口,来帮助管理完成的请求;// 第三个参数是用户自定义数据,你可以携带自己的数据结构,client_fd;CreateIoCompletionPort((HANDLE)client_fd, iocp, (DWORD)client_fd, 0);// 投递一个读的请求;post_recv(client_fd);// 重新投递一个接入客户端请求;free(io_data);post_accept(l_sock);}break;case IOCP_RECV:{// dwTrans 读到数据的大小;// socket, io_data->accpet_sock;// Buf io_data->wsabuffer.buf, io_data->pkg[dwTrans] = 0;printf("IOCP recv %d %s\n", dwTrans, io_data->pkg);send(io_data->accpet_sock, io_data->pkg, dwTrans, 0); // test// 再来投递下一个请求;DWORD dwRecv = 0;DWORD dwFlags = 0;int ret = WSARecv(io_data->accpet_sock, &(io_data->wsabuffer),1, &dwRecv, &dwFlags,&(io_data->overlapped), NULL);// end }break;}}
failed:if (l_sock) {closesocket(l_sock);}if (iocp != INVALID_HANDLE_VALUE) {CloseHandle(iocp); // 释放关闭完成端口;}WSACleanup();return 0;
}
windows多线程
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <Windows.h>/*
进程: 任务, 代码段, 数据段, 堆, 栈;
主线程:
线程: 创建出来的OS 可以独立调度的单元;
主线程, 线程1, 线程2, ....线程3;
1: 主线程+其他的线程都共用进程的 数据段, 堆, 代码段;
2: 每一个线程都有自己独立的栈,函数调用相互不受影响;
3: 线程是OS可以调度的独立的最小的单元,在执行的过程中,
线程随时有可能切换出去,到其他的进程或现程来运行;
*//* CreateThread 创建一个线程;
1>线程句柄;--> HANDLE
2>线程ID;
3>线程入口函数;
*//*
Sleep是windows API函数,能够让我们的线程休眠多少毫秒
*/
// 开始运行我们的线程;
// 独立运行入口
// 公用了进程的代码段,数据段, 堆;
static int g_value = 10;
char* ptr = NULL;
void test_func() {
}HANDLE wait_cond = INVALID_HANDLE_VALUE;
CRITICAL_SECTION lock;
/*
事件通知/等待:
线程A,等待线程B完成达到某个条件,才能够继续;
多媒体解码线程,等待输入线程输入数据,有数据了,在通知解码线程解码;1> 创建一个事件,要求线程都可以访问;
2> 等待的线程,调用函数来等待时间;
3> 触发的线程,当条件满足后触发;
*//* 线程安全机制;
数据段, 堆, 代码段是公用的,所以会有一个问题;
2个线程或多个线程,同时在访问同一个资源的时候,由于线程之间随时会切换
出去,所以就会导致他们访问同样的资源可能会有冲突;
3:如果线程和线程之间,访问资源出现了冲突,那么我们这个时候要加上一个锁的机制;1> 需要请求一个资源的时候,请求这个锁,一旦这个锁被占用了,我们就等待;
2> 如果请求成功了以后,我们就处理,处理完了以后我们释放这个锁,
等待这个锁的线程就会被唤醒;
3> 锁就能保证我们在处理共享资源的时候,同时只有一个线程在处理,
只有等这个线程处理完了,才能够被其他的线程处理;
4> 线程同步,线程同步锁;
5> 在锁的作用下,保证了我们的公共资源的同步;
*//* 线程死锁: 线程之间的互相傻等 死锁;
A 锁1,锁2 .... 释放锁2,锁1;
// B 锁2,锁1 ... 释放锁2,锁1;
B 锁1,锁2 ... 释放锁2,锁1;
就是要使用同样的顺序来获得我们多个锁;
*/
DWORD WINAPI thread_entry(LPVOID lpThreadParameter) {printf("threadid %d\n", GetCurrentThreadId());g_value = 9; // 和进程共用数据段;ptr[0] = 10; // 和进程共用堆;test_func(); // 和进程共用代码段;// 线程是OS独立的调度单元;Sleep(5000);SetEvent(wait_cond);while (1) {printf("thread called\n");//EnterCriticalSection(&lock); // 没有请求到,线程挂起,指导其他的线程释放了这个锁;g_value = 10;LeaveCriticalSection(&lock);Sleep(3000);}return 0;
}int main(int argc, char** argv) {ptr = malloc(100);// 不用手动的重置这个时间, bManualReset 是否人工重置;// ResetEvent(事件句柄);wait_cond = CreateEvent(NULL, FALSE, FALSE, NULL);InitializeCriticalSection(&lock);int threadid;HANDLE h = CreateThread(NULL, 0, thread_entry, NULL, 0, &threadid);test_func();// 超时, 假设是一直等printf("waiting.....\n");WaitForSingleObject(wait_cond, INFINITE);printf("waiting end .....\n");// 主线程;while (1) {printf("main thread\n");EnterCriticalSection(&lock); // 没有请求到,线程挂起,指导其他的线程释放了这个锁;g_value = 8;LeaveCriticalSection(&lock);Sleep(1500); // }return 0;
}
文件的异步读写
1: 普通的读写文件打开文件都是同步的,比如C的fopen, fclose, fread等;
2: 磁盘的访问速度远远的低于内存,所以OS要等待磁盘设备来读写。
3: 如果采用同步,那么任务将会挂机,等待磁盘读好数据好,通知OS。
4: 高性能的服务器,提高并发,读写文件都会采用异步的模式。
5: 异步的模式:
1>发出读文件的请求;
2>通完了以后通知应用程序,并处理;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <Windows.h> // 你将获得大部分的Windows API接口
// 句柄: 这个句柄是这个对象的唯一的标识;
// unicode字符串: 每个字符是2个字节, L
// OPEN_EXISTING 打开存在文件,如果不存在就会失败;
int main(int argc, char** argv) {// sync模式HANDLE hfile = INVALID_HANDLE_VALUE;hfile = CreateFile(L"in.txt", GENERIC_READ, 0, NULL,OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);if (hfile == INVALID_HANDLE_VALUE) {printf("open error\n");goto failed;}// 准备好内存;char buf[1024];int readed;// 当我们正在读取这个磁盘的数据的时候,// 这个任务将会被挂起,直到磁盘读好了数据,才会唤醒;// 同步;ReadFile(hfile, buf, 1024, &readed, NULL); buf[readed] = 0;printf("%s\n", buf);CloseHandle(hfile); // 关闭一个文件;// async 异步, FILE_FLAG_OVERLAPPED模式;// step1: 以异步的模式打开;hfile = CreateFile(L"in.txt", GENERIC_READ, 0, NULL,OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_ATTRIBUTE_NORMAL, NULL);if (hfile == INVALID_HANDLE_VALUE) {printf("open error\n");goto failed;}// step2: 读时候,发送请求, 准备一个OVERLAPPED这个对象;// 传给我们的OS,等我们的OS读完以后,// 会通过OVERLAPPED来给我们发送一个事件;OVERLAPPED ov;HANDLE hevent = CreateEvent(NULL, FALSE, FALSE, NULL);memset(&ov, 0, sizeof(ov));ov.hEvent = hevent; // 事件对象,完成请求请求触发;ov.Offset = 2; // 从哪里开始读起;// 马上返回,IO挂起,没有读到数据ReadFile(hfile, buf, 1024, &readed, &ov); // 不是马上会有的;if (GetLastError() == ERROR_IO_PENDING) { // 表示正在等待;// 等待数据来读完成;WaitForSingleObject(hevent, INFINITE);readed = ov.InternalHigh; // 读到了几个字节的数据;buf[readed] = 0;printf("%s\n", buf);CloseHandle(hfile); // 关闭一个文件}else {CloseHandle(hfile); // 关闭一个文件}// 数据还没有准备好,不能马上使用,但是你可以做别的;// 都是等,有什么区别呢?// 1> 同步等,API内部等,直接挂起;// 2> 异步等,是用户自己来写代码来等待;// 3> 我们用户,可以同时等待多个请求, 并发请求数;// WaitForMultipleObjects
failed:system("pause");return 0;
}
libuv
libuv简介
1: 开源跨平台的异步IO库, 主要功能有网络异步,文件异步等。
2: libuv主页: http://libuv.org/
3: libuv是node.js的底层库;
4: libuv的事件循环模型:
epoll, kqueue, IOCP, event ports;
异步 TCP 与 UDP sockets;
DNS 解析
异步文件读写;
信号处理;
高性能定时器;
进程/线程池;
libuv原理
1:异步: 在用户层同时管理多个句柄请求。
2: loop循环等待所有的事件和句柄,管理好所有的这些请求。
3: 当其中一个请求完成后,loop就会监测得到然后调用用户指定的回掉函数处理;
4: 例如loop监听所有的socket,有数据来了后,loop就会处理,然后转到用户指定的回调函数。
5: libuv编写思想:
1> 创建一个对象, 例如socket;
2> 给loop管理这个对象;
3> 并指定一个回调函数,当有事件发生的时候调用这个回调函数, callback;
6: 1>向loop发送请求;
2>指定结束后的回调函数;
3>当请求结束后,调用调函数;
TCP服务器搭建
首先需要下载libuv库,导入到工程中,设置好include的路径和链接上对应的库
代码如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"/*
uv_handle_s 数据结构:
UV_HANDLE_FIELDSuv_stream_t 数据结构;
UV_HANDLE_FIELDS
UV_STREAM_FIELDSuv_tcp_t 数据结构;
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
UV_TCP_PRIVATE_FIELDSuv_tcp_t is uv_stream_t is uv_handle_t;
*/static uv_loop_t* loop = NULL;
static uv_tcp_t l_server; // 监听句柄;// 当我们的event loop检车到handle上有数据可以读的时候,
// 就会调用这个函数, 让这个函数给event loop准备好读入数据的内存;
// event loop知道有多少数据,suggested_size,
// handle: 发生读时间的handle;
// suggested_size: 建议我们分配多大的内存来保存这个数据;
// uv_buf_t: 我们准备好的内存,通过uv_buf_t,告诉even loop;
static void
uv_alloc_buf(uv_handle_t* handle,size_t suggested_size,uv_buf_t* buf) {if (handle->data != NULL) {free(handle->data);handle->data = NULL;}buf->base = malloc(suggested_size + 1);buf->len = suggested_size;handle->data = buf->base;
}static void
on_close(uv_handle_t* handle) {printf("close client\n");if (handle->data) {free(handle->data);handle->data = NULL;}
}static void
on_shutdown(uv_shutdown_t* req, int status) {uv_close((uv_handle_t*)req->handle, on_close);free(req);
}static void
after_write(uv_write_t* req, int status) {if (status == 0) {printf("write success\n");}uv_buf_t* w_buf = req->data;if (w_buf) {free(w_buf);}free(req);
}// 参数:
// uv_stream_t* handle --> uv_tcp_t;
// nread: 读到了多少字节的数据;
// uv_buf_t: 我们的数据都读到到了哪个buf里面, base;
static void after_read(uv_stream_t* stream,ssize_t nread,const uv_buf_t* buf) {// 连接断开了;if (nread < 0) {uv_shutdown_t* reg = malloc(sizeof(uv_shutdown_t));memset(reg, 0, sizeof(uv_shutdown_t));uv_shutdown(reg, stream, on_shutdown);return;}// endbuf->base[nread] = 0;printf("recv %d\n", nread);printf("%s\n", buf->base);// 测试发送给我们的 客户端;uv_write_t* w_req = malloc(sizeof(uv_write_t));uv_buf_t* w_buf = malloc(sizeof(uv_buf_t));w_buf->base = buf->base;w_buf->len = nread;w_req->data = w_buf;uv_write(w_req, stream, w_buf, 1, after_write);
}static void
uv_connection(uv_stream_t* server, int status) {printf("new client comming\n");// 接入客户端;uv_tcp_t* client = malloc(sizeof(uv_tcp_t));memset(client, 0, sizeof(uv_tcp_t));uv_tcp_init(loop, client);uv_accept(server, (uv_stream_t*)client);// end// 告诉event loop,让他帮你管理哪个事件;uv_read_start((uv_stream_t*)client, uv_alloc_buf, after_read);
}int main(int argc, char** argv) {int ret;loop = uv_default_loop();// Tcp 监听服务;uv_tcp_init(loop, &l_server); // 将l_server监听句柄加入到event loop里面;// 你需要event loop来给你做那种管理呢?配置你要的管理类型;struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", 6080, &addr); // ip地址, 端口ret = uv_tcp_bind(&l_server, (const struct sockaddr*) &addr, 0);if (ret != 0) {goto failed;}// 让event loop来做监听管理,当我们的l_server句柄上有人连接的时候;// event loop就会调用用户指定的这个处理函数uv_connection;uv_listen((uv_stream_t*)&l_server, SOMAXCONN, uv_connection);uv_run(loop, UV_RUN_DEFAULT);failed:printf("end\n");system("pause");return 0;
}
UDP服务器搭建
客户端
客户端:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")int main(int argc, char** argv) {WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);SOCKET client = socket(AF_INET, SOCK_DGRAM, 0);// 可以bind也可以不绑,如果不要求别人先发给你可以不bind;// end SOCKADDR_IN addr;addr.sin_family = AF_INET;addr.sin_port = htons(6080);addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");int len = sizeof(SOCKADDR_IN);int send_len = sendto(client, "Hello", 5, 0, (const SOCKADDR*)&addr, len);printf("send_len = %d\n", send_len);char buf[128];SOCKADDR_IN sender_addr; // 收到谁发的数据包的地址;int recv_len = recvfrom(client, buf, 128, 0, &sender_addr, &len);if (recv_len > 0) {buf[recv_len] = 0; // 加上结尾符号;printf("%s\n", buf);}WSACleanup();system("pause");return 0;
}
不用libuv版本的服务器
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")int main(int argc, char** argv) {WSADATA ws;WSAStartup(MAKEWORD(2, 2), &ws);SOCKET client = socket(AF_INET, SOCK_DGRAM, 0);// 可以bind也可以不绑,如果不要求别人先发给你可以不bind;// end SOCKADDR_IN addr;addr.sin_family = AF_INET;addr.sin_port = htons(6080);addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");int len = sizeof(SOCKADDR_IN);int send_len = sendto(client, "Hello", 5, 0, (const SOCKADDR*)&addr, len);printf("send_len = %d\n", send_len);char buf[128];SOCKADDR_IN sender_addr; // 收到谁发的数据包的地址;int recv_len = recvfrom(client, buf, 128, 0, &sender_addr, &len);if (recv_len > 0) {buf[recv_len] = 0; // 加上结尾符号;printf("%s\n", buf);}WSACleanup();system("pause");return 0;
}
libuv的UDP服务器
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"static uv_loop_t* event_loop = NULL;
static uv_udp_t server; // UDP的句柄;static void
uv_alloc_buf(uv_handle_t* handle,size_t suggested_size,uv_buf_t* buf) {if (handle->data != NULL) {free(handle->data);handle->data = NULL;}handle->data = malloc(suggested_size + 1); // +1测试的时候,我要收字符串,所以呢要加上1来访结尾符号;buf->base = handle->data;buf->len = suggested_size;
}static void
on_uv_udp_send_end(uv_udp_send_t* req, int status) {if (status == 0) {printf("send sucess\n");}free(req);
}static void
after_uv_udp_recv(uv_udp_t* handle,ssize_t nread,const uv_buf_t* buf,const struct sockaddr* addr, // 发过来数据包的IP地址 + 端口;unsigned flags) {char ip_addr[128];uv_ip4_name((struct sockaddr_in*)addr, ip_addr, 128);int port = ntohs(((struct sockaddr_in*)addr)->sin_port);printf("ip: %s:%d nread = %d\n", ip_addr, port, nread);char* str_buf = handle->data;str_buf[nread] = 0;printf("recv %s\n", str_buf);uv_buf_t w_buf;w_buf = uv_buf_init("PING", 4);// 写数据;uv_udp_send_t* req = malloc(sizeof(uv_udp_send_t));uv_udp_send(req, handle, &w_buf, 1, addr, on_uv_udp_send_end);// end
}int main(int argc, char** argv) {event_loop = uv_default_loop();memset(&server, 0 ,sizeof(uv_udp_t));uv_udp_init(event_loop, &server);// bind端口;struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", 6080, &addr);uv_udp_bind(&server, (const struct sockaddr*)&addr, 0);// end // 告诉事件循环,你要他管理recv事件;uv_udp_recv_start(&server, uv_alloc_buf, after_uv_udp_recv);uv_run(event_loop, UV_RUN_DEFAULT);system("pause");return 0;
}
定时器设计
源码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"
#include "time_list.h"#define my_malloc malloc
#define my_free freestruct timer {uv_timer_t uv_timer; // libuv timer handlevoid(*on_timer)(void* udata);void* udata;int repeat_count; // -1一直循环;
};static struct timer*
alloc_timer(void(*on_timer)(void* udata),void* udata, int repeat_count) {struct timer* t = my_malloc(sizeof(struct timer));memset(t, 0, sizeof(struct timer));t->on_timer = on_timer;t->repeat_count = repeat_count;t->udata = udata;uv_timer_init(uv_default_loop(), &t->uv_timer);return t;
}static void
free_timer(struct timer* t) {my_free(t);
}static void
on_uv_timer(uv_timer_t* handle) {struct timer* t = handle->data;if (t->repeat_count < 0) { // 不断的触发;t->on_timer(t->udata);}else {t->repeat_count --;t->on_timer(t->udata);if (t->repeat_count == 0) { // 函数time结束uv_timer_stop(&t->uv_timer); // 停止这个timerfree_timer(t);}}}struct timer*
schedule(void(*on_timer)(void* udata),void* udata,int after_msec,int repeat_count) {struct timer* t = alloc_timer(on_timer, udata, repeat_count);// 启动一个timer;t->uv_timer.data = t;uv_timer_start(&t->uv_timer, on_uv_timer, after_msec, after_msec);// end return t;
}void
cancel_timer(struct timer* t) {if (t->repeat_count == 0) { // 全部触发完成,;return;}uv_timer_stop(&t->uv_timer);free_timer(t);
}struct timer*
schedule_once(void(*on_timer)(void* udata),void* udata,int after_msec) {return schedule(on_timer, udata, after_msec, 1);
}
#ifndef __MY_TIMER_LIST_H__
#define __MY_TIMER_LIST_H__// on_timer是一个回掉函数,当timer触发的时候调用;
// udata: 是用户传的自定义的数据结构;
// on_timer执行的时候 udata,就是你这个udata;
// after_sec: 多少秒开始执行;
// repeat_count: 执行多少次, repeat_count == -1一直执行;
// 返回timer的句柄;
struct timer;
struct timer*
schedule(void(*on_timer)(void* udata), void* udata, int after_msec,int repeat_count);// 取消掉这个timer;
void
cancel_timer(struct timer* t);struct timer*
schedule_once(void(*on_timer)(void* udata), void* udata, int after_msec);
#endif
使用
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"// 获取当前系统从开机到现在运行了多少毫秒;
#ifdef WIN32
#include <windows.h>
static unsigned int
get_cur_ms() {return GetTickCount();
}
#else
#include <sys/time.h>
#include <time.h>
#include <limits.h>static unsigned int
get_cur_ms() {struct timeval tv;// struct timezone tz;gettimeofday(&tv, NULL);return ((tv.tv_usec / 1000) + tv.tv_sec * 1000);
}
#endif static uv_loop_t* event_loop = NULL;#include "time_list.h"struct timer* t = NULL;
static void
on_time_func(void* udata) {static int count = 0;char* str = (udata);printf("%s\n", str);count++;if (count == 10) {cancel_timer(t);}
}static void
on_time_func2(void* udata) {char* str = (udata);printf("%s\n", str);
}int main(int argc, char** argv) {event_loop = uv_default_loop();// 每隔5秒掉一次,掉4次;t = schedule(on_time_func, "HelloWorld!!!", 1000, -1);// schedule_once(on_time_func2, "CallFunc!!!", 1000);uv_run(event_loop, UV_RUN_DEFAULT);system("pause");return 0;
}
异步文件读写
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>#include "uv.h"/*
uv_fs_open:loop: 事件循环,uv_fs_t req请求对象;path: 文件路径flags: 标志0mode: 可读,可写... O_RDONLY O_RDWR...
*/
static uv_loop_t* event_loop = NULL;
static uv_fs_t req;
static uv_fs_t w_req;
static uv_file fs_handle;
static char mem_buffer[1024];
/*
uv_file
文件句柄对象: 打开文件以后的文件handle
uv_fs_tresult,每次请求的结果都是这个值来返回;打开文件: result返回打开文件句柄对象uv_file;读文件: result读到的数据长度;写文件: result为写入的数据长度;
*//*
释放掉这个请求req所占的资源
uv_req_cleanup(req);*//*
stdin: 标注的输入文件, scanf, cin>>
stdout: 标准的输出文件 printf;
fprintf(stdout, "xxxxxxx");每个进程在运行的时候:
stdin文件句柄与stdout这个文件句柄始终是打开的;
stdin:标准的输入文件,
stdout: 标准的输出;
*/static void
after_read(uv_fs_t* req) {printf("read %d byte\n", req->result);mem_buffer[req->result] = 0; // 字符串结尾;printf("%s\n", mem_buffer);uv_fs_req_cleanup(req);uv_fs_close(event_loop, req, fs_handle, NULL);uv_fs_req_cleanup(req);
}static void
on_open_fs_cb(uv_fs_t* req) {// 打开文件fs_handle = req->result;uv_fs_req_cleanup(req);printf("open success end\n");uv_buf_t buf = uv_buf_init(mem_buffer, 1024);uv_fs_read(event_loop, req, fs_handle, &buf, 1, 0, after_read);
}int main(int argc, char** argv) {event_loop = uv_default_loop();// step1:打开文件:uv_fs_open(event_loop, &req, "./test.txt", 0, O_RDONLY, on_open_fs_cb);uv_buf_t w_buf = uv_buf_init("Good! BYCW!!!!", 12);uv_fs_write(event_loop, &w_req, (uv_file)1, &w_buf, 1, 0, NULL);uv_fs_req_cleanup(&w_req);uv_run(event_loop, UV_RUN_DEFAULT);system("pause");return 0;
}
websocket协议
1: websocket是基于TCP的一种协议,是H5的一种传输协议;
2: websocket连接协议;
3: websocket 发送数据协议;
4: websocket 接受数据协议;
5: websocket 关闭协议;
建立连接
1:客户端向服务器发送http报文,服务器处理后回客户端连接报文;
2: 客户端发过来的报文:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
3: 服务器回应客户端报文:
:key+migic , SHA-1 加密, base-64 加密
key=”来自客户端的随机”, migic = “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”;
static char *wb_accept = “HTTP/1.1 101 Switching Protocols\r\n”
“Upgrade:websocket\r\n”
“Connection: Upgrade\r\n”
“Sec-WebSocket-Accept: %s\r\n”
“WebSocket-Protocol:chat\r\n\r\n”;
Sec-WebSocket-Key/Accept的作用
- 避免服务端收到非法的websocket连接(比如http客户端不小心请求连接websocket服务,此时服务端可以直接拒绝连接)
- 确保服务端理解websocket连接。因为ws握手阶段采用的是http协议,因此可能ws连接是被一个http服务器处理并返回的,此时客户端可以通过Sec-WebSocket-Key来确保服务端认识ws协议。(并非百分百保险,比如总是存在那么些无聊的http服务器,光处理Sec-WebSocket-Key,但并没有实现ws协议。。。)
- 用浏览器里发起ajax请求,设置header时,Sec-WebSocket-Key以及其他相关的header是被禁止的。这样可以避免客户端发送ajax请求时,意外请求协议升级(websocket upgrade)
可以防止反向代理(不理解ws协议)返回错误的数据。比如反向代理前后收到两次ws连接的升级请求,反向代理把第一次请求的返回给cache住,然后第二次请求到来时直接把cache住的请求给返回(无意义的返回)。 - Sec-WebSocket-Key主要目的并不是确保数据的安全性,因为Sec-WebSocket-Key、Sec-WebSocket-Accept的转换计算公式是公开的,而且非常简单,最主要的作用是预防一些常见的意外情况(非故意的)。
关闭连接
1: 主动关闭socket
2: 客户端关闭socket:
收到 0x88 开头的数据包;
收到tcp socket关闭事件;
发送数据
- 固定字节(0x81)
- 包长度字节
- 原始数据
接收数据
1)固定字节(1000 0001或1000 0010);
2)包长度字节, 去掉最高位, 剩下7为得到一个整数(0, 127);125以内的长度直接表示就可以了;
126表示后面两个字节表示大小,127表示后面的8个字节是数据的长度;(高位存在低地址)
3)mark 掩码为包长之后的 4 个字节
4)兄弟数据:
得到真实数据的方法:将兄弟数据的每一字节 x ,和掩码的第 i%4 字节做 xor 运算,其中 i 是 x 在兄弟数据中的索引
代码
客户端网页
<!DOCTYPE html>
<html>
<head><title>skynet WebSocket example</title>
</head>
<body> <script>var ws = new WebSocket('ws://127.0.0.1:8001/ws');ws.onopen = function(){alert("open");ws.send('WebSocket'); };ws.onmessage = function(ev){alert(ev.data);};ws.onclose = function(ev){alert("close");};ws.onerror = function(ev){console.log(ev);alert("error");};</script>
</body>
</html>
服务器
该代码在前面的TCP服务器的基础上修改
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "../3rd/http_parser/http_parser.h"
#include "../3rd/crypto/sha1.h"
#include "../3rd/crypto/base64_encoder.h"#include "uv.h"struct ws_context {int is_shake_hand; // 是否已经握手char* data; // 读取数据的buf
};static uv_loop_t* loop = NULL;
static uv_tcp_t l_server; // 监听句柄;static void
uv_alloc_buf(uv_handle_t* handle,size_t suggested_size,uv_buf_t* buf) {struct ws_context* wc = handle->data;if (wc->data != NULL) {free(wc->data);wc->data = NULL;}buf->base = malloc(suggested_size + 1);buf->len = suggested_size;wc->data = buf->base;
}static void
on_close(uv_handle_t* handle) {printf("close client\n");if (handle->data) {struct ws_context* wc = handle->data;free(wc->data);wc->data = NULL;free(wc);handle->data = NULL;}free(handle);
}static void
on_shutdown(uv_shutdown_t* req, int status) {uv_close((uv_handle_t*)req->handle, on_close);free(req);
}static void
after_write(uv_write_t* req, int status) {if (status == 0) {printf("write success\n");}uv_buf_t* w_buf = req->data;if (w_buf) {free(w_buf->base);free(w_buf);}free(req);
}static void
send_data(uv_stream_t* stream, unsigned char* send_data, int send_len) {uv_write_t* w_req = malloc(sizeof(uv_write_t));uv_buf_t* w_buf = malloc(sizeof(uv_buf_t));unsigned char* send_buf = malloc(send_len);memcpy(send_buf, send_data, send_len);w_buf->base = send_buf;w_buf->len = send_len;w_req->data = w_buf;uv_write(w_req, stream, w_buf, 1, after_write);
}static char filed_sec_key[512];
static char value_sec_key[512];
static int is_sec_key = 0;
static int has_sec_key = 0;static int
on_ws_header_field(http_parser* p, const char *at, size_t length) {if (strncmp(at, "Sec-WebSocket-Key", length) == 0) {is_sec_key = 1;}else {is_sec_key = 0;}return 0;
}static int
on_ws_header_value(http_parser* p, const char *at, size_t length) {if (!is_sec_key) {return 0;}strncpy(value_sec_key, at, length);value_sec_key[length] = 0;has_sec_key = 1;return 0;
}//
static char* wb_migic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
// base64(sha1(key + wb_migic))
static char *wb_accept = "HTTP/1.1 101 Switching Protocols\r\n"
"Upgrade:websocket\r\n"
"Connection: Upgrade\r\n"
"Sec-WebSocket-Accept: %s\r\n"
"WebSocket-Protocol:chat\r\n\r\n";static void
ws_connect_shake_hand(uv_stream_t* stream, unsigned char* data, int data_len) {http_parser_settings settings;http_parser_settings_init(&settings);settings.on_header_field = on_ws_header_field;settings.on_header_value = on_ws_header_value;http_parser p;http_parser_init(&p, HTTP_REQUEST);is_sec_key = 0;has_sec_key = 0;http_parser_execute(&p, &settings, data, data_len);if (has_sec_key) { // 解析到了websocket里面的Sec-WebSocket-Keyprintf("Sec-WebSocket-Key: %s\n", value_sec_key);// key + migicstatic char key_migic[512];static char sha1_key_migic[SHA1_DIGEST_SIZE];static char send_client[512];int sha1_size;sprintf(key_migic, "%s%s", value_sec_key, wb_migic);crypt_sha1((unsigned char*)key_migic, strlen(key_migic), (unsigned char*)&sha1_key_migic, &sha1_size);int base64_len;char* base_buf = base64_encode(sha1_key_migic, sha1_size, &base64_len);sprintf(send_client, wb_accept, base_buf);base64_encode_free(base_buf);send_data(stream, (unsigned char*)send_client, strlen(send_client));}
}static void
ws_send_data(uv_stream_t* stream, unsigned char* data, int len) {int head_size = 2;if (len > 125 && len < 65536) { // 两个字节[0, 65535]head_size += 2;}else if (len >= 65536) { // 不做处理head_size += 8;}unsigned char* data_buf = malloc(head_size + len);data_buf[0] = 0x81;if (len <= 125) {data_buf[1] = len;}else if (len > 125 && len < 65536) {data_buf[1] = 126;data_buf[2] = (len & 0x0000ff00) >> 8;data_buf[3] = (len & 0x000000ff);}else { // 127不写了return;}memcpy(data_buf + head_size, data, len);send_data(stream, data_buf, head_size + len);free(data_buf);
}// 收到的是一个数据包;
static void
ws_on_recv_data(uv_stream_t* stream, unsigned char* data, unsigned int len) {if (data[0] != 0x81 && data[0] != 0x82) {return;}unsigned int data_len = data[1] & 0x0000007f;int head_size = 2;if (data_len == 126) { // 后面两个字节表示的是数据长度;data[2], data[3]data_len = data[3] | (data[2] << 8);head_size += 2;}else if (data_len == 127) { // 后面8个字节表示数据长度; 2, 3, 4, 5 | 6, 7, 8, 9unsigned int low = data[5] | (data[4] << 8) | (data[3] << 16) | (data[2] << 24);unsigned int hight = data[9] | (data[8] << 8) | (data[7] << 16) | (data[6] << 24);data_len = low;head_size += 8;}unsigned char* mask = data + head_size;unsigned char* body = data + head_size + 4;for (unsigned int i = 0; i < data_len; i++) { // 遍历后面所有的数据;body[i] = body[i] ^ mask[i % 4];}// teststatic char test_buf[4096];memcpy(test_buf, body, data_len);test_buf[data_len] = 0;printf("%s\n", test_buf);// 发送协议ws_send_data(stream, "Hello", strlen("Hello"));// end }static void after_read(uv_stream_t* stream,ssize_t nread,const uv_buf_t* buf) {// 连接断开了;if (nread < 0) {uv_shutdown_t* reg = malloc(sizeof(uv_shutdown_t));memset(reg, 0, sizeof(uv_shutdown_t));uv_shutdown(reg, stream, on_shutdown);return;}// endprintf("start websocket!!!\n");struct ws_context* wc = stream->data;// 如果没有握手,就进入websocket握手协议if (wc->is_shake_hand == 0) {ws_connect_shake_hand(stream, buf->base, buf->len);wc->is_shake_hand = 1;return;}// end // 如果客户端主动关闭;0x88, 状态码if ((unsigned char)(buf->base[0]) == 0x88) { // 关闭printf("ws closing!!!!");return;}// end // ws正常的数据, 暂时不处理粘包这些问题;// 假设我们一次性都可以收完websocket发过来的数据包;ws_on_recv_data(stream, buf->base, nread);// end }static void
uv_connection(uv_stream_t* server, int status) {printf("new client comming\n");uv_tcp_t* client = malloc(sizeof(uv_tcp_t));memset(client, 0, sizeof(uv_tcp_t));uv_tcp_init(loop, client);uv_accept(server, (uv_stream_t*)client);struct ws_context* wc = malloc(sizeof(struct ws_context));memset(wc, 0, sizeof(struct ws_context));client->data = wc;uv_read_start((uv_stream_t*)client, uv_alloc_buf, after_read);
}int main(int argc, char** argv) {int ret;loop = uv_default_loop();uv_tcp_init(loop, &l_server); struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", 8001, &addr); // ip地址, 端口ret = uv_tcp_bind(&l_server, (const struct sockaddr*) &addr, 0);if (ret != 0) {goto failed;}uv_listen((uv_stream_t*)&l_server, SOMAXCONN, uv_connection);uv_run(loop, UV_RUN_DEFAULT);failed:printf("end\n");system("pause");return 0;
}
总结
WebSocket是一种基于TCP协议的双向通信协议,与HTTP/HTTPS协议相比,具有更低的延迟和更高的实时性。使用WebSocket协议可以实现实时通信、数据推送、在线游戏等功能。但是,WebSocket协议并没有被广泛采用的原因有以下几个方面:
兼容性问题:WebSocket协议是HTML5标准中新增的协议,对于较老的浏览器可能不支持。虽然现代主流浏览器已经支持WebSocket协议,但是在一些特殊情况下(例如企业内部应用、旧版浏览器等),WebSocket协议的兼容性可能成为问题。
安全问题:由于WebSocket协议实现了双向通信,因此在网络安全方面需要更加关注。例如,在进行WebSocket通信时需要进行恰当的身份验证和加密,以避免数据泄露和劫持等问题。
部署和负载问题:WebSocket协议需要建立长连接,因此在部署WebSocket服务时需要考虑服务器负载和连接管理等问题。如果不恰当地部署WebSocket服务,可能会导致服务器资源浪费、连接管理不当等问题。
用处有限:对于大多数网站来说,HTTP请求已经能满足需求。使用WebSocket可以带来实时性改善,但对许多网站功能影响不大。所以如果没有实时交互等强需求,就不一定需要引入WebSocket。
尽管WebSocket协议存在一些问题,但是在特定的场景下仍然是一种非常有用的协议。例如,在实时通信、数据推送、在线游戏等场景下,WebSocket协议可以发挥出其优势。
HTTP服务器
步骤:
1: 等待socket 连接进来;
2: 接收socket发送过来的数据;–> http协议格式的请求数据包
3: http解析url
4: 根据url来找对应的响应处理;
5: 将要返回的数据打包成http响应格式,发给客户端;
6: 关闭客户端的连接;
以Get请求为例:
1: 客户端提交请求;
2: 服务端解析get的url找到对应的响应;
3: 服务端解析get参数;
5: 处理,返回结果给客户端:
“HTTP/1.1 %d %s\r\n” 状态码,状态描述
“transfer-encoding:%s\r\n”, “identity”
“content-length: %d\r\n\r\n” 内容长度
body数据
6: get携带参数格式?uname=xiaoming&key=18074532323
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"
#include "../3rd/http_parser/http_parser.h"/*
url 注册管理模块
*/typedef void(*web_get_handle)(uv_stream_t* stream, char* url);
typedef void(*web_post_handle)(uv_stream_t* stream, char* url, char* body);struct url_node {char* url; // url地址web_get_handle get; // url地址对应的处理函数;web_post_handle post; // url地址对应的post函数
};static struct url_node*
alloc_url_node(char* url, web_get_handle get, web_post_handle post) {struct url_node* node = malloc(sizeof(struct url_node));memset(node, 0, sizeof(struct url_node));node->url = strdup(url);node->get = get;node->post = post;return node;
}static struct url_node* url_node[1024];
static int url_count = 0;static void
register_web_handle(char* url, web_get_handle get, web_post_handle post) {url_node[url_count] = alloc_url_node(url, get, post);url_count ++;
}static struct url_node*
get_url_node(char* url, int len) {for (int i = 0; i < url_count; i++) {if (strncmp(url, url_node[i]->url, len) == 0) {return url_node[i];}}return NULL;
}/*
{
[100] = "Continue",
[101] = "Switching Protocols",
[200] = "OK",
[201] = "Created",
[202] = "Accepted",
[203] = "Non-Authoritative Information",
[204] = "No Content",
[205] = "Reset Content",
[206] = "Partial Content",
[300] = "Multiple Choices",
[301] = "Moved Permanently",
[302] = "Found",
[303] = "See Other",
[304] = "Not Modified",
[305] = "Use Proxy",
[307] = "Temporary Redirect",
[400] = "Bad Request",
[401] = "Unauthorized",
[402] = "Payment Required",
[403] = "Forbidden",
[404] = "Not Found",
[405] = "Method Not Allowed",
[406] = "Not Acceptable",
[407] = "Proxy Authentication Required",
[408] = "Request Time-out",
[409] = "Conflict",
[410] = "Gone",
[411] = "Length Required",
[412] = "Precondition Failed",
[413] = "Request Entity Too Large",
[414] = "Request-URI Too Large",
[415] = "Unsupported Media Type",
[416] = "Requested range not satisfiable",
[417] = "Expectation Failed",
[500] = "Internal Server Error",
[501] = "Not Implemented",
[502] = "Bad Gateway",
[503] = "Service Unavailable",
[504] = "Gateway Time-out",
[505] = "HTTP Version not supported",
}
*//*
uv_handle_s 数据结构:
UV_HANDLE_FIELDSuv_stream_t 数据结构;
UV_HANDLE_FIELDS
UV_STREAM_FIELDSuv_tcp_t 数据结构;
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
UV_TCP_PRIVATE_FIELDSuv_tcp_t is uv_stream_t is uv_handle_t;
*/static uv_loop_t* loop = NULL;
static uv_tcp_t l_server; // 监听句柄;// 当我们的event loop检车到handle上有数据可以读的时候,
// 就会调用这个函数, 让这个函数给event loop准备好读入数据的内存;
// event loop知道有多少数据,suggested_size,
// handle: 发生读时间的handle;
// suggested_size: 建议我们分配多大的内存来保存这个数据;
// uv_buf_t: 我们准备好的内存,通过uv_buf_t,告诉even loop;
static void
uv_alloc_buf(uv_handle_t* handle,size_t suggested_size,uv_buf_t* buf) {if (handle->data != NULL) {free(handle->data);handle->data = NULL;}buf->base = malloc(suggested_size + 1);buf->len = suggested_size;handle->data = buf->base;
}static void
on_close(uv_handle_t* handle) {printf("close client\n");if (handle->data) {free(handle->data);handle->data = NULL;}
}static void
on_shutdown(uv_shutdown_t* req, int status) {uv_close((uv_handle_t*)req->handle, on_close);free(req);
}static void
after_write(uv_write_t* req, int status) {if (status == 0) {printf("write success\n");}uv_buf_t* w_buf = req->data;if (w_buf) {free(w_buf);}free(req);
}static void
send_data(uv_stream_t* stream, unsigned char* send_data, int send_len) {uv_write_t* w_req = malloc(sizeof(uv_write_t));uv_buf_t* w_buf = malloc(sizeof(uv_buf_t));unsigned char* send_buf = malloc(send_len);memcpy(send_buf, send_data, send_len);w_buf->base = send_buf;w_buf->len = send_len;w_req->data = w_buf;uv_write(w_req, stream, w_buf, 1, after_write);
}static char req_url[4096];
int on_url(http_parser* p, const char *at, size_t length) {strncpy(req_url, at, length);req_url[length] = 0;return 0;
}static int
filter_url(char* url) {char* walk = url;int len = 0;while (*url != '?' && *url != '\0') {len++;url++;}return len;
}static void
on_http_request(uv_stream_t* stream, char* req, int len) {http_parser_settings settings;http_parser_settings_init(&settings);settings.on_url = on_url;http_parser p;http_parser_init(&p, HTTP_REQUEST);http_parser_execute(&p, &settings, req, len);// http get是可以携带参数的:// http://www.baidu.com:6080/test?name=xiaoming&age=34int url_len = filter_url(req_url);struct url_node* node = get_url_node(req_url, url_len);printf("%s\n", req_url);if (node == NULL) {return;}switch (p.method) { // 请求方法case HTTP_GET:if (node->get != NULL) {node->get(stream, req_url);}break;case HTTP_POST:if (node->post != NULL) {}break;}
}
// 参数:
// uv_stream_t* handle --> uv_tcp_t;
// nread: 读到了多少字节的数据;
// uv_buf_t: 我们的数据都读到到了哪个buf里面, base;
static void after_read(uv_stream_t* stream,ssize_t nread,const uv_buf_t* buf) {// 连接断开了;if (nread < 0) {uv_shutdown_t* reg = malloc(sizeof(uv_shutdown_t));memset(reg, 0, sizeof(uv_shutdown_t));uv_shutdown(reg, stream, on_shutdown);return;}// endbuf->base[nread] = 0;printf("recv %d\n", nread);printf("%s\n", buf->base);// 处理on_http_request(stream, buf->base, buf->len);// end
}static void
uv_connection(uv_stream_t* server, int status) {printf("new client comming\n");// 接入客户端;uv_tcp_t* client = malloc(sizeof(uv_tcp_t));memset(client, 0, sizeof(uv_tcp_t));uv_tcp_init(loop, client);uv_accept(server, (uv_stream_t*)client);// end// 告诉event loop,让他帮你管理哪个事件;uv_read_start((uv_stream_t*)client, uv_alloc_buf, after_read);
}static void
test_get(uv_stream_t* stream, char* url) {printf("%s\n", url);char* body = "SUCCESS TEST1";static char respons_buf[4096];char* walk = respons_buf;sprintf(walk, "HTTP/1.1 %d %s\r\n", 200, "OK");walk += strlen(walk);sprintf(walk, "transfer-encoding:%s\r\n", "identity");walk += strlen(walk);sprintf(walk, "content-length: %d\r\n\r\n", strlen(body));walk += strlen(walk);sprintf(walk, "%s", body);send_data(stream, respons_buf, strlen(respons_buf));
}static void
test2_get(uv_stream_t* stream, char* url) {printf("%s\n", url);char* body = "SUCCESS TEST2";static char respons_buf[4096];char* walk = respons_buf;sprintf(walk, "HTTP/1.1 %d %s\r\n", 200, "OK");walk += strlen(walk);sprintf(walk, "transfer-encoding:%s\r\n", "identity");walk += strlen(walk);sprintf(walk, "content-length: %d\r\n\r\n", strlen(body));walk += strlen(walk);sprintf(walk, "%s", body);send_data(stream, respons_buf, strlen(respons_buf));
}int main(int argc, char** argv) {// 注册一下web请求函数register_web_handle("/test", test_get, NULL);register_web_handle("/test2", test2_get, NULL);// end int ret;loop = uv_default_loop();// Tcp 监听服务;uv_tcp_init(loop, &l_server); // 将l_server监听句柄加入到event loop里面;// 你需要event loop来给你做那种管理呢?配置你要的管理类型;struct sockaddr_in addr;uv_ip4_addr("0.0.0.0", 6080, &addr); // ip地址, 端口ret = uv_tcp_bind(&l_server, (const struct sockaddr*) &addr, 0);if (ret != 0) {goto failed;}// 让event loop来做监听管理,当我们的l_server句柄上有人连接的时候;// event loop就会调用用户指定的这个处理函数uv_connection;uv_listen((uv_stream_t*)&l_server, SOMAXCONN, uv_connection);uv_run(loop, UV_RUN_DEFAULT);failed:printf("end\n");system("pause");return 0;
}
多线程与工作队列
1: 线程相关函数:
uv_thread_create: 创建一个线程;
uv_thread_self: 获取当前线程id号;
uv_thread_join: 等待线程结束;
2: 锁:
uv_mutex_init: 初始化锁;
uv_mutex_destroy: 销毁锁;
uv_mutex_lock: 获取锁,如果被占用,就等待;
uv_mutex_trylock: 尝试获取锁,如果获取不到,直接返回,不等待;
uv_mutex_unlock: 释放锁;
3: 等待/触发事件;
uv_cond_init: 创建条件事件;
uv_cond_destroy: 销毁条件事件;
uv_cond_signal: 触发条件事件;
uv_cond_broadcast: 广播条件事件;
uv_cond_wait/uv_cond_timedwait: 等待事件/等待事件超时;
工作队列:
1: libuv在启动的时候会创建一个线程池;
2: 线程池默认启动的线程数目是4个,最大是128个线程;
3: uv_queue_work工作队列原理:
step1: libuv启动线程池,等待任务的调度;
step2: 用户给工作队列一个执行函数,工作队列执行完后,通知主线程;
step3: 主线程在执行之前设置的回掉函数;
4: 工作队列的使用:
有很多操作,比如读文件,比如读数据库,比如读redis都会等待,所以使用工作队列,
可以工作队列来执行,然后通知主线程,这样就不会卡主线程了。给工作队列一个任务(函数),并指定好任务完成的回掉函数,线程池就会调度去执行这个任务函数,完成后,通知主线程,主线程调用回掉函数;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "uv.h"// 工作队列的用处:
// 1:复杂的算法放到工作队列 ;
// 2:IO放到我们工作队列,获取数据库结果;
// ....// 是在线程池里面另外一个线程里面调用,不在主线程;
static void
thread_work(uv_work_t* req) {// printf("user data = %d \n", (int)req->data);printf("thread_work id 0x%d:\n", uv_thread_self());
}// 当工作队列里面的线程执行完上面的任务后,通知主线程;
// 主线程调用这个函数;
static void
on_work_complete(uv_work_t* req, int status) {printf("on_work_complete thread id 0x%d:\n", uv_thread_self());
}int main(int argc, char** argv) {uv_work_t uv_work;printf("main id 0x%d:\n", uv_thread_self());uv_work.data = (void*)6;uv_queue_work(uv_default_loop(), &uv_work, thread_work, on_work_complete);uv_run(uv_default_loop(), UV_RUN_DEFAULT);system("pause");return 0;
}
mysql数据库的搭建和操作
1: 去官网下载mysql的服务端: https://www.mysql.com/
下载mysql服务器
2: 安装好mysql服务,设置好初始化的根用户密码: 记住根用户的密码;
3: windows 启动服务: mysql notifier或windows服务管理器启动;
4: mysql客户端:
Mac OS: Sequel Pro/命令行
Windows: heidisql windows客户端/命令行;
Linux: 命令行;
5: heidisql: 安装:
将heidisql可执行文件放到mysql server对应的目录下;
7: heidisql连接数据库:
配置文件
Windows为my.ini,linux为my.cnf
1: 通讯端口: port, 默认是3306;
2: bind-address = 127.0.0.1
如果不能远程登陆,记得检查下这个参数,是否打开;
3: mysql数据库X小时无连接自动关闭, 默认8个小时;
interactive_timeout=28800000
wait_timeout=28800000
使用数据库
1: 数据库:
创建CREATE DATABASE user_center
使用一个数据库 USE user_center;
1: 插入:
insert into table_name (col1, col2,…) values(v1, v2…);
2: 删除:
delete from table_name where 条件
3: 查找:
select * from table_name where 条件;
count(): 计算个数 ORDER BY 字段 / ORDER BY 字段 DESC
4: 修改
update table_name set col1 = value where 条件;
另外,项目中需要经常备份数据库.sql文件,可以在文件栏中选择导出对应的sql文件,并在导入时执行,数据就会回来
代码
1: mysql服务器提供了服务协议,客户端遵守它的协议给他发送数据;
2: mysql有很多针对不同开发语言的 实现了和服务器通讯的客户端库;
3: 开发人员使用这些遵守mysql协议的库与mysql进行数据通讯;
4: 这里使用: mysql-connector-c
环境搭建:
1: 创建项目;
2:下载 mysql-connector-c 库与头文件;
https://dev.mysql.com/downloads/connector/c/6.1.html
3: 配置好mysql的开发环境:
1>头文件搜索路径; 2>库文件搜索路径
4: 配置好win socket环境WSAStartup/WSACleanup
5: 运行时用的.dll文件;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>// 只要使用socket,最好在开始的时候
// 都加上初始化WSAStartup;#include <winsock.h>
#pragma comment(lib, "ws2_32.lib")
#pragma comment(lib, "libmysql.lib")#include "mysql.h"int main(int argc, char** argv) {WSADATA wsa;WSAStartup(MAKEWORD(2, 2), &wsa);// 创建一个mysql的连接;MYSQL* pConn = mysql_init(NULL);// 将这个连接,连接到对应的数据库,这样,我们的就和对应的数据库建立起了连接;mysql_real_connect(pConn, "127.0.0.1", "root", "123456", "class_sql", 3306, NULL, 0);// 数据库里面是utf8的编码,可是我们的VC里面不是得;// 客户端要设置一下客户端的编码格式是多少,这样服务器就会把数据当作哪种格式来使用;// mysql_query,执行命令的函数;mysql_query(pConn, "set names gbk"); // gbk字符编码;// 插入一条记录
#if 0int ret = mysql_query(pConn, "insert into class_test (name, age, sex) values(4, 34, 1)");if (ret != 0) {printf("%s\n", mysql_error(pConn));}
#endif// end // 修改一条记录
#if 0int ret = mysql_query(pConn, "update class_test set name = \"xiaomingm\" where id = 9");if (ret != 0) {printf("%s\n", mysql_error(pConn));}int lines = mysql_affected_rows(pConn); // 打印出来受影响的行数;printf("%d\n", lines);
#endif// // 删除一条记录
#if 0int ret = mysql_query(pConn, "delete from class_test where id = 9");if (ret != 0) {printf("%s\n", mysql_error(pConn));}
#endif// end// 查询一条记录,需要获取查询的结果;int ret = mysql_query(pConn, "select * from class_test");if (ret != 0) {printf("%s\n", mysql_error(pConn));}else { // 获得查询结果;MYSQL_RES *result = mysql_store_result(pConn);MYSQL_ROW row;while (row = mysql_fetch_row(result)) {printf("%s, %s, %s, %s\n", row[0], row[1], row[2], row[3]);}}// endmysql_close(pConn); // 关闭mysql连接;WSACleanup();system("pause");return 0;
}
redis操作与使用
1: Redis是完全在内存中保存数据的数据库,使用磁盘只是为了持久性目的
2: Redis相比许多键值数据存储系统有相对丰富的数据类型;
列表,集合,可排序集合,哈希表等数据类型
3: Redis可以将数据复制到任意数量的从服务器中;
4: Redis 操作速度快;
5: Redis 所有的操作都是原子的;
6: Redis我们常用来做内存数据库,把常用的需要查找的数据放入到redis中存放;
安装
1: 去官网下载: https://redis.io/
下载redis 服务器, windows版本redis要到github上下载,是微软开发组移植;
2: 安装好后启动 reidis;
3: redis自带reidis-client客户端工具;
4: 启动redis-server.exe redis.conf,最新版redis安装在windows上自动开启
5: redis client —> redis-cli.exe 客户端工具
如果直接redis-client.exe, 登陆的Ip: 127.0.0.1, 端口6379
redis-cli -h 127.0.0.1 -p 6379 -a yourpassword
6: Redis 设置密码
CONFIG set requirepass “password”
7: 验证密码: AUTH “password”
配置文件
1: port 6379 服务器监听的端口号
2: databases 表示redis服务器管理多少个数据库,数据库的编号从0开始 select dbid;
3: redis 备份策略 save 90 1 save 30 10 save 6 10000
4: 数据库备份文件的名字 dbfilename dump.rdb
5: dir 数据库生成的路径
6: 下次启动redis的时候,数据会从dump.rdb里面重建而来;
操作命令
1: 哈希表–> key, 表{字段, 值}
HMSET key name “xiaoming” age “1”
HGETALL key
HDEL key 字段 删除一个或多个字段
HEXISTS key 字段
HGET key 字段
HKEYS key 返回所有的字段filed
HMGET key filed
2: Hash表结果多用于存储数据, 存入在redis里面的都是字符串;
1: 有序集合
ZADD key 权重 value
ZRANGE key start stop 从0开始
ZRANGE key start stop WITHSCORES
ZREVRANGE key start stop
Zrem key filed
2: 多用于排序和排行榜;
配置开发环境
1: 下载redis代码: windows下载Windows平台;
2: 打开redis-server源码,编译msvc工程,生成了Hiredis.lib Win32_Interop.lib
Hiredis.lib 是redis编写的C client SDK静态库;
Win32_Interop.lib 是redis 为磨平linux与windows差异而做的win平台的静态库;
3: 整理好 hiredis 所依赖的头文件 hiredis头文件/Win32_Interop 头文件
4:编译器配置:
#include <hiredis.h>
#define NO_QFORKIMPL //这一行必须加才能正常使用
#include <Win32_Interop/win32fixes.h>
#pragma comment(lib,“hiredis.lib”)
#pragma comment(lib,“Win32_Interop.lib”)
(1)添加库搜索路径
(2)添加头文件搜索路径
(3)将win32fixes.c 加入到工程编译;
(4)win32fixes.h之前加上#define NO_QFORKIMPL
(5)右击项目->属性->配置属性->C/C+±>代码生成->运行库->改成多线程调试(/MTd)或多线程(/MT)
(6)右击项目->属性->配置属性->链接器->命令行中输入/NODEFAULTLIB:libcmt.lib
(7)右击项目->属性->配置属性->C/C+±>预处理器->预处理器定义->添加“_CRT_SECURE_NO_WARNINGS”
代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include <hiredis.h>
#define NO_QFORKIMPL
#include <Win32_Interop/win32fixes.h>#pragma comment(lib,"hiredis.lib")
#pragma comment(lib,"Win32_Interop.lib")int main(int argc, char** argv) {struct timeval timeout = { 1, 500000 }; // 1.5 secondsredisContext* c = redisConnectWithTimeout((char*)"127.0.0.1", 6379, timeout);if (c->err) {printf("Connection error: %s\n", c->errstr);goto end;}redisReply* replay = redisCommand(c, "select %d", 15);if (replay) {printf("%d, %s\n", replay->type, replay->str);freeReplyObject(replay);}else {printf("relay == NULL");}replay = redisCommand(c, "zadd world_rank 3000 xt");if (replay) {printf("%d, %d\n", replay->type, replay->integer);freeReplyObject(replay);}replay = redisCommand(c, "zrange world_rank 0 10 withscores");if (replay) {if (replay->type == REDIS_REPLY_ARRAY) {for (int i = 0; i < replay->elements; i++) {printf("%d: %s\n", i, replay->element[i]->str);}}freeReplyObject(replay);}
end:redisFree(c);system("pause");return 0;
}
Json数据格式的编码和解码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "./../3rd/mjson/json.h"/*
{
"uid" : 123,
"uname" : "hello!",
"is_new": true,
"vip": null,"man_prop": [1, "hello", "2"],
"weap_prop": {
"default": "putongzidan",
},
}
*//*
json的简单的规则
(1)key-value模式;
(2)key, 数字,字符串;
(3)value, 数字, 逻辑变量, 数组, 对象;
(4)数字, true/false, null, [], {}
(5)最高的层次上面object, {};
(6)JSON优点:(1)通用的传输方案; -->json文本-->解码回来, Lua, js, python...;(2)XML, json XML优点,省空间;(3)JSON对比 buf, 可读性很强;
(7)在可读性很强的情况下,占用空间较小,通用的编码解码传输方案;*/static char json_str[4096];
int main(int argc, char** argv) {// step1: 建立一个json_t对象; --> JS object C的数据结构;// json_t 以root为这个根节点的一颗树, json_t数据结构;json_t* root = json_new_object(); // {}json_t* number = json_new_number("123"); // json_insert_pair_into_object(root, "uid", number); // {uid: 123,}json_t* str = json_new_string("hello!");json_insert_pair_into_object(root, "uname", str);json_t* b_true = json_new_true();json_insert_pair_into_object(root, "is_new", b_true);json_t* j_null = json_new_null();json_insert_pair_into_object(root, "vip", j_null);// []json_t* j_array = json_new_array();json_insert_pair_into_object(root, "man_prop", j_array);number = json_new_number("1");json_insert_child(j_array, number);str = json_new_string("hello");json_insert_child(j_array, str);str = json_new_string("2");json_insert_child(j_array, str);// array end // {}json_t* j_object = json_new_object();json_insert_pair_into_object(root, "weap_prop", j_object);str = json_new_string("putongzidan");json_insert_pair_into_object(j_object, "default", str);// {} end// step2: 建立好的json_t对象树以及相关的依赖--> json文本;char* json_text;json_tree_to_string(root, &json_text); // 这个函数,来malloc json所需要的字符串的内存;printf("%s\n", json_text);strcpy(json_str, json_text);free(json_text); // 销毁json树,他会连同他的孩子对象一起销毁json_free_value(&root);root = NULL;// step3,将这个json_t文本专成我们对应的json对象;json_parse_document(&root, json_str); // 根据json文本产生一颗新的json对象树,// step4: 我们从json_t对象树里面获取里面的值;json_t* key = json_find_first_label(root, "uname");if (key) {json_t* value = key->child;switch (value->type) {case JSON_STRING:printf("key: %s value: %s\n", key->text, value->text);break;}}key = json_find_first_label(root, "uid");if (key) {json_t* value = key->child;switch (value->type) {case JSON_NUMBER:printf("key: %s value: %f\n", key->text, atof(value->text));break;}}json_free_value(&root);system("pause");return 0;
}
Base64_MD5
1:Base64是网络上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法
2: Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息
3: 编码后的数据是一个字符串,其中包含的字符为:A-Z、a-z、0-9、+、/
共64个字符:26 + 26 + 10 + 1 + 1 = 64。其实是65个字符,“=”是填充字符
4: 64个字符需要6位来表示,表示成数值为0~63
1: MD5算法:
1>压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2>容易计算:从原数据计算出MD5值很容易。
3>抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4>强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的
2: SHA1算法
和MD5类似的处理;
3: 加密用户密码,服务器存放用户密码都是MD5值;
4: 比较文件是否有修改,根据文件的内容来生成MD5值;
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "../3rd/crypto/base64_encoder.h"
#include "../3rd/crypto/base64_decoder.h"
#include "../3rd/crypto/md5.h"
#include "../3rd/crypto/sha1.h"int main(int argc, char** argv) {int encode_len;char* base64_buf = NULL;base64_buf = base64_encode("Hello", 5, &encode_len);printf("%s\n", base64_buf);char* decode_buf = NULL;int decode_len;decode_buf = base64_decode(base64_buf, encode_len, &decode_len);printf("decode base64 %s\n", decode_buf);base64_decode_free(decode_buf);base64_encode_free(base64_buf);// md5,二进制数据--> 固定长度的二进制;unsigned char md5_buf[MD5_HASHSIZE];md5("user_password", strlen("user_password"), md5_buf);// 16个字节的二进制数据;32文本;66 32 8d 07 96 67 e8 24 86 e6 63 32 54 99 49 89// 大写,小写;for (int i = 0; i < MD5_HASHSIZE; i++) {printf("%x", md5_buf[i]);}printf("\n");// end// SHA1unsigned char sha1_buf[128];int e_sz;crypt_sha1("user_password", strlen("user_password"), sha1_buf, &e_sz);for (int i = 0; i < e_sz; i++) {printf("%x", sha1_buf[i]);}printf("\n");// end system("pause");return 0;
}
HTTP报文解析
报文展示:
1: static char* http_get_req =
“GET /favicon.ico HTTP/1.1\r\n”
“Host: 0.0.0.0=5000\r\n”
“User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n”
“Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8\r\n”
“Accept-Language: en-us,en;q=0.5\r\n”
“Accept-Encoding: gzip,deflate\r\n”
“Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n”
“Keep-Alive: 300\r\n”
“Connection: keep-alive\r\n”
“\r\n”;
1:static char * http_post_req =
“POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n”
“Accept: /\r\n”
“Transfer-Encoding: identity\r\n”
“Content-Length: 5\r\n”
“\r\n”
“World”;
1: static char* http_get_respones =
“HTTP/1.1 200 OK\r\n”
“Date: Sat, 31 Dec 2005 23:59:59 GMT\r\n”
“Content-Type: text/html;charset=ISO-8859-1\r\n”
“Content-Length: 122\r\n”
“\r\n”
“<html>\r\n”
“<head>\r\n”
“<title>Wrox Homepage\r\n”
“</head>\r\n”
“<body>\r\n”
“<!–body goes here -->\r\n”
“</body>\r\n”
“</html>\r\n”;
代码:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>#include "./../3rd/http_parser/http_parser.h"/*
GET /favicon.ico HTTP/1.1
Host: 0.0.0.0=5000
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0
Accept-Language: en-us,en;q=0.5
Connection: keep-alive// 结束
*/
static char* http_get_req = "GET /favicon.ico HTTP/1.1\r\n"
"Host: 0.0.0.0=5000\r\n"
"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
"Accept-Language: en-us,en;q=0.5\r\n"
"Accept-Encoding: gzip,deflate\r\n"
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
"Keep-Alive: 300\r\n"
"Connection: keep-alive\r\n"
"\r\n";static char * http_post_req = "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n"
"Accept: */*\r\n"
"Transfer-Encoding: identity\r\n"
"Content-Length: 14\r\n"
"\r\n"
"HelloWorld!!!!";static char* http_get_respones = "HTTP/1.1 200 OK\r\n"
"Date: Sat, 31 Dec 2005 23:59:59 GMT\r\n"
"Content-Type: text/html;charset=ISO-8859-1\r\n"
"Content-Length: 122\r\n"
"\r\n"
"<html>\r\n"
"<head>\r\n"
"<title>Wrox Homepage</title>\r\n"
"</head>\r\n"
"<body>\r\n"
"<!--body goes here -->\r\n"
"</body>\r\n"
"</html>\r\n";static int
on_message_begin(http_parser* p) {printf("on_message_begin\n");return 0;
}static int
on_message_complete(http_parser* p) {printf("on_message_complete\n");return 0;
}static int
on_headers_complete(http_parser* p) {printf("on_headers_complete\n");return 0;
}static int
on_url(http_parser*p, const char *at, size_t length) {static char url_buf[1024];strncpy(url_buf, at, length);url_buf[length] = 0;printf("%s\n", url_buf);return 0;
}static int
on_header_filed(http_parser*p, const char *at, size_t length) {static char filed[1024];strncpy(filed, at, length);filed[length] = 0;printf("%s\n", filed);return 0;
}static int
on_header_value(http_parser*p, const char *at, size_t length) {static char value[1024];strncpy(value, at, length);value[length] = 0;printf("%s\n", value);return 0;
}static int
on_body(http_parser*p, const char *at, size_t length) {static char body[4096];strncpy(body, at, length);body[length] = 0;printf("body: %s\n", body);return 0;
}static int
on_status(http_parser*p, const char *at, size_t length) {static char status[1024];strncpy(status, at, length);status[length] = 0;printf("status: %s\n", status);return 0;
}// 配置回掉函数;
static http_parser_settings settings = {.on_message_begin = on_message_begin,.on_message_complete = on_message_complete,.on_url = on_url,.on_header_field = on_header_filed,.on_header_value = on_header_value,.on_headers_complete = on_headers_complete,.on_body = on_body,.on_status = on_status,
};static http_parser parser;
// end
int main(int argc, char** argv) {http_parser_init(&parser, HTTP_REQUEST);http_parser_execute(&parser, &settings, http_get_req, strlen(http_get_req));http_parser_execute(&parser, &settings, http_post_req, strlen(http_post_req));http_parser_init(&parser, HTTP_RESPONSE);http_parser_execute(&parser, &settings, http_get_respones, strlen(http_get_respones));system("pause");return 0;
}
protobuf
1:protobuf是一个跨语言,跨平台, 可扩展的序列化/反序列化数据结构的工具;
2: protobuf的使用基本步骤:
a:将要传递的数据结构做成 protobuf协议描述文件: .proto文件;
b:protobuf工具protoc将协议描述文件转成对应语言的代码(js, c++, c#, python等);
c: 使用代码来构造数据对象;
d: 使用数据对象的接口来初始化数据对象;
e: 利用自动生成的代码,将这个数据二进制序列化;
f: 传输二进制序列;
g: 在使用自动生成的代码反序列化(解码),生成数据对象使用;
3: protobuf优点:
json, xml 占用的空间比较大;
protobuf 占用的空间小,效率高;
protobuf也常用于应用层协议的编码和解码;
环境搭建
1: 下载protobuf源码: https://github.com/google/protobuf
2: 下载项目构建工具cmake: https://cmake.org/download/
完成cmake构建之后需要生成三个解决方案:libprotobuf、libprotoc、protoc,生成之后将Debug文件夹中的libprotobufd.lib、libprotocd.lib拉到项目中,在解决方案的属性中把这两个库给链接上。
3: 编写.proto协议文件,使用proto2或proto3
4: 使用protoc.exe和命令行生成对应语言的protobuf脚本,例如c++:
protoc.exe --cpp_out=./ person.proto
5: 把生成的代码文件拖到工程中,include .h文件
6: 把github下载的protobuf文件全部拖入项目中,在解决方案的C/C+±>常规->附加包含目录中设置对应的库文件根路径。例如C++对应protobuf的src文件夹。
7: 可以开始编写代码了
代码
#include <string.h>
#include <stdlib.h>
#include <string.h>#include <iostream>
#include <string>
using namespace std;#include "../proto/person.pb.h"#if 0
int main(int argc, char** argv) {// 定义一个你要传送数据的对象;// 初始化好了这个对象的数据成员;Person p;p.set_name("xiaoming");p.set_email("xiaoming@bycwedu.com");p.set_age(34);printf("%s %s %d\n", p.name().c_str(), p.email().c_str(), p.age());// 将这个数据对象序列化;string out;p.SerializeToString(&out);// 传输,解码// 使用string对象里面存放的数据,来解码成我们的数据对象;Person xiaoming;xiaoming.ParseFromString(out);cout << xiaoming.name() << xiaoming.age() << xiaoming.email() << endl;system("pause");return 0;
}
#else
// 传递一个消息类型的字符串,那么这个工厂就能帮助我们构造出对应的类的实例;
// "Person"字符串--> Person的实例--> 返回的是一个基类Message类的指针;
// 基类指针,指向子类实例;
// create_message --> delete 来删除这个msg对象实例;
google::protobuf::Message* create_message(const char* type_name) {google::protobuf::Message* message = NULL;// 根据名字,找到message的秒速对象;const google::protobuf::Descriptor* descriptor =google::protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(type_name);if (descriptor) {// 根据描述对象到对象工厂里面,生成对应的模板对象;// 根据模板复制出来一个;const google::protobuf::Message* prototype =google::protobuf::MessageFactory::generated_factory()->GetPrototype(descriptor);if (prototype) {message = prototype->New();}}return message;
}int main(int argc, char** argv) {// 基类的message--> Person类的实例;google::protobuf::Message* msg = create_message("Person"); /*Person* xiaoming = (Person*)msg;xiaoming->set_name("xiaoming");printf("%s\n", xiaoming->name().c_str());*//* 每一个Message对象有包含了两个对象; google::protobuf::Descriptor --> 获取Message的描述信息-->包含每一个filed的描述; google::protobuf::Reflection --> 使用它 + filed描述,能get/set 每个 filed的值;*/const google::protobuf::Descriptor* des = msg->GetDescriptor();const google::protobuf::Reflection* ref = msg->GetReflection();// 设置;// name:const google::protobuf::FieldDescriptor* fd_des = des->FindFieldByName("name");ref->SetString(msg, fd_des, "xiaoming");// age// fd_des = des->FindFieldByName("age");fd_des = des->FindFieldByNumber(2);ref->SetInt32(msg, fd_des, 34);// end // emailfd_des = des->FindFieldByName("email");ref->SetString(msg, fd_des, "xiaoming@bycwedu.com");// end// arrayfd_des = des->FindFieldByName("int_set");ref->AddInt32(msg, fd_des, 1);ref->AddInt32(msg, fd_des, 2);ref->AddInt32(msg, fd_des, 3);ref->AddInt32(msg, fd_des, 4);// end// 遍历我们的Descriptor里面的每一个filed;for (int i = 0; i < des->field_count(); i++) {// 获取每一个field的描述对象;const google::protobuf::FieldDescriptor* fd = des->field(i);// (1)获取我们的名字; fd->name()printf("%s\n", fd->name().c_str());if (fd->is_repeated()) { // 当前我们的字段是一个数组; FieldSize数组的长度;for (int walk = 0; walk < ref->FieldSize(*msg, fd); walk++) {switch (fd->cpp_type()) {case google::protobuf::FieldDescriptor::CppType::CPPTYPE_INT32:printf("%d\n", ref->GetRepeatedInt32(*msg, fd, walk));break;case google::protobuf::FieldDescriptor::CppType::CPPTYPE_STRING:printf("%s\n", ref->GetRepeatedString(*msg, fd, walk).c_str());break;// ...}}continue;}switch (fd->cpp_type()) {case google::protobuf::FieldDescriptor::CppType::CPPTYPE_INT32:printf("%d\n", ref->GetInt32(*msg, fd));break;case google::protobuf::FieldDescriptor::CppType::CPPTYPE_STRING:printf("%s\n", ref->GetString(*msg, fd).c_str());break;// ...}// 表示filed是一个数组, fd->is_optional, fd->is_repeated}// 删除掉这个msg;delete msg;system("pause");return 0;
}
#endif
上面使用了protobuf的高级用法:
1: 每一个Message对象都包含两个对象:
(1)google::protobuf::Descriptor 描述对象,是Message所有Filed的一个集合,它又包含了
FieldDescriptor 对象; 每个filed都对应一个FieldDescriptor;
(2)google::protobuf::Reflection 反射对象, 通过它 + FieldDescriptor, 能set/get filed对象的值;
2: 每一个Message对象,可以通过统一的对象工厂来构建, 根据协议生成了代码后,只要传入Message的名字,就能构建出对应的Message的类的实例;
1: 遍历每个filed;
const Descriptor* descriptor = message->GetDescriptor();
for (int32_t index = 0; index < descriptor->field_count(); ++index) {
const FieldDescriptor* fd = descriptor->field(index);
const string& name = fd->name();
}
2: filed:
name: filed的文本名字;
is_required: 是否为required 选项;
is_repeated: 是否为数组;
cpp_type: 返回类型;
优点:
1: 根据这个message的类型,我们可以统一的构造;
2: 方便统一解码,将结果转给其他的脚本语言,比如Lua, JS;
相关文章:
C/C++服务器基础(网络、协议、数据库)
Socket Socket是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。它可以看成是两个网络应用程序进行通信时,各自通信连接中的端点。Socket上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用…...
Mysql系列-Binlog主从同步
原文链接:https://zhuanlan.zhihu.com/p/669450627 一、主从同步概述 mysql主从同步,即MySQL Replication,可以实现将数据从一台数据库服务器同步到多台数据库服务器。MySQL数据库自带主 从同步功能,经过配置,可以实现基于库、表…...
java设计模式(六)——原型模式
一、模式介绍 原型模式: 创建型模式之一,就是基于原型创建对象,也就是一个对象的产生可以不由零起步, 直接从一个已经具备一定雏形的对象克隆,然后再修改为所需要的对象。节约创建对象时间。 使用场景 如果对象创建成本比较大,例如某个对象里面的数据需要访问数据库才能…...
arm (exti中断)
src/key_it.c 1 #include "key_it.h"2 3 //按键1中断配置4 void key1_config()5 {6 //RCC章节7 //1:使能gpio f8 RCC->MP_AHB4ENSETR | (0x1<<5);9 //因为exti和gic属于芯片内部 所以无需使能10 11 //GPIO章节12 //1:将…...
触摸屏虚拟键盘组件 jQuery Virtual Keyboard使用 自定义键盘
如何在触摸设备上为输入域添加虚拟键盘? 一个插件可以解决这个问题,关键还支持高度自定义(git地址): GitHub - Mottie/Keyboard: Virtual Keyboard using jQuery ~ 官网地址:Virtual Keyboard 使用步骤&…...
面试题07-09
知道了 InnoDB 的索引实现后,就很容易明白为什么不建议使用过长的字段作为主键,因为所有辅助索引都引用主索引,过长的主索引会令辅助索引变得过大。再例如,用非单调的字段作为主键在 InnoDB 中不是个好主意,因为 InnoD…...
MySQL之binlog日志
原文链接:https://zhuanlan.zhihu.com/p/697078870 目录: binlog 是什么binlog 配置和查看binlog 的类型binlog 如何恢复数据binlog 是逻辑日志还是物理日志binlog 作用 注意:以下所有的操作都在 MySQL 8.0 版本实现。 1、binlog 是什么 …...
【大数据】什么是数据湖?一文揭示数据湖的本质
很多人跟我一样,对于数据湖充满好奇,也许还读了不少数据湖文章,但无论别人怎么说,你还是会觉得难以把握数据湖的本质。 有些人会望文生义说,数据湖嘛,就是什么东西都可以往里面扔,特别是对非结构…...
CSS【详解】文本相关样式(含 font 系列,文本排版,文本装饰,分散对齐,渐变色文本等)
文本风格 font-style font-style:italic 值描述normal默认值。浏览器显示一个标准的字体样式。italic加载对应字体的斜体字体文件,若找不到斜体字体文件,则进行物理上的倾斜。 标签默认font-style:italicoblique浏览器会显示一个倾斜的字体样式。 文本粗…...
加油卡APP系统开发,优惠加油收益
目前,汽车已经成为了不可或缺的出行工具,汽车加油更是成为了家家户户要做的事。不过随着油价的波动,车主急需能够进行优惠加油的渠道,因此,加油卡APP成为了大众汽车加油新的选择方式,用户在下载APP后即可享…...
el-scrollbar实现自动滚动到底部(AI聊天)
目录 项目背景 实现步骤 实现代码 完整示例代码 项目背景 chatGPT聊天消息展示滚动面板,每次用户输入提问内容或者ai进行流式回答时需要不断的滚动到底部确保展示最新的消息。 实现步骤 采用element ui 的el-scrollbar作为聊天消息展示组件。 通过操作dom来实…...
开源去除背景的项目:rembg 安装和部署
下载colne项目代码 git clone https://github.com/danielgatis/rembg.git安装依赖 pip install rembg pip install click pip install filetype pip install watchdog pip install aiohttp pip install gradio pip install asyncer测试使用 rembg i 照片.jpg zhaopian.jpg照…...
Docker 使用基础(1)—镜像仓库
🎬慕斯主页:修仙—别有洞天 ♈️今日夜电波:秒針を噛む—ずっと真夜中でいいのに。 0:34━━━━━━️💟──────── 4:20 🔄 ◀️ ⏸ …...
Git详细安装和使用教程
文章目录 准备工作-gitee注册认识及安装GitGit配置用户信息本地初始化Git仓库记录每次更新到仓库查看及切换历史版本Git忽略文件和查看文件状态Git分支-查看及切换Git分支-创建分支Git分支-合并及删除分支Git分支-命令补充Git分支-冲突需求: 准备工作-gitee注册 传送门: gite…...
LeetCode题练习与总结:反转字符串中的单词--151
一、题目描述 给你一个字符串 s ,请你反转字符串中 单词 的顺序。 单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。 返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。 注意:输入字符串 s中可能会存在…...
2.pwn的linux基础(计算机内部数据结构存储形式)
linux基础 保护层级: 分为四个ring0-ring3 一般来说就两个,0和3 0为内核 3为用户 权限: 用户分为多个组 文件和目录等等的权限一般都是三个,即可读可写可执行。 读:R,写:W,执行:X 赋予一个可执行文件执行权限就是chmod x file…...
67.SAP FICO-凭证类型学习
目录 SAP凭证类型 凭证类型的作用 - OBA7 SAP默认的凭证类型更改 FI相应事务代码默认凭证类型 - OBU1 对FB50、60、70默认凭证类型的更改 - OBZO 后勤货物移动默认凭证类型 - OMBA 发货凭证类型 收货凭证类型 自动移动凭证类型 存货盘点凭证类型 发票默认的凭证类…...
井字游戏00
题目链接 井字游戏 题目描述 注意点 1 < board.length board[i].length < 100输入一定遵循井字棋规则 解答思路 如果某一方想要获胜,则其需要占满某一行或某一列或对角线,所以只需要根据第一行和第一列判断是否填充完某一行或某一列或对角线…...
GEE代码实例教程详解:地表温度与土地覆盖类型分析
简介 在本篇博客中,我们将使用Google Earth Engine (GEE) 对地表温度数据进行分析,并探究不同土地覆盖类型(特别是水体和城市区域)的地表温度变化。通过MODIS数据集,我们可以监测2001年至2024年间的数据。 背景知识 …...
RK3568------Openharmony 4.0-Release 浏览器部署安装
RK3568------Openharmony 4.0-Release 浏览器部署安装 文章目录 RK3568------Openharmony 4.0-Release 浏览器部署安装前言一、DevEco Studio开发工具安装与使用二、浏览器(Browser)样例代码编译三 、浏览器(Browser)部署四、遇到的问题五、效果展示总结 前言 上一篇文章讲解了…...
通过Wrangler CLI在worker中创建数据库和表
官方使用文档:Getting started Cloudflare D1 docs 创建数据库 在命令行中执行完成之后,会在本地和远程创建数据库: npx wranglerlatest d1 create prod-d1-tutorial 在cf中就可以看到数据库: 现在,您的Cloudfla…...
家政维修平台实战20:权限设计
目录 1 获取工人信息2 搭建工人入口3 权限判断总结 目前我们已经搭建好了基础的用户体系,主要是分成几个表,用户表我们是记录用户的基础信息,包括手机、昵称、头像。而工人和员工各有各的表。那么就有一个问题,不同的角色…...
智能在线客服平台:数字化时代企业连接用户的 AI 中枢
随着互联网技术的飞速发展,消费者期望能够随时随地与企业进行交流。在线客服平台作为连接企业与客户的重要桥梁,不仅优化了客户体验,还提升了企业的服务效率和市场竞争力。本文将探讨在线客服平台的重要性、技术进展、实际应用,并…...
解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错
出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上,所以报错,到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本,cu、torch、cp 的版本一定要对…...
NFT模式:数字资产确权与链游经济系统构建
NFT模式:数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新:构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议:基于LayerZero协议实现以太坊、Solana等公链资产互通,通过零知…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
【Oracle】分区表
个人主页:Guiat 归属专栏:Oracle 文章目录 1. 分区表基础概述1.1 分区表的概念与优势1.2 分区类型概览1.3 分区表的工作原理 2. 范围分区 (RANGE Partitioning)2.1 基础范围分区2.1.1 按日期范围分区2.1.2 按数值范围分区 2.2 间隔分区 (INTERVAL Partit…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
SQL慢可能是触发了ring buffer
简介 最近在进行 postgresql 性能排查的时候,发现 PG 在某一个时间并行执行的 SQL 变得特别慢。最后通过监控监观察到并行发起得时间 buffers_alloc 就急速上升,且低水位伴随在整个慢 SQL,一直是 buferIO 的等待事件,此时也没有其他会话的争抢。SQL 虽然不是高效 SQL ,但…...
