[转]websocket 连接 C Server的尝试

斯人原创,全部为作者一一分析得之,有不对的地方望赐教。
欢迎转载,转载请注明出处 。
本文地址:http://imsiren.com/archives/629

websocket:

它是基于 TCP链接的 全双工通讯,但与普通的TCP又不同,它提供的是消息流,而不是字节流。

基于 HTTP 长连接的“服务器推”技术
这项技术是在Ajax之后 备受追捧的一项技术,
推送技术Server Push的基础思想是将浏览器主动查询信息改为服务器主动发送信息。服务器发送一批数据,浏览器显示这些数据,同时保证与服务器的连接。当服务器需要再次 发送一批数据时,浏览器显示数据并保持连接。以后,服务器仍然可以发送批量数据,浏览器继续显示数据,依次类推。
客户端拉曳(Client Pull)
在客户端拖曳技术中,服务器发送一批数据,在HTTP响应或文档头标记中插入指令,让浏览器“在5秒内再次装入这些数据”或“10秒内前往某URL装入数据”。当指定的时间达到时,客户端就按照服务器的指示去做,或者刷新当前数据,或者调入新的数据。

而普通的HTTP,则没有这个链接,它通过 客户端主动像Server发起链接请求,主动获取数据,但这样就会有一个致命的缺点:“会产生很多无用的HTTP请求。”
比如 Discuz论坛的短消息:
它通过JS 设置一个时间循环事件,一定时间之后会激活事件向服务器发起一个HTTP请求,取到数据通过js更改当前页面的数据。


如果在没有新数据的情况下,对于服务器来讲,这个HTTP请求就是无意义的连接,在并发量很大的站点中,会给服务器造成很大的压力。
于是…服务器推送 出现了。
它有个别名 Comet!
在client会保持一个HTTP连接到server,这个连接成功之后,server会不断的循环检测数据是否存在,如果存在,那么就像客户端发送这些数据。
它的优点显而易见。
现在已经有很多不错的技术在生产环境中使用了:
1、 通过 Flash作为中间节点,客户端与server的请求通过Flash来转发。
此方法的缺点
客户端必须安装 Flash 播放器;
因为 XMLSocket 没有 HTTP 隧道功能,XMLSocket 类不能自动穿过防火墙;
因为是使用套接口,需要设置一个通信端口,防火墙、代理服务器也可能对非 HTTP 通道端口进行限制;
2、Java Applet 套接口
在客户端使用 Java Applet,通过 java.net.Socket 或 java.net.DatagramSocket 或 java.net.MulticastSocket 建立与服务器端的套接口连接,从而实现“服务器推”。
这种方案最大的不足在于 Java applet 在收到服务器端返回的信息后,无法通过 JavaScript 去更新 HTML 页面的内容。
3、基于Ajax的轮询
AJAX 的出现使得 JavaScript 可以调用 XMLHttpRequest 对象发出 HTTP 请求,JavaScript 响应处理函数根据服务器返回的信息对 HTML 页面的显示进行更新。使用 AJAX 实现“服务器推”与传统的 AJAX 应用不同之处在于:
服务器端会阻塞请求直到有数据传递或超时才返回。
客户端 JavaScript 响应处理函数会在处理完服务器返回的信息后,再次发出请求,重新建立连接。
当客户端处理接收的数据、重新建立连接时,服务器端可能有新的数据到达;这些信息会被服务器端保存直到客户端重新建立连接,客户端会一次把当前服务器端所有的信息取回。
4、基于 Iframe 及 htmlfile 的流(streaming)方式
iframe 是很早就存在的一种 HTML 标记, 通过在 HTML 页面里嵌入一个隐蔵帧,然后将这个隐蔵帧的 SRC 属性设为对一个长连接的请求,服务器端就能源源不断地往客户端输入数据。

浏览器支持

    Chrome                   Supported in version 4+
  • Firefox                    Supported in version 4+
  • Internet Explorer      Supported in version 10+
  • Opera                     Supported in version 10+
  • Safari                     Supported in version 5+

WebSocket 概述

建立一个websocket连接,客户端发送握手请求,服务器返回握手响应,
客户端发送的数据如下:

  1. GET /mychat HTTP/1.1  
  2. Host: server.example.com  
  3. Upgrade: websocket  
  4. Connection: Upgrade  
  5. Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==  
  6. Sec-WebSocket-Protocol: chat  
  7. Sec-WebSocket-Version: 13  
  8. Origin: http://example.com  

