• 实现一个WEBIM


    除了用C写过hello world,数据结构,第一次写这么多行。高手请忽略我。

    IM实现的方式现在有很多,我挑了一种来实践了一下。前端用jsonp发异步还能跨域的长轮询请求,后端用epoll写了一个支持长连接的chat server

    前端方面

    接收IM消息:发起一个http请求,这个请求在服务器端一直不返回,是个长连接。当服务器有信息反馈的时候,再发送一个长连接请求。

    这个也叫长轮询,是服务器推实现方式的一种。

    发送IM消息:发起一个http请求,将发送文本发给服务器,服务器根据发送对象,给出哪个长连接可以返回,这里就是短连接了。

    后端方面

    有多少人在线就得有多少个长连接一直在后端运行,所以不考虑一个用户一个线程的后端这种处理方式,可以考虑单线程,IO多路复用的非阻塞模型(epoll)。

    前端客户端

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>客户端</title>
    </head>
    <body>
    <script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.5.min.js"></script>
    <script type="text/javascript">
    
    function recv(){
    	$.getJSON('http://142.54.174.134:8080/recv.html?cmd=login&callback=?',function(data){
    		$("ul").append("<li>"+data+"</li>"); 
    		// $("#resp").html(data);
    		recv(); //这里就是在长轮询了,当长连接有数据返回,别且更新完html上的dom把它显示出来以后,再发起一个长连接,等待下次接受聊天
    	});
    }
    
    recv();
    
    function sendCmd(msg){
    	$.getJSON('http://142.54.174.134:8080/send.html?cmd=notify:' + msg + '&callback=?',function(data){
    		//$("#resp").html(data);
    	});
    }
    function go(){
    	sendCmd($("#exec_string").val());
    }
    </script>
    <form>
    	<div>响应数据</div>
    	<div id="resp" style="height:300px">
    		<ul>   
    	      
    	    </ul>
    	</div>
    	<textarea id="exec_string" style="height:100px"></textarea>
    	<input type="button" onclick="go()" value="excute"></input>
    </form>
    
    
    </body>
    </html>
    

    后端服务器

    #include <stdlib.h>
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <sys/epoll.h>
    #include <string.h>
    #include <unistd.h>
    #include <signal.h>
    
    #include "epoll.h"
    #include "sock.h"
    
    static volatile sig_atomic_t shutdown_flag = 0;
    static int efd;
    
    void signal_handler(int sig){
        printf("server shutdown\r\n");
        shutdown_flag = 1;
    }
    
    void notify_all(struct User* user, int self){
        int i=0;
        for(i=0;i<EPOLL_SIZE;i++){
            if(i!=self && user[i].in_use){
                user[i].resp = user[self].resp;
                socket_send(i, user);
                epoll_del(efd, i);
                close(i);
                free(user[i].callback);
                free(user[i].cmd);
                bzero(&user[i],sizeof(struct User));    
            }
        }
        //responce self
        socket_send(self, user);
        epoll_del(efd, self);
        close(self);
        free(user[self].callback);
        free(user[self].cmd);
        free(user[self].resp);
        bzero(&user[self],sizeof(struct User));    
    }
    
    int main(){
        signal(SIGTERM, signal_handler);
        signal(SIGINT, signal_handler);
        int server_sockfd, client_sockfd;
        int ret,addr_len = sizeof(struct sockaddr);
        struct sockaddr_in client_addr;
        struct epoll_event events[EPOLL_SIZE];
        struct User user[EPOLL_SIZE];
        
        bzero(&user,sizeof(user));
        //create
        server_sockfd = create();
     
        //bind
        bind2sock(server_sockfd, PORT); 
        
        //listen
        listening(server_sockfd); 
        
        efd = epoll_init(EPOLL_SIZE);
    
        epoll_prepare_fd(server_sockfd);
        epoll_add(efd, server_sockfd);
        int nfds,i=0;
        //accept loop
        while(!shutdown_flag){
            nfds = epoll_wait(efd, events, EPOLL_SIZE, -1);
            for(i=0;i<nfds;i++){
                // printf("triggered %d fds\r\n",nfds);
                if(events[i].data.fd==server_sockfd){
                    while(1){
                        client_sockfd = accept(server_sockfd, (struct sockaddr*)(&client_addr), &addr_len);
                        if(client_sockfd<0){
                            break;
                        }
                        epoll_prepare_fd(client_sockfd); 
                        epoll_add(efd, client_sockfd); 
                        user[client_sockfd].sockfd = client_sockfd;
                        user[client_sockfd].port =  client_addr.sin_port;
                        user[client_sockfd].ip =  (char*)inet_ntoa(client_addr.sin_addr);
                        user[client_sockfd].callback = NULL;
                        user[client_sockfd].cmd = NULL;
                        user[client_sockfd].resp = NULL;
                        user[client_sockfd].in_use = 1;
                    }
                }else if(events[i].events==EPOLLIN){
                    // printf("epoll in \r\n");
                    client_sockfd = events[i].data.fd;
                    if(socket_recv(client_sockfd, user) < 0){
                        printf("close when epoll in\r\n");
                        epoll_del(efd, client_sockfd);
                        close(client_sockfd);
                        bzero(&user[client_sockfd],sizeof(struct User));       
                    }else{
                        epoll_set(efd, client_sockfd, EPOLLOUT);  
                    }
                }else if(events[i].events==EPOLLOUT){
                    // printf("epoll out \r\n");
                    client_sockfd = events[i].data.fd;
                    if(user[client_sockfd].cmd && strstr(user[client_sockfd].cmd,"notify")){
                        printf("brocast msg\r\n");
                        user[client_sockfd].resp = strdup(strstr(user[client_sockfd].cmd,":"));
                        notify_all(user, client_sockfd);          //当发送连接来的时候,就把其他都在等待的长连接都返回。并把发送的聊天数据作为返回。
                    }else{
                        epoll_set(efd, client_sockfd, EPOLLIN);    //长连接的保持
                    }
                }else{
                    printf("other case \r\n");
                    close(events[i].data.fd);
                    epoll_del(efd, events[i].data.fd);
                    free(user[events[i].data.fd].callback);
                    free(user[events[i].data.fd].cmd);
                    free(user[client_sockfd].resp);
                    bzero(&user[events[i].data.fd],sizeof(struct User));
                    close(events[i].data.fd);
                }
            }
        }
        close(efd);
        close(server_sockfd);
        return 0;
    }
    

    打开页面的时候就发了个http://142.54.174.134:8080/recv.html?cmd=login&callback=?长连接请求,等待接收服务器数据。

    a页面发送了一个i am a,b页面发送了一个i am b

    ————————————————————————————————————————————————

    一个完整的IM server还应该考虑

    1.超时问题

    比如浏览器页面关闭,或者网络出现问题等。导致长连接一直没关闭从而占用服务器epoll event,一种办法是服务器定期发送心跳消息。

    2.后端负载

    单机情况,随着用户的增张,EPOLL_SIZE就需要更大。这样肯定不行,估计就要考虑一些切分,应该和网游的分服差不多。

    3.处理一些验证,离线留言等等。

    其实经常看有些成熟的web im 都是在chat server和客户端之间加一层php这种东西处理一些业务逻辑,用php来写业务逻辑肯定比c好些,而且也好维护。尽量还是让c这一层可以轻一些,以后好维护。

    参考

    Comet:基于 HTTP 长连接的“服务器推”技术

    JSONP跨域原理和jQuery.getJSON用法

    epoll 使用详解

    nextIM

  • 相关阅读:
    LeetCode24-Swap_Pairs
    LeeCode
    LeetCode3-Longest_Substring_Without_Repeating_Characters
    治愈 JavaScript 疲态的学习计划【转载】
    前端冷知识集锦[转载]
    知道这20个正则表达式,能让你少写1,000行代码[转载]
    关于简历和面试【整理自知乎】
    正念冥想方法
    一些职场经验【转载自知乎】
    犹太复国计划向世界展现了一个不一样的民族——观《犹太复国血泪史》有感
  • 原文地址:https://www.cnblogs.com/23lalala/p/2911199.html
Copyright © 2020-2023  润新知