淺談php中使用websocket

在PHP中,開發者須要考慮的東西比較多,從socket的鏈接、創建、綁定、監聽等都須要開發者本身去操做完成,對於初學者來講,難度方面也挺大的,因此本文的思路以下:javascript

一、socket協議的簡介php

二、介紹client與server之間的鏈接原理css

三、PHP中創建socket的過程講解html

四、用一個聊天室做爲實例詳細講解在PHP中如何使用socketjava

1、socket協議的簡介web

  WebSocket是什麼,有什麼優勢json

  WebSocket是一個持久化的協議,這是相對於http非持久化來講的。canvas

  舉個簡單的例子,http1.0的生命週期是以request做爲界定的,也就是一個request,一個response,對於http來講,本次client與server的會話到此結束;而在http1.1中,稍微有所改進,即添加了keep-alive,也就是在一個http鏈接中能夠進行多個request請求和多個response接受操做。然而在實時通訊中,並無多大的做用,http只能由client發起請求,server才能返回信息,即server不能主動向client推送信息,沒法知足實時通訊的要求。而WebSocket能夠進行持久化鏈接,即client只需進行一次握手,成功後便可持續進行數據通訊,值得關注的是WebSocket實現client與server之間全雙工通訊,即server端有數據更新時能夠主動推送給client端。數組

2、介紹client與server之間的socket鏈接原理瀏覽器

一、下面是一個演示client和server之間創建WebSocket鏈接時握手部分

  

 

二、client與server創建socket時握手的會話內容,即request與response

  a、client創建WebSocket時向服務器端請求的信息

  GET /chat HTTP/1.1 
  Host: server.example.com 
  Upgrade: websocket //告訴服務器如今發送的是WebSocket協議
  Connection: Upgrade 
  Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //是一個Base64 encode的值,這個是瀏覽器隨機生成的,用於驗證服務器端返回數據是不是WebSocket助理
  Sec-WebSocket-Protocol: chat, superchat 
  Sec-WebSocket-Version: 13 
  Origin: http://example.com

  b、服務器獲取到client請求的信息後,根據WebSocket協議對數據進行處理並返回,其中要對Sec-WebSocket-Key進行加密等操做

  HTTP/1.1 101 Switching Protocols 
  Upgrade: websocket //依然是固定的,告訴客戶端即將升級的是Websocket協議,而不是mozillasocket,lurnarsocket或者shitsocket
  Connection: Upgrade 
  Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= //這個則是通過服務器確認,而且加密事後的 Sec-WebSocket-Key,也就是client要求創建WebSocket驗證的憑證
  Sec-WebSocket-Protocol: chat

 

三、socket創建鏈接原理圖:

  

 

 

3、PHP中創建socket的過程講解

一、在PHP中,client與server之間創建socket通訊,首先在PHP中建立socket並監聽端口信息,代碼以下:

1

2

3

4

5

6

7

8

//傳相應的IP與端口進行建立socket操做

function WebSocket($address,$port){

    $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

    socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1表示接受全部的數據包

    socket_bind($server$address$port);

    socket_listen($server);

    return $server;

}

二、設計一個循環掛起WebSocket通道,進行數據的接收、處理和發送

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

//對建立的socket循環進行監聽,處理數據