Connection: Upgrade  
  • Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=  
  • Sec-WebSocket-Protocol: chat  

 

Note that each line ends with an EOL (end of line) sequence, \n or \r\n. There must be a blank line at the end.
这个握手很像HTTP,但是实际上却不是,它允许服务器以HTTP的方式解释一部分handshake的请求,然后切换为websocket
注意,每一行结尾都要以EOL 空白符结束,\n 或者\r\n。
这里是非常重要的地方!网上资料很少,我就是在这里遇到了问题,开始建立连接之后,永远都不返回句柄,我以为只需要客户端像server发送数据,而server不用返回确认数据,看到这里我才知道我错了!

The client sends a Sec-WebSocket-Key which is base64 encoded. To form a response, the magic string 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 is appended to this (undecoded) key. The resulting string is then hashed with SHA-1, then base64 encoded. Finally, the resulting reply occurs in the header Sec-WebSocket-Accept.
客 户端发送 base64加密的 Sec-WebSocket-Key,服务器接收到这个key之后,将258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼在 key之后,生成HSmrc0sMlYUkAGmm5OPpG2HaGWk=258EAFA5-E914-47DA-95CA- C5AB0DC85B11,然后将得到的结果进行sha-1哈希,最后base64加密,作为Sec-WebSocket-Accept的值 发送给客户端。
如果accept这个值错误的话会抛出 Error during WebSocket handshake: Sec-WebSocket-Accept mismatch
最后关闭连接。。一定要注意哦。

如图

1、构造函数   WebSocket(char *host);

