长沙口碑好的做网站公司哪家好,网站建设7个基本流程分析,黄石网站设计,网站建设王滨1983Socket
Socket是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。它可以看成是两个网络应用程序进行通信时#xff0c;各自通信连接中的端点。Socket上联应用进程#xff0c;下联网络协议栈#xff0c;是应用程序通过网络协议进行通信的接口#xff0c;是应用…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(Lin.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(Lin.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.hstatic 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.hstatic 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.hstruct 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: 服务器回应客户端报文: :keymigic 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
headtitleskynet WebSocket example/title
/head
body scriptvar 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.hstruct 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携带参数格式?unamexiaomingkey18074532323
#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?namexiaomingage34int 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复杂的算法放到工作队列
// 2IO放到我们工作队列,获取数据库结果;
// ....// 是在线程池里面另外一个线程里面调用不在主线程;
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.inilinux为my.cnf 1: 通讯端口: port, 默认是3306; 2: bind-address 127.0.0.1 如果不能远程登陆记得检查下这个参数是否打开; 3: mysql数据库X小时无连接自动关闭, 默认8个小时; interactive_timeout28800000 wait_timeout28800000
使用数据库
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.hint 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.hint 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.05000\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/xhtmlxml,application/xml;q0.9,/;q0.8\r\n” “Accept-Language: en-us,en;q0.5\r\n” “Accept-Encoding: gzip,deflate\r\n” “Accept-Charset: ISO-8859-1,utf-8;q0.7,*;q0.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?qsearch#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;charsetISO-8859-1\r\n” “Content-Length: 122\r\n” “\r\n” “html\r\n” “head\r\n” “titleWrox 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.05000
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0
Accept-Language: en-us,en;q0.5
Connection: keep-alive// 结束
*/
static char* http_get_req GET /favicon.ico HTTP/1.1\r\n
Host: 0.0.0.05000\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/xhtmlxml,application/xml;q0.9,*/*;q0.8\r\n
Accept-Language: en-us,en;q0.5\r\n
Accept-Encoding: gzip,deflate\r\n
Accept-Charset: ISO-8859-1,utf-8;q0.7,*;q0.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?qsearch#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;charsetISO-8859-1\r\n
Content-Length: 122\r\n
\r\n
html\r\n
head\r\n
titleWrox 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.proto5: 把生成的代码文件拖到工程中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(xiaomingbycwedu.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, xiaomingbycwedu.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;