Socket 與 Websocket通信交互 Linux/C++/epoll網絡模型

                 Socket 與 Websocket通信交互 Linux/C++/epoll網絡模型

 簡要:Websocket是基於http協議實現的,而Socket是基於TCP/IP協議實現的。所以要想使Socket與Websocket進行數據交互,就必須在網絡層手動解析http協議,大致分爲兩個步驟:握手連接 拆分協議幀。本實例使用Linux網絡庫,C++開發語言,epoll網絡模型(不熟悉的童鞋可以百度,兩者網絡數據通信和epoll網絡模型沒有聯繫)

示例代碼:

1.main測試cpp

#include <stdio.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/resource.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>

#include <map>
#include <sstream>

#include "define.h"
#include "SHA1.h"
#include "base64.hpp"

#define MAXBUF 1024
#define MAXEPOLLSIZE 10000

static int ConnType[MAXBUF] = {unknow};

// 解析http數據頭信息
int fetch_http_info(std::map<std::string, std::string>& header_map, const std::string& src)
{
    std::string end_char = "\r\n\r\n";
    std::string::size_type pos = src.find(end_char);
    if (std::string::npos == pos)
    {
        return 0;
    }

    std::istringstream s(src);
    std::string line;

    while (std::getline(s, line))
    {
        if (!line.empty() &&
            line[line.size() - 1] == '\r')
        {
            line.erase(line.end() - 1);
        }

         std::string::size_type end = line.find(": ", 0);
        if (end != std::string::npos)
        {
            std::string key = line.substr(0, end);
            std::string value = line.substr(end + 2);
            header_map[key] = value;
        }
    }

    return int(pos + end_char.size());
};

//解析數據
unsigned char * analyData(unsigned char * buf, unsigned long * bufLen)
{
    char fin, maskFlag,masks[4];
    unsigned char * payloadData;
    char temp[8];
    unsigned long n, payloadLen=0;
    unsigned int i=0;


    if (*bufLen < 2)
    {
        return NULL;
    }

    fin = (buf[0] & 0x80) == 0x80; // 1bit,1表示最後一幀
    if (!fin)
    {
        return NULL;// 超過一幀暫不處理
    }

    maskFlag = (buf[1] & 0x80) == 0x80; // 是否包含掩碼
    if (!maskFlag)
    {
        return NULL;// 不包含掩碼的暫不處理
    }

    payloadLen = buf[1] & 0x7F; // 數據長度
    if (payloadLen == 126)
    {
        memcpy(masks,buf+4, 4);
        payloadLen =(buf[2]&0xFF) << 8 | (buf[3]&0xFF);
        payloadData=(unsigned char *)malloc(payloadLen);
        memset(payloadData,0,payloadLen);
        memcpy(payloadData,buf+8,payloadLen);
    }
    else if (payloadLen == 127)
    {
        memcpy(masks,buf+10,4);
        for ( i = 0; i < 8; i++)
        {
            temp[i] = buf[9 - i];
        }

        memcpy(&n,temp,8);
        payloadData=(unsigned char *)malloc(n);
        memset(payloadData,0,n);
        memcpy(payloadData,buf+14,n);//toggle error(core dumped) if data is too long.
        payloadLen=n;
    }
    else
    {
        memcpy(masks,buf+2,4);
        payloadData=(unsigned char *)malloc(payloadLen);
        memset(payloadData,0,payloadLen);
        memcpy(payloadData,buf+6,payloadLen);
    }

    for (i = 0; i < payloadLen; i++)
    {
        payloadData[i] = (char)(payloadData[i] ^ masks[i % 4]);
    }

    printf("data(%ld):%s\n",payloadLen,payloadData);
    *bufLen = payloadLen;
    return payloadData;
}

//包裝數據
unsigned char * packData(const unsigned char * message,unsigned long * len)
{
    unsigned char * data=NULL;
    unsigned long n;

    n = strlen((const char *)message);
    if (n < 126)
    {
        data=(unsigned char *)malloc(n+2);
        memset(data,0,n+2);
        data[0] = 0x81;
        data[1] = n;
        memcpy(data+2,message,n);
        *len=n+2;
    }
    else if (n < 0xFFFF)
    {
        data=(unsigned char *)malloc(n+4);
        memset(data,0,n+4);
        data[0] = 0x81;
        data[1] = 126;
        data[2] = (n>>8 & 0xFF);
        data[3] = (n & 0xFF);
        memcpy(data+4,message,n);
        *len=n+4;
    }
    else
    {

        // 暫不處理超長內容
        *len=0;
    }

    return data;
}

