在HTML5中新增了WebSocket,使得通讯变得更加方便。这样一来,Web与硬件的交互除了CGI和XHR的方式外,又有了一个新的方式。那么使用WebSocket又如何与下层通信呢?看看WebSocket的相关介绍就会发现,其类似于HTTP协议的通信,但又不同于HTTP协议通信,其最终使用的是TCP通信。具体的可以参照该文WebScoket 规范 + WebSocket 协议。
我们先来看看通信的效果图
下面是实现的步骤
1.建立SOCKET监听
WebSocket也是TCP通信,所以服务端需要先建立监听,下面是实现的代码。
1. /* server.c */
2. #include <stdio.h>
3. #include <stdlib.h>
4. #include <string.h>
5. #include <unistd.h>
6. #include <sys/socket.h>
7. #include <netinet/in.h>
8.
9. #include "base64.h"
10. #include "sha1.h"
11. #include "intLib.h"
12.
13.
14. #define REQUEST_LEN_MAX 1024
15. #define DEFEULT_SERVER_PORT 8000
16. #define WEB_SOCKET_KEY_LEN_MAX 256
17. #define RESPONSE_HEADER_LEN_MAX 1024
18. #define LINE_MAX 256
19.
20.
21. void shakeHand(int connfd,const char *serverKey);
22. char * fetchSecKey(const char * buf);
23. char * computeAcceptKey(const char * buf);
24. char * analyData(const char * buf,const int bufLen);
25. char * packData(const char * message,unsigned long * len);
26. void response(const int connfd,const char * message);
27.
28. int main(int argc, char *argv[])
29. {
30. struct sockaddr_in servaddr, cliaddr;
31. socklen_t cliaddr_len;
32. int listenfd, connfd;
33. char buf[REQUEST_LEN_MAX];
34. char *data;
35. char str[INET_ADDRSTRLEN];
36. char *secWebSocketKey;
37. int i,n;
38. int connected=0;//0:not connect.1:connected.
39. int port= DEFEULT_SERVER_PORT;
40.
41. if(argc>1)
42. {
43. port=atoi(argv[1]);
44. }
45. if(port<=0||port>0xFFFF)
46. {
47. "Port(%d) is out of range(1-%d)\n",port,0xFFFF);
48. return;
49. }
50. listenfd = socket(AF_INET, SOCK_STREAM, 0);
51.
52. sizeof(servaddr));
53. servaddr.sin_family = AF_INET;
54. servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
55. servaddr.sin_port = htons(port);
56.
57. struct sockaddr *)&servaddr, sizeof(servaddr));
58.
59. listen(listenfd, 20);
60.
61. "Listen %d\nAccepting connections ...\n",port);
62. sizeof(cliaddr);
63. struct sockaddr *)&cliaddr, &cliaddr_len);
64. "From %s at PORT %d\n",
65. sizeof(str)),
66. ntohs(cliaddr.sin_port));
67.
68. while (1)
69. {
70.
71. memset(buf,0,REQUEST_LEN_MAX);
72. n = read(connfd, buf, REQUEST_LEN_MAX);
73. "---------------------\n");
74.
75.
76. if(0==connected)
77. {
78. "read:%d\n%s\n",n,buf);
79. secWebSocketKey=computeAcceptKey(buf);
80. shakeHand(connfd,secWebSocketKey);
81. connected=1;
82. continue;
83. }
84.
85. data=analyData(buf,n);
86. response(connfd,data);
87. }
88. close(connfd);
89. }
2.握手
在建立监听后,网页向服务端发现WebSocket请求,这时需要先进行握手。握手时,客户端会在协议中包含一个握手的唯一Key,服务端在拿到这个Key后,需要加入一个GUID,然后进行sha1的加密,再转换成base64,最后再发回到客户端。这样就完成了一次握手。此种握手方式是针对chrome websocket 13的版本,其他版本的可能会有所不同。下面是实现的代码。
1. char * fetchSecKey(const char * buf)
2. {
3. char *key;
4. char *keyBegin;
5. char *flag="Sec-WebSocket-Key: ";
6. int i=0, bufLen=0;
7.
8. char *)malloc(WEB_SOCKET_KEY_LEN_MAX);
9. memset(key,0, WEB_SOCKET_KEY_LEN_MAX);
10. if(!buf)
11. {
12. return NULL;
13. }
14.
15. keyBegin=strstr(buf,flag);
16. if(!keyBegin)
17. {
18. return NULL;
19. }
20. keyBegin+=strlen(flag);
21.
22. bufLen=strlen(buf);
23. for(i=0;i<bufLen;i++)
24. {
25. if(keyBegin[i]==0x0A||keyBegin[i]==0x0D)
26. {
27. break;
28. }
29. key[i]=keyBegin[i];
30. }
31.
32. return key;
33. }
34.
35. char * computeAcceptKey(const char * buf)
36. {
37. char * clientKey;
38. char * serverKey;
39. char * sha1DataTemp;
40. char * sha1Data;
41. short temp;
42. int i,n;
43. const char * GUID="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
44.
45.
46. if(!buf)
47. {
48. return NULL;
49. }
50. char *)malloc(LINE_MAX);
51. memset(clientKey,0,LINE_MAX);
52. clientKey=fetchSecKey(buf);
53.
54. if(!clientKey)
55. {
56. return NULL;
57. }
58.
59.
60. strcat(clientKey,GUID);
61.
62. sha1DataTemp=sha1_hash(clientKey);
63. n=strlen(sha1DataTemp);
64.
65.
66. char *)malloc(n/2+1);
67. memset(sha1Data,0,n/2+1);
68.
69. for(i=0;i<n;i+=2)
70. {
71. sha1Data[i/2]=htoi(sha1DataTemp,i,2);
72. }
73.
74. serverKey = base64_encode(sha1Data, strlen(sha1Data));
75.
76. return serverKey;
77. }
78.
79. void shakeHand(int connfd,const char *serverKey)
80. {
81. char responseHeader [RESPONSE_HEADER_LEN_MAX];
82.
83. if(!connfd)
84. {
85. return;
86. }
87.
88. if(!serverKey)
89. {
90. return;
91. }
92.
93. '\0',RESPONSE_HEADER_LEN_MAX);
94.
95. "HTTP/1.1 101 Switching Protocols\r\n");
96. "%sUpgrade: websocket\r\n", responseHeader);
97. "%sConnection: Upgrade\r\n", responseHeader);
98. "%sSec-WebSocket-Accept: %s\r\n\r\n", responseHeader, serverKey);
99.
100. "Response Header:%s\n",responseHeader);
101.
102. write(connfd,responseHeader,strlen(responseHeader));
103. }
注意:
1.Connection后面的值与HTTP通信时的不一样了,是Upgrade,而Upgrade又对应到了websocket,这样就标识了该通信协议是websocket的方式。
2.在sha1加密后进行base64编码时,使用sha1加密后的串必须将其当成16进制的字符串,将每两个字符合成一个新的码(0-0xFF间)来进一步计算后,才可以进行base64换算(我开始时就在这里折腾了很久,后面才弄明白还要加上这一步),如果是直接就base64,那就会握手失败。
3.对于sha1和base64网上有很多,后面也附上我所使用的代码。
3.数据传输
握手成功后就可以进行数据传输了,只要按照WebSocket的协议来解就可以了。下面是实现的代码
1. char * analyData(const char * buf,const int bufLen)
2. {
3. char * data;
4. char fin, maskFlag,masks[4];
5. char * payloadData;
6. char temp[8];
7. long n, payloadLen=0;
8. short usLen=0;
9. int i=0;
10.
11.
12. if (bufLen < 2)
13. {
14. return NULL;
15. }
16.
17. // 1bit,1表示最后一帧
18. if (!fin)
19. {
20. return NULL;// 超过一帧暂不处理
21. }
22.
23. // 是否包含掩码
24. if (!maskFlag)
25. {
26. return NULL;// 不包含掩码的暂不处理
27. }
28.
29. // 数据长度
30. if (payloadLen == 126)
31. {
32. memcpy(masks,buf+4, 4);
33. payloadLen =(buf[2]&0xFF) << 8 | (buf[3]&0xFF);
34. char *)malloc(payloadLen);
35. memset(payloadData,0,payloadLen);
36. memcpy(payloadData,buf+8,payloadLen);
37. }
38. else if (payloadLen == 127)
39. {
40. memcpy(masks,buf+10,4);
41. for ( i = 0; i < 8; i++)
42. {
43. temp[i] = buf[9 - i];
44. }
45.
46. memcpy(&n,temp,8);
47. char *)malloc(n);
48. memset(payloadData,0,n);
49. //toggle error(core dumped) if data is too long.
50. payloadLen=n;
51. }
52. else
53. {
54. memcpy(masks,buf+2,4);
55. char *)malloc(payloadLen);
56. memset(payloadData,0,payloadLen);
57. memcpy(payloadData,buf+6,payloadLen);
58. }
59.
60. for (i = 0; i < payloadLen; i++)
61. {
62. char)(payloadData[i] ^ masks[i % 4]);
63. }
64.
65. "data(%d):%s\n",payloadLen,payloadData);
66. return payloadData;
67. }
68.
69. char * packData(const char * message,unsigned long * len)
70. {
71. char * data=NULL;
72. long n;
73.
74. n=strlen(message);
75. if (n < 126)
76. {
77. char *)malloc(n+2);
78. memset(data,0,n+2);
79. data[0] = 0x81;
80. data[1] = n;
81. memcpy(data+2,message,n);
82. *len=n+2;
83. }
84. else if (n < 0xFFFF)
85. {
86. char *)malloc(n+4);
87. memset(data,0,n+4);
88. data[0] = 0x81;
89. data[1] = 126;
90. data[2] = (n>>8 & 0xFF);
91. data[3] = (n & 0xFF);
92. memcpy(data+4,message,n);
93. *len=n+4;
94. }
95. else
96. {
97.
98. // 暂不处理超长内容
99. *len=0;
100. }
101.
102.
103. return data;
104. }
105.
106. void response(int connfd,const char * message)
107. {
108. char * data;
109. long n=0;
110. int i;
111. if(!connfd)
112. {
113. return;
114. }
115.
116. if(!data)
117. {
118. return;
119. }
120. data=packData(message,&n);
121.
122. if(!data||n<=0)
123. {
124. "data is empty!\n");
125. return;
126. }
127.
128. write(connfd,data,n);
129.
130. }
注意:
1.对于超过0xFFFF长度的数据在分析数据部分虽然作了处理,但是在memcpy时会报core dumped的错误,没有解决,请过路的大牛帮忙指点。在packData部分也未对这一部分作处理。
2.在这里碰到了一个郁闷的问题,在命名函数时,将函数名写的过长了(fetchSecWebSocketAcceptkey),结果导致编译通过,但在运行时却莫名其妙的报core dumped的错误,试了很多方法才发现是这个原因,后将名字改短后就OK了。
3.在回复数据时,只要按websocket的协议进行回应就可以了。
附上sha1、base64和intLib的代码(sha1和base64是从网上摘来的)
sha1.h
1. //sha1.h:对字符串进行sha1加密
2. #ifndef _SHA1_H_
3. #define _SHA1_H_
4.
5. #include <stdio.h>
6. #include <stdlib.h>
7. #include <string.h>
8.
9.
10. typedef struct SHA1Context{
11. unsigned Message_Digest[5];
12. unsigned Length_Low;
13. unsigned Length_High;
14. char Message_Block[64];
15. int Message_Block_Index;
16. int Computed;
17. int Corrupted;
18. } SHA1Context;
19.
20. void SHA1Reset(SHA1Context *);
21. int SHA1Result(SHA1Context *);
22. void SHA1Input( SHA1Context *,const char *,unsigned);
23. #endif
24.
25.
26. #define SHA1CircularShift(bits,word) ((((word) << (bits)) & 0xFFFFFFFF) | ((word) >> (32-(bits))))
27.
28. void SHA1ProcessMessageBlock(SHA1Context *);
29. void SHA1PadMessage(SHA1Context *);
30.
31. void SHA1Reset(SHA1Context *context){// 初始化动作
32. context->Length_Low = 0;
33. context->Length_High = 0;
34. context->Message_Block_Index = 0;
35.
36. context->Message_Digest[0] = 0x67452301;
37. context->Message_Digest[1] = 0xEFCDAB89;
38. context->Message_Digest[2] = 0x98BADCFE;
39. context->Message_Digest[3] = 0x10325476;
40. context->Message_Digest[4] = 0xC3D2E1F0;
41.
42. context->Computed = 0;
43. context->Corrupted = 0;
44. }
45.
46.
47. int SHA1Result(SHA1Context *context){// 成功返回1,失败返回0
48. if (context->Corrupted) {
49. return 0;
50. }
51. if (!context->Computed) {
52. SHA1PadMessage(context);
53. context->Computed = 1;
54. }
55. return 1;
56. }
57.
58.
59. void SHA1Input(SHA1Context *context,const char *message_array,unsigned length){
60. if (!length) return;
61.
62. if (context->Computed || context->Corrupted){
63. context->Corrupted = 1;
64. return;
65. }
66.
67. while(length-- && !context->Corrupted){
68. context->Message_Block[context->Message_Block_Index++] = (*message_array & 0xFF);
69.
70. context->Length_Low += 8;
71.
72. context->Length_Low &= 0xFFFFFFFF;
73. if (context->Length_Low == 0){
74. context->Length_High++;
75. context->Length_High &= 0xFFFFFFFF;
76. if (context->Length_High == 0) context->Corrupted = 1;
77. }
78.
79. if (context->Message_Block_Index == 64){
80. SHA1ProcessMessageBlock(context);
81. }
82. message_array++;
83. }
84. }
85.
86. void SHA1ProcessMessageBlock(SHA1Context *context){
87. const unsigned K[] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 };
88. int t;
89. unsigned temp;
90. unsigned W[80];
91. unsigned A, B, C, D, E;
92.
93. for(t = 0; t < 16; t++) {
94. W[t] = ((unsigned) context->Message_Block[t * 4]) << 24;
95. W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16;
96. W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8;
97. W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]);
98. }
99.
100. for(t = 16; t < 80; t++) W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
101.
102. A = context->Message_Digest[0];
103. B = context->Message_Digest[1];
104. C = context->Message_Digest[2];
105. D = context->Message_Digest[3];
106. E = context->Message_Digest[4];
107.
108. for(t = 0; t < 20; t++) {
109. temp = SHA1CircularShift(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0];
110. temp &= 0xFFFFFFFF;
111. E = D;
112. D = C;
113. C = SHA1CircularShift(30,B);
114. B = A;
115. A = temp;
116. }
117. for(t = 20; t < 40; t++) {
118. temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
119. temp &= 0xFFFFFFFF;
120. E = D;
121. D = C;
122. C = SHA1CircularShift(30,B);
123. B = A;
124. A = temp;
125. }
126. for(t = 40; t < 60; t++) {
127. temp = SHA1CircularShift(5,A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
128. temp &= 0xFFFFFFFF;
129. E = D;
130. D = C;
131. C = SHA1CircularShift(30,B);
132. B = A;
133. A = temp;
134. }
135. for(t = 60; t < 80; t++) {
136. temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
137. temp &= 0xFFFFFFFF;
138. E = D;
139. D = C;
140. C = SHA1CircularShift(30,B);
141. B = A;
142. A = temp;
143. }
144. context->Message_Digest[0] = (context->Message_Digest[0] + A) & 0xFFFFFFFF;
145. context->Message_Digest[1] = (context->Message_Digest[1] + B) & 0xFFFFFFFF;
146. context->Message_Digest[2] = (context->Message_Digest[2] + C) & 0xFFFFFFFF;
147. context->Message_Digest[3] = (context->Message_Digest[3] + D) & 0xFFFFFFFF;
148. context->Message_Digest[4] = (context->Message_Digest[4] + E) & 0xFFFFFFFF;
149. context->Message_Block_Index = 0;
150. }
151.
152. void SHA1PadMessage(SHA1Context *context){
153. if (context->Message_Block_Index > 55) {
154. context->Message_Block[context->Message_Block_Index++] = 0x80;
155. while(context->Message_Block_Index < 64) context->Message_Block[context->Message_Block_Index++] = 0;
156. SHA1ProcessMessageBlock(context);
157. while(context->Message_Block_Index < 56) context->Message_Block[context->Message_Block_Index++] = 0;
158. else {
159. context->Message_Block[context->Message_Block_Index++] = 0x80;
160. while(context->Message_Block_Index < 56) context->Message_Block[context->Message_Block_Index++] = 0;
161. }
162. context->Message_Block[56] = (context->Length_High >> 24 ) & 0xFF;
163. context->Message_Block[57] = (context->Length_High >> 16 ) & 0xFF;
164. context->Message_Block[58] = (context->Length_High >> 8 ) & 0xFF;
165. context->Message_Block[59] = (context->Length_High) & 0xFF;
166. context->Message_Block[60] = (context->Length_Low >> 24 ) & 0xFF;
167. context->Message_Block[61] = (context->Length_Low >> 16 ) & 0xFF;
168. context->Message_Block[62] = (context->Length_Low >> 8 ) & 0xFF;
169. context->Message_Block[63] = (context->Length_Low) & 0xFF;
170.
171. SHA1ProcessMessageBlock(context);
172. }
173.
174. /*
175. int sha1_hash(const char *source, char *lrvar){// Main
176. SHA1Context sha;
177. char buf[128];
178.
179. SHA1Reset(&sha);
180. SHA1Input(&sha, source, strlen(source));
181.
182. if (!SHA1Result(&sha)){
183. printf("SHA1 ERROR: Could not compute message digest");
184. return -1;
185. } else {
186. memset(buf,0,sizeof(buf));
187. sprintf(buf, "%08X%08X%08X%08X%08X", sha.Message_Digest[0],sha.Message_Digest[1],
188. sha.Message_Digest[2],sha.Message_Digest[3],sha.Message_Digest[4]);
189. //lr_save_string(buf, lrvar);
190.
191. return strlen(buf);
192. }
193. }
194. */
195.
196. char * sha1_hash(const char *source){// Main
197. SHA1Context sha;
198. char *buf;//[128];
199.
200. SHA1Reset(&sha);
201. SHA1Input(&sha, source, strlen(source));
202.
203. if (!SHA1Result(&sha)){
204. "SHA1 ERROR: Could not compute message digest");
205. return NULL;
206. else {
207. char *)malloc(128);
208. sizeof(buf));
209. "%08X%08X%08X%08X%08X", sha.Message_Digest[0],sha.Message_Digest[1],
210. sha.Message_Digest[2],sha.Message_Digest[3],sha.Message_Digest[4]);
211. //lr_save_string(buf, lrvar);
212.
213. //return strlen(buf);
214. return buf;
215. }
216. }
base64.h
1. #ifndef _BASE64_H_
2. #define _BASE64_H_
3.
4. #include <stdio.h>
5. #include <stdlib.h>
6. #include <string.h>
7.
8. const char base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
9. char* base64_encode(const char* data, int data_len);
10. char *base64_decode(const char* data, int data_len);
11. static char find_pos(char ch);
12.
13. /* */
14. char *base64_encode(const char* data, int data_len)
15. {
16. //int data_len = strlen(data);
17. int prepare = 0;
18. int ret_len;
19. int temp = 0;
20. char *ret = NULL;
21. char *f = NULL;
22. int tmp = 0;
23. char changed[4];
24. int i = 0;
25. ret_len = data_len / 3;
26. temp = data_len % 3;
27. if (temp > 0)
28. {
29. ret_len += 1;
30. }
31. ret_len = ret_len*4 + 1;
32. char *)malloc(ret_len);
33.
34. if ( ret == NULL)
35. {
36. "No enough memory.\n");
37. exit(0);
38. }
39. memset(ret, 0, ret_len);
40. f = ret;
41. while (tmp < data_len)
42. {
43. temp = 0;
44. prepare = 0;
45. '\0', 4);
46. while (temp < 3)
47. {
48. //printf("tmp = %d\n", tmp);
49. if (tmp >= data_len)
50. {
51. break;
52. }
53. prepare = ((prepare << 8) | (data[tmp] & 0xFF));
54. tmp++;
55. temp++;
56. }
57. prepare = (prepare<<((3-temp)*8));
58. //printf("before for : temp = %d, prepare = %d\n", temp, prepare);
59. for (i = 0; i < 4 ;i++ )
60. {
61. if (temp < i)
62. {
63. changed[i] = 0x40;
64. }
65. else
66. {
67. changed[i] = (prepare>>((3-i)*6)) & 0x3F;
68. }
69. *f = base[changed[i]];
70. //printf("%.2X", changed[i]);
71. f++;
72. }
73. }
74. '\0';
75.
76. return ret;
77.
78. }
79. /* */
80. static char find_pos(char ch)
81. {
82. char *ptr = (char*)strrchr(base, ch);//the last position (the only) in base[]
83. return (ptr - base);
84. }
85. /* */
86. char *base64_decode(const char *data, int data_len)
87. {
88. int ret_len = (data_len / 4) * 3;
89. int equal_count = 0;
90. char *ret = NULL;
91. char *f = NULL;
92. int tmp = 0;
93. int temp = 0;
94. char need[3];
95. int prepare = 0;
96. int i = 0;
97. if (*(data + data_len - 1) == '=')
98. {
99. equal_count += 1;
100. }
101. if (*(data + data_len - 2) == '=')
102. {
103. equal_count += 1;
104. }
105. if (*(data + data_len - 3) == '=')
106. //seems impossible
107. equal_count += 1;
108. }
109. switch (equal_count)
110. {
111. case 0:
112. //3 + 1 [1 for NULL]
113. break;
114. case 1:
115. //Ceil((6*3)/8)+1
116. break;
117. case 2:
118. //Ceil((6*2)/8)+1
119. break;
120. case 3:
121. //Ceil((6*1)/8)+1
122. break;
123. }
124. char *)malloc(ret_len);
125. if (ret == NULL)
126. {
127. "No enough memory.\n");
128. exit(0);
129. }
130. memset(ret, 0, ret_len);
131. f = ret;
132. while (tmp < (data_len - equal_count))
133. {
134. temp = 0;
135. prepare = 0;
136. memset(need, 0, 4);
137. while (temp < 4)
138. {
139. if (tmp >= (data_len - equal_count))
140. {
141. break;
142. }
143. prepare = (prepare << 6) | (find_pos(data[tmp]));
144. temp++;
145. tmp++;
146. }
147. prepare = prepare << ((4-temp) * 6);
148. for (i=0; i<3 ;i++ )
149. {
150. if (i == temp)
151. {
152. break;
153. }
154. char)((prepare>>((2-i)*8)) & 0xFF);
155. f++;
156. }
157. }
158. '\0';
159. return ret;
160. }
161.
162. #endif
intLib.h
1. #ifndef _INT_LIB_H_
2. #define _INT_LIB_H_
3. int tolower(int c)
4. {
5. if (c >= 'A' && c <= 'Z')
6. {
7. return c + 'a' - 'A';
8. }
9. else
10. {
11. return c;
12. }
13. }
14.
15. int htoi(const char s[],int start,int len)
16. {
17. int i,j;
18. int n = 0;
19. if (s[0] == '0' && (s[1]=='x' || s[1]=='X')) //判断是否有前导0x或者0X
20. {
21. i = 2;
22. }
23. else
24. {
25. i = 0;
26. }
27. i+=start;
28. j=0;
29. for (; (s[i] >= '0' && s[i] <= '9')
30. 'a' && s[i] <= 'f') || (s[i] >='A' && s[i] <= 'F');++i)
31. {
32. if(j>=len)
33. {
34. break;
35. }
36. if (tolower(s[i]) > '9')
37. {
38. 'a');
39. }
40. else
41. {
42. '0');
43. }
44. j++;
45. }
46. return n;
47. }
48.
49.
50. #endif
51.