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系统中 ...
随机推荐
- linux各种顔色代表
@linux中的各文件的颜色表示什么 白色:表示普通文件 蓝色:表示目录 绿色:表示可执行文件 红色:表示压缩文件 浅蓝色:链接文件 红色闪烁:表示链接的文件有问题 黄色:表示设备文件 灰色:表示其它 ...
- 如何使用 volatile, synchronized, final 进行线程间通信
原文地址:https://segmentfault.com/a/1190000004487149.感谢作者的无私分享. 你是否真正理解并会用volatile, synchronized, final进 ...
- 五、Html表单标签
表单,表单控件的主要作用就是收集用户体验,当用户提交表单时,用户输入的内容将作为请求参数提交到远程服务器. 1,form标签 <form>:创建表单,该元素不会生成可视化的界面,但是其他控 ...
- VisionPro笔记:色彩区分
VisionPro:色彩区分 来自:blog.sina.com.cn/yangchao168 利用色彩来区分物体这类的项目没做过,总觉得很难,尤其是涉及到RGB和HSI等.看到VisionPro中有这 ...
- django xadmin 集成DjangoUeditor富文本编辑器
本文档记录自己的学习历程! 介绍 Ueditor HTML编辑器是百度开源的在线HTML编辑器,功能非常强大 额外功能 解决图片视频等无法上传显示问题 Ueditor下载地址 https://gith ...
- hexo建立github,gitcafe博客并实时同步的要点
把hexo博客的源码和生成的页面实时同步到github和gitcafe. 用搜索引擎搜索"github 博客"等关键字会出现大量很好的文章教小白一步步搭建.我这里列出一些关键点,希 ...
- python中的字符串格式化
Python中常见的字符串格式化方式包括两种:字符串插入(str%),format函数(str.format()) 1.字符串插入 字符串插入是设置字符串格式的简单方法,与C语言.Fortran语言差 ...
- 为什么说http协议是无状态的?
首先了解基本概念:什么是无状态,什么是无连接 无状态协议: 协议的状态是指下一次传输可以“记住”这次传输信息的能力. http是不会为了下一次连接而维护这次连接所传输的信息,为了保证服务器内存. 比如 ...
- Linux根据UUID自动挂载磁盘分区
一般服务器都有多个硬盘分区,在重启后,这些分区的逻辑位置加载时可能会发生变动,如果使用传统的设备名称(例如:/dev/sda)方式挂载磁盘,就可能因为磁盘顺序变化而造成混乱. Linux环境中每个Bl ...
- 网络服务器操作命令telnet
有些命令是内部的,系统自带的,在装好系统后,就可以随时使用有些命令是系统中没有的,要自己安装一下才能使用,比如你说的telnet,需要安装一下才能使用的.CentOS中用 yum install te ...