公司最近要求调研mina作为服务,在ios上实现客户端。
关于Apache mina我是在eclipse上写的,客户端在xcode上写的。Apache mina网上有很多例子,照着写就行。
我贴出自己修改的部分,其他都是和网上一样。部分代码多余,本人不太会java就没整理,请多谅解。
//我是基于系统自己提供的编解码器TextLineCodecFactory上实现文字图片传输,我想会有跟好的办法实现大图片的传输,可自行编写编解码器
//mina服务器,还有4个库可以到网上找
ClientMinaServerHanlder.java
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.sql.Date;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.apache.mina.core.future.CloseFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
public class ClientMinaServerHanlderextendsIoHandlerAdapter {
private staticfinalbooleanNO =false;
private intcount = 0;
private Map<String,Socket> users=new HashMap<String, Socket>();
// 当一个新客户端连接后触发此方法.
public void sessionCreated(IoSession session) {
System.out.println("新客户端连接");
}
// 当一个客端端连结进入时 @Override
public void sessionOpened(IoSession session)throws Exception {
count++;
System.out.println("第 " +count +" 个 client 登陆!address: : "
+ session.getRemoteAddress());
}
// 当客户端发送的消息到达时:
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
// // // 我们己设定了服务器解析消息的规则是一行一行读取,这里就可转为String:
String content = message.toString();
String[] msg=content.split(",");
if (msg[0]!=null & msg[1]!=null){
if (msg[0].equals("msg_login")){
session.setAttribute("userID", msg[1]); //其实第一条信息在端口连接成功时我就发送信息是为了让服务器记录当前发送信息用户的唯一标志,为以后找特定客户聊天做标记
System.out.println(msg[1]+"登陆成功");
}else{
Collection<IoSession> sessions = session.getService().getManagedSessions().values();
for (IoSession tmpsess : sessions) {
String toUserID = (String) tmpsess.getAttribute("userID");
if(toUserID !=null){
if(toUserID.equals(msg[2])){
System.out.println("msg[2]"+msg[2]);
System.out.println("消息发送给:"+toUserID);
tmpsess.write(message);
}
}
}
}
}
//下面是通过IP地址做标记,真是情况下每个人的ip是会变的,不可取,如果只是想试验可以用ip
// boolean i = NO;
// if(i){
// // 拿到所有的客户端Session
// Collection<IoSession> sessions = session.getService().getManagedSessions().values();
//
// //转发对应的移动端
// String[] msg=content.split(",");
// if (msg[0]!=null & msg[1]!=null)
// {
// //是注册的
// if (msg[0].equals("msg0"))
// {
// session.write(msg[1]+"欢迎您上线");
// System.out.println(msg[1]+"欢迎您上线");
// }
// //聊天信息 格式 //输入格式: 0-格式 1-发送人 2-接收人 3-聊天内容
// else if (msg[0].equals("msg")){
//
// // 向所有客户端发送数据
// for (IoSession sess : sessions) {
// SocketAddress getip = sess.getRemoteAddress();
// InetSocketAddress inetSocketAddress1 = (InetSocketAddress)getip;
// String address = inetSocketAddress1.getAddress().getHostAddress();
// String[] postmsg=msg[2].split(":");
// if(postmsg[0].equals(address)){
// sess.write(msg[2]+"说: "+msg[3]);
// System.out.println(msg[2]+"对"+msg[1]+"说: "+msg[3]);
// System.out.println(msg[2]);
// }
//
// }
// }
// }
// else
// {
// session.write("消息不合规范");
// }
// }else{
// session.write(message);
//
// }
//
HimiObject ho = (HimiObject) message;
System.out.println(ho.getName());
ho.setName("serverHimi");
session.write(ho);
}
// 当信息已经传送给客户端后触发此方法.
@Override
public void messageSent(IoSession session, Object message) {
System.out.println("信息已经传送给客户端");
}
// 当一个客户端关闭时
@Override
public void sessionClosed(IoSession session) {
System.out.println("当一个客户端关闭时");
}
// 当连接空闲时触发此方法.
@Override
public void sessionIdle(IoSession session, IdleStatus status) {
System.out.println("连接空闲");
}
// 当接口中其他方法抛出异常未被捕获时触发此方法
@Override
public void exceptionCaught(IoSession session, Throwable cause) {
System.out.println("其他方法抛出异常");
}
}
MinaServer.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.SocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
public class MinaServer {
public staticvoid main(String[] args) {
//创建一个非阻塞的server端Socket ,用NIO
SocketAcceptor acceptor = new NioSocketAcceptor();
/*---------接收对象---------*/
//创建接收数据的过滤器
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
//设定这个过滤器将以对象为单位读取数据
// acceptor.getFilterChain().addLast("codec",
// new ProtocolCodecFilter(objectSerializationCodecFactory));
// acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MyTextLineCodecFactory()));
//
TextLineCodecFactory factory = new TextLineCodecFactory();
factory.setDecoderMaxLineLength(10 * 1024 * 1024);//(系统默认MyTextLineCodecFactory传输限制为1k,这里设置可以扩大传输,主要用于较大图片传输)
factory.setEncoderMaxLineLength(10 * 1024 * 1024);
ProtocolCodecFilter filter= new ProtocolCodecFilter(factory);
// ProtocolCodecFilter filter= new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("utf-8")));
chain.addLast("mychin", filter);
//下面3个我是随便设置,为了试试能不能扩大MyTextLineCodecFactory的默认传输限制
acceptor.getSessionConfig().setSendBufferSize(10 * 1024 * 1024);
acceptor.getSessionConfig().setReadBufferSize(10 * 1024 * 1024);
acceptor.getSessionConfig().setReceiveBufferSize(10 * 1024 * 1024);
//设定服务器消息处理器
acceptor.setHandler(new ClientMinaServerHanlder());
// 设置session配置,30秒内无操作进入空闲状态
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30);
//服务器绑定的端口
int bindPort = 9988;
//绑定端口,启动服务器
try {
acceptor.bind(new InetSocketAddress(bindPort));
} catch (IOException e) {
System.out.println("Mina Server start for error!"+bindPort);
e.printStackTrace();
}
System.out.println("Mina Server run done! on port:"+bindPort);
}
}
//ios客户端,我是基于在code4app上照的一个关于GCDAsyncSocket的demo,叫zxSocket。(里面还有用其做服务器的代码,推荐一看)
//还有服务器text那个自带编解码接受数据摇\n为结束符,所以我在ios代码上自行拼接\n
#import "ViewController.h"
@implementation ViewController
@synthesize socket;
@synthesize host;
@synthesize port;
@synthesize m_imageView,m_count,m_getmsgTextView,m_sendMessageTextView,m_getcontent,m_getlength,m_gettype;
#define MSG_LOGIN @"msg_login"
#define MSG_STRING @"msg_string"
#define MSG_IMAGE @"msg_image"
//和别的客户端交互时,设置时要设反以下2个客户ID(例子里只实现2个用户聊天300001和300002)
#define USRID_SEND @"300001"//我设定的客户端自己的唯一标示ID
#define USRID_GET @"300002"//要发送的人的ID
-(void)addText:(NSString *)str
{
m_getmsgTextView.text = [m_getmsgTextView.textstringByAppendingFormat:@"%@\n",str];
}
- (void)viewDidLoad
{
[superviewDidLoad];
host.text =@"192.168.2.15";
port.text = @"9988";
m_count = 0;
m_contentlength = 0;
m_msgData = [[NSMutableDataalloc]init];
self.m_getmsgTextView.returnKeyType =UIReturnKeyDone;//返回键的类型
m_getmsgTextView.delegate =self;
m_sendMessageTextView.delegate =self;
// Do any additional setup after loading the view, typically from a nib.
}
-(BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if ([text isEqualToString:@"\n"]) {
[textView resignFirstResponder];
return NO;
}
return YES;
}
-(BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
- (void)viewDidUnload
{
[self setHost:nil];
[self setPort:nil];
[superviewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation !=UIInterfaceOrientationPortraitUpsideDown);
}
- (IBAction)connect:(id)sender {
socket = [[GCDAsyncSocketalloc]initWithDelegate:selfdelegateQueue:dispatch_get_main_queue()];
//socket.delegate = self;
NSError *err = nil;
if(![socket connectToHost:host.text onPort:[port.text intValue] error:&err])
{
[self addText:err.description];
}else
{
[selfaddText:@"客户端300339登陆成功"];
}
}
-(void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
[self addText:[NSStringstringWithFormat:@"连接到:%@:%d",host,port]];
//登陆让服务器接受当前连接的客户端自己设置的ID,和服务器协定数据格式(消息类型+客户ID)
NSString *m_loginstr = [NSStringstringWithFormat:@"%@,%@\n",MSG_LOGIN,USRID_SEND];
[socketwriteData:[m_loginstrdataUsingEncoding:NSUTF8StringEncoding]withTimeout:-1tag:0];
[socketreadDataWithTimeout:-1tag:0];
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
}
//发送文字
- (IBAction)send:(id)sender {
NSString *msg_content = [NSStringstringWithFormat:@"%@\n",m_sendMessageTextView.text];
NSString *msg_type = MSG_STRING;
NSString *msg_length = [NSStringstringWithFormat:@"%d",msg_content.length];
//服务器协定数据格式(消息类型+客户ID+摇接受人的ID+消息长度+消息内容)
NSString *sendmsgstr = [NSStringstringWithFormat:@"%@,%@,%@,%@,%@",msg_type,USRID_SEND,USRID_GET,msg_length,msg_content];
[socketwriteData:[sendmsgstrdataUsingEncoding:NSUTF8StringEncoding]withTimeout:-1tag:0];
[selfaddText:[NSStringstringWithFormat:@"%@:%@\n",USRID_SEND,m_sendMessageTextView.text]];
[m_sendMessageTextViewresignFirstResponder];
[m_getmsgTextViewresignFirstResponder];
[socketreadDataWithTimeout:-1tag:0];
}
//发送图片,我这边自己为实现大图片能否传输成功就随便发送本地图片,可自己调用相册里的照片
- (IBAction)sendImage:(id)sender {
NSString *path = [[NSBundlemainBundle]pathForResource:@"6"ofType:@"png"];
NSData *dataObj1 = [NSDatadataWithContentsOfFile:path];
NSString *imageBase64String;
// base64 encode the binary data into a string format
if ([dataObj1 respondsToSelector:@selector(base64EncodedStringWithOptions:)]) {
imageBase64String = [dataObj1 base64EncodedStringWithOptions:0];// iOS 7+
} else {
imageBase64String = [dataObj1 base64Encoding]; // pre-iOS7
}
NSString *msg_content = [NSStringstringWithFormat:@"%@\n",imageBase64String];
NSString *msg_type = MSG_IMAGE;
NSString *msg_length = [NSStringstringWithFormat:@"%d",[imageBase64StringdataUsingEncoding:NSUTF8StringEncoding].length];
//服务器协定数据格式(消息类型+客户ID+摇接受人的ID+消息长度+消息内容)
NSString *sendmsgstr = [NSStringstringWithFormat:@"%@,%@,%@,%@,%@",msg_type,USRID_SEND,USRID_GET,msg_length,msg_content];
[socketwriteData:[sendmsgstrdataUsingEncoding:NSUTF8StringEncoding]withTimeout:-1tag:0];
[socketreadDataWithTimeout:-1tag:0];
}
//图片大的话,服务器会分几次传送给你,你要连续接受自行拼装,单纯文字,就直接显示
-(void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
int datalength = data.length;
m_contentlength += datalength;
[m_msgData appendData:data];
NSString *newMessage = [[NSStringalloc]initWithData:dataencoding:NSUTF8StringEncoding];
NSArray *getmsgarr = [newMessage componentsSeparatedByString:@","];
if([getmsgarr count] ==5){//接收到头部
self.m_gettype = [getmsgarrobjectAtIndex:0];
self.m_getlength = [getmsgarrobjectAtIndex:3];
self.m_getcontent = [getmsgarrobjectAtIndex:4];
}
if([self.m_gettypeisEqualToString:MSG_STRING]){
m_contentlength = 0;
[m_msgData setData:nil];
[self addText:[NSStringstringWithFormat:@"%@:%@",[getmsgarrobjectAtIndex:1],self.m_getcontent]];
}elseif([self.m_gettypeisEqualToString:MSG_IMAGE]){
if(m_contentlength > [self.m_getlengthintValue]){
NSString *newMessage = [[NSStringalloc]initWithData:m_msgDataencoding:NSUTF8StringEncoding];
NSArray *getmsgarr = [newMessagecomponentsSeparatedByString:@","];
if([getmsgarr count] == 5){//接收到头部
self.m_getcontent = [getmsgarrobjectAtIndex:4];
}
NSData *decodedImageData = [[NSDataalloc]initWithBase64Encoding:self.m_getcontent];
UIImage *decodedImage = [UIImageimageWithData:decodedImageData];
m_imageView.image = decodedImage;
}
}
[socketreadDataWithTimeout:-1tag:0];
}