var ws=new WebSocket(“ws://10.32.21.27:9880”);

连接成功后触发

连接出错触发

server端有数据返回时触发

关闭连接时触发

6、ws.send

7、ws.close

>  

  • <html>  
  • <head>  
  •     <meta http-equiv="content-type" content="text/html" />  
  •     <meta name="author" content="blog.anchen8.net" />  
  •    
  •     <title>Untitled 2</title>  
  •     <script>  
  • var socket;  
  • function connect(){  
  •     try{  
  •         socket=new WebSocket('ws://10.32.21.27:9880');  
  •     }catch(e){  
  •         alert('error');  
  •         return;  
  •     }  
  •     socket.onopen = sOpen;  
  •     socket.onerror=sError;  
  •     socket.onmessage=sMessage;  
  •     socket.onclose=sClose  
  •    
  • }  
  • function sOpen(){  
  •     alert('connect success!')  
  • }  
  • function sError(){  
  •     alert('connect error')  
  • }  
  • function sMessage(msg){  
  •     alert('server says:'+msg)  
  • }  
  •    
  • function sClose(){  
  •     alert('connect close')  
  • }  
  • function send(){  
  •     socket.send('hello ,i am siren!')  
  • }  
  • function close(){  
  •     socket.close();  
  • }  
  • </script>  
  • </head>  
  •    
  • <body>  
  • <input id="msg" type="text">  
  • <button id="connect" onclick="connect();">Connect</button>  
  • <button id="send" onclick="send();">Send</button>  
  • <button id="close" onclick="close();">Close</button>  
  •    
  • </body>  
  •    
  • </html>  

 

 * ===================================================================================== 

  •  * 
  •  *       Filename:  socket.c 
  •  * 
  •  *    Description: 
  •  * 
  •  *        Version:  1.0 
  •  *        Created:  07/09/2012 07:00:25 PM 
  •  *       Revision:  none 
  •  *       Compiler:  gcc 
  •  * 
  •  *         Author:  斯人,imsiren.com 
  •  *   Organization: 
  •  * 
  •  * ===================================================================================== 
  •  */  
  • #include <stdlib.h>  
  • #include <stdio.h>  
  • #include <arpa/inet.h>  
  • #include <netinet/in.h>  
  • #include <sys/types.h>  
  • #include <sys/socket.h>  
  • #include <string.h>  
  • #include <openssl/sha.h>  
  • #define PORT 9880  
  • #define MAXLENGTH 1024+1  
  • void parsestr(char *request,char *data){  
  •         int needle;  
  •         strcat(request,"HTTP/1.1 101 WebSocket Protocol Handshake\r\n");  
  •         strcat(request,"Upgrade:WebSocket\r\n");  
  •         strcat(request,"Connection:Upgrade\r\n");  
  • //这个值等于 base64_encode(sha1(key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11)) 由于我这里没有合适的base64算法和sha1算法,所以就不写了。  
  •         strcat(request,"Sec-WebSocket-Accept:ZmQ5OWUxMjgwMTViNTEyM2FmZTRlOGViODZkNTk3OTBjMWRiYjBiYg==\r\n");  
  • }  
  • int main(int argc,char ** argv){  
  •         int sockfd,len,maxfd,ret,retval,newfd;  
  •         int reuse=1;  
  •         fd_set rwfd;  
  •         struct sockaddr_in l_addr;  
  •         struct sockaddr_in c_addr;  
  •         struct timeval tv;  
  •         char buf[MAXLENGTH];  
  •         char request[MAXLENGTH];  
  •   sockfd=socket(AF_INET,SOCK_STREAM,0);  
  •         setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(int));  
  •         if(sockfd<0)  
  •                 perror("socket");  
  •         l_addr.sin_port=htons(PORT);  
  •         l_addr.sin_family=AF_INET;  
  •         l_addr.sin_addr.s_addr=INADDR_ANY;  
  •         if(bind(sockfd,(struct sockaddr*)&l_addr,sizeof(struct sockaddr))<0)  
  •                 perror("bind");  
  •         listen(sockfd,5);  
  •         len=sizeof(struct sockaddr);  
  •         tv.tv_sec=5;  
  •         tv.tv_usec=0;  
  •         bzero(request,MAXLENGTH);  
  •         while(1){  
  •                 newfd=accept(sockfd,(struct sockaddr*)&c_addr,&len);  
  •                 if(newfd==-1){  
  •                         perror("accept");  
  •                         exit(1);  
  •                 }else{  
  •                         printf("%s is comming\n",inet_ntoa(c_addr.sin_addr));  
  •                 }  
  •                 while(1){  
  •                         FD_ZERO(&rwfd);  
  •                         FD_SET(0,&rwfd);  
  •                         FD_SET(newfd,&rwfd);  
  •                         maxfd=0;  
  •                         if(newfd>maxfd)  
  •                                 maxfd=newfd;  
  •                         retval=select(maxfd+1,&rwfd,NULL,NULL,&tv);  
  •                         if(retval==-1){  
  •                                 perror("select");  
  •                                 break;  
  •                         /*  }else if(retval==0){ 
  •                                 printf("no data\n"); 
  •                                 continue; 
  • */  
  •                         }else{  
  •                                 if(FD_ISSET(0,&rwfd)){  
  •                                         bzero(buf,MAXLENGTH);  
  •                                         fgets(buf,MAXLENGTH,stdin);  
  •                                         parsestr(request,buf);  
  •                                         len=send(newfd,request,MAXLENGTH,0);  
  •                                         if(len>0)  
  •  printf("i sayed:%s\n",buf);  
  •                                 }  
  •                                 if(FD_ISSET(newfd,&rwfd)){  
  •                                         len=recv(newfd,buf,MAXLENGTH,0) ;  
  •                                          if(len>0){  
  •                                                 if(strncmp(buf,"quit",4)==0){  
  •                                                         close(newfd);  
  •                                                 }  
  •                                         }else{  
  •                                                 printf("client says:%s\n",buf);  
  •                                         }  
  •                                 }  
  •                         }  
  •                 }  
  •         }  
  •         return 0;  

 

没什么特别的,只是一个简单的收发server,client客户端连接之后,
需要在server输入字符确认连接,握手才能完成哦。
开启监听,客户端连接后 如下图

我们可以看到 发过来的头内容。


这个是我们发回给client的头信息。。

一次握手之后就完成了。。。
虽然websocket非常好用,但是目前来看 很难应用到生产环境中,因为websocket是html5之后才有的,对浏览器的要求较高,上面虽然提到了浏览器支持的版本,但是都还不稳定,websocket目前只属于草案阶段,IE6/7/8甚至9都不支持。
参考文献:

http://en.wikipedia.org/wiki/WebSocket

原文出处:http://imsiren.com/archives/629

posted @ 2015-05-18 15:03  alxe_yu  阅读(327)  评论(0)    收藏  举报