精华内容
下载资源
问答
  • 本文旨在用Delphi面向对象的方法实现P2P(Peer To Peer)(类QQ)会话系统。本文可作为学习P2P通信与面向对象程序设计的用例。 一、 通信组件:采用TIdUDPServer(属于Indy Servers组件板)。 二、 通信原理::利用P2P之...
  • 简单的socket聊天工具,实现在发送文件和语音聊天等功能,还能聊天的同时听音乐,分服务器端和客户端两部分
  • 网页客户聊天系统源码,一句话简介:只需要1个静态HTML的页面就能互相聊天。...相关原理和教程: https://blog.csdn.net/sb11011bs/article/details/112133845 QQ 773179801 任何问题请与我联系。
  • 实战:客户端互聊原理实现 之前写过一篇非严肃的微信单聊原理,得到广大网友的一致好评,有很多读者留言问我如何使用 Netty 来具体实现这个逻辑,学完本小节,你会发现其实很简单。 在开始本小节之前,我们先...

    实战:客户端互聊原理与实现

    之前写过一篇非严肃的微信单聊原理,得到广大网友的一致好评,有很多读者留言问我如何使用 Netty 来具体实现这个逻辑,学完本小节,你会发现其实很简单。

    在开始本小节之前,我们先来看一下本小节学完之后,单聊的实现的效果是什么样的?

    1. 最终效果

    服务端

     

    image.png

     

     

    服务端启动之后,两个客户端陆续登录

    客户端 1

     

    image.png

     

     

    客户端 2

     

    image.png

     

     

    1. 客户端启动之后,我们在控制台输入用户名,服务端随机分配一个 userId 给客户端,这里我们省去了通过账号密码注册的过程,userId 就在服务端随机生成了,生产环境中可能会持久化在数据库,然后每次通过账号密码去“捞”。

    2. 当有两个客户端登录成功之后,在控制台输入userId + 空格 + 消息,这里的 userId 是消息接收方的标识, 消息接收方的控制台接着就会显示另外一个客户端发来的消息。

    一对一单聊的本质其实就这么简单,稍加改动其实就可以用在生产环境下,下面,我们就来一起学习一下如何实现控制台一对一单聊

    2. 一对一单聊原理

    一对一单聊的原理我们在 仿微信 IM 系统简介 已经学习过,我们再来重温一下

     

    单聊流程

     

     

    1. 如上图,A 要和 B 聊天,首先 A 和 B 需要与服务器建立连接,然后进行一次登录流程,服务端保存用户标识和 TCP 连接的映射关系。
    2. A 发消息给 B,首先需要将带有 B 标识的消息数据包发送到服务器,然后服务器从消息数据包中拿到 B 的标识,找到对应的 B 的连接,将消息发送给 B。

    原理掌握之后,接下来我们就来逐个实现这里面的逻辑

    3. 一对一单聊实现

    3.1 用户登录状态与 channel 的绑定

    我们先来看一下,服务端在单聊实现中是如何处理登录消息的

    LoginRequestHandler.java

    // 我们略去了非关键部分的代码,详细可以本地更新下代码,切换到本小节名称对应的 git 分支
    protected void channelRead0(ChannelHandlerContext ctx, LoginRequestPacket loginRequestPacket) {
        LoginResponsePacket loginResponsePacket = xxx;
        String userId = randomUserId();
        loginResponsePacket.setUserId(userId);
        SessionUtil.bindSession(new Session(userId, loginRequestPacket.getUserName()), ctx.channel());
    
        // 登录响应
        ctx.channel().writeAndFlush(loginResponsePacket);
    }
    
    // 用户断线之后取消绑定
    public void channelInactive(ChannelHandlerContext ctx) {
        SessionUtil.unBindSession(ctx.channel());
    }
    

    登录成功之后,服务端创建一个 Session 对象,这个对象表示用户当前的会话信息,在我们这个应用程序里面,Session 只有两个字段

    Session.java

    public class Session {
        // 用户唯一性标识
        private String userId;
        private String userName;
    }
    

    实际生产环境中 Session 中的字段可能较多,比如头像 url,年龄,性别等等。

    然后,我们调用 SessionUtil.bindSession() 保存用户的会话信息,具体实现如下

    SessionUtil.java

    public class SessionUtil {
        // userId -> channel 的映射
        private static final Map<String, Channel> userIdChannelMap = new ConcurrentHashMap<>();
    
    
        public static void bindSession(Session session, Channel channel) {
            userIdChannelMap.put(session.getUserId(), channel);
            channel.attr(Attributes.SESSION).set(session);
        }
    
        public static void unBindSession(Channel channel) {
            if (hasLogin(channel)) {
                userIdChannelMap.remove(getSession(channel).getUserId());
                channel.attr(Attributes.SESSION).set(null);
            }
        }
        
        public static boolean hasLogin(Channel channel) {
    
            return channel.hasAttr(Attributes.SESSION);
        }
    
        public static Session getSession(Channel channel) {
    
            return channel.attr(Attributes.SESSION).get();
        }
    
        public static Channel getChannel(String userId) {
    
            return userIdChannelMap.get(userId);
        }
    }
    
    1. SessionUtil 里面维持了一个 useId -> channel 的映射 map,调用 bindSession() 方法的时候,在 map 里面保存这个映射关系,SessionUtil 还提供了 getChannel() 方法,这样就可以通过 userId 拿到对应的 channel。
    2. 除了在 map 里面维持映射关系之外,在 bindSession() 方法中,我们还给 channel 附上了一个属性,这个属性就是当前用户的 Session,我们也提供了 getSession() 方法,非常方便地拿到对应 channel 的会话信息。
    3. 这里的 SessionUtil 其实就是前面小节的 LoginUtil,这里重构了一下,其中 hasLogin() 方法,只需要判断当前是否有用户的会话信息即可。
    4. 在 LoginRequestHandler 中,我们还重写了 channelInactive() 方法,用户下线之后,我们需要在内存里面自动删除 userId 到 channel 的映射关系,这是通过调用 SessionUtil.unBindSession() 来实现的。

    关于用户会话信息的保存的逻辑其实就这么多,总结一点就是:登录的时候保存会话信息,登出的时候删除会话信息,接下来,我们就来实现服务端接收消息并转发的逻辑。

    3.2 服务端接收消息并转发的实现

    我们重新来定义一下客户端发送给服务端的消息的数据包格式

    MessageRequestPacket.java

    public class MessageRequestPacket extends Packet {
        private String toUserId;
        private String message;
    }
    

    数据包格式很简单,toUserId 表示要发送给哪个用户,message 表示具体内容,接下来,我们来看一下服务端的消息处理 handler 是如何来处理消息的

    MessageRequestHandler.java

    public class MessageRequestHandler extends SimpleChannelInboundHandler<MessageRequestPacket> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, MessageRequestPacket messageRequestPacket) {
            // 1.拿到消息发送方的会话信息
            Session session = SessionUtil.getSession(ctx.channel());
    
            // 2.通过消息发送方的会话信息构造要发送的消息
            MessageResponsePacket messageResponsePacket = new MessageResponsePacket();
            messageResponsePacket.setFromUserId(session.getUserId());
            messageResponsePacket.setFromUserName(session.getUserName());
            messageResponsePacket.setMessage(messageRequestPacket.getMessage());
    
            // 3.拿到消息接收方的 channel
            Channel toUserChannel = SessionUtil.getChannel(messageRequestPacket.getToUserId());
    
            // 4.将消息发送给消息接收方
            if (toUserChannel != null && SessionUtil.hasLogin(toUserChannel)) {
                toUserChannel.writeAndFlush(messageResponsePacket);
            } else {
                System.err.println("[" + messageRequestPacket.getToUserId() + "] 不在线,发送失败!");
            }
        }
    }
    
    1. 服务端在收到客户端发来的消息之后,首先拿到当前用户,也就是消息发送方的会话信息。
    2. 拿到消息发送方的会话信息之后,构造一个发送给客户端的消息对象 MessageResponsePacket,填上发送消息方的用户标识、昵称、消息内容。
    3. 通过消息接收方的标识拿到对应的 channel。
    4. 如果消息接收方当前是登录状态,直接发送,如果不在线,控制台打印出一条警告消息。

    这里,服务端的功能相当于消息转发:收到一个客户端的消息之后,构建一条发送给另一个客户端的消息,接着拿到另一个客户端的 channel,然后通过 writeAndFlush() 写出。接下来,我们再来看一下客户端收到消息之后的逻辑处理。

    3.3 客户端收消息的逻辑处理

    MessageResponseHandler.java

    public class MessageResponseHandler extends SimpleChannelInboundHandler<MessageResponsePacket> {
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, MessageResponsePacket messageResponsePacket) {
            String fromUserId = messageResponsePacket.getFromUserId();
            String fromUserName = messageResponsePacket.getFromUserName();
            System.out.println(fromUserId + ":" + fromUserName + " -> " + messageResponsePacket .getMessage());
        }
    }
    

    客户端收到消息之后,只是把当前消息打印出来,这里把发送方的用户标识打印出来是为了方便我们在控制台回消息的时候,可以直接复制 ^ ^,到了这里,所有的核心逻辑其实已经完成了,我们还差最后一环:在客户端的控制台进行登录和发送消息逻辑。

    3.4 客户端控制台登录和发送消息

    我们回到客户端的启动类,改造一下控制台的逻辑

    NettyClient.java

    private static void startConsoleThread(Channel channel) {
        Scanner sc = new Scanner(System.in);
        LoginRequestPacket loginRequestPacket = new LoginRequestPacket();
    
        new Thread(() -> {
            while (!Thread.interrupted()) {
                if (!SessionUtil.hasLogin(channel)) {
                    System.out.print("输入用户名登录: ");
                    String username = sc.nextLine();
                    loginRequestPacket.setUserName(username);
    
                    // 密码使用默认的
                    loginRequestPacket.setPassword("pwd");
    
                    // 发送登录数据包
                    channel.writeAndFlush(loginRequestPacket);
                    waitForLoginResponse();
                } else {
                    String toUserId = sc.next();
                    String message = sc.next();
                    channel.writeAndFlush(new MessageRequestPacket(toUserId, message));
                }
            }
        }).start();
    }
    
    private static void waitForLoginResponse() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException ignored) {
        }
    }
    

    我们在客户端启动的时候,起一个线程

    1. 如果当前用户还未登录,我们在控制台输入一个用户名,然后构造一个登录数据包发送给服务器,发完之后,我们等待一个超时时间,可以当做是登录逻辑的最大处理时间,这里就简单粗暴点了。
    2. 如果当前用户已经是登录状态,我们可以在控制台输入消息接收方的 userId,然后输入一个空格,再输入消息的具体内容,然后,我们就可以构建一个消息数据包,发送到服务端。

    关于单聊的原理和实现,这小节到这里就结束了,最后,我们对本小节内容做一下总结。

    总结

    1. 我们定义一个会话类 Session 用户维持用户的登录信息,用户登录的时候绑定 Session 与 channel,用户登出或者断线的时候解绑 Session 与 channel。
    2. 服务端处理消息的时候,通过消息接收方的标识,拿到消息接收方的 channel,调用 writeAndFlush() 将消息发送给消息接收方。
    展开全文
  • 原理:Android平台上,典型的以腾讯的QQ、微信这些聊天消息界面通常可以采用ListView设计与实现,需要使用ListView 适配器Adapter的getItemViewType()和getViewTypeCount()。 在ListView的适配器中,每一次getView...
    
    原理:Android平台上,典型的以腾讯的QQ、微信这些聊天消息界面通常可以采用ListView设计与实现,需要使用ListView 适配器Adapter的getItemViewType()和getViewTypeCount()。
    在ListView的适配器中,每一次getView时候,首先要判断view的类型getItemViewType(),然后根据不同的类型加载不同的布局view。
    至于底部发送消息的窗口,每次发送完消息,需要将ListView滚动到底部,以免输入键盘遮挡住数据而致使用户看不到刚刚发送的消息。
    现在给出一个Android平台上简单的设计与实现方案。效果如图所示:



    测试的主Activity MainActivity.java:

    <span class="hljs-keyword">package</span> zhangphil.chat;
    
    <span class="hljs-keyword">import</span> java.util.ArrayList;
    <span class="hljs-keyword">import</span> java.util.HashMap;
    
    <span class="hljs-keyword">import</span> android.app.Activity;
    <span class="hljs-keyword">import</span> android.content.Context;
    <span class="hljs-keyword">import</span> android.os.Bundle;
    <span class="hljs-keyword">import</span> android.view.ContextMenu;
    <span class="hljs-keyword">import</span> android.view.LayoutInflater;
    <span class="hljs-keyword">import</span> android.view.View;
    <span class="hljs-keyword">import</span> android.view.ViewGroup;
    <span class="hljs-keyword">import</span> android.view.ContextMenu.ContextMenuInfo;
    <span class="hljs-keyword">import</span> android.widget.ArrayAdapter;
    <span class="hljs-keyword">import</span> android.widget.Button;
    <span class="hljs-keyword">import</span> android.widget.EditText;
    <span class="hljs-keyword">import</span> android.widget.ListView;
    <span class="hljs-keyword">import</span> android.widget.TextView;
    
    <span class="hljs-keyword">public</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainActivity</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Activity</span> </span>{
    
    	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> VIEW_TYPE = <span class="hljs-number">0xb01</span>;
    	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> VIEW_TYPE_LEFT = -<span class="hljs-number">10</span>;
    	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> VIEW_TYPE_RIGHT = -<span class="hljs-number">11</span>;
    
    	<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">int</span> MESSAGE = <span class="hljs-number">0xb02</span>;
    
    	<span class="hljs-keyword">private</span> ArrayList<HashMap<Integer, Object>> items = <span class="hljs-keyword">null</span>;
    
    	<span class="hljs-annotation">@Override</span>
    	<span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onCreate</span><span class="hljs-params">(Bundle savedInstanceState)</span> </span>{
    		<span class="hljs-keyword">super</span>.onCreate(savedInstanceState);
    		setContentView(R.layout.activity_main);
    
    		<span class="hljs-keyword">final</span> ListView listView = (ListView) findViewById(android.R.id.list);
    
    		items = <span class="hljs-keyword">new</span> ArrayList<HashMap<Integer, Object>>();
    
    		<span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < <span class="hljs-number">8</span>; i++) {
    			<span class="hljs-keyword">if</span> (i % <span class="hljs-number">2</span> == <span class="hljs-number">0</span>) {
    				HashMap<Integer, Object> map = <span class="hljs-keyword">new</span> HashMap<Integer, Object>();
    				map.put(VIEW_TYPE, VIEW_TYPE_LEFT);
    				map.put(MESSAGE, <span class="hljs-string">"对方说的消息"</span> + i);
    				items.add(map);
    			} <span class="hljs-keyword">else</span> {
    				HashMap<Integer, Object> map = <span class="hljs-keyword">new</span> HashMap<Integer, Object>();
    				map.put(VIEW_TYPE, VIEW_TYPE_RIGHT);
    				map.put(MESSAGE, <span class="hljs-string">"我说的消息"</span> + i);
    				items.add(map);
    			}
    		}
    
    		<span class="hljs-keyword">final</span> MyAdapter adapter = <span class="hljs-keyword">new</span> MyAdapter(<span class="hljs-keyword">this</span>, -<span class="hljs-number">1</span>);
    		listView.setAdapter(adapter);
    
    		<span class="hljs-keyword">final</span> EditText msgEditText = (EditText) findViewById(R.id.msgEditText);
    		Button button = (Button) findViewById(R.id.msgSend);
    		button.setOnClickListener(<span class="hljs-keyword">new</span> View.OnClickListener() {
    
    			<span class="hljs-annotation">@Override</span>
    			<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onClick</span><span class="hljs-params">(View v)</span> </span>{
    				String msg = msgEditText.getText() + <span class="hljs-string">""</span>;
    
    				HashMap<Integer, Object> map = <span class="hljs-keyword">new</span> HashMap<Integer, Object>();
    				map.put(VIEW_TYPE, VIEW_TYPE_RIGHT);
    				map.put(MESSAGE, msg);
    				items.add(map);
    
    				adapter.notifyDataSetChanged();
    
    				<span class="hljs-comment">// 发送后清空输入框内容</span>
    				msgEditText.setText(<span class="hljs-keyword">null</span>);
    
    				<span class="hljs-comment">// 输入框发送消息后将ListView滚动到最底部</span>
    				listView.setSelection(ListView.FOCUS_DOWN);
    			}
    		});
    	}
    
    	<span class="hljs-keyword">private</span> <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyAdapter</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ArrayAdapter</span> </span>{
    
    		<span class="hljs-keyword">private</span> LayoutInflater layoutInflater;
    
    		<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-title">MyAdapter</span><span class="hljs-params">(Context context, <span class="hljs-keyword">int</span> resource)</span> </span>{
    			<span class="hljs-keyword">super</span>(context, resource);
    			layoutInflater = LayoutInflater.from(context);
    		}
    
    		<span class="hljs-annotation">@Override</span>
    		<span class="hljs-function"><span class="hljs-keyword">public</span> View <span class="hljs-title">getView</span><span class="hljs-params">(<span class="hljs-keyword">int</span> pos, View convertView, ViewGroup parent)</span> </span>{
    			<span class="hljs-keyword">int</span> type = getItemViewType(pos);
    			String msg = getItem(pos);
    
    			<span class="hljs-keyword">switch</span> (type) {
    			<span class="hljs-keyword">case</span> VIEW_TYPE_LEFT:
    				convertView = layoutInflater.inflate(R.layout.left, <span class="hljs-keyword">null</span>);
    				TextView textLeft = (TextView) convertView.findViewById(R.id.textView);
    				textLeft.setText(msg);
    				<span class="hljs-keyword">break</span>;
    				
    			<span class="hljs-keyword">case</span> VIEW_TYPE_RIGHT:
    				convertView = layoutInflater.inflate(R.layout.right, <span class="hljs-keyword">null</span>);
    				TextView textRight = (TextView) convertView.findViewById(R.id.textView);
    				textRight.setText(msg);
    				<span class="hljs-keyword">break</span>;
    			}
    
    			<span class="hljs-keyword">return</span> convertView;
    		}
    
    		<span class="hljs-annotation">@Override</span>
    		<span class="hljs-function"><span class="hljs-keyword">public</span> String <span class="hljs-title">getItem</span><span class="hljs-params">(<span class="hljs-keyword">int</span> pos)</span> </span>{
    			String s = items.get(pos).get(MESSAGE) + <span class="hljs-string">""</span>;
    			<span class="hljs-keyword">return</span> s;
    		}
    
    		<span class="hljs-annotation">@Override</span>
    		<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getCount</span><span class="hljs-params">()</span> </span>{
    			<span class="hljs-keyword">return</span> items.size();
    		}
    
    		<span class="hljs-annotation">@Override</span>
    		<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getItemViewType</span><span class="hljs-params">(<span class="hljs-keyword">int</span> pos)</span> </span>{
    			<span class="hljs-keyword">int</span> type = (Integer) items.get(pos).get(VIEW_TYPE);
    			<span class="hljs-keyword">return</span> type;
    		}
    
    		<span class="hljs-annotation">@Override</span>
    		<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">getViewTypeCount</span><span class="hljs-params">()</span> </span>{
    			<span class="hljs-keyword">return</span> <span class="hljs-number">2</span>;
    		}
    	}
    }
    


    MainActivity.java需要的布局文件activity_main.xml:

    <span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span>
    <span class="hljs-tag"><<span class="hljs-title">RelativeLayout</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span>
        <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
        <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span> ></span>
    
        <span class="hljs-tag"><<span class="hljs-title">ListView</span>
            <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@android:id/list"</span>
            <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
            <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_above</span>=<span class="hljs-value">"@+id/commentLinearLayout"</span>
            <span class="hljs-attribute">android:layout_alignParentTop</span>=<span class="hljs-value">"true"</span>
            <span class="hljs-attribute">android:divider</span>=<span class="hljs-value">"@android:color/transparent"</span>
            <span class="hljs-attribute">android:dividerHeight</span>=<span class="hljs-value">"15dip"</span>
            <span class="hljs-attribute">android:scrollbars</span>=<span class="hljs-value">"none"</span> /></span>
    
        <span class="hljs-tag"><<span class="hljs-title">LinearLayout</span>
            <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/commentLinearLayout"</span>
            <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
            <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_alignParentBottom</span>=<span class="hljs-value">"true"</span>
            <span class="hljs-attribute">android:background</span>=<span class="hljs-value">"#e0e0e0"</span>
            <span class="hljs-attribute">android:orientation</span>=<span class="hljs-value">"horizontal"</span> ></span>
    
            <span class="hljs-tag"><<span class="hljs-title">EditText</span>
                <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/msgEditText"</span>
                <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"0dip"</span>
                <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
                <span class="hljs-attribute">android:layout_weight</span>=<span class="hljs-value">"8"</span>
                <span class="hljs-attribute">android:hint</span>=<span class="hljs-value">"发送消息"</span> /></span>
    
            <span class="hljs-tag"><<span class="hljs-title">Button</span>
                <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/msgSend"</span>
                <span class="hljs-attribute">style</span>=<span class="hljs-value">"?android:attr/buttonStyleSmall"</span>
                <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"0dip"</span>
                <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
                <span class="hljs-attribute">android:layout_weight</span>=<span class="hljs-value">"2"</span>
                <span class="hljs-attribute">android:text</span>=<span class="hljs-value">"发送"</span> /></span>
        <span class="hljs-tag"></<span class="hljs-title">LinearLayout</span>></span>
    
    <span class="hljs-tag"></<span class="hljs-title">RelativeLayout</span>></span>


    ListView适配器Adapter在每一次getView时候,首先判断view的type,然后根据不同的view type加载不同的布局view。


    left.xml表示是对方说的消息在ListView界面的左边:

    <span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span>
    <span class="hljs-tag"><<span class="hljs-title">RelativeLayout</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span>
        <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
        <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span>
        <span class="hljs-attribute">android:orientation</span>=<span class="hljs-value">"horizontal"</span> ></span>
    
        <span class="hljs-tag"><<span class="hljs-title">ImageView</span>
            <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/imageView"</span>
            <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_alignParentLeft</span>=<span class="hljs-value">"true"</span>
            <span class="hljs-attribute">android:layout_centerVertical</span>=<span class="hljs-value">"true"</span>
            <span class="hljs-attribute">android:singleLine</span>=<span class="hljs-value">"false"</span>
            <span class="hljs-attribute">android:src</span>=<span class="hljs-value">"@drawable/ic_launcher"</span> /></span>
    
        <span class="hljs-tag"><<span class="hljs-title">TextView</span>
            <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/textView"</span>
            <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_centerVertical</span>=<span class="hljs-value">"true"</span>
            <span class="hljs-attribute">android:layout_toRightOf</span>=<span class="hljs-value">"@+id/imageView"</span>
            <span class="hljs-attribute">android:background</span>=<span class="hljs-value">"#ff5252"</span>
            <span class="hljs-attribute">android:text</span>=<span class="hljs-value">"left"</span> /></span>
    
    <span class="hljs-tag"></<span class="hljs-title">RelativeLayout</span>></span>


    right.xml表示是自己说的消息,在消息聊天界面的右边:

    <span class="hljs-pi"><?xml version="1.0" encoding="utf-8"?></span>
    <span class="hljs-tag"><<span class="hljs-title">RelativeLayout</span> <span class="hljs-attribute">xmlns:android</span>=<span class="hljs-value">"http://schemas.android.com/apk/res/android"</span>
        <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"match_parent"</span>
        <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"match_parent"</span>
        <span class="hljs-attribute">android:orientation</span>=<span class="hljs-value">"horizontal"</span> ></span>
    
        <span class="hljs-tag"><<span class="hljs-title">ImageView</span>
            <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/imageView"</span>
            <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_alignParentRight</span>=<span class="hljs-value">"true"</span>
            <span class="hljs-attribute">android:layout_centerVertical</span>=<span class="hljs-value">"true"</span>
            <span class="hljs-attribute">android:src</span>=<span class="hljs-value">"@drawable/ic_launcher"</span> /></span>
    
        <span class="hljs-tag"><<span class="hljs-title">TextView</span>
            <span class="hljs-attribute">android:id</span>=<span class="hljs-value">"@+id/textView"</span>
            <span class="hljs-attribute">android:layout_width</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_height</span>=<span class="hljs-value">"wrap_content"</span>
            <span class="hljs-attribute">android:layout_centerVertical</span>=<span class="hljs-value">"true"</span>
            <span class="hljs-attribute">android:layout_toLeftOf</span>=<span class="hljs-value">"@+id/imageView"</span>
            <span class="hljs-attribute">android:background</span>=<span class="hljs-value">"#2196f3"</span>
            <span class="hljs-attribute">android:singleLine</span>=<span class="hljs-value">"false"</span>
            <span class="hljs-attribute">android:text</span>=<span class="hljs-value">"right"</span> /></span>
    
    <span class="hljs-tag"></<span class="hljs-title">RelativeLayout</span>></span>



    另外,QQ、微信这些聊天消息界面的消息背景是一个气泡,这个气泡其实是一个.9.png图片,将这个气泡的.9.png作为TextView的背景衬图衬上去即可,QQ、微信的聊天气泡.9.png不是本文要着重探讨的内容,在次不再展开叙述。

    展开全文
  • 主要为大家详细介绍了基于python实现聊天室程序,该程序由客户端与服务器构成,使用UDP服务,实现了群发、私发、点对点文件互传功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
  • 基于WWW的ASP聊天实现原理.pdf
  • Java 聊天室的简单实现原理

    千次阅读 2017-09-09 15:57:40
    简单的JAVA聊天室:一般是使用Socket基于 C/S 架构,其设计的过程如下: 1)服务器通过某个端口监听是否有客户端发送Socket链接请求。 2)客户端向服务器端发送一个Socket链接请求。 3)服务器端调用accept()方法...

    先贴一个简单的思路,后续有时间再把代码写好贴上来。

    简单的JAVA聊天室:一般是使用Socket基于 C/S 架构,其设计的过程如下:
    1)服务器通过某个端口监听是否有客户端发送Socket链接请求。
    2)客户端向服务器端发送一个Socket链接请求。
    3)服务器端调用accept()方法接收客户端Scoket 并建立链接。
    4)通过Socket对象的getInputStream / getOutputStream 方法进行IO流的操作,服务器端和客户端进行信息交流。
    5)关闭服务器和Socket。

    服务器端的实现:
    创建一个一个 ServerSocket 并指定端口号: ServerSocket ServerSocket = new ServerSocket(port,max) ,max 为最大连接数。
    用一个LinkList 来存储用户信息,接收客户端的信息后,遍历LinkList ,将信息发送给所有的用户。如果是发送给私人的信息,则遍历LinkList 找到对应的用户,发送信息给该用户。用户信息用Node类表示,里面包括:userName ,socket ,outputStream 和 InputStream 。
    当服务器侦听到有新用户连接时,将用户信息添加到 LinkList 中。

    使用ObjectInputStream 和 ObjectOutputStream 进信息的传输。
    用户发送信息时选择是 私聊 还是将信息发送给所有人,将标志字段写入ObjectOutputStream 中,接着讲其他标志信息如自己用户名以及发送的具体信息也写入到ObjectOutputStream 中,通过socket 发送到 服务器。
    服务器接收用户发送来的信息,并调用ObjectInputStream 的 readObject()对象对信息进行一个简单的解析,获取标志信息和具体的信息,判断是私聊信息还是群聊信息,进行下一步的发送操作。
    但是这样太丑陋!ObjectInputStream 和 ObjectOutputStream 因为要进行对象解析,再传输的时候也比较慢。

    注意: 在向ObjectOutputStream 写入信息时先写入一个特殊的字符串作为判别字段,服务器以此来判断是否是一条新信息。
    改进:如果是现在,那么优先采用 Json 来传输数据,Json技术已经日渐成熟,而且解析快速方便,适合做为少量数据传输的额载体。具体的做法: 客户端将信息构造成Json的格式,在服务器端接收数据流然后解析成Json对象,再根据Json对象的性质获取对应信息。

    展开全文
  • NULL 博文链接:https://duxingke11010.iteye.com/blog/2092079
  • 多人聊天室python实现

    2018-06-26 09:16:09
    我的python课设,绝对可以运行的,注意如果运行出错要查看自己的python环境有没有弄好,里面代码有wx包,注意下载对应版本的wx包。
  • 本文旨在用C#面向对象的方法实现P2P(Peer To Peer)(类QQ)会话系统。本文可作为学习C# P2P通信与面向对象程序设计的用例(包括所有源码)。 一、 通信组件:System.Net.Sockets.System.Net.Sockets.UdpClient。 二、 ...
  • 网页聊天室的原理

    万次阅读 2011-12-22 16:50:36
     web聊天室的实现方法有多种,包括:基于ajax技术的实现,基于Comet(Pushlet)技术的实现,基于XMPP协议的实现,以及基于flash的XmlSocket和远程共享对象的实现。 (1) 基于ajax技术的实现。  ajax(异步...
      
    

    目前,无论是网页游戏、论坛博客、电子商场,随处都可以看到web聊天室。
       web聊天室的实现方法有多种,包括:基于ajax技术的实现,基于Comet(Pushlet)技术的实现,基于XMPP协议的实现,以及基于flash的XmlSocket和远程共享对象的实现。

    (1) 基于ajax技术的实现。
       ajax(异步JavaScript和XML,Asynchronous javascript and xml),它的作用就是可以实现页面与服务器端的无刷新交互。用ajax来实现web聊天室的基本原理是:在页面上每隔一段时间就通过ajax从服务器中获取数据,然后更新页面显示。这种方法简单明了,缺点是实时性不高。

    (2) 基于Comet技术的实现。
       Comet 是一种新的 Web 应用架构。基于这种架构开发的应用中,服务器端会主动以异步的方式向客户端程序推送数据,而不需要客户端显式的发出请求。Comet 架构非常适合事件驱动的 Web 应用,以及对交互性和实时性要求较高的应用,如股票交易行情分析、聊天室和 Web 版在线游戏等。
       Pushlet是一种comet实现(Pushlet 是开源的Comet 框架):在Servlet机制下,数据从服务器的Java对象直接推送(push)到客户端的页面,而无需任何Java applet或者插件的帮助。它使server端可以周期性地更新client的web页面,这与传统的request/response方式不同。
       Pushlet基于HTTP流,这种技术常常用在多媒体视频、通讯应用中,比如QuickTime。与装载HTTP页面之后马上关闭HTTP连接的做法相反,Pushlet采用HTTP流方式将新数据源源不断地推送到client,再此期间HTTP连接一直保持打开。有关如何在Java中实现这种Keep-alive的长连接请参看Sun提供的《HTTP Persistent Connection》和W3C的《HTTP1.1规范》。
     (3)基于XMPP协议的实现
       XMPP(可扩展消息处理现场协议)是基于XML的协议,是专为及时通信系统设计的通信协议,用于即时消息以及在线现场探测。它在促进服务器之间的准即时操作。这个协议可能最终允许因特网用户向因特网上的其他任何人发送即时消息,即使其操作系统和浏览器不同。XMPP的前身是Jabber,一个开源形式组织产生的网络即时通信协议。著名的开源聊天系统服务器Openfire就是基于XMPP协议的Jabber服务器。
      可以通过Flash或ajax与Jabber服务器进行交互,实现webIM的功能,
     (4)基于flash的XmlSocket的实现
       Flash Media Server是一个很强大的流媒体服务器,它基于rtmp协议,提供了强壮的流媒体交互功能。在FMS中,提供一种远程共享对象(SharedObject)的机制,客户端可以创建并连接到服务器端的远程共享对象。可以有很多个客户端连接到同一个远程共享对象中,任何一个客户端对共享对象进行了修改,服务器都会将共享对象的修改信息发送给所有其他连接到这个共享对象的客户端。这种远程共享对象的机制可以很方面地实现以下功能:&middot;       远程控制幻灯片放映   &middot;       文字聊天   &middot;       网络对战   &middot;       远程选择和播放歌曲   &middot;       现场拍卖   &middot;     客户服务应用程序。
       远程共享对象很适合用于实现web聊天室中的群聊功能。为每一个群都建立一个远程共享对象,这样的话,任何用户在群上发信息,就可以通过服务器自动发送到所有的群成员。

        用远程共享对象来实现单聊是不实际的。对应单聊的实现,我们需要借助socket。客户端通过socket服务器与其他客户端进行私聊。聊天信息通过socket服务器进行转发。
       这种方式是效率最高的web聊天室实现方式。

    最近在研究聊天系统,主要是基于Pushlet服务器推技术,系统完成后再与大家分享吧!
    展开全文
  • 一个简单的P2P即时聊天系统,实现功能如下: 1) 点对点的单人聊天 2) 多人同时聊天 3) 用户可以自由加入和退出。
  • 本文实例讲述了Python实现基于C/S架构的聊天室功能。分享给大家供大家参考,具体如下: 一、课程介绍 1.简介 本次项目课是实现简单聊天室程序的服务器端和客户端。 2.知识点 服务器端涉及到asyncore、asynchat和...
  • QQ聊天通讯原理

    万次阅读 2013-07-18 10:40:50
    QQ聊天程序分成了两个程序,一个安装在腾讯公司的服务器上 ,我们称之为服务端程序,一个安装在QQ用户的计算机上,我们称之为客户端程序. 在许多介绍网络通讯编程的书籍中有关网络聊天的例子,当一个客户要和第二个客户...
  • NodeJS把javascript推动为互联网发展核心驱动力,给前端界带来的更大的...论文基于NodeJS的事件驱动及异步编程原理,就一个基于NodeJS的聊天室的实例结合HTML5的最新技术来展示javascript技术在后端服务器领域的前景。
  • 在linux下的基于TCP/IP,采用socket通信的聊天室,实现进入聊天室,进行多人群聊,指定人进行私聊,群主管理员功能,颗进行禁言,提出群聊等操作。个人账号可修改昵称或者修改密码,还可进行找回密码等功能
  • qt 使用qsocket实现简单聊天室,不同IP下可以进行聊天
  • 基于Yii+Swoole+Redis实现的IM方案 github:https://github.com/melodyne/swoole-im 文档:https://github.com/melodyne/swoole-im/wiki 主要功能: 支持群聊 支持头像,昵称 文本消息 支持发送图片 表情...
  • 聊天软件原理

    千次阅读 2018-04-13 12:31:29
    前提:socker长连接建立
  • 自然语言处理原理实现,邱锡鹏
  • 使用Qt实现QQ群聊功能,文件中包含具体实现的步骤及流程!
  • 本文主要为音视频同步的原理实现方案,一起来学习下
  • Android即时通讯实现原理

    万次阅读 2016-07-19 20:58:38
    即时通讯实现原理即时通讯(Instant Messenger,简称IM)软件多是基于TCP/IP和UDP进行通讯的,TCP/IP和UDP都是建立在更低层的IP协议上的两种通讯传输协议。前者是以数据流的形式,将传输数据经分割、打包后,通过两...
  • 基于tomcat的websocket,实现的一对一通讯,支持https协议。
  • Java语音聊天

    热门讨论 2011-11-28 10:25:31
    一个Java语音聊天工具,可实现如同QQ或其它语音聊天一样的功能
  • IM即时通讯实现原理

    万次阅读 2010-06-11 14:57:00
    其他的即时通信软件原理与此大同小异。   一般的步骤: 首先,用户A输入自己的用户名和密码登录即时通讯服务器,服务器通过读取用户数据库来验证用户身份,如果用户名、密码都正确,就...
  • WebSocket实现原理

    千次阅读 2020-01-09 01:33:55
    websocket实现原理 1)握手环节:验证服务端是否支持websocket协议 a)创建连接,浏览器连接服务端,创建连接成功 b)浏览器生成一个随机字符串,浏览器存一份该随机字符串,同时发一份给服务端(基于http协议发到...
  • 通过webscoket 简单实现聊天,就是个学习的,通过webscoket 简单实现聊天,就是个学习的。 可以改成群聊 可以是一对一的

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 54,694
精华内容 21,877
关键字:

聊天实现原理