function run(){

    //死循環,直到socket斷開

    while(true){

        $changes=$this->sockets;

        $write=NULL;

        $except=NULL;

         

        /*

        //這個函數是同時接受多個鏈接的關鍵,個人理解它是爲了阻塞程序繼續往下執行。

        socket_select ($sockets, $write = NULL, $except = NULL, NULL);

 

        $sockets能夠理解爲一個數組,這個數組中存放的是文件描述符。當它有變化(就是有新消息到或者有客戶端鏈接/斷開)時,socket_select函數纔會返回,繼續往下執行。

        $write是監聽是否有客戶端寫數據,傳入NULL是不關心是否有寫變化。

        $except是$sockets裏面要被排除的元素,傳入NULL是」監聽」所有。

        最後一個參數是超時時間

        若是爲0:則當即結束

        若是爲n>1: 則最多在n秒後結束,如遇某一個鏈接有新動態,則提早返回

        若是爲null:如遇某一個鏈接有新動態,則返回

        */

        socket_select($changes,$write,$except,NULL);

        foreach($changes as $sock){

             

            //若是有新的client鏈接進來,則

            if($sock==$this->master){

 

                //接受一個socket鏈接

                $client=socket_accept($this->master);

 

                //給新鏈接進來的socket一個惟一的ID

                $key=uniqid();

                $this->sockets[]=$client;  //將新鏈接進來的socket存進鏈接池

                $this->users[$key]=array(

                    'socket'=>$client,  //記錄新鏈接進來client的socket信息

                    'shou'=>false       //標誌該socket資源沒有完成握手

                );

            //不然1.爲client斷開socket鏈接,2.client發送信息

            }else{

                $len=0;

                $buffer='';

                //讀取該socket的信息,注意:第二個參數是引用傳參即接收數據,第三個參數是接收數據的長度

                do{

                    $l=socket_recv($sock,$buf,1000,0);

                    $len+=$l;

                    $buffer.=$buf;

                }while($l==1000);

 

                //根據socket在user池裏面查找相應的$k,即健ID

                $k=$this->search($sock);

 

                //若是接收的信息長度小於7,則該client的socket爲斷開鏈接

                if($len<7){

                    //給該client的socket進行斷開操做,並在$this->sockets和$this->users裏面進行刪除

                    $this->send2($k);

                    continue;

                }

                //判斷該socket是否已經握手

                if(!$this->users[$k]['shou']){

                    //若是沒有握手,則進行握手處理

                    $this->woshou($k,$buffer);

                }else{

                    //走到這裏就是該client發送信息了,對接受到的信息進行uncode處理

                    $buffer $this->uncode($buffer,$k);

                    if($buffer==false){

                        continue;

                    }

                    //若是不爲空,則進行消息推送操做

                    $this->send($k,$buffer);

                }

            }

        }

         

    }

     

}

三、以上服務器端完成的WebSocket的前期工做後,就等着client鏈接進行,client建立WebSocket很簡單,代碼以下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

var ws = new WebSocket("ws://IP:端口");

//握手監聽函數

ws.onopen=function(){

     //狀態爲1證實握手成功,而後把client自定義的名字發送過去

    if(so.readyState==1){

         //握手成功後對服務器發送信息

     so.send('type=add&ming='+n);

    }

}

//錯誤返回信息函數

ws.onerror = function(){

    console.log("error");

};

//監聽服務器端推送的消息

ws.onmessage = function (msg){

    console.log(msg);

}

 

//斷開WebSocket鏈接

ws.onclose = function(){

    ws = false;

}

4、聊天室實例代碼

一、PHP部分

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

<?php

error_reporting(E_ALL ^ E_NOTICE);

ob_implicit_flush();

 

//地址與接口,即建立socket時須要服務器的IP和端口

$sk=new Sock('127.0.0.1',8000);

 

//對建立的socket循環進行監聽,處理數據

$sk->run();

 

//下面是sock類

class Sock{

    public $sockets//socket的鏈接池,即client鏈接進來的socket標誌

    public $users;   //全部client鏈接進來的信息,包括socket、client名字等

    public $master;  //socket的resource,即前期初始化socket時返回的socket資源

     

    private $sda=array();   //已接收的數據

    private $slen=array();  //數據總長度

    private $sjen=array();  //接收數據的長度

    private $ar=array();    //加密key

    private $n=array();

     

    public function __construct($address$port){

 

        //建立socket並把保存socket資源在$this->master

        $this->master=$this->WebSocket($address$port);

 

        //建立socket鏈接池

        $this->sockets=array($this->master);

    }

     

    //對建立的socket循環進行監聽,處理數據

