亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 操作系統 > 正文

Socket網絡編程--聊天程序(9)

2024-06-28 13:25:02
字體:
來源:轉載
供稿:網友
Socket網絡編程--聊天程序(9)

  這一節應該是聊天程序的最后一節了,現在回顧我們的聊天程序,看起來還有很多功能沒有實現,但是不管怎么說,都還是不錯的。這一節我們將講多服務器問題(高大上的說法就是負載問題了。)至于聊天程序的文件發送(也即二進制文件發送例如圖片)和單點登陸(就是多加一個數組fd_L[],用來記錄是否已經登陸過了。),這些問題就不討論了。

  支持多服務器實現負載問題的聊天程序

  今天才知道原來我們一直使用的select來處理IO多路復用的這個函數最多只能有1024個連接,因為內部實現里面的數組就是只有1024,多了不行。什么?一個準備上萬人用的聊天程序就只能1000個人?怎么可能,作為強大的服務器,肯定還有其他可以解決的辦法,系統提供了一個poll和epoll等函數用來處理這個問題。還有一種辦法就是創建多進程或多線程,不同的進程和線程中用一個select,就可以實現1024個以上的連接。所以只要判斷conn_amount的個數如果大于1024那么就創建一個進程(線程)來繼續接收更多的連接。

  可是我們今天要實現的是多個服務器,其原理跟多進程是一樣的。

  程序的運行是這樣的。server2和server3到Server1中注冊,表示對應的服務器可以使用,然后就是各個客戶端了,首先Client1發送請求通訊連接到Server1,然后由Server1發送一個可以使用的服務器(Server2或Server3)ip地址和端口給Client1,再然后由Client1向獲取到的IP和端口的服務器發送連接請求。假如是連接到Server2,就可以建立通訊了。同理Client2,3,4,5都是這樣建立到Server2,Server3的連接。這樣5個客戶端就可以分發到兩個服務器了。至于分配的方法,就可以自己定義了,可以是隨機分配,或者存到數據庫中,如果是存到數據庫中的話,那么是不是很像群功能呢?而且群里的人還是固定的。如果像上圖,如何使Client1和Client4進行通訊的呢?可以判斷Client是否在Server2中,如果不在就由Server2對數據轉發到Server3,再由Server3發送到Client4。這樣就可以了。

  不過我們這一節就沒有完成那么多的功能,只是實現Client1間接鏈接到Server2,Client3間接連接到Server2,然后讓Client1與Client3通訊。其他的服務器之間通訊就不實現了。

  好了廢話不多說,代碼走起。

  client.c 代碼修改如下

    ... 15 struct user 16 {     ...  19 }; 20  21 /*下面增加多服務器代碼*/ 22 struct Addr 23 { 24     char host[64]; 25     int port; 26 }; 27  28 int query_addr(struct Addr *paddr,char *phost,int port) 29 { 30     int sockfd; 31     struct Addr addr; 32     struct hostent * host; 33     struct sockaddr_in servAddr; 34     int size; 35     host=gethostbyname(phost); 36     if(host==NULL) 37     { 38         perror("host 為空"); 39         exit(-1); 40     } 41  42     if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) 43     { 44         perror("socket 失敗"); 45     } 46  47     servAddr.sin_family=AF_INET; 48     servAddr.sin_port=htons(port); 49     servAddr.sin_addr=*((struct in_addr *)host->h_addr); 50     bzero(&(servAddr.sin_zero),8); 51  52     if(connect(sockfd,(struct sockaddr *)&servAddr,sizeof(struct sockaddr_in))==-1) 53     { 54         perror("connect 失敗"); 55         exit(-1); 56     } 57     memset(paddr,0,sizeof(struct Addr)); 58     size=recv(sockfd,(char *)paddr,sizeof(struct Addr),0); 59  60     return 0; 61 } 62  63 int main(int argc,char *argv[]) 64 {    ... 74     struct Addr addr; 75  76  77     if(argc != 5) 78     { 79         perror("use: ./client [hostname] [PRot] [username] [passWord]"); 80         exit(-1); 81     } 82     query_addr(&addr,argv[1],atoi(argv[2])); 83     printf("從服務器獲取到的IP:%s/n/t/t端口:%d/n",addr.host,addr.port); 84     strcpy(use.name,argv[3]); 85     strcpy(use.pwd,argv[4]); 86  87     host=gethostbyname(addr.host);     ...104     servAddr.sin_family=AF_INET;105     servAddr.sin_port=htons(addr.port);106     servAddr.sin_addr=*((struct in_addr *)host->h_addr);107     //servAddr.sin_addr.s_addr=inet_addr("127.0.0.1");108     bzero(&(servAddr.sin_zero),8);109 110     /*connect the socket*/      ... ...168     close(sockfd);169     //kill(0,SIGKILL);//0表示同一進程組的進程170 171     return 0;172 }

  這次增加了一個結構體Addr用來保存服務器的IP地址和端口號的。命令行參數填寫的是super-server的IP地址和端口。然后調用query_addr函數,獲取從super-server返回來的當前可用的服務器的IP地址和端口。然后在進行通訊。

  增加一個super-server.c文件

  1 #include <stdio.h>  2 #include <stdlib.h>  3 #include <errno.h>  4 #include <string.h>  5 #include <netdb.h>  6 #include <sys/types.h>  7 #include <sys/socket.h>  8 #include <sys/time.h>  9 #include <sys/un.h> 10 #include <sys/ioctl.h> 11 #include <sys/wait.h> 12 #include <sys/select.h> 13 #include <netinet/in.h> 14 #include <arpa/inet.h> 15 #include <unistd.h> 16 #include <time.h> 17  18  19 #define SERVER_PORT 12138 20 #define BACKLOG 20 21 #define MAX_CON_NO 10 22 #define MAX_DATA_SIZE 4096 23  24 #define MAX_ADDR 64 25 struct Addr 26 { 27     char host[64]; 28     int port; 29 }; 30  31 struct AddrList //保存所有可用的服務器IP地址和端口,flag表示該地址是否可用,因為服務器可能中途斷開了。 32 { 33     int flag; 34     struct Addr addr; 35 }; 36  37  38 int main(int argc,char *argv[]) 39 { 40     struct sockaddr_in clientSockaddr; 41     int clientfd; 42     char sendBuf[MAX_DATA_SIZE]; 43     int sendSize; 44     int sockfd; 45     int on; 46     int sinSize; 47     struct Addr addr; 48     struct AddrList addrlist[MAX_ADDR]; 49     int addrlist_count=0; 50     int i,ilist; 51  52     memset(addrlist,0,sizeof(addrlist)); 53  54     if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1) 55     { 56         perror("創建socket失敗"); 57         exit(-1); 58     } 59  60     clientSockaddr.sin_family=AF_INET; 61     clientSockaddr.sin_port=htons(SERVER_PORT); //super-server默認使用12138作為服務端口 62     clientSockaddr.sin_addr.s_addr=htonl(INADDR_ANY); 63     bzero(&(clientSockaddr.sin_zero),8); 64  65     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)); 66  67     if(bind(sockfd,(struct sockaddr *)&clientSockaddr,sizeof(struct sockaddr))==-1) 68     { 69         perror("bind 失敗"); 70         exit(-1); 71     } 72  73     //backlog是積壓值,對于TCP,通常建立連接時,會有3/4次握手的過程,一個client連接在完成了建立連接的握手過程,而還沒有被應用層(應用程序)所響應時,這個連接被置于backlog隊列中。當達到backlog隊列以滿時,client的連接請求會返回超時的錯誤。 74     if(listen(sockfd,5)==-1) 75     { 76         perror("listen 失敗"); 77         exit(-1); 78     } 79  80     sinSize=sizeof(clientSockaddr); 81  82     for(i=0;i<MAX_ADDR;i++) 83     { 84         addrlist[i].flag=0; 85     } 86  87     for(i=0;i<2;i++)//這里先固定成兩個,可以修改成select然后實現動態增加服務器和減少服務器 88     { 89         //加入進來的服務器server 90         if((clientfd=accept(sockfd,(struct sockaddr *)&clientSockaddr,&sinSize))==-1) 91         { 92             perror("accept 失敗"); 93             exit(-1); 94         } 95  96         if((sendSize=recv(clientfd,(char *)&addr,sizeof(struct Addr),0))!=sizeof(struct Addr)) 97         { 98             perror("send 失敗"); 99             exit(-1);100         }101         printf("server發過來的地址 %s:%d/n",addr.host,addr.port);102         addrlist[i].flag=1;103         strcpy(addrlist[i].addr.host,addr.host);//保存服務器ip/端口信息到super-server中104         addrlist[i].addr.port=addr.port;105         close(clientfd);106     }107     /*108     addrlist[0].flag=1;109     addrlist[1].flag=1;110     strcpy(addrlist[0].addr.host,"localhost");111     strcpy(addrlist[1].addr.host,"localhost");112     addrlist[0].addr.port=12137;113     addrlist[1].addr.port=12139;114     */115 116     ilist=0;117     i=0;118     while(1)119     {120         /*分配域名/IP和端口*//*分配的方法是輪詢*/121         i=ilist+1;122         while(i<MAX_ADDR)123         {124             if(addrlist[i].flag!=0)125             {126                 ilist=i;127                 break;128             }129             i++;130             i=i%MAX_ADDR;131         }132 133         strcpy(addr.host,addrlist[ilist].addr.host);134         addr.port=addrlist[ilist].addr.port;135         printf("發送給客戶端的id=%d 域名/IP:%s  port:%d /n",ilist,addr.host,addr.port);136 137         if((clientfd=accept(sockfd,(struct sockaddr *)&clientSockaddr,&sinSize))==-1)138         {139             perror("accept 失敗");140             exit(-1);141         }142 143         if((sendSize=send(clientfd,(char *)&addr,sizeof(struct Addr),0))!=sizeof(struct Addr))144         {145             perror("send 失敗");146             exit(-1);147         }148         close(clientfd);149     }150 151     return 0;152 }

  在第87行處是使用固定兩臺服務器server的,這個可以修改成select或poll等進行復用,實時監聽是否有新的服務器server到來或者有服務器離開。這個select版本我就不寫了,看了之前的博客內容就應該會寫,如果還不會那就等Socket網絡編程系列的另外一個程序了,由于程序代碼越來越多,調試起來比較麻煩,講解也不太好講解,所以就準備出新的系列了。還希望多支持啊!╮(╯3╰)╭

  第120行處,采用的分配服務器的方法是輪詢。依靠生成環境的不同這里可以進行修改,比如是隨機分配,依靠數據庫用戶表中的數據選擇指定的服務器進行登陸(這個像不像玩游戲時那個分區啊,什么電信一區,網通二區。就是根據數據庫判斷的)。還有根據用戶的IP獲取用戶所在的城市,然后進行服務器的分配的,以獲得最佳連通效果。QQ群等等什么的都是差不多這樣吧。我猜的!

  最后一個代碼是server.c

    ... 25 struct user 26 {    ... 29 }; 30  31 int MAX(int a,int b) 32 {    ... 36 } 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 //根據用戶名返回該用戶名在fd_A中的位置 95 //fd=-1,表示沒有該用戶 //fd>0 正常返回 96 int fd_ctoa(char fd_C[][32],char *ch) 97 {    ...109 }110 111 /*下面部分是多服務器增加的代碼*/112 struct Addr113 {114     char host[64];115     int port;116 };117 118 int server_register(char *super_server_host,int super_server_port,struct Addr addr)119 {120     int sockfd;121     struct hostent * host;122     struct sockaddr_in servAddr;123     int size;124     host=gethostbyname(super_server_host);125     if(host==NULL)126     {127         perror("host 為空");128         exit(-1);129     }130 131     if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)132     {133         perror("socket 失敗");134     }135 136     servAddr.sin_family=AF_INET;137     servAddr.sin_port=htons(super_server_port);138     servAddr.sin_addr=*((struct in_addr *)host->h_addr);139     bzero(&(servAddr.sin_zero),8);140 141     if(connect(sockfd,(struct sockaddr *)&servAddr,sizeof(struct sockaddr_in))==-1)142     {143         perror("connect 失敗");144         exit(-1);145     }146 147     size=send(sockfd,(char *)&addr,sizeof(struct Addr),0);//傳一個Addr信息過去148 149     printf("連接到超級主機 %s:%d 上,本地打開地址 %s:%d/n",super_server_host,super_server_port,addr.host,addr.port);150 151     return 0;152 }153 154 155 156 int main(int argc,char *argv[])157 {      ...177     struct Addr addr;178 179 180     if(argc != 5)181     {182         printf("usage: ./server [super-server host] [super-server port] [local_host] [local port]/n");183         exit(1);184     }185     strcpy(addr.host,argv[3]);//本機的IP或域名186     addr.port=atoi(argv[4]);//本機的端口187     server_register(argv[1],atoi(argv[2]),addr);//向super-server發送IP和端口,告訴super-server如果有client來連接,那就請把我的地址告訴它,讓它來連接我。188       ...197     /*init sockaddr_in*/198     serverSockaddr.sin_family=AF_INET;199     serverSockaddr.sin_port=htons(atoi(argv[4]));//改一下端口200     serverSockaddr.sin_addr.s_addr=htonl(INADDR_ANY);201     bzero(&(serverSockaddr.sin_zero),8);202 203     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));      ... 234     while(1)235     {236         FD_ZERO(&servfd);//清空所有server的fd237         FD_ZERO(&recvfd);//清空所有client的fd238         FD_SET(sockfd,&servfd);239         //timeout.tv_sec=30;//可以減少判斷的次數240         switch(select(max_servfd+1,&servfd,NULL,NULL,&timeout))241         {             ...289         }290         //FD_COPY(recvfd,servfd);291         for(i=0;i<MAX_CON_NO;i++)//最大隊列進行判斷,優化的話,可以使用鏈表292         {          ...297         }298 299         switch(select(max_recvfd+1,&recvfd,NULL,NULL,&timeout))300         {              ... ... 408         }//end-switch409     }410     return 0;411 }

  雖然我演示的時候所有的操作都是在一臺機器上運行的。其實是可以多個機器同時協作運行的。修改185行處的IP就可以實現不同的機器了。

  程序的makefile

1 main:2         gcc client.c -o client3         gcc server.c `mysql_config --cflags --libs` -o server4         gcc super-server.c -o super-server

  (No picture say a JB)接下來是程序運行時的截圖。由于程序運行過程有點復雜,我們一步一步來。

  首先,運行super-server ,運行的命令是 ./super-server 默認打開的是12138這個端口進行監聽,用于處理服務器和客戶端的連接問題。

  然后就運行兩個server程序,運行的命令分別是

./server localhost 12138 localhost 11111./server localhost 12138 localhost 22222

  打開兩個終端,分別輸入,表示連接超級服務器super-server的12138端口,并且自己的服務器使用11111和22222進行監聽。

  運行后三者的截圖如下

  這樣就兩個服務器啟動了,接下來是啟動三個客戶端,這樣就能保證有兩個是在同一個服務器中了,三者的運行命令分別如下

./client loclhost 12138 user1 123456./client loclhost 12138 user2 123456./client loclhost 12138 user3 123456

  表示連接到超級服務器super-server的12138服務端口,使用用戶名密碼驗證。運行后截圖如下

  從上圖可以看到user1和user3是被分配到同一個服務器中去的。超級服務器中也實現了輪詢的效果了。

  最后一步了,就是看看以前寫的聊天功能還在不在了

  嗯,好了,由于client1和client3是在同一個服務器上,所以進行通訊是沒有問題的,但是client2不在同一個服務器中,就通訊不了了。實現不同服務器上用戶的通訊也不是很難,就是在服務器上增加一個服務器之間的轉發功能就可以了。還有一個問題就是程序中為了方便,有很多地方沒有進行合法性的判斷,而且還有很多很多的BUG。

  

  小結:經過9天,實現了一個小小的聊天程序,有群聊功能,私聊功能,用戶驗證功能,指令系統功能,數據庫連接問題,服務器負載問題。雖然內容沒有什么高大上,但是對于一個初學者來說,想想就有點小激動。

  本系列Socket網絡編程--聊天程序所有章節傳送門如下:

  Socket網絡編程--聊天程序(1)http://www.CUOXin.com/wunaozai/p/3870156.html  Socket網絡編程--聊天程序(2)http://www.CUOXin.com/wunaozai/p/3870194.html  Socket網絡編程--聊天程序(3)http://www.CUOXin.com/wunaozai/p/3870258.html  Socket網絡編程--聊天程序(4)http://www.CUOXin.com/wunaozai/p/3870338.html  Socket網絡編程--聊天程序(5)http://www.CUOXin.com/wunaozai/p/3871563.html  Socket網絡編程--聊天程序(6)http://www.CUOXin.com/wunaozai/p/3875506.html  Socket網絡編程--聊天程序(7)http://www.CUOXin.com/wunaozai/p/3876134.html  Socket網絡編程--聊天程序(8)http://www.CUOXin.com/wunaozai/p/3878374.html  Socket網絡編程--聊天程序(9)http://www.CUOXin.com/wunaozai/p/3880462.html

  所有開發過程中的代碼:http://files.CUOXin.com/wunaozai/Socket-Chat.zip

  因為每一個版本都是上一個版本的修改版,在學習的過程中,如果想知道這一小節增加了什么內容,可以用 vimdiff file1 file2 比較兩個文件,就知道修改了哪些內容。

  


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
日韩免费高清在线观看| 韩国精品美女www爽爽爽视频| 日韩中文字幕在线观看| 国产精品久久久久久亚洲调教| 国产精品日韩在线播放| 久久久999精品| 青青草一区二区| 91中文在线视频| 亚洲女同精品视频| 福利一区视频在线观看| 欧美性猛交xxxx乱大交3| 2019中文字幕在线| 国产在线视频一区| 国产精品999| 欧美激情xxxx性bbbb| 日本午夜精品理论片a级appf发布| 高清欧美性猛交xxxx| 亚洲一区中文字幕| 91理论片午午论夜理片久久| 国产精品成人久久久久| 亚洲图片欧美午夜| 国语自产精品视频在线看抢先版图片| 亚洲毛片在线观看.| 中文字幕日韩欧美精品在线观看| 在线日韩日本国产亚洲| 亚洲综合日韩中文字幕v在线| 国产日韩欧美成人| 国产精品旅馆在线| 国产91精品黑色丝袜高跟鞋| **欧美日韩vr在线| 日韩欧美精品中文字幕| 日本伊人精品一区二区三区介绍| 国内精品免费午夜毛片| 日韩精品久久久久| 中文字幕亚洲欧美一区二区三区| 欧美精品精品精品精品免费| 久久香蕉国产线看观看av| 欧美另类精品xxxx孕妇| 在线精品高清中文字幕| 国产在线播放91| 91综合免费在线| 亚洲欧美激情一区| 亚洲人成在线免费观看| 日韩精品免费在线播放| 中文字幕在线精品| 日韩精品免费综合视频在线播放| 国外视频精品毛片| 国产成人精品久久二区二区91| 成人在线免费观看视视频| 日韩中文字幕精品| 国产精品美女久久| 性欧美激情精品| 欧美在线中文字幕| 一区二区三区视频免费在线观看| 欧美日韩在线免费| 欧美日韩在线观看视频| 欧美激情视频一区二区| 久久琪琪电影院| 91美女片黄在线观看游戏| 亚洲色图50p| 欧洲精品在线视频| 国产在线高清精品| 九九热这里只有精品免费看| 美女久久久久久久久久久| 亚洲护士老师的毛茸茸最新章节| 视频在线一区二区| 亚洲国产精彩中文乱码av| 欧美成人午夜激情在线| 91精品国产高清久久久久久91| 中文字幕亚洲一区二区三区五十路| 中文字幕亚洲无线码a| 日韩福利伦理影院免费| 亚洲人成网站999久久久综合| 亚洲欧洲视频在线| 日韩一区二区三区在线播放| 欧美日韩免费网站| 欧美日韩国产精品一区二区三区四区| 日韩精品有码在线观看| 日韩大片在线观看视频| 久久久91精品| 国产一区二中文字幕在线看| 日韩av色综合| 亚洲成色www8888| 欧美激情亚洲一区| 国产精品美女www| 日韩电影大全免费观看2023年上| 色综合导航网站| 欧美精品一区二区三区国产精品| 日韩精品在线私人| 日韩免费看的电影电视剧大全| 97在线日本国产| 国产成人精品在线播放| 亚洲激情视频网站| 国产精品一区二区三区久久久| 一区二区三区国产在线观看| 亚洲精品久久久久久久久久久久| 中文字幕免费精品一区高清| 国产成人精品久久亚洲高清不卡| 亚洲色图国产精品| 日韩专区中文字幕| 中文字幕欧美日韩在线| 国产精品久久久久久久9999| 国产精品xxx视频| 欧美在线观看一区二区三区| 欧美亚洲另类激情另类| 亚洲天堂第二页| 欧美日韩成人在线播放| 久久91亚洲人成电影网站| 亚洲国产精品悠悠久久琪琪| 欧美裸体xxxx极品少妇| 日本欧美国产在线| 精品无人区太爽高潮在线播放| 国产精品免费网站| 久久综合免费视频影院| 亚洲aⅴ日韩av电影在线观看| 亚洲人在线视频| 久久亚洲综合国产精品99麻豆精品福利| 亚洲第一福利在线观看| 成人www视频在线观看| 国产成人久久久| 影音先锋欧美在线资源| 国产精品一区二区三区久久| 日韩的一区二区| 91精品久久久久久久久久久久久| 国产激情久久久久| 亚洲自拍偷拍一区| 亚洲白虎美女被爆操| 久久九九亚洲综合| 欧美巨乳美女视频| 7777kkkk成人观看| 日韩亚洲第一页| 亚洲欧洲在线播放| 亚洲视频一区二区三区| 欧美亚洲国产日韩2020| 欧美日韩中文字幕在线| 亚洲成人激情在线| 久久视频中文字幕| 国产一区二区三区在线看| 国产三级精品网站| 韩国国内大量揄拍精品视频| 日韩在线观看网址| 精品国产91久久久| 久久在线视频在线| 欧美极品美女视频网站在线观看免费| 亚洲精品wwww| 高清一区二区三区四区五区| 日韩福利在线播放| 色偷偷91综合久久噜噜| 国产精品白丝jk喷水视频一区| 九九精品视频在线观看| 亚洲国产成人精品一区二区| 亚洲理论片在线观看| 日韩美女视频免费看| 欧美午夜视频一区二区| 高清欧美性猛交xxxx| 国产成人精品视频在线| 日韩视频―中文字幕| 国产精品福利在线观看| 日韩av在线免费观看一区| 日韩精品免费一线在线观看| 欧美激情精品久久久久久免费印度| 亚洲精品美女久久久| 中文字幕一区二区精品| 91久久国产精品91久久性色|