extern int do_use_fd(int connfd);

int main(int argc, char *argv[])
{
	//設置端口
	if(argc != 2)
	{  
		printf("請設置端口號!\n");
	}
	int port = atoi(argv[1]);  
	
	int listener, conn_sock, kdpfd, nfds, n, ret, curfds;
	socklen_t len;
	struct sockaddr_in server_addr, client_addr;
	struct epoll_event ev;
	struct epoll_event pevent[MAXEPOLLSIZE];
	struct rlimit rt;
	rt.rlim_max = rt.rlim_cur = MAXEPOLLSIZE;
	
	//設置系統資源,打開最大文件數
	if (setrlimit(RLIMIT_NOFILE, &rt) == -1)
	{
		perror("setrlimit");
		exit(EXIT_FAILURE);
	}
	else
	{
		printf("設置系統資源參數成功!\n");
	}
	
	//創建socket
	if( (listener = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		perror("socket");
		exit(EXIT_FAILURE);
	}
	else
	{
		printf("socket 創建成功!\n");
	}
	
	//設置非堵塞
	if (fcntl(listener, F_SETFL, fcntl(listener, F_GETFL, 0) | O_NONBLOCK) == -1)
	{
		perror("fcntl");
		exit(EXIT_FAILURE);
	}
	
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port);
	server_addr.sin_addr.s_addr = INADDR_ANY;  //0.0.0.0所有地址
	
	//綁定
	if ( bind( listener, (struct sockaddr*)&server_addr, sizeof(struct sockaddr)) == -1 )
	{
		perror("bind");
		exit(EXIT_FAILURE);
	}
	else
	{
		printf("IP 地址和端口綁定成功\n");
	}
	
	if (listen(listener, 10) == -1)
	{
		perror("listen");
		exit(EXIT_FAILURE);
	}
	else
	{
		printf("開啓服務成功!\n");
	}
 
	//創建epoll爲ET模式
	kdpfd = epoll_create(MAXEPOLLSIZE);
	len = sizeof(struct sockaddr_in);
	ev.events = EPOLLIN | EPOLLET;
	ev.data.fd = listener;
	
	//socket加入epoll
	if( epoll_ctl(kdpfd, EPOLL_CTL_ADD, listener, &ev) < 0 )
	{
		fprintf( stderr, "epoll set insertion error: fd=%d\n", listener );
		exit(EXIT_FAILURE);
	}
	else
	{
		printf("監聽 socket 加入 epoll 成功!\n");
	}
	
	//設置延遲和事件個數,事件由累加完成
	curfds = 1;
	//int timeout = 10*1000;
	while(1)
	{
		//等待有事件發生
		//nfds = epoll_wait(kdpfd, pevent, curfds, timeout);
		nfds = epoll_wait(kdpfd, pevent, curfds, -1);
		if( nfds == -1 )
		{
			perror("epoll_wait");
			break;
		}
		else if (nfds == 0)
		{
			printf("waiting for connecting...\n");
			continue;
		}
		
		for (n = 0; n < nfds; ++n)
		{
			if ((pevent[n].events & EPOLLERR) || (pevent[n].events & EPOLLHUP) || (!(pevent[n].events & EPOLLIN)))
			{
				//此FD上發生錯誤,或者套接字未準備好讀取(那麼爲什麼通知我們?)
				fprintf (stderr, "epoll error\n");
				close(pevent[n].data.fd);
				continue;
			}
			else if (pevent[n].data.fd == listener)
			{
				//我們在監聽套接字上有一個通知,這意味着一個或多個傳入連接
				while (1)
				{
					conn_sock = accept(listener, (struct sockaddr*)&client_addr, &len);
					if( conn_sock == -1 )
					{
						if ((errno == EAGAIN) || (errno == EWOULDBLOCK))
						{
							//我們已經處理了所有傳入的連接
							break;
						}
						else
						{
							perror ("accept error");
							break;
						}
					}
					//else	
					//	printf("有連接來自於: %s:%d, 分配的 socket 爲:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), conn_sock);
					
					char hbuf[1024], sbuf[1024];
					if ( 0 == getnameinfo((struct sockaddr*)&client_addr, len, hbuf, sizeof(hbuf), sbuf, sizeof(sbuf), NI_NUMERICHOST | NI_NUMERICSERV))
						printf("Accepted connection on descriptor %d (host=%s, port=%s)\n", conn_sock, hbuf, sbuf);
					
					if (fcntl(conn_sock, F_SETFL, fcntl(conn_sock, F_GETFL, 0) | O_NONBLOCK) == -1)
					{
						perror("fcntl");
						break;
					}
 
					ev.events = EPOLLIN | EPOLLET;
					ev.data.fd = conn_sock;
					
					if( -1 == epoll_ctl( kdpfd, EPOLL_CTL_ADD, conn_sock, &ev))
					{
						fprintf(stderr, "把 socket '%d' 加入 epoll 失敗!%s\n", conn_sock, strerror(errno));
						exit(EXIT_FAILURE);
					}
					
					curfds ++;					
				}
				continue;
			}
			else
			{
				if (do_use_fd(pevent[n].data.fd) < 0)
				{
					printf ("關閉 %d\n", pevent[n].data.fd);					
					epoll_ctl(kdpfd, EPOLL_CTL_DEL, pevent[n].data.fd,&ev);
                    close(pevent[n].data.fd);
					curfds--;
				}
			}
		}
	}
	
	close(listener);
	close(kdpfd);
	return 0;
}
 
