魔域服务端源码
发一段给你吧。这一段是关于Socket的,呵呵,多学学。
bool CSocketKernel::Create(IMessagePort pPort)
{
m_idNpcSocket = SOCKET_NONE;
m_pMsgPort = pPort;
m_pMsgPort->Open();
// TODO: 请在此添加初始化代码
m_pListen = m_cListenGetInterface();
if(!ServerSocketInit())
return false;
if(!m_pListen->Listen(SERVER_LISTEN_PORT, SOCKET_SENDBUFSIZE))
return false;
return true; // return false : 创建失败,程序关闭。
}
bool CSocketKernel::ProcessMsg(OBJID idPacket, void buf, int nType, int nFrom)
{
// TODO: 请在此添加消息处理代码
switch(idPacket)
{
case SOCKET_CLOSESOCKET:
{
SOCKET_ID idSocket = (int)buf;
if(idSocket >= m_setSocketsize())
break;
LOGDEBUG("DEBUG:通讯核心收到关闭网络消息,SOCKET_ID[%u]", idSocket);
if(m_setSocket[idSocket]) // readmetxt (1-7)
{
m_setSocket[idSocket]->Close(true);
m_setSocket[idSocket] = NULL;
CServerSocket::Destroy(m_setServerSocket[idSocket]);
m_setProcessID[idSocket] = PROCESS_NONE;
m_setNetBreakFlag[idSocket] = false;
if(m_idNpcSocket == idSocket)
{
LOGDUMP("m_idNpcSocket(%d) = SOCKET_NONE", m_idNpcSocket);
m_idNpcSocket = SOCKET_NONE;
char pText = "NPC server broken connect";
m_pMsgPort->Send(MSGPORT_SHELL, SHELL_PRINTTEXT, STRING_TYPE(pText), pText);
}
}
}
break;
case SOCKET_SETPROCESSID:
{
const CHANGE_USERDATA pPack = (CHANGE_USERDATA)buf;
if(pPack->idSocket >= 0 && pPack->idSocket < m_setSocketsize()
&& m_setSocket[pPack->idSocket]
&& !IsNetBreakFlag(pPack->idSocket) ) // 网络正常
m_setProcessID[pPack->idSocket] = pPack->nData;
else
m_pMsgPort->Send(nFrom, KERNEL_CLOSEKERNEL, VARTYPE_INT, &pPack->idSocket); // readmetxt (1-6)
}
break;
case SOCKET_SENDCLIENTMSG:
{
const SENDCLIENTMSG_PACKET0 pPack = (SENDCLIENTMSG_PACKET0)buf;
if(pPack->idSocket >= m_setSocketsize())
break;
if(m_setSocket[pPack->idSocket])
{
char buf[MAX_PACKETSIZE];
int nNetPacketSize = UnitePacket(buf, MAX_PACKETSIZE, pPack->idPacket, pPack->buf, pPack->nSize);
ASSERT(nNetPacketSize);
if(nNetPacketSize)
{
#ifdef PALED_DEBUG
OBJID idPacket = ((unsigned short)buf + 1);
将Android项目导入import进Eclipse。 注意SDK版本是否匹配 。 服务器部署到Tomcat下。 你得在数据库中将这个点菜系统的数据库和表建好,或者导入。在服务器的代码中修改好你的数据库名和密码。
1、服务器一台,1-1-1也可以搭建,但人数不可以多,任务开启不可多下载链接里面的服务端搭建教程:1先安装宝塔2、放行安全组的相应端口具体要放行的端口有:3306、888、8888、5000、8101、8110、8120、8160-8168(这个是范围之8160是一线,依次类推)
2、安装数据库配置环境
3、登陆宝塔面板,在首页弹出的框中选择要安装的环境,第三个不用选择,其中mysql改为51,phpmyadmin改为40,然后选择编译安装即可。等待大约30-50分钟自动安装结束
4、配置数据库设置数据库的超级用户(root)的密码,并添加允许远程访问的root账户②、用宝塔面板的软件管理页面,重载mysql服务配置,使我们添加的远程访问生效
5、开始部署游戏数据①、第一次使用N11连接到我们服务器的mysql数据库(连接编码选择utf8),执行allsql文件以创建游戏所需的数据库和基础数据②、断开数据库连接,设置数据库连接编码为936,然后重新连接,将dl_adb_all数据库转存为sql文件(结构和数据)③、用n++编辑我们保存好的dl_adb_allsql文件,具体操作就是替换里面的所有外网ip为我们自己的服务器ip,如果需要改区名,就替换里面的所有问道一区为你想要的区名④、用n11删除掉dl_adball数据库中的表,然后运行我们保存的dl_adb_allsql文件进行重建表结构和数据
6、部署服务器端的文件配置服务器端文件,修改aaa ccs csa dba目录下的ini配置文件的信息①修改数据库相关的IP为127001,修改_daili的地址为服务器的IP②修改数据密码为我们设置的数据库密码2、复制线路驱动文件rungs(想架设几条线就复制几个),复制gs/gs目录下的gs1ini文件(同样是几条线路就复制几个)①修改gs1ini中的ip为服务器IP②修改gsini中的线路名分别问你自己数据库中设置的线路名
现在作为一个开发人员,http server相关的内容已经是无论如何都要了解的知识了。用curl发一个请求,配置一下apache,部署一个web server对我们来说都不是很难,但要想搞清楚这些背后都发生了什么技术细节还真不是很简单的。所以新的系列将是分享我学习Http Server的过程。
NanoHttpd是Github上的一个开源项目,号称只用一个java文件就能创建一个http server,我将通过分析NanoHttpd的源码解析如何开发自己的HttpServer。Github 地址:https://githubcom/NanoHttpd/nanohttpd
在开始前首先简单说明HttpServer的基本要素:
1能接受HttpRequest并返回HttpResponse
2满足一个Server的基本特征,能够长时间运行
关于Http协议一般HttpServer都会声明支持Http协议的哪些特性,nanohttpd作为一个轻量级的httpserver只实现了最简单、最常用的功能,不过我们依然可以从中学习很多。
首先看下NanoHttpd类的start函数
[java] view plaincopy
public void start() throws IOException {
myServerSocket = new ServerSocket();
myServerSocketbind((hostname != null) new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));
myThread = new Thread(new Runnable() {
@Override
public void run() {
do {
try {
final Socket finalAccept = myServerSocketaccept();
registerConnection(finalAccept);
finalAcceptsetSoTimeout(SOCKET_READ_TIMEOUT);
final InputStream inputStream = finalAcceptgetInputStream();
asyncRunnerexec(new Runnable() {
@Override
public void run() {
OutputStream outputStream = null;
try {
outputStream = finalAcceptgetOutputStream();
TempFileManager tempFileManager = tempFileManagerFactorycreate();
HTTPSession session = new HTTPSession(tempFileManager, inputStream, outputStream, finalAcceptgetInetAddress());
while (!finalAcceptisClosed()) {
sessionexecute();
}
} catch (Exception e) {
// When the socket is closed by the client, we throw our own SocketException
// to break the "keep alive" loop above
if (!(e instanceof SocketException && "NanoHttpd Shutdown"equals(egetMessage()))) {
eprintStackTrace();
}
} finally {
safeClose(outputStream);
safeClose(inputStream);
safeClose(finalAccept);
unRegisterConnection(finalAccept);
}
}
});
} catch (IOException e) {
}
} while (!myServerSocketisClosed());
}
});
myThreadsetDaemon(true);
myThreadsetName("NanoHttpd Main Listener");
myThreadstart();
}
1创建ServerSocket,bind制定端口
2创建主线程,主线程负责和client建立连接
3建立连接后会生成一个runnable对象放入asyncRunner中,asyncRunnerexec会创建一个线程来处理新生成的连接。
4新线程首先创建了一个HttpSession,然后while(true)的执行httpSessionexec。
这里介绍下HttpSession的概念,HttpSession是java里Session概念的实现,简单来说一个Session就是一次httpClient->httpServer的连接,当连接close后session就结束了,如果没结束则session会一直存在。这点从这里的代码也能看到:如果socket不close或者exec没有抛出异常(异常有可能是client段断开连接)session会一直执行exec方法。
一个HttpSession中存储了一次网络连接中server应该保存的信息,比如:URI,METHOD,PARAMS,HEADERS,COOKIES等。
5这里accept一个client的socket就创建一个独立线程的server模型是ThreadServer模型,特点是一个connection就会创建一个thread,是比较简单、常见的socket server实现。缺点是在同时处理大量连接时线程切换需要消耗大量的资源,如果有兴趣可以了解更加高效的NIO实现方式。
当获得client的socket后自然要开始处理client发送的httprequest。
Http Request Header的parse:
[plain] view plaincopy
// Read the first 8192 bytes
// The full header should fit in here
// Apache's default header limit is 8KB
// Do NOT assume that a single read will get the entire header at once!
byte[] buf = new byte[BUFSIZE];
splitbyte = 0;
rlen = 0;
{
int read = -1;
try {
read = inputStreamread(buf, 0, BUFSIZE);
} catch (Exception e) {
safeClose(inputStream);
safeClose(outputStream);
throw new SocketException("NanoHttpd Shutdown");
}
if (read == -1) {
// socket was been closed
safeClose(inputStream);
safeClose(outputStream);
throw new SocketException("NanoHttpd Shutdown");
}
while (read > 0) {
rlen += read;
splitbyte = findHeaderEnd(buf, rlen);
if (splitbyte > 0)
break;
read = inputStreamread(buf, rlen, BUFSIZE - rlen);
}
}
1读取socket数据流的前8192个字节,因为http协议中头部最长为8192
2通过findHeaderEnd函数找到header数据的截止位置,并把位置保存到splitbyte内。
[java] view plaincopy
if (splitbyte < rlen) {
inputStreamunread(buf, splitbyte, rlen - splitbyte);
}
parms = new HashMap<String, String>();
if(null == headers) {
headers = new HashMap<String, String>();
}
// Create a BufferedReader for parsing the header
BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, rlen)));
// Decode the header into parms and header java properties
Map<String, String> pre = new HashMap<String, String>();
decodeHeader(hin, pre, parms, headers);
1使用unread函数将之前读出来的body pushback回去,这里使用了pushbackstream,用法比较巧妙,因为一旦读到了header的尾部就需要进入下面的逻辑来判断是否需要再读下去了,而不应该一直读,读到没有数据为止
2decodeHeader,将byte的header转换为java对象
1Http协议第一行是Method URI HTTP_VERSION
2后面每行都是KEY:VALUE格式的header
3uri需要经过URIDecode处理后才能使用
4uri中如果包含?则表示有param,httprequest的param一般表现为:/indexjspusername=xiaoming&id=2
下面是处理cookie,不过这里cookie的实现较为简单,所以跳过。之后是serve方法,serve方法提供了用户自己实现httpserver具体逻辑的很好接口。在NanoHttpd中的serve方法实现了一个默认的简单处理功能。
这个默认的方法处理了PUT和POST方法,如果不是就返回默认的返回值。
parseBody方法中使用了tmpFile的方法保存httpRequest的content信息,然后处理,具体逻辑就不细说了,不是一个典型的实现。
最后看一下发response的逻辑:
sendAsFixedLength(outputStream, pending);
}
outputStreamflush();
safeClose(data);
} catch (IOException ioe) {
// Couldn't write No can do
}
}
发送response的步骤如下:
1设置mimeType和Time等内容。
2创建一个PrintWriter,按照HTTP协议依次开始写入内容
3第一行是HTTP的返回码
4然后是content-Type
5然后是Date时间
6之后是其他的HTTP Header
7设置Keep-Alive的Header,Keep-Alive是Http11的新特性,作用是让客户端和服务器端之间保持一个长链接。
8如果客户端指定了ChunkedEncoding则分块发送response,Chunked Encoding是Http11的又一新特性。一般在response的body比较大的时候使用,server端会首先发送response的HEADER,然后分块发送response的body,每个分块都由chunk length\r\n和chunk data\r\n组成,最后由一个0\r\n结束。
9如果没指定ChunkedEncoding则需要指定Content-Length来让客户端指定response的body的size,然后再一直写body直到写完为止。
最后总结下实现HttpServer最重要的几个部分:
1能够accept tcp连接并从socket中读取request数据
2把request的比特流转换成request对象中的对象数据
3根据http协议的规范处理http request
4产生http response再写回到socket中传给client。
0条评论