    function run(){

        //死循環,直到socket斷開

        while(true){

            $changes=$this->sockets;

            $write=NULL;

            $except=NULL;

             

            /*

            //這個函數是同時接受多個鏈接的關鍵,個人理解它是爲了阻塞程序繼續往下執行。

            socket_select ($sockets, $write = NULL, $except = NULL, NULL);

 

            $sockets能夠理解爲一個數組,這個數組中存放的是文件描述符。當它有變化(就是有新消息到或者有客戶端鏈接/斷開)時,socket_select函數纔會返回,繼續往下執行。

            $write是監聽是否有客戶端寫數據,傳入NULL是不關心是否有寫變化。

            $except是$sockets裏面要被排除的元素,傳入NULL是」監聽」所有。

            最後一個參數是超時時間

            若是爲0:則當即結束

            若是爲n>1: 則最多在n秒後結束,如遇某一個鏈接有新動態,則提早返回

            若是爲null:如遇某一個鏈接有新動態,則返回

            */

            socket_select($changes,$write,$except,NULL);

            foreach($changes as $sock){

                 

                //若是有新的client鏈接進來,則

                if($sock==$this->master){

 

                    //接受一個socket鏈接

                    $client=socket_accept($this->master);

 

                    //給新鏈接進來的socket一個惟一的ID

                    $key=uniqid();

                    $this->sockets[]=$client;  //將新鏈接進來的socket存進鏈接池

                    $this->users[$key]=array(

                        'socket'=>$client,  //記錄新鏈接進來client的socket信息

                        'shou'=>false       //標誌該socket資源沒有完成握手

                    );

                //不然1.爲client斷開socket鏈接,2.client發送信息

                }else{

                    $len=0;

                    $buffer='';

                    //讀取該socket的信息,注意:第二個參數是引用傳參即接收數據,第三個參數是接收數據的長度

                    do{

                        $l=socket_recv($sock,$buf,1000,0);

                        $len+=$l;

                        $buffer.=$buf;

                    }while($l==1000);

 

                    //根據socket在user池裏面查找相應的$k,即健ID

                    $k=$this->search($sock);

 

                    //若是接收的信息長度小於7,則該client的socket爲斷開鏈接

                    if($len<7){

                        //給該client的socket進行斷開操做,並在$this->sockets和$this->users裏面進行刪除

                        $this->send2($k);

                        continue;

                    }

                    //判斷該socket是否已經握手

                    if(!$this->users[$k]['shou']){

                        //若是沒有握手,則進行握手處理

                        $this->woshou($k,$buffer);

                    }else{

                        //走到這裏就是該client發送信息了,對接受到的信息進行uncode處理

                        $buffer $this->uncode($buffer,$k);

                        if($buffer==false){

                            continue;

                        }

                        //若是不爲空,則進行消息推送操做

                        $this->send($k,$buffer);

                    }

                }

            }

             

        }

         

    }

     

    //指定關閉$k對應的socket

    function close($k){

        //斷開相應socket

        socket_close($this->users[$k]['socket']);

        //刪除相應的user信息

        unset($this->users[$k]);

        //從新定義sockets鏈接池

        $this->sockets=array($this->master);

        foreach($this->users as $v){

            $this->sockets[]=$v['socket'];

        }

        //輸出日誌

        $this->e("key:$k close");

    }

     

    //根據sock在users裏面查找相應的$k

    function search($sock){

        foreach ($this->users as $k=>$v){

            if($sock==$v['socket'])

            return $k;

        }

        return false;

    }

     

    //傳相應的IP與端口進行建立socket操做

    function WebSocket($address,$port){

        $server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);

        socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);//1表示接受全部的數據包

        socket_bind($server$address$port);

        socket_listen($server);

        $this->e('Server Started : '.date('Y-m-d H:i:s'));

        $this->e('Listening on   : '.$address.' port '.$port);

        return $server;

    }

     

     

    /*

    * 函數說明:對client的請求進行迴應,即握手操做

    * @$k clien的socket對應的健,即每一個用戶有惟一$k並對應socket

    * @$buffer 接收client請求的全部信息

    */

    function woshou($k,$buffer){

 

        //截取Sec-WebSocket-Key的值並加密,其中$key後面的一部分258EAFA5-E914-47DA-95CA-C5AB0DC85B11字符串應該是固定的

        $buf  substr($buffer,strpos($buffer,'Sec-WebSocket-Key:')+18);

        $key  = trim(substr($buf,0,strpos($buf,"\r\n")));

        $new_key base64_encode(sha1($key."258EAFA5-E914-47DA-95CA-C5AB0DC85B11",true));

         

        //按照協議組合信息進行返回

        $new_message "HTTP/1.1 101 Switching Protocols\r\n";

        $new_message .= "Upgrade: websocket\r\n";

        $new_message .= "Sec-WebSocket-Version: 13\r\n";

        $new_message .= "Connection: Upgrade\r\n";

        $new_message .= "Sec-WebSocket-Accept: " $new_key "\r\n\r\n";

        socket_write($this->users[$k]['socket'],$new_message,strlen($new_message));

 

        //對已經握手的client作標誌

        $this->users[$k]['shou']=true;

        return true;

         

    }

     

    //解碼函數

    function uncode($str,$key){

        $mask array(); 

        $data ''

        $msg = unpack('H*',$str);

        $head substr($msg[1],0,2); 

        if ($head == '81' && !isset($this->slen[$key])) { 

            $len=substr($msg[1],2,2);

            $len=hexdec($len);//把十六進制的轉換爲十進制

            if(substr($msg[1],2,2)=='fe'){

                $len=substr($msg[1],4,4);

                $len=hexdec($len);

                $msg[1]=substr($msg[1],4);

            }else if(substr($msg[1],2,2)=='ff'){

                $len=substr($msg[1],4,16);

                $len=hexdec($len);

                $msg[1]=substr($msg[1],16);

            }

            $mask[] = hexdec(substr($msg[1],4,2)); 

            $mask[] = hexdec(substr($msg[1],6,2)); 

            $mask[] = hexdec(substr($msg[1],8,2)); 

            $mask[] = hexdec(substr($msg[1],10,2));

            $s = 12;

            $n=0;

        }else if($this->slen[$key] > 0){

            $len=$this->slen[$key];

            $mask=$this->ar[$key];

            $n=$this->n[$key];

            $s = 0;

        }

         

        $e strlen($msg[1])-2;

        for ($i=$s$i<= $e$i+= 2) { 

            $data .= chr($mask[$n%4]^hexdec(substr($msg[1],$i,2))); 

            $n++; 

        

        $dlen=strlen($data);

         

        if($len > 255 && $len $dlen+intval($this->sjen[$key])){

            $this->ar[$key]=$mask;

            $this->slen[$key]=$len;

            $this->sjen[$key]=$dlen+intval($this->sjen[$key]);

            $this->sda[$key]=$this->sda[$key].$data;

            $this->n[$key]=$n;

            return false;

        }else{

            unset($this->ar[$key],$this->slen[$key],$this->sjen[$key],$this->n[$key]);

            $data=$this->sda[$key].$data;

            unset($this->sda[$key]);

            return $data;

        }

         

    }

     

    //與uncode相對

    function code($msg){

        $frame array(); 

        $frame[0] = '81'

        $len strlen($msg);

        if($len < 126){

            $frame[1] = $len<16?'0'.dechex($len):dechex($len);

        }else if($len < 65025){

            $s=dechex($len);

            $frame[1]='7e'.str_repeat('0',4-strlen($s)).$s;

        }else{

            $s=dechex($len);

            $frame[1]='7f'.str_repeat('0',16-strlen($s)).$s;

        }

        $frame[2] = $this->ord_hex($msg);

        $data = implode('',$frame); 

        return pack("H*"$data); 

    }

     

    function ord_hex($data)  { 

        $msg ''

        $l strlen($data); 

        for ($i= 0; $i<$l$i++) { 

            $msg .= dechex(ord($data{$i})); 

        

        return $msg

    }

     

    //用戶加入或client發送信息

    function send($k,$msg){

        //將查詢字符串解析到第二個參數變量中,以數組的形式保存如:parse_str("name=Bill&age=60",$arr)

        parse_str($msg,$g);

        $ar=array();

 

        if($g['type']=='add'){

            //第一次進入添加聊天名字,把姓名保存在相應的users裏面

            $this->users[$k]['name']=$g['ming'];

            $ar['type']='add';

            $ar['name']=$g['ming'];

            $key='all';

        }else{

            //發送信息行爲,其中$g['key']表示面對你們仍是我的,是前段傳過來的信息

            $ar['nrong']=$g['nr'];

            $key=$g['key'];

        }

        //推送信息

        $this->send1($k,$ar,$key);

    }

     

    //對新加入的client推送已經在線的client

    function getusers(){

        $ar=array();

        foreach($this->users as $k=>$v){

            $ar[]=array('code'=>$k,'name'=>$v['name']);

        }

        return $ar;

    }

     

    //$k 發信息人的socketID $key接受人的 socketID ,根據這個socketID能夠查找相應的client進行消息推送,即指定client進行發送

    function send1($k,$ar,$key='all'){

        $ar['code1']=$key;

        $ar['code']=$k;

        $ar['time']=date('m-d H:i:s');

        //對發送信息進行編碼處理

        $str $this->code(json_encode($ar));

        //面對你們即全部在線者發送信息

        if($key=='all'){

            $users=$this->users;

            //若是是add表示新加的client

            if($ar['type']=='add'){

                $ar['type']='madd';

                $ar['users']=$this->getusers();        //取出全部在線者,用於顯示在在線用戶列表中

                $str1 $this->code(json_encode($ar)); //單獨對新client進行編碼處理,數據不同

                //對新client本身單獨發送,由於有些數據是不同的

                socket_write($users[$k]['socket'],$str1,strlen($str1));

                //上面已經對client本身單獨發送的,後面就無需再次發送,故unset

                unset($users[$k]);

            }

            //除了新client外,對其餘client進行發送信息。數據量大時,就要考慮延時等問題了

            foreach($users as $v){

                socket_write($v['socket'],$str,strlen($str));

            }

        }else{

            //單獨對我的發送信息,即雙方聊天

            socket_write($this->users[$k]['socket'],$str,strlen($str));

            socket_write($this->users[$key]['socket'],$str,strlen($str));

        }

    }

     

    //用戶退出向所用client推送信息

    function send2($k){

        $this->close($k);

        $ar['type']='rmove';

        $ar['nrong']=$k;

        $this->send1(false,$ar,'all');

    }

     

    //記錄日誌

    function e($str){

        //$path=dirname(__FILE__).'/log.txt';

        $str=$str."\n";

        //error_log($str,3,$path);

        //編碼處理

        echo iconv('utf-8','gbk//IGNORE',$str);

    }

}

?>

  

二、client部分

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

<!doctype html>

<html>

<head>

<meta charset="utf-8">

<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>

<title>HTML5 websocket 網頁聊天室 javascript php</title>

<style type="text/css">

body,p{margin:0px; padding:0px; font-size:14px; color:#333; font-family:Arial, Helvetica, sans-serif;}

#ltian,.rin{width:98%; margin:5px auto;}

#ltian{border:1px #ccc solid;overflow-y:auto; overflow-x:hidden; position:relative;}

#ct{margin-right:111px; height:100%;overflow-y:auto;overflow-x: hidden;}

#us{width:110px; overflow-y:auto; overflow-x:hidden; float:right; border-left:1px #ccc solid; height:100%; background-color:#F1F1F1;}

#us p{padding:3px 5px; color:#08C; line-height:20px; height:20px; cursor:pointer; overflow:hidden; white-space:nowrap; text-overflow:ellipsis;}

#us p:hover,#us p:active,#us p.ck{background-color:#069; color:#FFF;}

#us p.my:hover,#us p.my:active,#us p.my{color:#333;background-color:transparent;}

button{float:right; width:80px; height:35px; font-size:18px;}

input{width:100%; height:30px; padding:2px; line-height:20px; outline:none; border:solid 1px #CCC;}

.rin p{margin-right:160px;}

.rin span{float:right; padding:6px 5px 0px 5px; position:relative;}

.rin span img{margin:0px 3px; cursor:pointer;}

.rin span form{position:absolute; width:25px; height:25px; overflow:hidden; opacity:0; top:5px; right:5px;}

.rin span input{width:180px; height:25px; margin-left:-160px; cursor:pointer}

 

#ct p{padding:5px; line-height:20px;}

#ct a{color:#069; cursor:pointer;}

#ct span{color:#999; margin-right:10px;}

.c2{color:#999;}

.c3{background-color:#DBE9EC; padding:5px;}

.qp{position:absolute; font-size:12px; color:#666; top:5px; right:130px; text-decoration:none; color:#069;}

#ems{position:absolute; z-index:5; display:none; top:0px; left:0px; max-width:230px; background-color:#F1F1F1; border:solid 1px #CCC; padding:5px;}

#ems img{width:44px; height:44px; border:solid 1px #FFF; cursor:pointer;}

#ems img:hover,#ems img:active{border-color:#A4B7E3;}

#ems a{color:#069; border-radius:2px; display:inline-block; margin:2px 5px; padding:1px 8px; text-decoration:none; background-color:#D5DFFD;}

#ems a:hover,#ems a:active,#ems a.ck{color:#FFF; background-color:#069;}

.tc{text-align:center; margin-top:5px;}

</style>

</head>

 

<body>

<div id="ltian">

    <div id="us" class="jb"></div>

    <div id="ct"></div>

    <a href="javascript:;" class="qp" onClick="this.parentNode.children[1].innerHTML=''">清屏</a>

</div>

<div class="rin">

    <button id="sd">發送</button>

    <span><img src="http://www.yxsss.com/ui/sk/t.png" title="表情" id="imgbq"><img src="http://www.yxsss.com/ui/sk/e.png" title="上傳圖片"><form><input type="file" title="上傳圖片" id="upimg"></form></span>

    <p><input id="nrong"></p>

</div>

<div id="ems"><p></p><p class="tc"></p></div>

<script>

if(typeof(WebSocket)=='undefined'){

    alert('你的瀏覽器不支持 WebSocket ,推薦使用Google Chrome 或者 Mozilla Firefox'); 

}

</script>

<script src="http://www.yxsss.com/ui/p/a.js" type="text/javascript"></script>

<script>

(function(){

    var key='all',mkey;

    var users={};

    var url='ws://127.0.0.1:8000';

    var so=false,n=false;

    var lus=A.$('us'),lct=A.$('ct');

    function st(){

        n=prompt('請給本身取一個響亮的名字:');

        n=n.substr(0,16);

        if(!n){

            return ;   

        }

        //建立socket,注意URL的格式:ws://ip:端口

        so=new WebSocket(url);

        //握手監聽函數

        so.onopen=function(){

            //狀態爲1證實握手成功,而後把client自定義的名字發送過去

            if(so.readyState==1){

                so.send('type=add&ming='+n);

            }

        }

         

        //握手失敗或者其餘緣由鏈接socket失敗,則清除so對象並作相應提示操做

        so.onclose=function(){

            so=false;

            lct.appendChild(A.$$('<p class="c2">退出聊天室</p>'));

        }

         

        //數據接收監聽,接收服務器推送過來的信息,返回的數據給msg,而後進行顯示

        so.onmessage=function(msg){

            eval('var da='+msg.data);

            var obj=false,c=false;

            if(da.type=='add'){

                var obj=A.$$('<p>'+da.name+'</p>');

                lus.appendChild(obj);

                cuser(obj,da.code);

                obj=A.$$('<p><span>['+da.time+']</span>歡迎<a>'+da.name+'</a>加入</p>');

                c=da.code;

            }else if(da.type=='madd'){

                mkey=da.code;

                da.users.unshift({'code':'all','name':'你們'});

                for(var i=0;i<da.users.length;i++){

                    var obj=A.$$('<p>'+da.users[i].name+'</p>');

                    lus.appendChild(obj);

                    if(mkey!=da.users[i].code){

                        cuser(obj,da.users[i].code);

                    }else{

                        obj.className='my';

                        document.title=da.users[i].name;

                    }

                }

                obj=A.$$('<p><span>['+da.time+']</span>歡迎'+da.name+'加入</p>');

                users.all.className='ck';

            }

             

            if(obj==false){

                if(da.type=='rmove'){

                    var obj=A.$$('<p class="c2"><span>['+da.time+']</span>'+users[da.nrong].innerHTML+'退出聊天室</p>');

                    lct.appendChild(obj);

                    users[da.nrong].del();

                    delete users[da.nrong];

                }else{

                    da.nrong=da.nrong.replace(/{\\(\d+)}/g,function(a,b){

                        return '<img src="sk/'+b+'.gif">';

                    }).replace(/^data\:image\/png;base64\,.{50,}$/i,function(a){

                        return '<img src="'+a+'">';

                    });

                    //da.code 發信息人的code

                    if(da.code1==mkey){

                        obj=A.$$('<p class="c3"><span>['+da.time+']</span><a>'+users[da.code].innerHTML+'</a>對我說:'+da.nrong+'</p>');

                        c=da.code;

                    }else if(da.code==mkey){

                        if(da.code1!='all')

                        obj=A.$$('<p class="c3"><span>['+da.time+']</span>我對<a>'+users[da.code1].innerHTML+'</a>說:'+da.nrong+'</p>');

                        else

                        obj=A.$$('<p><span>['+da.time+']</span>我對<a>'+users[da.code1].innerHTML+'</a>說:'+da.nrong+'</p>');

                        c=da.code1;

                    }else if(da.code==false){

                        obj=A.$$('<p><span>['+da.time+']</span>'+da.nrong+'</p>');

                    }else if(da.code1){

                        obj=A.$$('<p><span>['+da.time+']</span><a>'+users[da.code].innerHTML+'</a>對'+users[da.code1].innerHTML+'說:'+da.nrong+'</p>');

                        c=da.code;

                    }

                }

            }

            if(c){

                    obj.children[1].onclick=function(){

                        users[c].onclick();

                    }

                }

            lct.appendChild(obj);

            lct.scrollTop=Math.max(0,lct.scrollHeight-lct.offsetHeight);

             

        }

    }

    A.$('sd').onclick=function(){

        if(!so){

             return st();

        }

        var da=A.$('nrong').value.trim();

        if(da==''){

            alert('內容不能爲空');

            return false;  

        }

        A.$('nrong').value='';

        so.send('nr='+esc(da)+'&key='+key);

    }

    A.$('nrong').onkeydown=function(e){

        var e=e||event;

        if(e.keyCode==13){

            A.$('sd').onclick();

        }

    }

    function esc(da){

        da=da.replace(/</g,'<').replace(/>/g,'>').replace(/\"/g,'"');

        return encodeURIComponent(da);

    }

    function cuser(t,code){

        users[code]=t;

        t.onclick=function(){

            t.parentNode.children.rcss('ck','');

            t.rcss('','ck');

            key=code;

        }

    }

    A.$('ltian').style.height=(document.documentElement.clientHeight - 70)+'px';

    st();

     

 

    var bq=A.$('imgbq'),ems=A.$('ems');

    var l=80,r=4,c=5,s=0,p=Math.ceil(l/(r*c));

    var pt='sk/';

    bq.onclick=function(e){

        var e=e||event;

        if(!so){

             return st();

        }

        ems.style.display='block';

        document.onclick=function(){

            gb();  

        }

        ct();

        try{e.stopPropagation();}catch(o){}

    }

     

    for(var i=0;i<p;i++){

        var a=A.$$('<a href="javascript:;">'+(i+1)+'</a>');

        ems.children[1].appendChild(a);

        ef(a,i);

    }

    ems.children[1].children[0].className='ck';

     

    function ct(){

        var wz=bq.weiz();

        with(ems.style){

            top=wz.y-242+'px';

            left=wz.x+bq.offsetWidth-235+'px';

        }

    }

         

    function ef(t,i){

        t.onclick=function(e){

            var e=e||event;

            s=i*r*c;

            ems.children[0].innerHTML='';

            hh();

            this.parentNode.children.rcss('ck','');

            this.rcss('','ck');

            try{e.stopPropagation();}catch(o){}

        }

    }

     

    function hh(){

        var z=Math.min(l,s+r*c);

        for(var i=s;i<z;i++){

            var a=A.$$('<img src="'+pt+i+'.gif">');

            hh1(a,i);

            ems.children[0].appendChild(a);

        }

        ct();

    }

     

    function hh1(t,i){

        t.onclick=function(e){

            var e=e||event;

            A.$('nrong').value+='{\\'+i+'}';

            if(!e.ctrlKey){

                gb();

            }

            try{e.stopPropagation();}catch(o){}

        }

    }

     

    function gb(){

        ems.style.display='';

        A.$('nrong').focus();

        document.onclick='';

    }

    hh();

    A.on(window,'resize',function(){

        A.$('ltian').style.height=(document.documentElement.clientHeight - 70)+'px';

        ct();

    }) 

 

    var fimg=A.$('upimg');

    var img=new Image();

    var dw=400,dh=300;

    A.on(fimg,'change',function(ev){

        if(!so){

            st();

            return false;

        }

        if(key=='all'){

            alert('因爲資源限制 發圖只能私聊');

            return false;  

        }

        var f=ev.target.files[0];

        if(f.type.match('image.*')){

            var r = new FileReader();

            r.onload = function(e){

                img.setAttribute('src',e.target.result);

            };

            r.readAsDataURL(f);

        }

    });

    img.onload=function(){

        ih=img.height,iw=img.width;

        if(iw/ih > dw/dh && iw > dw){

            ih=ih/iw*dw;

            iw=dw;

        }else if(ih > dh){

            iw=iw/ih*dh;

            ih=dh;

        }

        var rc = A.$$('canvas');

        var ct = rc.getContext('2d');

        rc.width=iw;

        rc.height=ih;

        ct.drawImage(img,0,0,iw,ih);

        var da=rc.toDataURL();

        so.send('nr='+esc(da)+'&key='+key);

    }

     

})();

</script>

</body>

</html>

相關文章
相關標籤/搜索