Linux下用C编写WebSocet服务以响应HTML5的WebSocket请求

来源:互联网 发布:淘宝极有家和中国质造 编辑:程序博客网 时间:2024/03/28 22:13

在HTML5中新增了WebSocket,使得通讯变得更加方便。这样一来,Web与硬件的交互除了CGI和XHR的方式外,又有了一个新的方式。那么使用WebSocket又如何与下层通信呢?看看WebSocket的相关介绍就会发现,其类似于HTTP协议的通信,但又不同于HTTP协议通信,其最终使用的是TCP通信。具体的可以参照该文WebScoket 规范 + WebSocket 协议。

我们先来看看通信的效果图



下面是实现的步骤

1.建立SOCKET监听

WebSocket也是TCP通信,所以服务端需要先建立监听,下面是实现的代码。

/* server.c */#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include "base64.h"#include "sha1.h"#include "intLib.h"#define REQUEST_LEN_MAX 1024#define DEFEULT_SERVER_PORT 8000#define WEB_SOCKET_KEY_LEN_MAX 256#define RESPONSE_HEADER_LEN_MAX 1024#define LINE_MAX 256void shakeHand(int connfd,const char *serverKey);char * fetchSecKey(const char * buf);char * computeAcceptKey(const char * buf);char * analyData(const char * buf,const int bufLen);char * packData(const char * message,unsigned long * len);void response(const int connfd,const char * message);int main(int argc, char *argv[]){struct sockaddr_in servaddr, cliaddr;socklen_t cliaddr_len;int listenfd, connfd;char buf[REQUEST_LEN_MAX];char *data;char str[INET_ADDRSTRLEN];char *secWebSocketKey;int i,n;int connected=0;//0:not connect.1:connected.int port= DEFEULT_SERVER_PORT;if(argc>1)  {    port=atoi(argv[1]);  }if(port<=0||port>0xFFFF)  {    printf("Port(%d) is out of range(1-%d)\n",port,0xFFFF);    return;  }listenfd = socket(AF_INET, SOCK_STREAM, 0);bzero(&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(port);    bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));listen(listenfd, 20);printf("Listen %d\nAccepting connections ...\n",port);cliaddr_len = sizeof(cliaddr);connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);printf("From %s at PORT %d\n",       inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),       ntohs(cliaddr.sin_port));while (1)  {memset(buf,0,REQUEST_LEN_MAX);n = read(connfd, buf, REQUEST_LEN_MAX);printf("---------------------\n");if(0==connected)  {    printf("read:%d\n%s\n",n,buf);    secWebSocketKey=computeAcceptKey(buf);    shakeHand(connfd,secWebSocketKey);    connected=1;    continue;  }data=analyData(buf,n);response(connfd,data);}close(connfd);}

2.握手

在建立监听后,网页向服务端发现WebSocket请求,这时需要先进行握手。握手时,客户端会在协议中包含一个握手的唯一Key,服务端在拿到这个Key后,需要加入一个GUID,然后进行sha1的加密,再转换成base64,最后再发回到客户端。这样就完成了一次握手。此种握手方式是针对chrome websocket 13的版本,其他版本的可能会有所不同。下面是实现的代码。

char * fetchSecKey(const char * buf){  char *key;  char *keyBegin;  char *flag="Sec-WebSocket-Key: ";  int i=0, bufLen=0;  key=(char *)malloc(WEB_SOCKET_KEY_LEN_MAX);  memset(key,0, WEB_SOCKET_KEY_LEN_MAX);  if(!buf)    {      return NULL;    }   keyBegin=strstr(buf,flag);  if(!keyBegin)    {      return NULL;    }  keyBegin+=strlen(flag);  bufLen=strlen(buf);  for(i=0;i<bufLen;i++)    {      if(keyBegin[i]==0x0A||keyBegin[i]==0x0D){  break;}      key[i]=keyBegin[i];    }    return key;}char * computeAcceptKey(const char * buf){  char * clientKey;  char * serverKey;   char * sha1DataTemp;  char * sha1Data;  short temp;  int i,n;  const char * GUID="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";   if(!buf)    {      return NULL;    }  clientKey=(char *)malloc(LINE_MAX);  memset(clientKey,0,LINE_MAX);  clientKey=fetchSecKey(buf);   if(!clientKey)    {      return NULL;    }   strcat(clientKey,GUID);  sha1DataTemp=sha1_hash(clientKey);  n=strlen(sha1DataTemp);  sha1Data=(char *)malloc(n/2+1);  memset(sha1Data,0,n/2+1);   for(i=0;i<n;i+=2)    {            sha1Data[i/2]=htoi(sha1DataTemp,i,2);        }   serverKey = base64_encode(sha1Data, strlen(sha1Data));   return serverKey;}void shakeHand(int connfd,const char *serverKey){  char responseHeader [RESPONSE_HEADER_LEN_MAX];  if(!connfd)    {      return;    }  if(!serverKey)    {      return;    }  memset(responseHeader,'\0',RESPONSE_HEADER_LEN_MAX);  sprintf(responseHeader, "HTTP/1.1 101 Switching Protocols\r\n");  sprintf(responseHeader, "%sUpgrade: websocket\r\n", responseHeader);  sprintf(responseHeader, "%sConnection: Upgrade\r\n", responseHeader);  sprintf(responseHeader, "%sSec-WebSocket-Accept: %s\r\n\r\n", responseHeader, serverKey);   printf("Response Header:%s\n",responseHeader);  write(connfd,responseHeader,strlen(responseHeader));}

