Bootstrap

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")
#endif


int main(int argc, char** argv) {
	int ret;
	// 配置一下windows socket 版本
	// 一定要加上这个,否者低版本的socket会出很多莫名的问题;
#ifdef WIN32
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	ret = WSAStartup(wVersionRequested, &wsaData);
	if (ret != 0) {
		printf("WSAStart up failed\n");
		system("pause");
		return -1;
	}
#endif

	int 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 WIN32
	WSACleanup();
#endif

	system("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 WIN32
	WORD 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); // TCP
	if (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); // 在这里像卡住了一样的;OS
		printf("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 WIN32
	WSACleanup();
#endif
// end 
	system("pause");
	return 0;
}

说明

这两个部分的代码实现了客户端和服务器的最简单通信,先运行服务器程序监听端口,再打开客户端程序,客户端程序会与服务器建立TCP连接,并发送“Hello”字符串,服务器收到后会回复一个“Hello”给客户端

Select管理模型

用于监听所有Socket,在服务器监听连接端口的同时,能够接收所有客户端传过来的数据。

步骤:

  1. 准备一个句柄集合
  2. 将Socket句柄加入到这个集合
  3. 调用Select函数等待在这个集合上
  4. 当其中一个句柄有时间发生的时候,OS唤醒任务从Select返回
  5. 处理事件,继续Select

根据上一小节服务器代码修改:

  static int client_fd[4096];
  static int socket_count = 0;
  #define MAX_BUF_LEN 4096
  static 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);

        }
      }
    }
  }

缺点:

  1. 每次有事件都需要遍历所有句柄,性能不好
  2. 能够管理句柄的数目有限
  3. 读写仍然是同步的

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 8192

struct io_package {
	// 自己定义的, 一定要在第一个就要用WSAOVERLAPPED 结构体;?
	// 所有的请求的等待,都是等在这个结构对象上的,必须是第一个;
	WSAOVERLAPPED overlapped;

	// 操作类型, 监听,读,写, IOCP_ACCPET, IOCP_RECV, IOCP_WRITE
	int 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 --> WSABUF
	pkg->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_socket
				int 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_FIELDS
 
uv_stream_t  数据结构;
UV_HANDLE_FIELDS
UV_STREAM_FIELDS

uv_tcp_t 数据结构;
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
UV_TCP_PRIVATE_FIELDS

uv_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;
	}
	// end

	buf->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 free


struct timer {
	uv_timer_t uv_timer; // libuv timer handle
	void(*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); // 停止这个timer
			free_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: 标志0
	mode: 可读,可写... 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_t
  result,每次请求的结果都是这个值来返回;
  打开文件: 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关闭事件;

发送数据
  1. 固定字节(0x81)
  2. 包长度字节
  3. 原始数据
接收数据

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-Key
		printf("Sec-WebSocket-Key: %s\n", value_sec_key);
		// key + migic
		static 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, 9
		unsigned 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];
	}

	// test
	static 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;
	}
	// end
	printf("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_FIELDS

uv_stream_t  数据结构;
UV_HANDLE_FIELDS
UV_STREAM_FIELDS

uv_tcp_t 数据结构;
UV_HANDLE_FIELDS
UV_STREAM_FIELDS
UV_TCP_PRIVATE_FIELDS

uv_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=34
   int 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;
   }
   // end

   buf->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 0
	int  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 0
	int 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 0
	int 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]);
		}
	}
	// end

	mysql_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 seconds
	redisContext* 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

	// SHA1
	unsigned 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("[email protected]");
	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 

	// email
	fd_des = des->FindFieldByName("email");
	ref->SetString(msg, fd_des, "[email protected]");
	// end

	// array
	fd_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;

;