轉載: http://codingnow.cn/android/884.html
android網遊開發之socket的簡單設計和實現
對於普通應用的網絡模塊一般使用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
email :[email protected]
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
email :[email protected]
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
email :[email protected]
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
email :[email protected]
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