这一节应该是聊天程序的最后一节了,现在回顾我们的聊天程序,看起来还有很多功能没有实现,但是不管怎么说,都还是不错的。这一节我们将讲多服务器问题(高大上的说法就是负载问题了。)至于聊天程序的文件发送(也即二进制文件发送例如图片)和单点登陆(就是多加一个数组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 Addr
113 {
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的fd
237         FD_ZERO(&recvfd);//清空所有client的fd
238         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-switch
409     }
410     return 0;
411 }

  虽然我演示的时候所有的操作都是在一台机器上运行的。其实是可以多个机器同时协作运行的。修改185行处的IP就可以实现不同的机器了。

  程序的makefile

1 main:
2         gcc client.c -o client
3         gcc server.c `mysql_config --cflags --libs` -o server
4         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.cnblogs.com/wunaozai/p/3870156.html
  Socket网络编程--聊天程序(2) http://www.cnblogs.com/wunaozai/p/3870194.html
  Socket网络编程--聊天程序(3) http://www.cnblogs.com/wunaozai/p/3870258.html
  Socket网络编程--聊天程序(4) http://www.cnblogs.com/wunaozai/p/3870338.html
  Socket网络编程--聊天程序(5) http://www.cnblogs.com/wunaozai/p/3871563.html
  Socket网络编程--聊天程序(6) http://www.cnblogs.com/wunaozai/p/3875506.html
  Socket网络编程--聊天程序(7) http://www.cnblogs.com/wunaozai/p/3876134.html
  Socket网络编程--聊天程序(8) http://www.cnblogs.com/wunaozai/p/3878374.html
  Socket网络编程--聊天程序(9) http://www.cnblogs.com/wunaozai/p/3880462.html

  所有开发过程中的代码: http://files.cnblogs.com/wunaozai/Socket-Chat.zip

  因为每一个版本都是上一个版本的修改版,在学习的过程中,如果想知道这一小节增加了什么内容,可以用 vimdiff file1 file2 比较两个文件,就知道修改了哪些内容。

  

Socket网络编程--聊天程序(9)相关推荐

  1. Socket网络编程--聊天程序(8)

    上一节已经完成了对用户的身份验证了,既然有了验证,那么接下来就能对不同的客户端进行区分了,所以这一节讲实现私聊功能.就是通过服务器对客户端的数据进行转发到特定的用户上, 实现私聊功能的聊天程序 实现的 ...

  2. Socket网络编程--聊天程序(3)

    上一小节,已经讲到可以每个人多说话,而且还没有限制,简单的来说,我们已经完成了聊天的功能了,那么接下来我们要实现什么功能呢?一个聊天程序至少应该支持一对多的通讯吧,接下来就实现多个客户端往服务器发送数 ...

  3. 基于Udp的Socket网络编程聊天程序

    1.新建一个工程区Net 在工作区中添加两个工程 NetSrv 和 NetClient 为两个工程添加库文件 (Link中) ws2_32.lib 2.在工程NetSrv中添加Server.cpp文件 ...

  4. Socket网络编程--聊天程序(2)

    上一节简单如何通过Socket创建一个连接,然后进行通信.只是每个人只能说一句话.而且还是必须说完才会接收到信息,总之是很不方便的事情.所以这一小节我们将对上一次的程序进行修改,修改成每个人可以多说话 ...

  5. Socket网络编程--小小网盘程序(5)

    http://www.cnblogs.com/wunaozai/p/3893469.html 各位好呀!这一小节应该就是这个小小网盘程序的最后一小节了,这一节将实现最后的三个功能,即列出用户在服务器中 ...

  6. Socket网络编程(2)--服务端实现

    中秋了,首先祝大家中秋快乐,闲着无事在家整一个socket的聊天程序,有点仿QQ界面,就是瞎折腾,不知道最后是不是能将所有功能实现. 如果你对socket不了解,请看这篇文章:http://www.c ...

  7. MFC socket网络编程(流程示例)

    MFC socket网络编程(流程示例) 1.TCP流式套接字的编程步骤 在使用之前须链接库函数:工程->设置->Link->输入ws2_32.lib,OK! 服务器端程序: 1.加 ...

  8. Netty网络编程聊天项目

                                     Netty网络编程聊天项目 后端编写 导入依赖    <dependencies><dependency>&l ...

  9. BIO,Socket网络编程入门代码示例,NIO网络编程入门代码示例,AIO 网络编程

    BIO,Socket网络编程入门代码示例 1.BIO服务器端程序 package cn.itcast.bio;import java.io.InputStream; import java.io.Ou ...

  10. python网络编程讲解_详解Python Socket网络编程

    Socket 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的,例如我们每天浏览网页.QQ ...

最新文章

  1. R语言使用forestplot包绘制森林图:编码创建森林图仿真数据、汇总线修改、元素位置调整、垂直线、字体、风格、置信区间、线型、图例、刻度、标签等
  2. 大量数据快速导出的解决方案-Kettle
  3. Linux——Windows连接CentOS XRDP 错误信息(login failed for display 0)
  4. 一篇文章彻底明白Hive数据存储的各种模式
  5. c语言拍飞虫课程设计报告,C++语言课程计一拍飞虫.doc
  6. 中国石化:五年要建充换电站5000座
  7. C++ 修改 Windows Service【转】
  8. android系统功耗问题:systrace
  9. java 判断是否为车牌_java车牌检测识别库
  10. NLP学习一 形式语言与自动机
  11. TC118AH单通道内置MOS单通道直流无刷马达驱动IC
  12. 传统工厂如何利用物联网模块实现智能化生产?
  13. Maven 参数详情
  14. 免服务器软件库源码实现超级管理动态发布会员系统卡密系统充值对接卡密网软件发布板块后台功能 软件商店1.3.1
  15. 小程序云函数new Date()获取的时间和new Date().getDay()获取的时间不一致 / 云函数存入的时间不对 /小程序云开发配置时区
  16. python子图标题_python, 如何在subplot在总的图画上面加title?
  17. usb3.0 驱动安装方法
  18. 仿站后台路径怎么查找?
  19. 19年深圳二手房交易分析
  20. 异步电机模型预测磁链控制(MPFC)

热门文章

  1. 多个项目共用同一个redis_比Redis快5倍的中间件,为啥这么快?
  2. android改变textview文字,如何在Android TextView中更改文本
  3. mysql字段分隔符拆分_MySQL——字符串拆分(含分隔符的字符串截取)
  4. java 单例模式_谈谈Java中的单例模式
  5. mysql中数据导出到json文件的读取办法:
  6. linux工程常用的应用命令总结:
  7. 帆软扩展后排序(超链排序)
  8. linux通过数字权限设置密码,linux--权限管理和用户管理
  9. 2d模型文件_Supergiant美术师:如何将2D美术做成3D游戏模型?
  10. thinkphp5---join联合查询