注意:

1.Connection后面的值与HTTP通信时的不一样了,是Upgrade,而Upgrade又对应到了websocket,这样就标识了该通信协议是websocket的方式。

2.在sha1加密后进行base64编码时,使用sha1加密后的串必须将其当成16进制的字符串,将每两个字符合成一个新的码(0-0xFF间)来进一步计算后,才可以进行base64换算(我开始时就在这里折腾了很久,后面才弄明白还要加上这一步),如果是直接就base64,那就会握手失败。

3.对于sha1和base64网上有很多,后面也附上我所使用的代码。


3.数据传输

握手成功后就可以进行数据传输了,只要按照WebSocket的协议来解就可以了。下面是实现的代码

char * analyData(const char * buf,const int bufLen){  char * data;  char fin, maskFlag,masks[4];  char * payloadData;  char temp[8];  unsigned long n, payloadLen=0;  unsigned short usLen=0;  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=(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=(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=(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(%d):%s\n",payloadLen,payloadData);     return payloadData;}char *  packData(const char * message,unsigned long * len) {         char * data=NULL; unsigned long n; n=strlen(message);            if (n < 126)            {      data=(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=(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; }void response(int connfd,const char * message){  char * data;  unsigned long n=0;  int i;  if(!connfd)    {      return;    }  if(!data)    {      return;    }  data=packData(message,&n);    if(!data||n<=0)    {      printf("data is empty!\n");      return;    }    write(connfd,data,n);  }

注意:

1.对于超过0xFFFF长度的数据在分析数据部分虽然作了处理,但是在memcpy时会报core dumped的错误,没有解决,请过路的大牛帮忙指点。在packData部分也未对这一部分作处理。

2.在这里碰到了一个郁闷的问题,在命名函数时,将函数名写的过长了(fetchSecWebSocketAcceptkey),结果导致编译通过,但在运行时却莫名其妙的报core dumped的错误,试了很多方法才发现是这个原因,后将名字改短后就OK了。

3.在回复数据时,只要按websocket的协议进行回应就可以了。

附上sha1、base64和intLib的代码(sha1和base64是从网上摘来的)

sha1.h

//sha1.h:对字符串进行sha1加密#ifndef _SHA1_H_#define _SHA1_H_#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct SHA1Context{unsigned Message_Digest[5];      unsigned Length_Low;             unsigned Length_High;            unsigned char Message_Block[64]; int Message_Block_Index;         int Computed;                    int Corrupted;                   } SHA1Context;void SHA1Reset(SHA1Context *);int SHA1Result(SHA1Context *);void SHA1Input( SHA1Context *,const char *,unsigned);#endif#define SHA1CircularShift(bits,word) ((((word) << (bits)) & 0xFFFFFFFF) | ((word) >> (32-(bits))))void SHA1ProcessMessageBlock(SHA1Context *);void SHA1PadMessage(SHA1Context *);void SHA1Reset(SHA1Context *context){// 初始化动作context->Length_Low             = 0;context->Length_High            = 0;context->Message_Block_Index    = 0;context->Message_Digest[0]      = 0x67452301;context->Message_Digest[1]      = 0xEFCDAB89;context->Message_Digest[2]      = 0x98BADCFE;context->Message_Digest[3]      = 0x10325476;context->Message_Digest[4]      = 0xC3D2E1F0;context->Computed   = 0;context->Corrupted  = 0;}int SHA1Result(SHA1Context *context){// 成功返回1,失败返回0if (context->Corrupted) {return 0;}if (!context->Computed) {SHA1PadMessage(context);context->Computed = 1;}return 1;}void SHA1Input(SHA1Context *context,const char *message_array,unsigned length){if (!length) return;if (context->Computed || context->Corrupted){context->Corrupted = 1;return;}while(length-- && !context->Corrupted){context->Message_Block[context->Message_Block_Index++] = (*message_array & 0xFF);context->Length_Low += 8;context->Length_Low &= 0xFFFFFFFF;if (context->Length_Low == 0){context->Length_High++;context->Length_High &= 0xFFFFFFFF;if (context->Length_High == 0) context->Corrupted = 1;}if (context->Message_Block_Index == 64){SHA1ProcessMessageBlock(context);}message_array++;}}void SHA1ProcessMessageBlock(SHA1Context *context){const unsigned K[] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 };int         t;                unsigned    temp;             unsigned    W[80];            unsigned    A, B, C, D, E;    for(t = 0; t < 16; t++) {W[t] = ((unsigned) context->Message_Block[t * 4]) << 24;W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16;W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8;W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]);}for(t = 16; t < 80; t++)  W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);A = context->Message_Digest[0];B = context->Message_Digest[1];C = context->Message_Digest[2];D = context->Message_Digest[3];E = context->Message_Digest[4];for(t = 0; t < 20; t++) {temp =  SHA1CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0];temp &= 0xFFFFFFFF;E = D;D = C;C = SHA1CircularShift(30,B);B = A;A = temp;}for(t = 20; t < 40; t++) {temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];temp &= 0xFFFFFFFF;E = D;D = C;C = SHA1CircularShift(30,B);B = A;A = temp;}for(t = 40; t < 60; t++) {temp = SHA1CircularShift(5,A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];temp &= 0xFFFFFFFF;E = D;D = C;C = SHA1CircularShift(30,B);B = A;A = temp;}for(t = 60; t < 80; t++) {temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];temp &= 0xFFFFFFFF;E = D;D = C;C = SHA1CircularShift(30,B);B = A;A = temp;}context->Message_Digest[0] = (context->Message_Digest[0] + A) & 0xFFFFFFFF;context->Message_Digest[1] = (context->Message_Digest[1] + B) & 0xFFFFFFFF;context->Message_Digest[2] = (context->Message_Digest[2] + C) & 0xFFFFFFFF;context->Message_Digest[3] = (context->Message_Digest[3] + D) & 0xFFFFFFFF;context->Message_Digest[4] = (context->Message_Digest[4] + E) & 0xFFFFFFFF;context->Message_Block_Index = 0;}void SHA1PadMessage(SHA1Context *context){if (context->Message_Block_Index > 55) {context->Message_Block[context->Message_Block_Index++] = 0x80;while(context->Message_Block_Index < 64)  context->Message_Block[context->Message_Block_Index++] = 0;SHA1ProcessMessageBlock(context);while(context->Message_Block_Index < 56) context->Message_Block[context->Message_Block_Index++] = 0;} else {context->Message_Block[context->Message_Block_Index++] = 0x80;while(context->Message_Block_Index < 56) context->Message_Block[context->Message_Block_Index++] = 0;}context->Message_Block[56] = (context->Length_High >> 24 ) & 0xFF;context->Message_Block[57] = (context->Length_High >> 16 ) & 0xFF;context->Message_Block[58] = (context->Length_High >> 8 ) & 0xFF;context->Message_Block[59] = (context->Length_High) & 0xFF;context->Message_Block[60] = (context->Length_Low >> 24 ) & 0xFF;context->Message_Block[61] = (context->Length_Low >> 16 ) & 0xFF;context->Message_Block[62] = (context->Length_Low >> 8 ) & 0xFF;context->Message_Block[63] = (context->Length_Low) & 0xFF;SHA1ProcessMessageBlock(context);}/*int sha1_hash(const char *source, char *lrvar){// MainSHA1Context sha;char buf[128];SHA1Reset(&sha);SHA1Input(&sha, source, strlen(source));if (!SHA1Result(&sha)){printf("SHA1 ERROR: Could not compute message digest");return -1;} else {memset(buf,0,sizeof(buf));sprintf(buf, "%08X%08X%08X%08X%08X", sha.Message_Digest[0],sha.Message_Digest[1],sha.Message_Digest[2],sha.Message_Digest[3],sha.Message_Digest[4]);//lr_save_string(buf, lrvar);return strlen(buf);}}*/char * sha1_hash(const char *source){// MainSHA1Context sha;char *buf;//[128];SHA1Reset(&sha);SHA1Input(&sha, source, strlen(source));if (!SHA1Result(&sha)){printf("SHA1 ERROR: Could not compute message digest");return NULL;} else {  buf=(char *)malloc(128);memset(buf,0,sizeof(buf));sprintf(buf, "%08X%08X%08X%08X%08X", sha.Message_Digest[0],sha.Message_Digest[1],sha.Message_Digest[2],sha.Message_Digest[3],sha.Message_Digest[4]);//lr_save_string(buf, lrvar);//return strlen(buf);return buf;}}

base64.h

#ifndef _BASE64_H_#define _BASE64_H_ #include <stdio.h> #include <stdlib.h>#include <string.h>const char base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; char* base64_encode(const char* data, int data_len); char *base64_decode(const char* data, int data_len); static char find_pos(char ch); /* */ char *base64_encode(const char* data, int data_len) {     //int data_len = strlen(data);     int prepare = 0;     int ret_len;     int temp = 0;     char *ret = NULL;     char *f = NULL;     int tmp = 0;     char changed[4];     int i = 0;     ret_len = data_len / 3;     temp = data_len % 3;     if (temp > 0)     {         ret_len += 1;     }     ret_len = ret_len*4 + 1;     ret = (char *)malloc(ret_len);           if ( ret == NULL)     {         printf("No enough memory.\n");         exit(0);     }     memset(ret, 0, ret_len);     f = ret;     while (tmp < data_len)     {         temp = 0;         prepare = 0;         memset(changed, '\0', 4);         while (temp < 3)         {             //printf("tmp = %d\n", tmp);             if (tmp >= data_len)             {                 break;             }             prepare = ((prepare << 8) | (data[tmp] & 0xFF));             tmp++;             temp++;         }         prepare = (prepare<<((3-temp)*8));         //printf("before for : temp = %d, prepare = %d\n", temp, prepare);         for (i = 0; i < 4 ;i++ )         {             if (temp < i)             {                 changed[i] = 0x40;             }             else             {                 changed[i] = (prepare>>((3-i)*6)) & 0x3F;             }             *f = base[changed[i]];             //printf("%.2X", changed[i]);             f++;         }     }     *f = '\0';           return ret;       } /* */ static char find_pos(char ch)   {     char *ptr = (char*)strrchr(base, ch);//the last position (the only) in base[]     return (ptr - base); } /* */ char *base64_decode(const char *data, int data_len) {     int ret_len = (data_len / 4) * 3;     int equal_count = 0;     char *ret = NULL;     char *f = NULL;     int tmp = 0;     int temp = 0;     char need[3];     int prepare = 0;     int i = 0;     if (*(data + data_len - 1) == '=')     {         equal_count += 1;     }     if (*(data + data_len - 2) == '=')     {         equal_count += 1;     }     if (*(data + data_len - 3) == '=')     {//seems impossible         equal_count += 1;     }     switch (equal_count)     {     case 0:         ret_len += 4;//3 + 1 [1 for NULL]         break;     case 1:         ret_len += 4;//Ceil((6*3)/8)+1         break;     case 2:         ret_len += 3;//Ceil((6*2)/8)+1         break;     case 3:         ret_len += 2;//Ceil((6*1)/8)+1         break;     }     ret = (char *)malloc(ret_len);     if (ret == NULL)     {         printf("No enough memory.\n");         exit(0);     }     memset(ret, 0, ret_len);     f = ret;     while (tmp < (data_len - equal_count))     {         temp = 0;         prepare = 0;         memset(need, 0, 4);         while (temp < 4)         {             if (tmp >= (data_len - equal_count))             {                 break;             }             prepare = (prepare << 6) | (find_pos(data[tmp]));             temp++;             tmp++;         }         prepare = prepare << ((4-temp) * 6);         for (i=0; i<3 ;i++ )         {             if (i == temp)             {                 break;             }             *f = (char)((prepare>>((2-i)*8)) & 0xFF);             f++;         }     }     *f = '\0';     return ret; }#endif

intLib.h

#ifndef _INT_LIB_H_#define _INT_LIB_H_int tolower(int c) {     if (c >= 'A' && c <= 'Z')     {         return c + 'a' - 'A';     }     else     {         return c;     } } int htoi(const char s[],int start,int len) {   int i,j;     int n = 0;     if (s[0] == '0' && (s[1]=='x' || s[1]=='X')) //判断是否有前导0x或者0X    {         i = 2;     }     else     {         i = 0;     }     i+=start;    j=0;    for (; (s[i] >= '0' && s[i] <= '9')    || (s[i] >= 'a' && s[i] <= 'f') || (s[i] >='A' && s[i] <= 'F');++i)     {           if(j>=len){  break;}        if (tolower(s[i]) > '9')         {             n = 16 * n + (10 + tolower(s[i]) - 'a');         }         else         {             n = 16 * n + (tolower(s[i]) - '0');         } j++;    }     return n; } #endif

转载请注明出处http://blog.csdn.net/xxdddail/article/details/19070149






0 0
原创粉丝点击