int do_use_fd(int connfd)
{
	int done = 0;
	
	while(1)
	{
		char buf[MAXBUF + 1];
		bzero(buf, MAXBUF + 1);
		int nread;
		
		//讀取客戶端socket流
		nread = recv(connfd, buf, MAXBUF, 0);
		if (nread == -1)
		{
			if (errno != EAGAIN)
			{
				perror ("recv");
				done = -1;
			}
			break;
		}
		else if (nread == 0)
		{
			done = -1;
			break;
		}
		
		printf("%d接收消息成功:'%s',共%d個字節的數據\n", connfd, buf, nread);
		
		//臨時測試寫法 實際用這樣會越界
		if(ConnType[connfd] == unknow)
		{
			// 握手
            std::map<std::string, std::string> header_map;

            int offset = fetch_http_info(header_map, std::string(buf, buf+nread));
            ConnType[connfd] = header_map.find("Sec-WebSocket-Key") != header_map.end() ? websocket : common;

            if (ConnType[connfd] == websocket)
            {
               	nread -= offset;
                if (nread > 0) memcpy(buf, buf + offset ,nread);

                // 握手
                const std::string& client_key = header_map["Sec-WebSocket-Key"];
                const std::string& mask = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

                CSHA1 sha1;
                sha1.Update((unsigned char*)client_key.data(), client_key.size());
                sha1.Update((unsigned char*)mask.data(), mask.size());
                sha1.Final();
                unsigned char key_sh[20] = "";
                sha1.GetHash(key_sh);

                std::string server_key = websocketpp::base64_encode(key_sh, 20);

                std::string response;
                response += "HTTP/1.1 101 Switching Protocols\r\n";
                response += "Upgrade: websocket\r\n";
                response += "Connection: Upgrade\r\n";

                response += "Sec-WebSocket-Accept: ";
                response += server_key;
                response += "\r\n";
                response += "\r\n";

                send(connfd, (unsigned char*)response.data(), response.size(), 0);

                // if connect type is websocket, do pack msg by websocket protocl
                //nPackLen = webPackLen;
               // analyData(pPacket, (unsigned long *)&webPackLen);
            }
		}

		/*if(ConnType[connfd] == websocket)
		{
			std::string bufStr;
			int len = 0;
			len = websocket_struct(bufStr, (unsigned char*)buf, nread);

			//消息不完整
			if(0 == len)
			{
				continue;
			}
		}*/

		if(ConnType[connfd] == websocket)
		{
			const char *callBackData = "Hello, I'm epoll Server....\n";

			data_frame des;
			des.fin = 1;
			des.opcode = 2;
			des.mask = 0;
			des.payload_length = strlen(callBackData);
			des.payload_data = new unsigned char[des.payload_length];
			memcpy(des.payload_data, callBackData, strlen(callBackData));

			std::string bufStr = des.make_data();

			//響應客戶端 
			if ( -1 == send(connfd, (unsigned char*)bufStr.data(), bufStr.size(), 0))
				perror ("write");
		}
	}
 
    return done;
}

2.示例用到的頭文件

define.h

#ifndef __DEFINE_H__
#define __DEFINE_H__

//套接字連接類型
/*enum ServiceType
{
	unknow = 0,	        // 未知
	common,	        // 二進制
	websocket,
};*/


typedef struct ConnectStruct
{
	int connfd;
	int conntype;
}ConnStruct;

#define unknow 0	        // 未知
#define common 1	        // 二進制
#define websocket 2

#endif

l另外需要三個base64.hpp SHA1.cpp SHA1.h文件,百度都可以下載到,所以不添加

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章