客户端Cocos2dx(C++)
服务器Mina(Java)
客户端主要使用了Linux的BSD Socket接口进行socket的数据传输连接
服务器直接用了Mina框架来接受socket
具体参考的博客:
1.【C/S通信交互之Socket篇】Cocos2dx(Client)使用BSD Socket与Mina(Server)手机网游通信框架! ----李华明Himi
http://blog.csdn.net/xiaominghimi/article/details/7603123
本站文章均为 李华明Himi 原创,转载务必在明显处注明:
转载自【黑米GameDev街区】 原文链接: http://www.himigame.com/iphone-cocos2dx/844.html
☞ 点击订阅 ☜ 本博客最新动态!及时将最新博文通知您!
其实对于此篇算是对于这段时间网络研究的一个总结。
对于手游网络通信的交互,一般情况下,Socket长连接直接使用Mina框架即可,对于Http短连接使用Servlet 入口即可(那么对于后期将陆续更新Servlet博文)
那么本篇主要介绍Socket长连接,当然与此配对的跨平台通信则选择了BSD Socket,当然还有其他的,这里只说BSD Socket;
对于BSD Socket不是很熟悉的请自行google学习下,Himi需要提醒大家的是BSD Socket不是第三方类库,而是UNIX/Linux系统中通用的网络接口;
首先连接到Server端,这里Himi简单封装一个函数提供大家使用;
导入 #include <netdb.h>
两个参数:1:IP地址 2:端口
其中有个socket成员变量:
int socketHandle = 0;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
int HSocket::connect( const char * ip, unsigned short port){ struct sockaddr_in sa; struct hostent* hp; hp = gethostbyname(ip); if (!hp){ return -1; } memset (&sa, 0, sizeof (sa)); memcpy (( char *)&sa.sin_addr, hp->h_addr, hp->h_length); sa.sin_family = hp->h_addrtype; sa.sin_port = htons(port); socketHandle = socket(sa.sin_family, SOCK_STREAM, 0); if (socketHandle < 0){ printf ( "failed to create socket
" ); return -1; } if (::connect(socketHandle, (sockaddr*)&sa, sizeof (sa)) < 0){ printf ( "failed to connect socket
" ); ::close(socketHandle); return -1; } CCLOG( "Client connect OK ! IP: %s:%d " ,ip,port); return 0; } |
两点注意:
1. 对于bsd socket 的 ::connect()函数进行连接服务器的时候会阻塞你的主线程,所以将Himi封装好的connect()函数在另一个线程调用则是一个好的处理方式;否则一旦网络比较差,你的游戏就假死ing~ 悲剧;
2. 对于线程我们直接使用 pThread 就可以了,那么这里Himi就给一个创建线程的例子吧:
定义一个线程成员变量:
pthread_t threadHimi;
然后Himi也为大家封装一个函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
int HSocket::threadStart(){ int errCode = 0; do { pthread_attr_t tAttr; errCode = pthread_attr_init(&tAttr); CC_BREAK_IF(errCode!=0) errCode = pthread_attr_setdetachstate(&tAttr, PTHREAD_CREATE_DETACHED); if (errCode!=0) { pthread_attr_destroy(&tAttr); break ; } errCode = pthread_create(&threadHimi, &tAttr, thread_function, this ); } while (0); return errCode; } |
1)创建线程其实就是pthread_create()函数,但是上面这个函数其他内容则主要为你创建的线程设定为分离式;这里 thread_function 是个函数;童鞋们对于pthread不太熟悉的请自行百度和google;
2)当然这里Himi要提醒大家,pthread是c库,不是c++库,它要求是全局函数,所以得static的!
那么连接到Server端之后我们就应该关心, BSD Socket 对于数据的发送和接收!
1.发送: send 函数:
send(socketHandle,buffer,length,0)
socketHandle : 你已经连接的socket
buffer:发送的缓存数据
length:数据长度
2. 接收: recv 函数:
recv(socketHandle, p, length, 0)
socketHandle : 你已经连接的socket
p : 存放数据的容器
length:获取服务器数据的长度;
注意:
1. 对于recv 函数的其中参数 length长度,大家务必要仔细,很清楚服务器应该发来的数据长度,因为一旦recv函数执行,那么不从Server端读取出length长度就不会罢休的!
2. 如果你的Server端是Java的,那么要注意大端 ,小端的问题!Java属于大端模式,c++属于小端模式;(对于大小端不熟悉的,也请自行google,这里仍旧不赘述)
所以:
Client->recv到数据后->数据转换成小端
Client->send数据时->数据转换成大端
这样才能保证Java服务器与cocos2dx的Client端正常交互;
2.【mina框架简单搭建服务器,并且实现简单的Client和Server交互】---李华明
http://www.himigame.com/apache-mina/831.html
对于Apache Mina不太连接的童鞋,请移步到如下百度百科连接进行学习了解:
http://baike.baidu.com/view/2668084.htm
首先建立一个new project(Server端),这里Himi使用IDE是 eclipse;
OK,首先我们这里先配置下环境:对于Mina的日志输出使用的是slf4j,对于slf4j在开发Hibernate的时候已经很熟悉了,不需要再介绍了。另外一方面就是加入mina的core核心jar包;
1. mina-core.jar 2. slf4j-api.jar 3.slf4j-simple.jar
然后我们首先创建两个类:
HimiObject.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
/** * @author Himi */ import java.io.Serializable; public class HimiObject implements Serializable{ public HimiObject( int id,String name){ this .id=id; this .name=name; } private int id; private String name; public int getId() { return id; } public void setId( int id) { this .id = id; } public String getName() { return name; } public void setName(String name) { this .name = name; } } |
这个类是个消息Object,它用于server与client端的交互的数据,它需要序列化,所以我们使用Serializable接口;至于在mina框架中起到什么作用这个后续来说;
ClientMinaServerHanlder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
|
/** * @author Himi */ import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; public class ClientMinaServerHanlder extends IoHandlerAdapter { private int count = 0 ; // 当一个新客户端连接后触发此方法. 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 s = (String) message; // // Write the received data back to remote peer // System.out.println("收到客户机发来的消息: " + s); // // 测试将消息回送给客户端 session.write(s+count); count++; 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( "one Clinet Disconnect !" ); } // 当连接空闲时触发此方法. @Override public void sessionIdle(IoSession session, IdleStatus status) { System.out.println( "连接空闲" ); } // 当接口中其他方法抛出异常未被捕获时触发此方法 @Override public void exceptionCaught(IoSession session, Throwable cause) { System.out.println( "其他方法抛出异常" ); } } |
本类主要是继承IoHandlerAdapter并且重写其类的一些函数,至于每个函数的作用Himi都已经在代码中加以注视;本类的作用:
此类是用以处理消息的也可说是个消息处理器,当客户端有消息传给server端的时候,或者server端传递给Client端的时候(Client端也会有个消息处理器)都会通过消息处理器进行处理。
OK,下面我们来书写server端的main函数类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
/** * @author Himi */ import java.io.IOException; import java.net.InetSocketAddress; import org.apache.mina.core.filterchain.DefaultIoFilterChainBuilder; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory; import org.apache.mina.transport.socket.SocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; public class MinaServer { /** * @param args */ public static void main(String[] args) { //创建一个非阻塞的server端Socket ,用NIO SocketAcceptor acceptor = new NioSocketAcceptor(); /*---------接收字符串---------*/ // //创建一个接收数据过滤器 // DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); // //设定过滤器一行行(/r/n)的读取数据 // chain.addLast("mychin", new ProtocolCodecFilter(new TextLineCodecFactory() )); /*---------接收对象---------*/ //创建接收数据的过滤器 DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); //设定这个过滤器将以对象为单位读取数据 ProtocolCodecFilter filter= new ProtocolCodecFilter( new ObjectSerializationCodecFactory()); chain.addLast( "objectFilter" ,filter); //设定服务器消息处理器 acceptor.setHandler( new ClientMinaServerHanlder()); //服务器绑定的端口 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); } } |
IoService 是负责底层通讯接入,而 IoHandler 是负责业务处理的。那么 MINA 架构图中的 IoFilter 作何用途呢?答案是你想作何用途都可以。但是有一个用途却是必须的,那就是作为 IoService 和 IoHandler 之间的桥梁。IoHandler 接口中最重要的一个方法是 messageReceived,这个方法的第二个参数是一个 Object 型的消息,总所周知,Object 是所有 Java 对象的基础,那到底谁来决定这个消息到底是什么类型呢?这个取决于我们后面设定的过滤器!
对于在mina中建立一个server,步骤如下:
1. 建立一个SockerAcceptor ,除了启动server之外它还可以为我们可以生成过滤器DefaultIoFilterChainBuilder、设置消息处理器等功能;
2.设置过滤器
3. 设置消息处理器
其实很容易不是么? 哈哈;
OK,这里多说一些:
对于消息处理器 DefaultIoFilterChainBuilder,它的作用是用于设定收发的形式,例如:
//创建一个接收数据过滤器 DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); //设定过滤器一行行(/r/n)的读取数据 chain.addLast("mychin", new ProtocolCodecFilter(new TextLineCodecFactory() ));
这样设置一个过滤器作用是将来自客户端输入的信息转换成一行行的文本后传递给 IoHandler,因此我们可以在 messageReceived 中直接将 msg 对象强制转换成 String 对象。
ps.而如果我们不提供任何过滤器的话,那么在 messageReceived 方法中的第二个参数类型就是一个 byte 的缓冲区,对应的类是 org.apache.mina.common.ByteBuffer。虽然你也可以将解析客户端信息放在 IoHandler 中来做,但这并不是推荐的做法,使原来清晰的模型又模糊起来,变得 IoHandler 不只是业务处理,还得充当协议解析的任务。
mina自身带有一些常用的过滤器,例如LoggingFilter(日志记录)、BlackListFilter(黑名单过滤)、CompressionFilter(压缩)、SSLFilter(SSL加密)等。
当我们设置如下:
1
2
3
4
5
|
//创建接收数据的过滤器 DefaultIoFilterChainBuilder chain = acceptor.getFilterChain(); //设定这个过滤器将以对象为单位读取数据 ProtocolCodecFilter filter= new ProtocolCodecFilter( new ObjectSerializationCodecFactory()); chain.addLast( "objectFilter" ,filter); |
这样以来我们server可以收发Object对象啦;
1
|
acceptor.setHandler( new ClientMinaServerHanlder()); |
这里是设置消息处理器,绑定在我们的ClientMinaServerHanlder类上,其实mina对于收发处理已经完全交给开发者来进行处理,所以至于在消息处理器如何编写和处理就放任不会管了;
OK,现在我们可以run一下启动server端了;
当然我们现在也可以来测试了,当前我们还没有书写Client端的代码,所以我们使用terminal终端进行测试,OK,打开你的terminal
然后输入 telnet localhost 9988
观察服务器打印:
OK,没有任何问题;但是这时候大家不要在终端书写内容给server,因为我们server的消息处理器中的接受Client的函数中做了如下处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 当客户端发送的消息到达时: @Override public void messageReceived(IoSession session, Object message) throws Exception { // // 我们己设定了服务器解析消息的规则是一行一行读取,这里就可转为String: // String s = (String) message; // // Write the received data back to remote peer // System.out.println("收到客户机发来的消息: " + s); // // 测试将消息回送给客户端 session.write(s+count); count++; HimiObject ho = (HimiObject) message; System.out.println(ho.getName()); ho.setName( "serverHimi" ); session.write(ho); } |
Himi这里server处理client端发来的数据处理函数(如上代码)中,当Client发送数据过来的时候,将消息message强制转换成了一个HimiObject对象,然后改个name重写发给Client端,但是由于Client端是使用终端模拟登陆根本无法接受这个对象,所以会出异常;
但是到这里大家应该懂得,HimiObject类的作用了;哈哈