java socket 模拟im 即时通讯
自己想了一下怎么实现,就写了,没有深究是否合理.更多处理没有写下去,例如收件人不在线,应该保存在数据库,等下一次连接的时候刷新map,再把数据发送过去,图片发送也没有做,也没有用json格式
socket很奇怪,我用客户端连接上了服务器,没有发送消息的情况下,断开电脑网络,是不会出现问题,然后在把电脑网络连接上,通讯依然正常,正常断开也不出问题,但是用idea直接按stop键,那么服务端就会出问题了,读取事件会一直为true,造成死循环,消耗CPU,所以必须要判断一下客户端连接是否断开了
只需要把客户端代码启动几个,修改一些userName以及收件人,就可以测试,实现类似QQ微信即时通讯,聊天功能
服务端代码
- package serversocketchannel;
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.ServerSocketChannel;
- import java.nio.channels.SocketChannel;
- import java.nio.charset.Charset;
- import java.util.Iterator;
- import java.util.concurrent.ConcurrentHashMap;
- /**
- *
- * @author ZhenWeiLai
- *
- */
- public class ServerSocketChannelNonBlocking {
- private static ServerSocketChannel serverSocketChannel = null;
- private static Charset charset = Charset.forName("GBK");//设置编码集,用于编码,解码
- private static Selector selector = null;
- //保存客户端的map
- private static final ConcurrentHashMap<String,SocketChannel> clientSockets = new ConcurrentHashMap<>();
- static{
- try {
- serverSocketChannel = ServerSocketChannel.open();
- serverSocketChannel.socket().setReuseAddress(true);
- serverSocketChannel.socket().bind(new InetSocketAddress(8000));
- serverSocketChannel.configureBlocking(false);//设置为非阻塞
- selector = Selector.open();//实例化一个选择器
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public static void main(String[] args) {
- service();
- }
- private static void service(){
- SocketChannel clientChannel = null;
- SelectionKey selectionKey = null;
- SocketChannel targetChannel = null;
- try {
- serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);//服务端监听连接
- while(true){
- selector.select();//阻塞至有新的连接就开始处理
- Iterator<SelectionKey> selectionKeys = selector.selectedKeys().iterator();
- while(selectionKeys.hasNext()){
- selectionKey = selectionKeys.next();
- if(selectionKey.isAcceptable()){//如果事件是连接事件
- ServerSocketChannel serverChannel = (ServerSocketChannel)selectionKey.channel();//获取事件绑定的channel
- clientChannel = serverChannel.accept();//连接获取带客户端信息的socketChannel
- clientChannel.configureBlocking(false);//客户设置为非阻塞,因为非阻塞才支持选择器.避免盲等浪费资源
- ByteBuffer byteBuffer = ByteBuffer.allocate(1024);//作为每一个客户端的附件缓冲器
- /**
- * 只监听读事件,这里千万别监听写事件,因为只要连接有效,那么写事件会一直为true,导致死循环,很耗资源
- * 可以跟serverSocket用同一个选择器,因为绑定的channel不同
- */
- clientChannel.register(selector,SelectionKey.OP_READ,byteBuffer);
- }else if(selectionKey.isReadable()){//只要有客户端写入,那么就可以处理
- //获取客户端附件,也就是写入的数据
- ByteBuffer byteBuffer = (ByteBuffer)selectionKey.attachment();
- //从selectionKey获取客户端的channel
- SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
- //把附件读出,解码为字符串
- String msg = read(socketChannel,byteBuffer);
- //这里用了->分割收件人,->后面跟着的字符串是收件人
- if(msg.indexOf("->")!=-1){
- //内容
- String content = msg.substring(0,msg.lastIndexOf("->"));
- //从map里获取收件人的socket
- targetChannel = clientSockets.get(msg.substring(msg.lastIndexOf("->")+2));
- //实例化一个缓冲区,用来写出到收件人的socketChannel
- ByteBuffer temp = ByteBuffer.allocate(1024);
- temp.put(charset.encode(content));
- //写出
- handleWrite(targetChannel,temp);
- }else{
- //如果内容没有收件人,那么视为第一次连接,客户端发过来的userName,作为KEY存入MAP
- clientSockets.put(msg,socketChannel);
- }
- }
- selectionKeys.remove();
- }
- }
- } catch (IOException e) {
- try {
- if(selectionKey!=null)selectionKey.cancel();
- if(clientChannel!=null){
- clientChannel.shutdownInput();
- clientChannel.shutdownOutput();
- clientChannel.close();
- }
- if(targetChannel!=null){
- targetChannel.shutdownInput();
- targetChannel.shutdownOutput();
- targetChannel.close();
- }
- } catch (IOException e1) {
- // TODO Auto-generated catch block
- e1.printStackTrace();
- }
- e.printStackTrace();
- }
- }
- private static String read(SocketChannel socketChannel,ByteBuffer byteBuffer){
- //重置position limit为写入做准备
- byteBuffer.clear();
- try {
- int flag =socketChannel.read(byteBuffer);
- //判断客户端是否断开连接
- if(flag==-1){
- //如果客户端无故断开,一定要关闭,否则读取事件一直为true造成死循环,非常耗资源
- socketChannel.close();
- }
- } catch (IOException e) {
- try {
- socketChannel.close();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- e.printStackTrace();
- }
- //position =0 limit等于有效下标,为写出做准备
- byteBuffer.flip();
- return charset.decode(byteBuffer).toString();
- }
- //写出
- private static void handleWrite(SocketChannel socketChannel,ByteBuffer byteBuffer){
- synchronized (byteBuffer) {
- byteBuffer.flip();
- try {
- socketChannel.write(byteBuffer);
- } catch (IOException e) {
- try {
- socketChannel.close();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- e.printStackTrace();
- }
- }
- }
- }
客户端代码
- package socketchannel;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.net.InetSocketAddress;
- import java.net.SocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.ClosedChannelException;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.SocketChannel;
- import java.nio.charset.Charset;
- import java.util.Iterator;
- /**
- * Created by lzw on 17-2-28.
- */
- public class SocketChannelNonBlockingClient {
- private static Charset charset = Charset.forName("GBK");
- private static ByteBuffer receiveBuffer = ByteBuffer.allocate(10240);
- private static ByteBuffer sendBuffer = ByteBuffer.allocate(10240);
- private static SocketChannel socketChannel = null;
- private static Selector selector = null;
- private static String userName = "client1";//客户端名
- private static String targetName = "client2";//收件人名
- public static void main(String[] args) {
- try {
- socketChannel = SocketChannel.open();
- //连接到服务端
- SocketAddress socketAddress = new InetSocketAddress("19.95.103.112",8000);
- selector = Selector.open();//实例化一个选择器
- socketChannel.configureBlocking(false);//设置为非阻塞
- //先监听一个连接事件
- socketChannel.register(selector,SelectionKey.OP_CONNECT);
- //连接
- socketChannel.connect(socketAddress);
- //jdk 1.8的lambda表达式,用一个线程监控控制台输入
- new Thread(()->{
- try {
- receiveFromUser();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }).start();
- talk();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- private static void talk(){
- try {
- while(true){
- selector.select();//阻塞直到连接事件
- Iterator<SelectionKey> readyKeys = selector.selectedKeys().iterator();
- while(readyKeys.hasNext()){
- SelectionKey key =readyKeys.next();
- if(key.isConnectable()){
- //非阻塞的情况下可能没有连接完成,这里调用finishConnect阻塞至连接完成
- socketChannel.finishConnect();
- //连接完成以后,先发送自己的userName以便保存在服务端的客户端map里面
- synchronized (sendBuffer){
- SocketChannel socketChannel1 = (SocketChannel)key.channel();
- sendBuffer.clear();
- sendBuffer.put(charset.encode(userName));
- send(socketChannel1);
- socketChannel.register(selector,SelectionKey.OP_READ);//仅监听一个读取事件
- }
- }else if(key.isReadable()){
- //处理读事件
- receive(key);
- }
- readyKeys.remove();
- }
- }
- } catch (ClosedChannelException e) {
- try {
- socketChannel.close();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- e.printStackTrace();
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- /**
- * 从控制台获取用户输入
- * @throws IOException
- */
- private static void receiveFromUser() throws IOException{
- //阻塞直到控制台有输入
- BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
- for(String msg = br.readLine();msg!=null&&!msg.equals("bye");msg = br.readLine()){
- //同步锁避免线程竞争
- synchronized (sendBuffer) {
- sendBuffer.clear();
- //编码
- sendBuffer.put(charset.encode(msg));
- //分割副
- sendBuffer.put(charset.encode("->"));
- //目标名
- sendBuffer.put(charset.encode(targetName));
- send(socketChannel);
- }
- }
- }
- /**
- * 接收服务端的数据
- * @param key
- */
- private static void receive(SelectionKey key) throws IOException {
- //获取服务端的channel
- SocketChannel channel = (SocketChannel) key.channel();
- //为写入缓冲器做准备position=0,limit=capacity
- receiveBuffer.clear();
- //从服务端的channel把数据读入缓冲器
- channel.read(receiveBuffer);
- //position=0,limit=有效下标最后一位
- receiveBuffer.flip();
- //解码
- String msg = charset.decode(receiveBuffer).toString();
- //输出到控制台
- System.out.println(msg);
- }
- /**
- * 发送到服务端
- */
- private static void send(SocketChannel sendChannel) throws IOException {
- if(sendBuffer.remaining()!=0){
- synchronized (sendBuffer){
- sendBuffer.flip();
- sendChannel.write(sendBuffer);
- }
- }
- }
- }
java socket 模拟im 即时通讯的更多相关文章
- 一款Java开源的Springboot即时通讯 IM,附源码
# 开篇 电商平台最不能缺的就是即时通讯,例如通知类下发,客服聊天等.今天,就来给大家分享一个开源的即时通讯系统.如对文章不感兴趣可直接跳至文章末尾,有获取源码链接的方法. 但文章内容是需要你简单的过 ...
- Java Socket 模拟HTTP请求
public static void main(String[] args) { try { String url = "192.168.1.103"; Socket socket ...
- TCP UDP Socket 即时通讯 API 示例 MD
Markdown版本笔记 我的GitHub首页 我的博客 我的微信 我的邮箱 MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina ...
- java SSM框架 代码生成器 快速开发平台 websocket即时通讯 shiro redis
A代码编辑器,在线模版编辑,仿开发工具编辑器,pdf在线预览,文件转换编码 B 集成代码生成器 [正反双向](单表.主表.明细表.树形表,快速开发利器)+快速表单构建器 freemaker模版技术 , ...
- 通过python的socket库实现简易即时通讯小程序
前言 最近学习了一下有关tcp协议和socket有关的知识,看到许多socket实战都喜欢教如何做一个聊天程序,于是想着试试能不能不看教程自己写一个.当然我没太多时间做一个像qq一样的ui界面,所以做 ...
- java socket通讯(二)处理多个客户端连接
通过java socket通讯(一) 入门示例,就可以实现服务端和客户端的socket通讯,但是上一个例子只能实现一个服务端和一个客户端之间的通讯,如果有多个客户端连接服务端,则需要通过多线程技术来实 ...
- Socket网络通讯开发总结之:Java 与 C进行Socket通讯 + [备忘] Java和C之间的通讯
Socket网络通讯开发总结之:Java 与 C进行Socket通讯 http://blog.sina.com.cn/s/blog_55934df80100i55l.html (2010-04-08 ...
- node.js和socket.io纯js实现的即时通讯实例分享
在这个例子中,其实node.js并没有真正起到服务器的作用,因为我们这里可以直接运行client.html文件,而不用输入url请求,当 然,要想输入url请求页面内容还需要加入请求静态文件的代码.这 ...
- iOS开发之即时通讯之Socket(AsyncSocket)
1.AsyncSocket介绍 如果需要在项目中像QQ微信一样做到即时通讯,必须使用socket通讯. iOS中Socket编程的方式: BSD Socket: BSD Socket 是UNIX系统中 ...
随机推荐
- js二维码插件总结
jquery.qrcode.js生成二维码插件&转成图片格式 http://blog.csdn.net/u011127019/article/details/51226104
- awk说明书(转)
ref:http://blog.chinaunix.net/uid-429659-id-122573.html awk使用手册 作者:awk使用手册什么是awk? 你可能对UNIX比较熟悉,但你可能对 ...
- 4.ES核心慨念
一. 和lucene的关系 lucene是最先进,功能最强大的搜索库.但是使用复杂(要深入理解其中原理. elasticsearch,基于lucene,隐藏复杂性,提供简单易用的restful api ...
- 监听器应用【统计网站人数、自定义session扫描器、踢人小案例】
从第一篇已经讲解过了监听器的基本概念,以及Servlet各种的监听器.这篇博文主要讲解的是监听器的应用. 统计网站在线人数 分析 我们在网站中一般使用Session来标识某用户是否登陆了,如果登陆了, ...
- 理解JavaScript中的作用域
什么是变量,什么是作用域? 变量:简单来说就是在特定时间内保存特定值的一个名字而已,由于不存在定义某个变量必须要保存某种数据类型值的规则,所以变量的值及其数据类型可以在脚本生命周期内任意改变,变量可 ...
- dubbo refrence bean(服务引用)
在xml上写一个dubbo标签就可以把远程的服务引用到本地使用: <dubbo:reference id="buyFoodService" interface="c ...
- JDBC学习笔记(四)
减少各个Dao类间的重复代码,有以下几种方式: 写一个DBConnectionManager,将公共的查询逻辑做成方法,将sql语句作为参数传递给方法. public class DBConnecti ...
- SDN期末作业
期末项目 代码仓库:传送门 视频:组长已经发送给朱老师 选题:负载均衡场景3 选题内容: 该拓扑是数据中心拓扑的一部分,其中h1是数据中心外的一台客户机,h2-h5是数据中心内的服务器,请根据该拓扑实 ...
- .net使用AsposeWord导出word table表格
本文为原创,转载请注明出处 1.前言 .net平台下导出word文件还可以使用Microsoft.Office.Interop和NPOI,但是这两者都有缺点,微软的Office.Interop组件需要 ...
- Xcode的SVN提示"The request timed out."的解决方案
问题描述 在利用Xcode的SourceControl进行SVN代码检出时,确认输入地址.帐号密码都正确的情况下,总是提示"The request timed out.".该问题的 ...