android網遊開發之socket的簡單設計和實現

轉載: http://codingnow.cn/android/884.html

 

android網遊開發之socket的簡單設計和實現

2012年11月9日 Alex Zhou 發表評論 閱讀評論 2,479 人閱讀    

對於普通應用的網絡模塊一般使用http文本協議,在android開發中使用http協議比較簡單,sdk已經做了很好的封裝了,具體使用方法可以參考我的這篇博文。而在遊戲開發中,可以結合使用http和socket,當然了http協議底層也是基於tcp協議的。http協議是無連接、無狀態的,每次連接只能處理一個請求,然後就斷了,而且發一個請求需要附加額外信息(請求行、請求頭),每次請求都需要重新建立連接;使用socket的好處是更高效和省流量,建立一次連接後,只要不手動或者出現異常斷開,就可以一直互相發送數據,而且是直接以字節的形式發送,不需要額外的附加信息,缺點就是難度加大了,需要服務端和客戶端很好的配合,保證發送和讀取時數據的順序一致。本文通過一個簡單的demo介紹開發android網遊時socket的使用方法,主要包括:android客戶端和一個簡單的使用java實現的server端,實現客戶端和服務端互相發送數據的功能。
1.客戶端代碼實現
首先創建一個Android應用,名稱:AndroidSocketTest
然後分別創建4個文件:BytesUtil.java、BytesReader.java、BytesWriter.java、TCPCommunication.java,下面分別介紹這幾個文件的用處和源碼:
BytesUtil.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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
author:alexzhou 
date  :2012-11-7
 **/
  
public final class BytesUtil {
  
    /**
     *整型轉換成字節數組 
     * @param value 要轉換的整型值
     * @return
     */
    public static byte[] shortToBytes(int value) {
        byte []write = new byte[2];
        write[0] = (byte)( (value >>> 8) & 0xFF);
        write[1] = (byte)( (value >>> 0) & 0xFF);
        return write;
    }
  
    public static byte[] intToBytes(int value) {
        byte []write = new byte[4];
        write[0] = (byte)( (value >>> 24) & 0xFF);
        write[1] = (byte)( (value >>> 16) & 0xFF);
        write[2] = (byte)( (value >>> 8) & 0xFF);
        write[3] = (byte)( (value >>> 0) & 0xFF);
        return write;
    }
  
    public static byte[] longToBytes(long value) {
        byte []write = new byte[8];
        write[0] = (byte)( (value >>> 56) & 0xFF);
        write[1] = (byte)( (value >>> 48) & 0xFF);
        write[2] = (byte)( (value >>> 40) & 0xFF);
        write[3] = (byte)( (value >>> 32) & 0xFF);
        write[4] = (byte)( (value >>> 24) & 0xFF);
        write[5] = (byte)( (value >>> 16) & 0xFF);
        write[6] = (byte)( (value >>> 8) & 0xFF);
        write[7] = (byte)( (value >>> 0) & 0xFF);
        return write;
    }
  
    /**
     * 字節數組轉換成整型
     * @param value
     * @return
     */
    public static int bytesToInt(byte []value) {
        int i1 = (value[0] & 0xFF) << 24;
        int i2 = (value[1] & 0xFF) << 16;
        int i3 = (value[2] & 0xFF) << 8;
        int i4 = (value[3] & 0xFF) << 0;
        return (i1 | i2 | i3 | i4);
    }
  
    public static short bytesToShort(byte[] value) {
        int s1 = (value[0] & 0xFF) << 8;
        int s2 = (value[1] & 0xFF) << 0;
        return (short)(s1 | s2);
    }
  
    public static long bytesToLong(byte[] value) {
        long L1 = (value[0] & 0xFF) << 56;
        long L2 = (value[1] & 0xFF) << 48;
        long L3 = (value[2] & 0xFF) << 40;
        long L4 = (value[3] & 0xFF) << 32;
        long L5 = (value[4] & 0xFF) << 24;
        long L6 = (value[5] & 0xFF) << 16;
        long L7 = (value[6] & 0xFF) << 8;
        long L8 = (value[7] & 0xFF) << 0;
        return (L1 | L2 | L3 | L4 | L5 | L6 | L7 | L8);
    }
  
