Socket网络编程--聊天程序(8)
上一節(jié)已經(jīng)完成了對用戶的身份驗證了,既然有了驗證,那么接下來就能對不同的客戶端進(jìn)行區(qū)分了,所以這一節(jié)講實現(xiàn)私聊功能。就是通過服務(wù)器對客戶端的數(shù)據(jù)進(jìn)行轉(zhuǎn)發(fā)到特定的用戶上,
實現(xiàn)私聊功能的聊天程序
實現(xiàn)的技術(shù)細(xì)節(jié)是:對客戶端發(fā)送的數(shù)據(jù)增加一個標(biāo)識頭,由于我們處理的是純文本,所以為了講解的方便就把標(biāo)識頭加到聊天信息的前面,然后在服務(wù)器中判斷。如果是要在做成產(chǎn)品的話,因為要考慮傳送純文本,圖片,文件,特定的結(jié)構(gòu)體等等其他非純文本信息,那么我們可以對一次聊天信息,發(fā)送兩次數(shù)據(jù),第一次用TCP發(fā)送一個結(jié)構(gòu)體,該結(jié)構(gòu)體包含接下來要接收的信息的格式,大小等信息,然后第二次就發(fā)送真正的數(shù)據(jù)塊。
關(guān)于第二次發(fā)送為什么要用UDP呢?這個是學(xué)騰訊qq的,因為標(biāo)識結(jié)構(gòu)體比較小,而且是必須要有的(為什么?)所以使用TCP,而信息那一部分,往往是數(shù)據(jù)比較大的,都用tcp傳的話,會占用更多的資源。所以我們聊天的時候,有時候會出現(xiàn)這樣一條信息,“由于網(wǎng)絡(luò)問題,該信息可能發(fā)送失敗”。想想,如果是tcp傳輸,那么就只有成功和失敗,沒有什么可能的問題,注意傳文件是兩個客戶端進(jìn)行tcp連接的,不然怎么確保正確性呢,哎,其實還是很復(fù)雜的。(真的嗎?求辟謠!)
回到我們的程序中來吧,我的處理辦法是在服務(wù)器里判斷第一個單詞是不是simple,如果是就讀取第二個單詞,為用戶名,然后根據(jù)用戶名從fd_C中查找,看fd_C對應(yīng)fd_A的socket號碼,然后根據(jù)這個fd號碼進(jìn)行轉(zhuǎn)發(fā),而不是進(jìn)行群發(fā)。(如果要增加什么功能,如傳文件的話那么,道理一樣,判斷第一個單詞是不是file,如果是第二個單詞是文件名什么的,具體就是這樣做的。如果是做有界面的客戶端,就可以進(jìn)行選擇要聊天的用戶,然后在后臺生成simple這個標(biāo)識號了。就對用戶友好一點。)
好了,到了激動人心的時刻了,下面是代碼講解。
client.c 基本不變
server.c?
... 17 #include <mysql.h>//用于mysql連接... 24 25 struct user 26 {...29 }; 30 31 int MAX(int a,int b)...
37 38 void print_time(char * ch,time_t *now) 39 {...
43 } 44 45 46 int mysql_check_login(struct user su) 47 {...
91 return 0; 92 } 93 94 //根據(jù)用戶名返回該用戶名在fd_A中的位置 95 //fd=-1,表示沒有該用戶 //fd>0 正常返回 96 int fd_ctoa(char fd_C[][32],char *ch) 97 { 98 int i,j; 99 int fd=-1; 100 for(i=0;i<BACKLOG;i++) 101 { 102 if(strcmp(fd_C[i],ch)==0) 103 { 104 fd=i; 105 break; 106 } 107 } 108 return fd; 109 } 110 111 int main(int argc,char *argv[]) 112 {... 187 while(1) 188 { 189 FD_ZERO(&servfd);//清空所有server的fd 190 FD_ZERO(&recvfd);//清空所有client的fd 191 FD_SET(sockfd,&servfd); 192 //timeout.tv_sec=30;//可以減少判斷的次數(shù) 193 switch(select(max_servfd+1,&servfd,NULL,NULL,&timeout)) 194 {
... ...
242 } 243 //FD_COPY(recvfd,servfd); 244 for(i=0;i<MAX_CON_NO;i++)//最大隊列進(jìn)行判斷,優(yōu)化的話,可以使用鏈表 245 {...
250 } 251 252 switch(select(max_recvfd+1,&recvfd,NULL,NULL,&timeout)) 253 { 254 case -1: 255 //select error 256 break; 257 case 0: 258 //timeout 259 break; 260 default: 261 for(i=0;i<conn_amount;i++) 262 { 263 if(FD_ISSET(fd_A[i],&recvfd)) 264 { 265 /*receive datas from client*/ 266 if((recvSize=recv(fd_A[i],recvBuf,MAX_DATA_SIZE,0))==-1 || recvSize==0) 267 {...
273 } 274 else//客戶端發(fā)送數(shù)據(jù)過來,然后這里進(jìn)行轉(zhuǎn)發(fā) 275 { 276 /*send datas to client*/ 277 /*下面是私聊代碼,為了方便講解所以寫在這里*/ 278 sscanf(recvBuf,"%s%s",ch,username); 279 if(strcmp(ch,"simple")==0)//判斷第一個單詞是不是simple私聊的標(biāo)識符 280 { 281 printf("私聊信息處理: %s\n",recvBuf); 282 for(j=0;j<strlen(recvBuf);j++)//為了方便我規(guī)定聊天信息中以#符號后面為發(fā)送的聊天文本前面為標(biāo)識符號(一切都是為了方便 ^v^) 283 { 284 if(recvBuf[j]=='#') 285 { 286 j++; 287 break; 288 } 289 } 290 if(j<strlen(recvBuf)) 291 { 292 printf("%s對%s私聊說:%s\n",fd_C[i],username,&recvBuf[j]);//打印在服務(wù)器控制臺方便調(diào)試 293 fd=fd_ctoa(fd_C,username);//根據(jù)用戶名得到該用戶名所對應(yīng)的fd_A中的位置 294 printf("fd=%d\n",fd);//打印描述符號,用于調(diào)試 295 if(fd>=0)//表示找到對應(yīng)的用戶名 296 { 297 strcpy(sendBuf,fd_C[i]); 298 strcat(sendBuf," 對您私聊 "); 299 print_time(ch,&now); 300 strcat(sendBuf,ch);//加個時間戳 301 strcat(sendBuf,"\t\t"); 302 strcat(sendBuf,&recvBuf[j]); 303 //sendSize=send(fd_A[fd],&recvBuf[j],strlen(recvBuf)-j,0); 304 sendSize=send(fd_A[fd],sendBuf,strlen(sendBuf),0);//發(fā)往指定的客戶端 305 } 306 else 307 { 308 strcpy(ch,"私聊信息發(fā)送失敗,可能是沒有該用戶"); 309 sendSize=send(fd_A[i],ch,strlen(ch),0); 310 311 } 312 } 313 else 314 { 315 strcpy(ch,"私聊信息發(fā)送失敗,可能是沒有 # 符號"); 316 sendSize=send(fd_A[i],ch,strlen(ch),0); 317 318 } 319 break; 320 } 321 else 322 { 323 } 324 //其他else就是其他命令了,為了方便就不支持其他命令了 325 ...
352 } 353 } 354 } 355 break; 356 }//end-switch 357 }//end-while(1) 358 return 0; 359 }
? 照例給個運(yùn)行時的截圖,提提神。
好了,我們已經(jīng)完成群聊和私聊的功能了,作為一個聊天程序?qū)崿F(xiàn)這兩個基本功能也就差不多啦。
一點小小的補(bǔ)充:
//上一節(jié)忘了說mysql怎么設(shè)置開機(jī)啟動了,指令如下,root用戶執(zhí)行 chkconfig mysqld on service mysqld start另一個知識點的補(bǔ)充,也是今天才注意到的。以前我們每登陸一個客戶端都會分配一個文件描述符fd,而服務(wù)器中對每個連接產(chǎn)生的fd號是從3開始,連一個就加一個。而現(xiàn)在分配的ID(fd)號是從4開始的不說,還每次增加2。這就奇怪了。
1 [myuser@localhost client-server]$ ./server ser 2 username:ser 3 Success to establish a socket... 4 Success to bind the socket... 5 Success to accpet a connection request... 6 >>>>>> 127.0.0.1:54880 join in! ID(fd):4 7 加入的時間是:06:39:04 8 9 客戶端發(fā)來的用戶名是:user3,密碼:123456 10 查詢的sql:select * from clients where username="user3" and password="123456"; 11 驗證成功! 12 Success to accpet a connection request... 13 >>>>>> 127.0.0.1:54881 join in! ID(fd):6 14 加入的時間是:06:39:04 15 16 客戶端發(fā)來的用戶名是:user1,密碼:123456 17 查詢的sql:select * from clients where username="user1" and password="123456"; 18 驗證成功! 19 Success to accpet a connection request... 20 >>>>>> 127.0.0.1:54882 join in! ID(fd):8 21 加入的時間是:06:39:04 22 23 客戶端發(fā)來的用戶名是:user2,密碼:123456 24 查詢的sql:select * from clients where username="user2" and password="123456"; 25 驗證成功! 26 數(shù)據(jù)是:user2 06:39:04就是那幾個大紅色標(biāo)出來的fd號,連接3個客戶端居然是分配到4,6,8。而不是3,4,5
還好我們的代碼每次都增加不多,可以很快就知道為什么?因為有了數(shù)據(jù)庫的連接。
解釋:文件描述符0,1,2這三個默認(rèn)分配給stdin,stdout,stderr,然后接下來就按需分配了。3號是服務(wù)器用于接收客戶端請求而創(chuàng)建的sockfd,在一開始就創(chuàng)建了。4號就是client1了,5號就是client1下連接數(shù)據(jù)庫而創(chuàng)建的。由于我們的服務(wù)器對每個連接都要有一次訪問數(shù)據(jù)庫,所以對應(yīng)單數(shù)的那些fd都是用在數(shù)據(jù)庫連接上了。(什么是文件描述符?自己上網(wǎng)查咯)
?
一些小總結(jié),其實網(wǎng)絡(luò)編程還是很有趣的,了解后就會發(fā)現(xiàn)很多看起來很叼的技術(shù),其內(nèi)部底層還是很簡單的實現(xiàn)的。就我們常常聽到的下面這些技術(shù) 防火墻,遠(yuǎn)程控制,遠(yuǎn)程SHELL,VPN,內(nèi)網(wǎng)穿透等等看起來很厲害的技術(shù),都基本上都是使用服務(wù)器,實現(xiàn)一對一的轉(zhuǎn)發(fā)而已。只不過特定的功能還要靠特定的優(yōu)化辦法(如一些特定的IO操作,算法,安全性等)處理而已,也就是優(yōu)化處理速度與安全性。如果是一般的使用,那我們其實都是可以實現(xiàn)的。所以別看一個小小的聊天程序的一個私聊功能,其實還是很多高級應(yīng)用的基礎(chǔ)(麻雀雖小,五臟俱全)。(由于本人技術(shù)問題,本博只提供思路,想法和一個小小的入門級程序。)
?
參考資料
關(guān)于標(biāo)識符包頭的詳解:?http://blog.csdn.net/jia162/article/details/1926576?
?
本文地址:?http://www.cnblogs.com/wunaozai/p/3878374.html
總結(jié)
以上是生活随笔為你收集整理的Socket网络编程--聊天程序(8)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: puppetSvn集成
- 下一篇: VBS建立快捷方式