身在创业公司,有很多东东需要写,服务我们可以用tomcat,apache-http, apache-ftp,而有些服务不得不自己写。本文就是尝试使用java io写一个socket服务。
思路:
服务端:服务类(SocketServer),线程监听类(WorkThread),处理类(SocketHandler)
客户端:消息请求类和响应接收类
实现流程:服务端初始化配置,启动一定数量线程监听,监听到请求后,调用SocketHandler进行处理,返回应答给客户端。
分析:这是常见的IO阻塞式服务,效率自然不高,扩展性不强,自己用可以,想学习socket服务及开发思路的新手可以看下,欢迎拍砖。
值得学习:SocketServer如何层层调用SocketHandelr实例的。
下面主要讲述服务端及相关代码实现:
1、BaseSocket Socket流数据的读写操作
package com.zhs.common;
/*
* Socket流数据的读写操作
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import org.apache.log4j.Logger;
import com.zhs.common.util.ByteBuffer;
public class BaseSocket {
Socket socket;
private final static Logger log = Logger.getLogger(BaseSocket.class);
public BaseSocket(Socket s)
{
socket =s;
}
final static int BUF_SIZE=60000;
byte[] inbuf=new byte[BUF_SIZE];
int in_len=0,in_used=0;
byte[] outbuf=new byte[BUF_SIZE];
int out_used=0;
public byte getbyte() throws Exception
{
if(in_len-in_used<1)
{
int num= readToBuf();
if(num<=0) throw new Exception("cannot read enough data from socket!");
}
in_used++;
log.debug("getbyte "+inbuf[in_used-1]);
return inbuf[in_used-1];
}
private int readToBuf() throws IOException
{
InputStream is=socket.getInputStream();
if(in_len-in_used>0)
{
copy(inbuf,in_used,in_len-in_used,inbuf,0);
in_len=in_len-in_used;
in_used=0;
}else if(in_len-in_used==0)
{
in_len=0;
in_used=0;
}
int num=is.read(inbuf,in_len,BUF_SIZE-in_len);
if(num>0)in_len+=num;
return num;
}
public long getlong()throws IOException, Exception
{
long i0=getint();
long i1=getint();
return (i0&0xffffffffl) +(i1<<32);
}
public int getint() throws IOException, Exception
{ int left=in_len-in_used;
if(left<4)
{
int num= readToBuf();
if(num<=0) throw new Exception("cannot read enough data from socket!");
return getint();
}
int v=((int)inbuf[in_used++]&0xff);
v+=((int)inbuf[in_used++]&0xff)<<8;
v+=((int)inbuf[in_used++]&0xff)<<16;
v+=((int)inbuf[in_used++]&0xff)<<24;
log.debug("getint "+v);
return v;
}
public String getString(int len) throws IOException,Exception
{
if(len==0)return null;
ByteBuffer bb=new ByteBuffer(len);
int left=in_len-in_used;
if(left>=len)
{ bb.append(inbuf,in_used,len);
in_used+=len;
}
else
{ int num=in_len-in_used;
bb.append(inbuf,in_used,num);
in_used=in_len;
while(readToBuf()>0)
{
if(len-num>in_len-in_used)
{
num+=in_len-in_used;
bb.append(inbuf,in_used,in_len);
in_used=in_len;
}
else
{ bb.append(inbuf,in_used,len-num);
in_used=len-num;
num=len;
break;
}
}
if(num!=len)throw new Exception("there are no enough data to read ");
}
String s= bb.toString("utf-8");
// log.debug("getString:"+s);
return s;
}
public int readByteArray(byte [] bb,int begin,int len) throws IOException,Exception
{
int left=in_len-in_used;
if(left==0)
{
if(readToBuf()<=0)
throw new Exception("there are no enough data to read");
}
left=in_len-in_used;
if(left>=len)
{ copy(inbuf, in_used, len , bb, begin);
in_used+=len;
return len;
}
int num=in_len-in_used;
copy(inbuf, in_used, num , bb, begin);
in_used=in_len;
return num;
}
public int readByteArray(byte [] bb,int len) throws IOException,Exception
{
int left=in_len-in_used;
if(left==0)
{
if(readToBuf()<=0)
throw new Exception("there are no enough data to read");
}
left=in_len-in_used;
if(left>=len)
{ copy(inbuf, in_used, len , bb, 0);
in_used+=len;
return len;
}
int num=in_len-in_used;
copy(inbuf, in_used, num , bb, 0);
in_used=in_len;
return num;
}
private void copy(byte[] from, int begin, int len, byte[] to, int start)
{
for(int i=0;i<len;i++)
to[start+i]=from[begin+i];
}
public void close() throws IOException
{
socket.close();
}
public void save(byte b) throws IOException
{
// log.debug("save byte:"+b);
if(out_used+1<BUF_SIZE)
outbuf[out_used++]=b;
else
{ send();
save(b);
}
}
public void send() throws IOException
{
OutputStream out=socket.getOutputStream();
out.write(outbuf,0,out_used);
out_used=0;
}
public void save(int i) throws IOException
{
log.debug("save int:"+i);
if(out_used+4<BUF_SIZE)
{
outbuf[out_used++]=(byte)(i&0xff);
outbuf[out_used++]=(byte)(i>>8&0xff);
outbuf[out_used++]=(byte)(i>>16&0xff);
outbuf[out_used++]=(byte)(i>>24&0xff);
}
else
{ send();
save(i);
}
}
public void save(long l) throws IOException
{
log.debug("save long:"+l);
int i=(int) (l &0xffffffffl);
save(i);
i=(int) (l>>32);
save(i);
}
public void save(byte b[],int begin ,int len ) throws IOException
{
// log.debug("save byte[] :len="+len);
if(len<BUF_SIZE-out_used)
{
copy(b,begin,len,outbuf,out_used);
out_used+=len;
}else
{
int left=BUF_SIZE-out_used;
copy(b,begin,left,outbuf,out_used);
out_used+=left;
send();
save(b,begin+left,len-left);
}
}
public void send(byte b) throws IOException
{
//log.debug("send byte:"+b);
save(b);send();
}
public void send(int b) throws IOException
{
// log.debug("send int:"+b);
save(b);send();
}
public void write(String text) throws IOException
{
if(text==null)
save(0);
else
{
byte[]bb=text.getBytes("utf-8");
save(bb.length);
save(bb,0,bb.length);
}
}
public void write(int i) throws IOException
{
save(i);
}
public void write(long l) throws IOException
{
save(l);
}
public void write(boolean b) throws IOException
{ if(b)
save((byte)1);
else
save((byte)0);
}
public boolean readboolean() throws Exception
{
byte b=getbyte();
return b==1;
}
public int readint()throws Exception
{
return getint();
}
public long readlong() throws IOException, Exception
{
return getlong();
}
public String readString() throws IOException, Exception
{
int l=getint();
if(l==0)return null;
return getString(l);
}
public static void main(String [] argv)
{
long l=System.currentTimeMillis();
int i=(int)l;
System.out.println(l);
System.out.println(l&0xffffffffl);
System.out.println(i);
}
}
3 常量类
package com.zhs.common;
public class ControlerVariable
{
//连接成功
public static final int SUCCESS = 0;
//服务忙
public static final int ERR_BUSY = 1;
public static final int CONNECT_STATUS = 70;
public static final int ASKPERSON = 71;
public static final int ERROR_ASKPERSON = 72;
public static final int LOOPACK = 100;
//尝试连接
public static final int ACK = 211;
public static final int ERROR_REQUEST_ITEM=219;
public static final int EQUALS_REQUEST_ITEM=220;
public static final int LOWER_REQUEST_ITEM=221;
public static final int LARGER_REQUEST_ITEM=222;
public static final String SOCK_IP="system.net.sock.ip";
public static final String SOCK_QUERY_PORT="system.net.sock.query.port";
public static final String SOCK_TIMEOUT_S="socket.timeout.second";
public static final String SYS_CHARSET="system.charset";
}
3、SocketServer server端的服务类,调用了SocketHandler和WorkThread类作为其成员,进行请求的处理
package com.zhs.common.server;
import java.net.ServerSocket;
import java.util.ArrayList;
import java.util.List;
import com.zhs.common.BaseSocket;
import com.zhs.common.client.ControlerVariable;
public class SocketServer {
ServerSocket server;
int workthreadnum;
int status;
public static final int RUN=0;
public static final int QUIT=1;
List handlers=new ArrayList(8);
WorkThread [] workers;
public SocketServer(int port,int wn) throws Exception
{
workthreadnum=wn;
server=new ServerSocket(port);
status= RUN;
}
public void registerHandler(SocketHandler sl)
{
handlers.add(sl);
}
public void startup()throws Exception
{
for(int i=0;i<handlers.size();i++)
{
SocketHandler sl=(SocketHandler)handlers.get(i);
sl.onServerStart(); //初始化动作
}
System.out.println("socket server startup with "+workthreadnum+" working threads");
//初始化工作线程,进行监听
workers=new WorkThread[workthreadnum];
for(int i=0;i<workthreadnum;i++)
{
workers[i]=new WorkThread(this,i);
for(int j=0;j<handlers.size();j++)
{
SocketHandler sh=(SocketHandler)handlers.get(j);
sh.onThreadStart(workers[i]);//可根据需要进行扩展,此处没有相应实现
}
workers[i].start();
}
}
public synchronized BaseSocket getRequest()throws Exception
{
return new BaseSocket(server.accept());
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public void handle(WorkThread wt, int command, BaseSocket client) throws Exception
{
while(true)
{
boolean responsed=false;
for(int i=0;i<handlers.size();i++)
{
SocketHandler sl=(SocketHandler)handlers.get(i);
if(sl.handle(wt,command,client))
{
responsed=true;
break;
}
}
if(!responsed)
{
client.send(14);
}
int ack=client.readint();
if(ack!=ControlerVariable.LOOPACK)
break;
command=client.getint();
}
}
public void setquit()
{
status=QUIT;
}
}
4、服务端处理类
package com.zhs.common.server;
import com.zhs.common.BaseSocket;
public class SocketHandler {
public boolean handle(WorkThread wt,int command ,BaseSocket sscocket)throws Exception
{
return false;
}
public void onServerStart()throws Exception
{
}
public void onServerClose()
{
}
public void onThreadStart(WorkThread wt)throws Exception
{
}
public void onThreadClose(WorkThread wt)
{
}
}
5、工作线程WorkThread,在ss.getRequest()处阻塞,监听socket请求,交由handler进行处理
package com.zhs.common.server;
import com.zhs.common.BaseSocket;
public class WorkThread extends Thread{
SocketServer ss;
public int seq;
public WorkThread(SocketServer socketServer, int i) {
this.ss = socketServer;
this.seq = i;
}
@Override
public void run(){
while(ss.status != SocketServer.QUIT) {
BaseSocket client = null;
try {
client = ss.getRequest();
for(int i= 0; i < 32; i++) {
client.getbyte();
}
int command=client.getint();
ss.handle(this,command,client);
client.close();
if(ss.status==SocketServer.QUIT)
break;
} catch (Exception e) {
e.printStackTrace();
if(client!=null)
try{
client.close();
}catch(Exception ex) {}
}
}
}
}
上面的都是服务的core类型的类,下面实现,main起来
6、配置文件放在了properties类型的文件中
自己使用前注意修改system.net.sock.ip
#
system.net.sock.query.port = 7888
system.net.sock.ip = 192.168.32.242
#server
get.thread.number=2
add.thread.number=1
system.charset = GBK
socket.timeout.second=3000
7、自己扩展实现SocketHandler 的子类MyHandler
import org.apache.log4j.Logger;
import com.zhs.common.BaseSocket;
import com.zhs.common.ControlerVariable;
import com.zhs.common.server.SocketHandler;
import com.zhs.common.server.SocketServer;
import com.zhs.common.server.WorkThread;
import com.zhs.common.util.ByteBuffer;
public class MyHandler extends SocketHandler {
private SocketServer ss;
final static Logger log = Logger.getLogger(MyHandler.class);
public MyHandler(SocketServer ss) {
this.ss = ss;
}
@Override
public void onServerStart() throws Exception {
// TODO Auto-generated method stub
super.onServerStart();
}
@Override
public boolean handle(WorkThread wt,int command, BaseSocket client) throws Exception{
switch(command){
case 0://??
client.readString();
ss.setquit();
client.save(0);
client.save(0);
client.send();
return true;
case ControlerVariable.CONNECT_STATUS://
log.info("receive connect request");
client.readString();
client.save(0);
client.save(0);
client.send();
return true;
case ControlerVariable.ASKPERSON://
log.info("receive askperson:"+ControlerVariable.ASKPERSON);
//query from db or index server
//response
if(true){
ByteBuffer buffer = new ByteBuffer();
buffer.append("ssssssssssssssss");
client.save(0);//request flag
client.save(buffer.getUsed());//返回数据长度,不包括自身
client.save(buffer.array(),0,buffer.getUsed()); //如果长度大于0,就save字节数据
client.send();
return true;
}
client.save(ControlerVariable.ERROR_ASKPERSON);
client.save(0);
client.send();
return true;
case ControlerVariable.ACK://
}
return false;
}
}
package com.zhs.common.myserver;
import org.apache.log4j.PropertyConfigurator;
import com.zhs.common.ControlerVariable;
import com.zhs.common.conf.ConfFactory;
import com.zhs.common.conf.Configuration;
import com.zhs.common.server.SocketServer;
public class ServerExampleMain {
Configuration conf;
public void init(String conf_file) throws Exception{
conf = ConfFactory.getConf(conf_file);
}
public void startup() throws Exception{
int qport=conf.getInt(conf.get(ControlerVariable.SOCK_QUERY_PORT),7888);
int qtn=conf.getInt(conf.get("get.thread.number"),2);
SocketServer qrys=new SocketServer(qport,qtn);
qrys.registerHandler(new MyHandler(qrys));
qrys.startup();
}
/**
* @param args
*/
public static void main(String[] args) throws Exception {
// TODO Auto-generated method stub
ServerExampleMain security = new ServerExampleMain();
security.init("socket.properties");
PropertyConfigurator.configure("conf/log4j.properties");
security.startup();
}
}
运行ServerExampleMain和ClientExampleMain可以看到serverExample的打印日志,MyHandler接受到了askperson的请求,当然这只是一个例子,ClientExampleMain没有打印返回值。
代码见:http://download.csdn.net/detail/lzlchangqi/8175207
下载后导入Eclipse,add log4j.***.jar 到path后 ,运行即可,更详细可以打断点调试