    /**
     * 從指定字節數組中拷貝部分數據
     * @param origin
     * @param from
     * @param to
     * @return
     */
    public static byte[] copyBytes(byte[] origin,int from,int to) {
        int len = to - from;
        if(len < 0 || origin.length - from <= 0) {
            throw new IllegalArgumentException("copyBytes->error arguments:to="+to+",from="+from);
        }
        byte[] ret = new byte[len];
        if(len == 0) return ret;
        System.arraycopy(origin, from, ret, 0, Math.min(len, origin.length - from));
        return ret;
    }
  
    /**
     * 重置字節數組的大小,然後把原內容複製到新的字節數組中
     * @param origin
     * @param newSize
     * @return
     */
    public static byte[] resizeBytes(byte[] origin,int newSize) {
        if(newSize < 0) {
            throw new IllegalArgumentException("resizeBytes->newSize must >= 0");
        }
        byte[] ret = new byte[newSize];
        if(newSize == 0) return ret;
        System.arraycopy(origin,0,ret,0,Math.min(origin.length, newSize));
        return ret;
    }
  
    /**
     * 讀取輸入流中字節,並保存到指定的字節數組中
     * @param is
     * @param data
     * @param off
     * @param len
     */
    public static void readData(InputStream is, byte data[], int off, int len) {
        int hasRead = 0;
        final int BUFFER = 1024;
        while(hasRead < len) {
            try {
                int remain = len - hasRead;
                int count = is.read(data, off + hasRead, remain > BUFFER ? BUFFER : remain);
                if(count < 0) throw new IOException("readData->read data error");
                hasRead += count;
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

BytesReader.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
70
71
72
73
74
75
76
77
/**
 * 接受服務端數據時,讀取字節並轉換到相應類型
author:alexzhou 
date  :2012-11-7
 **/
  
public final class BytesReader {
  
    private final byte []data;
    //字節數組的大小
    private final int size;
    //當前讀取的位置
    private int position;
  
    public BytesReader(byte []data) {
        this.data = data;
        this.size = data.length;
        this.position = 0;
    }
  
    public byte[] read(int len) {
        if(len < 0) return null;
        byte[] value = BytesUtil.copyBytes(data, position, position + len);
        this.position += len;
        return value;
    }
  
    public int getSize() {
        return size;
    }
  
    public boolean isAvailable() {
        return size - position > 0;
    }
  
    public short readShort() {
        byte[] value = read(2);
        return BytesUtil.bytesToShort(value);
    }
  
    public int readInt() {
        byte[] value = read(4);
        return BytesUtil.bytesToInt(value);
    }
  
    public long readLong() {
        byte[] value = read(8);
        return BytesUtil.bytesToLong(value);
    }
  
    public byte readByte() {
        int value = this.isAvailable() ? (0xFF & data[position++]) : -1;
        return (byte)value;
    }
  
    public byte[] readBytes() {
        int len = readShort();
        //讀取大數據
        if(len >= 0xFFFF) {
            len = this.readInt();
        }
        return len == 0 ? null : read(len);
    }
  
    public String readUTF() {
        byte[] bytes = readBytes();
        if(null != bytes) {
            try {
                return new String(bytes,"UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return null;
    }
}

BytesWriter.java:向服務端發送數據時使用,跟BytesReader.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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
/**
author:alexzhou 
date  :2012-11-9
 **/
  
public final class BytesWriter {
  
    private byte[] data;
    private int count;
  
    public BytesWriter() {
        this(64);
    }
  
    public BytesWriter(int size) {
        this.data = new byte[size];
    }
  
    public byte[] getBytes() {
        return this.data.length == count ? data : count == 0 ? null : BytesUtil.resizeBytes(this.data, count);
    }
  
    public void write(byte[] value) {
        this.write(value, 0, value == null ? 0 : value.length);
    }
  
    public void write(byte[] d, int offset, int len) {
        if(d == null || len == 0) return;
        int newCount = count + len;
        if(newCount > this.data.length) {
            int newSize = Math.max(this.data.length << 1, newCount);
            this.data = BytesUtil.resizeBytes(this.data, newSize);
        }
        System.arraycopy(d, offset, this.data, this.count, len);
        this.count = newCount;
    }
  
    public void writeInt(int value) {
        this.write(BytesUtil.intToBytes(value));
    }
  
    public void writeShort(int value) {
        this.write(BytesUtil.shortToBytes(value));
    }
  
    public void writeLong(long value) {
        this.write(BytesUtil.longToBytes(value));
    }
  
    public void writeByte(byte value) {
        int newCount = count + 1;
        if(newCount > this.data.length) {
            int newSize = Math.max(this.data.length << 1, newCount);
            this.data = BytesUtil.resizeBytes(this.data, newSize);
        }
        this.data[count] = value;
        this.count = newCount;
    }
  
    public void writeBytes(byte[] value) {
        int length = (value == null ? 0 : value.length);
        //發送大數據時
        if(length >= 0xFFFF) {
            this.writeShort(0xFFFF);
            this.writeInt(length);
        }else {
            //告訴服務端發送的數據的大小
            this.writeShort(length);
        }
        this.write(value);
    }
  
    public void writeUTF(String value) {
        if(value == null || value.length() == 0) {
            this.writeShort(0);
        }
        byte[] bytes = null;
        try {
            bytes = value.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        this.writeBytes(bytes);
    }
  
}

TCPCommunication.java:上面的三個類都是輔助工具類,是給它使用的,它負責socket的創建和連接服務器、讀取和發送數據,以及通過Handler發送消息給UI線程更新界面顯示數據。由於我們的目的是使用socket通信,所以去掉了很多代碼,這裏只是簡單的建立一次連接,發送和接收完數據後就關閉連接了。源碼如下:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public class TCPCommunication implements Runnable {
  
    private Socket mSocket;
    private DataInputStream mDis;
    private DataOutputStream mDos;
    private Handler mHandler;
  
    private volatile boolean isRunning = false;
  
    public TCPCommunication(Handler handler) {
        this.mHandler = handler;
    }
  
    public boolean isRunning() {
        return isRunning;
    }
  
    public void startWork() {
        Thread t = new Thread(this);
        t.setPriority(Thread.MAX_PRIORITY);
        t.start();
    }
  
    public void stopWork() {
        this.isRunning = false;
    }
  
    @Override
    public void run() {
        try {
            mSocket = new Socket("10.0.2.2",8888);
            mDis = new DataInputStream(mSocket.getInputStream());
            mDos = new DataOutputStream(mSocket.getOutputStream());
            //開始發送數據到服務端
            BytesWriter bw = new BytesWriter();
            String username = "alexzhou";
            String password = "123456";
            int flag1 = 12345;
            short flag2 = 12;
            long flag3 = 100000000L;
            bw.writeUTF(username);
            bw.writeUTF(password);
            bw.writeInt(flag1);
            bw.writeShort(flag2);
            bw.writeLong(flag3);
            byte[] data = bw.getBytes();
  
            BytesWriter out = null;
            if(data.length >= 0xFFFF) {
                out = new BytesWriter(data.length + 6);
                out.writeShort(0xFFFF);
                out.writeInt(data.length);
            }else{
                out = new BytesWriter(data.length + 2);
                out.writeShort(data.length);
            }
            out.write(data);
            mDos.write(out.getBytes());
  
            //開始從服務端接收數據
            int len = mDis.readShort();
            if(len == 0xFFFF) {
                len = mDis.readInt();
            }
            byte[] inputData = new byte[len];
            BytesUtil.readData(mDis, inputData, 0, len);
            BytesReader br = new BytesReader(inputData); 
            String user_id = br.readUTF();
            String recv_username = br.readUTF();
            String nickname = br.readUTF();
            int i = br.readInt();
            short s = br.readShort();
            long l = br.readLong();
  
            String result = "登錄成功~您的信息如下\n id:"+user_id + "\n用戶名:" + recv_username + "\n暱稱:" + nickname + "\n 序列號:" + i + "-" + s + "-" + l;
            Message msg = mHandler.obtainMessage();
            msg.what = 0;
            msg.obj = result;
            mHandler.sendMessage(msg);
  
            mDis.close();
            mDos.close();
            mSocket.close();
  
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    };
  
}

MainActivty.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
public class MainActivity extends Activity {
  
    private TextView mTextView;
    private Button mButton;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = (TextView)this.findViewById(R.id.text);
        mButton = (Button)this.findViewById(R.id.button_send);
        mButton.setOnClickListener(new OnClickListener() {
  
            @Override
            public void onClick(View v) {
                new TCPCommunication(mHandler).startWork();
            }
        });
    }
  
    private Handler mHandler = new Handler() {
  
        @Override
        public void handleMessage(Message msg) {
            String result = (String)msg.obj;
            mTextView.setText(result);
        }
  
    };
  
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }
}

到此爲止,Android客戶端的代碼已經全部完成了,記得給應用添加訪問網絡的權限哦:在配置文件中添加

1
<uses-permission android:name="android.permission.INTERNET" />

2.服務端代碼實現
服務端的代碼比較簡單,當然了,一般在實際的開發中,服務端和客戶端不是同一個人寫的,這裏我就把客戶端裏的那幾個工具類拿來直接用了。
創建一個java項目,然後把BytesReader.java、BytesWriter.java、BytesUtil.java三個文件拷貝到項目的包下,然後創建Server.java,功能比較簡單,監聽8888端口,然後等待客戶端連接,接收和發送數據。源碼如下:

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/**
author:alexzhou 
date  :2012-11-7
 **/
  
public final class Server implements Runnable{
  
    private ServerSocket mServerSocket;
    private Socket mClient;
    private DataInputStream mDis;
    private DataOutputStream mDos;
  
    private boolean isRunning = false;
  
    public void start() {
        this.isRunning = true;
        Thread t = new Thread(this);
        t.start();
    }
  
    public void stop() {
        this.isRunning = false;
    }
  
    public static void main(String []args) {
        new Server().start();
    }
    @Override
    public void run() {
        try {
            mServerSocket = new ServerSocket(8888);
            System.out.println("start server");
            while(isRunning) {
                System.out.println("wait client connect!!");
                mClient = mServerSocket.accept();
  
                //接收客戶端發送的數據
                mDis = new DataInputStream(mClient.getInputStream());
                //數據的長度
                int len = mDis.readShort();
                if(len == 0xFFFF) {
                    len = mDis.readInt();
                }
                System.out.println("client data size:" + len);
                byte[] inputData = new byte[len];
                BytesUtil.readData(mDis, inputData, 0, len);
                BytesReader br = new BytesReader(inputData);
                String username = br.readUTF();
                String password = br.readUTF();
                int i = br.readInt();
                short s = br.readShort();
                long l = br.readLong();
                System.out.println("username:"+username+";password="+password+"Long="+l);
  
                //向客戶端發送數據
                mDos = new DataOutputStream(mClient.getOutputStream());
                BytesWriter bw = new BytesWriter(32);
                String user_id = "123456";
                String nickname = "周江海";
                bw.writeUTF(user_id);
                bw.writeUTF(username);
                bw.writeUTF(nickname);
                bw.writeInt(i);
                bw.writeShort(s);
                bw.writeLong(l);
                byte[] data = bw.getBytes(); 
                BytesWriter out = null;
                if(data.length >= 0xFFFF) {
                    out = new BytesWriter(data.length + 6);
                    out.writeShort(0xFFFF);
                    out.writeInt(data.length);
                }else {
                    out = new BytesWriter(data.length + 2);
                    out.writeShort(data.length);
                }
                out.write(data);
                mDos.write(out.getBytes());
  
                mDos.close();
                mDis.close();
                mClient.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }
}

現在驗證一下,先啓動Server,然後運行Android客戶端應用,屏幕中間會出現TextView和一個button,TextView的內容是HelloWorld,點擊button就會發送登陸信息給服務端,然後服務端返回數據顯示到TextView上。下面分別是服務端的打印消息和客戶端的顯示截圖。


主要需要掌握基本數據類型和字符串跟字節數組的轉換,然後按照指定的順序發送。最重要的是要注意客戶端和服務端從字節數組讀取和往字節數組寫入數據時的順序要對應。
下面是源碼鏈接:http://download.csdn.net/detail/zhoujianghai/4751981

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章