[转载]Android与IIS身份验证——基本验证

mikel阅读(1052)

[转载]Android与IIS身份验证——基本验证 – 刘冬.NET – 博客园.

内容摘要

前言

1.服务器端

2.Android客户端

3.IIS部署

4.运行效果

Android移动项目开发中,访问服务器时,为了简洁方便,我们经常使用http协议来传递JSON格式的数据。然而有些项目需要有一定的 安全性,如使用Android客户端登陆到MIS系统。虽然我们是通过Android手机客户端的登陆Activity中登陆到系统的,但是略懂电脑的黑 客是能够跳过登陆Activity,从而直接进入系统的。这样,会造成一些由于系统的不安全所带来的麻烦。建立一种防止黑客强行登录的身份验证模式尤为重 要。此时,系统的身份验证成为阻挡黑客登陆的一道屏障。那么,怎样实现一个身份验证呢?让我们以IIS为宿主,一步一步的实现身份验证吧。

一、ASP.NET服务器端

首先,我们使用VS2010创建一个web项目(可以是WebForms,也可以是MVC,我这里使用的是ASP.NET MVC项目)。图1.1所示

图1.1

然后,在HomeController的Index Action中输入:登陆成功。

[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
return Content(登陆成功);
}
}

二、Android客户端

首先,创建一个Android项目,并新建一个MainActivity类。

接着,编写一个访问IIS服务器的类。

package ld.com.authorize;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.DefaultHttpClient;

import android.util.Log;

public abstract class HttpHelper {

private final static String TAG = HttpHelper;

public static String invoke() {
String result
= null;
try {
final String url = http://192.168.1.104:180/;

HttpPost httpPost = new HttpPost(url);
DefaultHttpClient httpClient
= new DefaultHttpClient();

//基本身份验证
BasicCredentialsProvider bcp = new BasicCredentialsProvider();
String userName
= liudong;
String password
= 123;
bcp.setCredentials(AuthScope.ANY,
new UsernamePasswordCredentials(
userName, password));
httpClient.setCredentialsProvider(bcp);

HttpResponse httpResponse = httpClient.execute(httpPost);

StringBuilder builder = new StringBuilder();
BufferedReader reader
= new BufferedReader(new InputStreamReader(
httpResponse.getEntity().getContent()));
for (String s = reader.readLine(); s != null; s = reader.readLine()) {
builder.append(s);
}
result
= builder.toString();
Log.d(TAG,
result is ( + result + ));
}
catch (Exception e) {
Log.e(TAG, e.toString());
}
Log.d(TAG,
over);
return result;
}
}

注意的是,我这里用户名和密码分别是:liudong和123。

然后,修改layout文件:main.xml

<?xml version=”1.0″ encoding=”utf-8″?>
<LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android”
android:orientation
=”vertical” android:layout_width=”fill_parent”
android:layout_height
=”fill_parent”>
<TextView android:layout_width=”fill_parent”
android:layout_height
=”wrap_content” android:text=”@string/hello” />
<Button android:text=”身份码验证” android:id=”@+id/btnPassword”
android:layout_width
=”fill_parent” android:layout_height=”wrap_content”></Button>
</LinearLayout>

最后,修改MainActivity。

package ld.com.authorize;

import android.app.Activity;
import android.app.ProgressDialog;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;

public class MainActivity extends Activity {

private final String TAG = this.getClass().getSimpleName();

private Button btnPassword;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

btnPassword = (Button) this.findViewById(R.id.btnPassword);

setInvokeOnClick();
}

private void setInvokeOnClick() {
btnPassword.setOnClickListener(
new OnClickListener() {

@Override
public void onClick(View v) {
// TODO Auto-generated method stub
AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {

private ProgressDialog progressDialog;

@Override
protected void onPostExecute(String result) {
// TODO Auto-generated method stub
// super.onPostExecute(result);
progressDialog.cancel();
Toast.makeText(MainActivity.
this, result,
Toast.LENGTH_SHORT).show();

Log.d(TAG, result);
}

@Override
protected void onPreExecute() {
// TODO Auto-generated method stub
super.onPreExecute();
progressDialog
= new ProgressDialog(MainActivity.this);
progressDialog
.setProgressStyle(ProgressDialog.STYLE_SPINNER);
progressDialog.setTitle(
调用中,请稍后…);
progressDialog.show();
}

@Override
protected String doInBackground(Void… arg0) {
// TODO Auto-generated method stub
try {
return HttpHelper.invoke();
}
catch (Exception e) {
return null;
}
}

};
task.execute();
}
});
}
}

设置访问权限为:<uses-permission android:name=”android.permission.INTERNET” />

运行模拟器的效果如图2.1所示。

图2.1

三、IIS部署

首先、添加一个网站,如图3.1所示。

如图3.1

接着,在进入计算机管理–>本地用户和组–>用户,新建一个用户,如图3.2所示。

图3.2

然后,设置IIS的身份验证(图3.3所示)。

图3.3

设置其身份验证模式为:基本验证或Window身份s验证(图3.4所示)。

图3.4

最后,我们在浏览器中输入网址进行验证,验证结果见图3.5和图3.6。

图3.5

图3.6

从图中我们可以发现,浏览该网页时需要用户名和密码。我们输入了正确的用户名和密码后就可以登录这个页面了。

四,运行效果。

见图4.1和图4.2所示。

图4.1

图4.2

代码下载

出处:http://www.cnblogs.com/GoodHelper/archive/2011/08/17/android_iis_01.html

作者:刘冬.NET

欢迎转载,但须保留版权。

[转载]Android通过摇晃手机的频率来控制声音的频率

mikel阅读(1195)

[转载]Android通过摇晃手机的频率来控制声音的频率 – stay – 博客园.

通过晃动手机的频率来修改播放声音的频率。效果很给力的说。主要通过sensor来算手机摇晃的频率,摇晃的频率越高,播放声音的速度越快。

/**
* @author Stay
* 通过摇晃手机的频率来改变声音的速度
*/
public class ShakeSound extends Activity implements SensorEventListener,OnClickListener {
private static final float SHAKE_THRESHOLD = 50;
private static final String TAG = “ActivityTest”;
private SensorManager manager;
private SoundManager sm;
private long curTime, lastUpdate;
private float last_x, last_y, last_z;
private float x,y,z;
private boolean isPlaying;
private int count = -2;
private float force;
private int audioCount = 0;
private float audioForce;
private Button btn;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btn = (Button) this.findViewById(R.id.hello);
btn.setOnClickListener(this);
manager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sm = new SoundManager(this);
sm.addSound(1, “heixiu.ogg”);
}

@Override
public void onSensorChanged(SensorEvent event) {
curTime = System.currentTimeMillis();
if ((curTime – lastUpdate) > 100) {
count ++ ;
if (count < 0) { return; } if (count == 0) { if (!isPlaying) { Log.i(TAG, "sm.play(1, 0, 1.0f);"); sm.play(1, 0, 1.0f); } last_x = event.values[SensorManager.DATA_X]; last_y = event.values[SensorManager.DATA_Y]; last_z = event.values[SensorManager.DATA_Z]; return; } lastUpdate = curTime; x = event.values[SensorManager.DATA_X]; y = event.values[SensorManager.DATA_Y]; z = event.values[SensorManager.DATA_Z]; curTime = System.currentTimeMillis(); // 每100毫秒检测一次 float deltaForce = Math.abs(x + y + z - last_x - last_y - last_z); force = force + deltaForce; updateAudioRate(deltaForce); if (count >= SHAKE_THRESHOLD) {
Log.i(TAG, “unSensorListener”);
// onShakeCallBack(force / count); get the score
unSensorListener();
if (isPlaying) {
sm.stop();
isPlaying = false;
}
count = – 2;
force = 0;
return;
}
last_x = x;
last_y = y;
last_z = z;
}
}

@Override
public void onAccuracyChanged(Sensor sensor, int accuracy) {
}

private void updateAudioRate(float force) {
float rate=0;
float prvAudioRate = 1;
audioCount =audioCount+1;
audioForce=audioForce+force;
if(audioCount>3){
//from 0-50 maps to 0.6 to 2
//rate=audioForce/audioCount*0.03+0.5;
//from 0-50 maps to 1 to 1.8
rate=(float) (audioForce/audioCount*0.012+1.0);
//myAlert(rate);
prvAudioRate=prvAudioRate+(rate-prvAudioRate)/3;
sm.setRate(prvAudioRate);
Log.i(TAG, “sm.setRate=” + prvAudioRate);
audioForce=0;
audioCount=0;
//prvAudioRate=rate;
}
}

private void setSensorListener() {
Log.i(TAG, “setSensorListener”);
Sensor sensor = manager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
manager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_GAME);
}

private void unSensorListener() {
Log.i(TAG, “unregisterListener”);
manager.unregisterListener(this);
}

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.hello:
setSensorListener();
break;
default:
break;
}
}

/**
* @author Stay
* 声音管理类
*/
public class SoundManager {
public SoundPool mSoundPool;
private HashMap mSoundPoolMap;
private AudioManager mAudioManager;
private Context mContext;
private int mStreamID;
static final String LOG_TAG = “SoundManager”;
private boolean mSoundEnable = true;
private float mRate = 1f;
private boolean playing = false;
private int loopMode = 0;
private int mPlayIndex = -1;

public SoundManager(Context mContext) {
this.mContext = mContext;
mSoundPool = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);
mSoundPoolMap = new HashMap();
mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
}

public void addSound(int index, String audioName) {
// mSoundPoolMap.put(1, mSoundPool.load(mContext, SoundID, 1));
try {
mSoundPoolMap.put(index, mSoundPool.load(mContext.getAssets().openFd(audioName), 1));
} catch (IOException e) {
e.printStackTrace();
}
}

// loopMode=0:play once; loopMode=-1:loop mode;
public void play(int index, int loopMode, float rate) {
if (mSoundEnable) {
this.loopMode = loopMode;
mRate = checkRate(rate);

int streamVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
// float streamVolume = 0.1f;
// notes: for loop mode, the priority should be set to 0, else it can’t be stopped
// mStreamID=mSoundPool.play(mSoundPoolMap.get(index), streamVolume, streamVolume, 1, loopMode, mRate);
if (mPlayIndex < 0) { mStreamID = mSoundPool.play(mSoundPoolMap.get(index), streamVolume, streamVolume, 0, loopMode, mRate); } else { mStreamID = mSoundPool.play(mSoundPoolMap.get(mPlayIndex), streamVolume, streamVolume, 0, loopMode, mRate); } playing = true; } } // added for v 1.0.1, enable changing the audio remotely public void setPlayIndex(int index) { mPlayIndex = index; } public void setRate(float rate) { if (mSoundEnable) { mRate = checkRate(rate); mSoundPool.setRate(mStreamID, mRate); } } private float checkRate(float rate) { if (rate > 2f) {
return 2f;
} else if (rate < 0.5f) { return 0.5f; } else { return rate; } } public void stepRate(float step) { if (mSoundEnable) { mRate = mRate + step; mRate = checkRate(mRate); mSoundPool.setRate(mStreamID, mRate); } } public void pause() { if (mSoundEnable) { mSoundPool.pause(mStreamID); // mSoundPool.autoPause(); } } public void resume() { if (mSoundEnable) { if (false == playing) { play(1, loopMode, mRate); } else { mSoundPool.resume(mStreamID); } } // mSoundPool.autoResume(); } public void stop() { if (mSoundEnable) { playing = false; mSoundPool.stop(mStreamID); } } public void setSound(boolean soundEnable) { mSoundEnable = soundEnable; } public void release() { if (mSoundEnable) { playing = false; mSoundPool.release(); } mSoundPool.release(); mSoundPool = null; mSoundPoolMap.clear(); mSoundPoolMap = null; } } [/java]

[转载]重构代码的7个阶段

mikel阅读(795)

[转载]重构代码的7个阶段 | 酷壳 – CoolShell.cn.

你曾去想重构一个很老的模块,但是你只看了一眼你就恶心极了。文档,奇怪的函数和类的命名,等等,整个模块就像一个带着脚镣的衣衫褴褛的人,虽然能 走,但是其已经让人感到很不舒服。面对这种情况,真正的程序员会是不会认输的,他们会接受挑战认真分析,那怕重写也在所不惜。最终那个模块会被他们重构, 就像以前和大家介绍过的那些令人销魂的编程方式中的屠宰式编程一样。下面是重构代码的几个阶段,文章来自:The 7 stages of refactoring,下面的翻译只是意译。

第一阶段 – 绝望

在你开始去查看你想要重构的模块的,你会觉得好像很简单,这里需要改一个类,那里需要改两到三个函数,重写几个 函数,看上去没什么大不了的,一两天就搞定了。于是你着手开始重构,然后当你调整重构了一些代码,比如改了一些命名,修理了一些逻辑,渐渐地,你会发现这 个怪物原来体型这么大,你会看到与代码不符甚至含糊不清的注释,完全摸不着头脑的数据结构,还有一些看似不需要方法被调了几次,你还会发现无法搞清一个函 数调用链上的逻辑。你感到这个事可能一周都搞不定,你开始绝望了。

第二阶段 – 找最简单的做

你承认你要重构的这个模块就是一个可怕的怪物,不是一两下就可以搞定的,于是你开始着干一些简单的事,比如重新命名一下几个函数,移除一些代码的阻碍,产生几个常量来消除magic number,等等,你知道这样做至少不会让代码变得更糟糕。

第三阶段 – 再次绝望

但是接下来的事会让你再次撞墙。你会发现那些代码的瑕疵是些不痛不痒的事,改正这些事完全于事无补,你应该要做 的事就是重写所有的东西。但是你却没有时间这么干,而这些代码剪不乱理还乱,耦合得太多,让你再一次绝望。所以,你只能部分重写那些不会花太多时间的部 分,这样至少可以让这些老的代码能被更多的重用。虽然不完美,但是至少可以试试。

第四阶段 – 开始乐观

在你试着部分重构这个模块几天之后,随着重构了几个单元后,虽然你发现改善代码的进度太慢了,但此时,你已知道代码应该要被改成什么样,你在痛苦之 后也锁定了那些那修改的类。是的,虽然你的时间预算已经超支,虽然要干的事比较多,但你还是充满希望,觉得那是值得的。你胸中的那团火又被点燃了。

第五阶段  – 快速了结

在这个时候,你发现你已花了太多的时间,而情况越来越复杂,你感到你所面对的情况越来越让你越到不安,你明白你自己已经陷入了困境。你原本以为只需 要一次简单的重构,然而现在你要面对的是重写所有的东西。你开始意识到原因是因为你是一个完美主义者,你想让代码变得完美。于是你开始在怠慢你文档,并想 找到一个捷径来重写老的代码,你开始采用一些简单而粗暴,快速而有点肮脏的方法。虽然不是很完美,但你就是这样去做了。然后,你开始运行测试做UT,发现 UT报告上全是红色,几乎全都失败了,你恐慌了,于是快速地fix代码,然后让UT 能工作。此时,你拍拍自己胸口,说到,没问题 ,于是就把代码提交了。

第六阶段 – 修改大量的Bug

你的重写并不完美,虽然其过了测试,但是那些UT测试对于你的新的代码有点不太合适,虽然他们都没有报错,但是他们测试得范围太小了,没有覆盖到所 有的情况和边界。所以,在这以后,你还需要几周或是更长的时间不得不来修正越来越多的bug,这使得你的设计和代码在每一次quick-fix后就变得越 来越难看。此时,代码已经不像你所期望的那样完美了,但你依然觉得他还是比一开始要好一些。这个阶段可能历经几个月。

第七阶段  – 觉悟

经过了6个月,你重写的模块又出了一个比较严重的bug。这让你重构的那个模块变得更难堪。你发现出的这个问题是和当初的设计不一致,你还发现被你 重构掉的那段老的代码并不是当初看上去的那么坏,那段老的代码确实考虑到了一些你未曾考虑到的事情。这个时候,你团队里有人站出来说这个模块应该被重构或 是重写,而你却不动声色地一言不发,并希望那个站出来的人能在几个月后能觉悟起来。

——————

不知道这是不是你的经历,我经历过很多次这样的事。对于很多维护性质的项目,我犯过的错误让我成了一个实实在在的保守派,我几乎不敢动,那怕看到代 码很不合口味。当然,那些从来没有写过代码的敏捷咨询师一定会说用TDD或是UT可以让你的重构更有效也更容易,因为这样会让他们显得更我价值,但我想告 诉你,这种脱离实际的说法很不负责任,这就好比说—— 我在杀猪的时候遇到了一些麻烦,因为我对猪的生理结构不清楚,或是这本来就是一头畸形的猪,导致我杀的猪很难看,而伟大的敏捷咨询师却告诉我,要用一把更快更漂亮的刀。软件开发永远不是那么简单的事,杀猪也一样。

[转载]Repository模式

mikel阅读(1449)

[转载]Repository模式.

近来发现很多ASP.NET MVC的例子中都使用了Repository模式,比如Oxite,ScottGu最近发布的免费的ASP.NET MVC教程都使用了该模式。就简单看了下。

在《企业架构模式》中,译者将Repository翻译为资源库。给出如下说明:
通过用来访问领域对象的一个类似集合的接口,在领域与数据映射层之间进行协调。

在《领域驱动设计:软件核心复杂性应对之道》中,译者将Repository翻译为仓储,给出如下说明:
一种用来封装存储,读取和查找行为的机制,它模拟了一个对象集合。

使用该模式的最大好处就是将领域模型从客户代码和数据映射层之间解耦出来。

我们来看下在LinqToSQL中如何应用该模式。
1. 我们将对实体的公共操作部分,提取为IRepository接口,比如常见的增加,删除等方法。如下代码:

interface IRepository<T> where T : class
{
    IEnumerable<T> FindAll(Func<T, bool> exp);
    void Add(T entity);
    void Delete(T entity);
    void Save();
}

2.下面我们实现一个泛型的类来具体实现上面的接口的方法。

public class Repository<T> : IRepository<T> where T : class
{
    public DataContext context;
    public Repository(DataContext context)
    {
        this.context = context;
    }
    public IEnumerable<T> FindAll(Func<T, bool> exp)
    {
        return context.GetTable<T>().Where(exp);
    }
    public void Add(T entity)
    {
        context.GetTable<T>().InsertOnSubmit(entity);
    }
    public void Delete(T entity)
    {
        context.GetTable<T>().DeleteOnSubmit(entity);
    }
    public void Save()
    {
        context.SubmitChanges();
    }
}

3.上面我们实现是每个实体公共的操作,但是实际中每个实体都有符合自己业务的逻辑。我们单独定义另外一个接口,例如:

interface IBookRepository : IRepository<Book>
{
    IList<Book> GetAllByBookId(int id);
}

4.最后该实体的Repository类实现如下:

public class BookRepository : Repository<Book>, IBookRepository
{
    public BookRepository(DataContext dc)
        : base(dc)
    { }
    public IList<Book> GetAllByBookId(int id)
    {
        var listbook = from c in context.GetTable<Book>()
                       where c.BookId == id
                       select c;
        return listbook.ToList();
    }
}

上面只是为大家提供了一个最基本使用框架。

作者:生鱼片
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
精彩评论:
进一步讨论一下,感觉使用泛型 Repository接口并不太合适,因为Repository接口是提供给Domain层的操作契约,不同的entity对于Domain来说可能有不 同的操作约束,比如User可能不应该被删除,bookOrder可能不应该被修改,也就是说domain层根本就不应该能调用 _repository<User>.Delete(user),_repository<BookOrder>.Update(bookOrder) 这样的操作.
因此Repository接口还是应该单独针对每个Eneity类来定义,比如User
interface IUserRepository
{
IEnumerable<User> FindAllUser();
void Add(User user);
//void Delete(User user); //User不应该能被删除,因此接口中没有此方法
string GetPassword(int userId);//User特有的操作
}
而泛型的Repository<T>类仍然用来减少重复代码,只是不能被UserRepository类直接继承,因为这样 Delete方法将侵入User类,所以改为在UserRepository中组合一个Repository<T>,将开放给domain可 见且又能使用泛型重用的功能委托给这个Repository<T>,
public class UserRepository : IUserRepository
{
private DataContext context;
private Repository<User> internalGenericRepository;

public BookRepository(DataContext dc)
{
this.context = dc;
this.internalGenericRepository = new Repository<User>(dc);
}

public void Add(User user);
{
this.internalGenericRepository.Add(user);
}

public string GetPassword(int userId)
{
string password = context.User.where(u=>u.id==userId).select(u=>u.password);
}

}

也就是说泛型接口->泛型类->具体接口继承泛型接口->具体类继承具体接口和泛型类的方式变为
具体接口->具体类继承具体接口+组合泛型类

再补充两句,IUserRepository和通常三层架构的IUserDal从形式和功能上看差不多,个人感觉区别两者在意图上有所不同.
Repository是DDD中的概念,强调Repository是受Domain驱动的,Repository中定义的功能要体现Domain的意图和约束,而Dal更纯粹的就是提供数据访问的功能,并不严格受限于Business层.
就拿上面写的User的例子来说,使用Repository,隐含着一种意图倾向,就是Domain需要什么我才提供什么,不该提供的功能就不要提供,一切都是以Domain的需求为核心;
而使用Dal,其意图倾向在于我Dal层能使用的数据库访问操作提供给Business层,你Business要用哪个自己选.换一个Business也可以用我这个Dal,一切是以我Dal能提供什么操作为核心.

[新闻]谷歌125亿美元收购摩托罗拉移动

mikel阅读(991)

大早晨打开电脑就弹出窗口“谷歌125亿美元收购摩托罗拉移动”,好消息啊!

新闻内容:

北京时间8月15日消息,据国外媒体报道,谷歌和摩托罗拉移动今天宣布,两家公司已经达成一致协议,谷歌将以每股40美元现金的价格收购摩托罗拉移动。这 笔交易的总价值达到约125亿美元,按照上周五的收盘价计算,该收购价比摩托罗拉移动的股价溢价63%。这笔交易已经分别获得谷歌和摩托罗拉移动董事会的 一致通过。

不知道google又要有什么大动作,下面是谷歌CEO拉里-佩奇(Larry Page)说是为了巩固Android的地位?!看来Google从微软和苹果的专利联合攻击中发现“搞硬件才能拳头硬”

以下为佩奇声明全文:

自2007年11月推出以来,Android不仅极大地丰富了用户的选择空间,而且还改善了用户的整个移动使用体验。目前,全球累计激活的Android设备超过1.5亿部,每天激活的设备数量高达55万部,这些来自全球39家硬件设备厂商的Android设备登陆了123个国家的231家运营商的网络。鉴于Android所取得的巨大成功,我们一直在寻求新的方式来进一步巩固Android的市场地位。这就是我今天如此高兴地宣布收购摩托罗拉移动的原因所在。

在通信技术、移动产品和知识产权开发三大领域,摩托罗拉有着80多年的创新历史,正是这些创新推动着我们今天的移动计算领域不断向前发展。摩托罗拉的里程碑主要包括:约30年前推出全球首款便携式手机,当时全球最小、最轻的手机StarTAC。 2007年,摩托罗拉率先参与组建了“开放手机联盟”(Open Handset Alliance),共同推广Android平台。我个人一直非常欣赏摩托罗拉手机手机,从当时的StarTAC到现在的DROID系列。

2008年,摩托罗拉就把全部的赌注都压在了Android系统身上,将Android作为其全部智能手机的唯一操作系统。这是一个明智的选择,我们为摩托罗拉今天所取得的成功感到无比兴奋。我们相信摩托罗拉的移动业务将继续呈现上行趋势,并获得爆炸式增长。

摩托罗拉同时还是家庭设备和视频解决方案市场的领跑者。由于整个市场向互联网领域转移,我们非常乐于与摩托罗拉和整个行业合作来为我们的伙伴提供支持,推动市场创新。

摩托罗拉全身心地投入到Android平台的发展是我们收购摩托罗拉的原因之一。除此之外,两家公司还有很多共同之处。合并后,我们将为用户提供更出色的用户体验,从而进一步巩固整个Android生态系统,这对用户、合作伙伴和开发人员来说都是十分有利的。而且收购摩托罗拉还会进一步增强谷歌的专利实力,以更好地保护Android系统免受微软、苹果和其他公司的侵权诉讼纠纷。

该交易不仅会进一步巩固Android的市场地位,而且还将提升移动操作系统市场的竞争激烈程度,并为消费者提供持续的创新产品、丰富的选择空间以及完美的使用体验。我坚信,这些体验将会为我们的股东创造出更多的价值。

我非常欢迎摩托罗拉员工成为谷歌大家庭中的一员。

[转载]用TCP/IP实现自己简单的应用程序协议:其余部分

mikel阅读(888)

[转载]用TCP/IP实现自己简单的应用程序协议:其余部分 – 浪雪 – 博客园.

接着上次用TCP/IP实现自己简单的应用程序协议:成帧器部分,现在把接下来的部分也说完

代码下载:

http://download.csdn.net/source/3519903

客户端的调用

    public class VoteClientTCP {

      public static int CANDIDATEID = 888;//随便写了一个      

      public static void Main(String[] args) {

        int port = 5555;
        IPEndPoint ipep = new IPEndPoint(GetLocalhostIPv4Addresses().First(), port);

        Socket sock = new Socket(AddressFamily.InterNetwork,
                    SocketType.Stream, ProtocolType.Tcp);
        try
        {
            sock.Connect(ipep);
        }
        catch (SocketException e)
        {
            Console.WriteLine("Can not connect {0} ,reason:{1},NativeErrorCode:{2},SocketErrorCode:{3}", ipep.Address, e.Message, e.NativeErrorCode, e.SocketErrorCode);
            Console.ReadKey();
            return;
        }

        IVoteMsgCoder coder = new VoteMsgTextCoder();

        IFramer framer = new LengthFramer(sock);

        VoteMsg msg = new VoteMsg(true, false, CANDIDATEID, 1);
        byte[] encodedMsg = coder.toWire(msg);

        // 发送查询
        Console.WriteLine("Sending Inquiry (" + encodedMsg.Length + " bytes): ");
        framer.frameMsg(encodedMsg);

        // 投第一票给候选人888
        msg.IsInquiry = false;
        encodedMsg = coder.toWire(msg);
        Console.WriteLine("Sending Vote (" + encodedMsg.Length + " bytes): ");
        framer.frameMsg(encodedMsg);

        // 投第二票给候选人888
        msg.IsInquiry = false;
        encodedMsg = coder.toWire(msg);
        Console.WriteLine("Sending Vote (" + encodedMsg.Length + " bytes): ");
        framer.frameMsg(encodedMsg);

        // 再次查询
        msg.IsInquiry = true;
        encodedMsg = coder.toWire(msg);
        Console.WriteLine("Sending Inquiry (" + encodedMsg.Length + " bytes): ");
        framer.frameMsg(encodedMsg);
        
        encodedMsg = framer.nextMsg();
        msg = coder.fromWire(encodedMsg);
        Console.WriteLine("Received Response (" + encodedMsg.Length
                   + " bytes): ");
        Console.WriteLine(msg);


        msg = coder.fromWire(framer.nextMsg());
        Console.WriteLine("Received Response (" + encodedMsg.Length
               + " bytes): ");
        Console.WriteLine(msg);

        msg = coder.fromWire(framer.nextMsg());
        Console.WriteLine("Received Response (" + encodedMsg.Length
               + " bytes): ");
        Console.WriteLine(msg);

        msg = coder.fromWire(framer.nextMsg());
        Console.WriteLine("Received Response (" + encodedMsg.Length
               + " bytes): ");
        Console.WriteLine(msg);

        sock.Shutdown(SocketShutdown.Both);
        sock.Close();
        Console.ReadLine();
      }

    //辅助方法
      public static IPAddress[] GetLocalhostIPv4Addresses()
      {
          IPHostEntry hostEntry = Dns.GetHostEntry(Dns.GetHostName());
          List<IPAddress> list = new List<IPAddress>();
          foreach (IPAddress address in hostEntry.AddressList)
          {
              if (address.AddressFamily == AddressFamily.InterNetwork)
              {
                  list.Add(address);
              }
          }
          return list.ToArray();
      }

    }

服务端接收并返回数据:

    public class VoteServerTCP {

      public static void Main(String[] args){

        int port = 5555;

        Socket servSock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );;
        //编码对象
        IVoteMsgCoder coder = new VoteMsgTextCoder();
          //服务对象
        VoteService service = new VoteService();
        //创建IP终结点
        IPAddress localAddress = AddressHelper.GetLocalhostIPv4Addresses().First();        
        IPEndPoint ipep = new IPEndPoint(localAddress, port);

        servSock.Bind(ipep);
        servSock.Listen(1);

        while (true) {
          Socket clntSock = servSock.Accept();
          Console.WriteLine("Handling client at " + clntSock.AddressFamily);

          IFramer framer = new LengthFramer(clntSock);
          try {
            byte[] req;
            while ((req = framer.nextMsg()) != null) { 
              Console.WriteLine("Received message (" + req.Length + " bytes)");
              VoteMsg responseMsg = service.handleRequest(coder.fromWire(req));
                //发回反馈消息
              framer.frameMsg(coder.toWire(responseMsg));
            }
          } catch (SocketException soe) {
              //比如说客户端提发送多条消息,又在全部接收前关闭socket时就会引发此类异常。
            Console.WriteLine("Error handling client: " + soe.Message);
          } finally {
            Console.WriteLine("Closing connection");
            clntSock.Close();
          }
        }
      }
    }

对字符串编码的简单实现

    public class VoteMsgTextCoder : IVoteMsgCoder
    {
        // 下面是需要进行编码的字段
        public static readonly String MAGIC = "Voting";
        public static readonly String VOTESTR = "v";
        public static readonly String INQSTR = "i";
        public static readonly String RESPONSESTR = "R";

        public static readonly String DELIMSTR = " ";
        UTF8Encoding encoder = null;


        public static readonly int MAX_WIRE_LENGTH = 2000;


        public VoteMsgTextCoder()
        {
            encoder = new UTF8Encoding();
        }



        public byte[] toWire(VoteMsg msg)
        {
            String msgString = MAGIC + DELIMSTR + (msg.IsInquiry ? INQSTR : VOTESTR)
                + DELIMSTR + (msg.IsResponse ? RESPONSESTR + DELIMSTR : "")
                + msg.CandidateID.ToString() + DELIMSTR
                + msg.VoteCount.ToString();
            byte[] data = encoder.GetBytes(msgString);
            return data;
        }

        public VoteMsg fromWire(byte[] message)
        {

            bool isInquiry;
            bool isResponse;
            int candidateID;
            long voteCount;
            string token;

            string StringSentByClient = Encoding.UTF8.GetString(message);
            string[] fields = StringSentByClient.Split(new char[] { ' ' });

            try
            {
                token = fields[0];
                if (!token.Equals(MAGIC))
                {
                    throw new IOException("Bad magic string: " + token);
                }
                token = fields[1];
                if (token.Equals(VOTESTR))
                {
                    isInquiry = false;
                }
                else if (!token.Equals(INQSTR))
                {
                    throw new IOException("Bad vote/inq indicator: " + token);
                }
                else
                {
                    isInquiry = true;
                }

                token = fields[2];
                if (token.Equals(RESPONSESTR))
                {
                    isResponse = true;
                    token = fields[3];
                }
                else
                {
                    isResponse = false;
                }

                candidateID = int.Parse(token);
                if (isResponse)
                {
                    token = fields[4];
                    voteCount = long.Parse(token);
                }
                else
                {
                    voteCount = 0;
                }
            }
            catch (IOException ioe)
            {
                throw new IOException("Parse error...");
            }

            return new VoteMsg(isInquiry, isResponse, candidateID, voteCount);

        }
    }

运行结果:

[转载]用TCP/IP实现自己简单的应用程序协议:成帧器部分

mikel阅读(828)

[转载]用TCP/IP实现自己简单的应用程序协议:成帧器部分 – 浪雪 – 博客园.

在前面《字节和字符,对信息进行编码》,《Socket=>流,TCP连接,TCP可靠性概述》一系列的随笔中我们已经表述了相应的理论知识,现在可以动手实现一个自己的应用程序协议。

将 数据转换成在线路上传输的字节序列只完成了一半的工作,在接收端还必须将接受到的字节序列还原成原始信息。如果以流作为传输的形式,那么首先面临的问题就 是在接收端如何确定这是一条消息,换句话说就是如何定位一条消息的开始和结束。值得注意的是,这个工作应该是在应用程序协议这一层来完成而不是在TCP这 一层来完成,应用程序协议必须指定消息的接受者如何确定何时消息已完整接收。


TCP协议中没有消息边界的概念,这会让我们在解析信息的时候产生一些问题。
如果接收者试图从套接字中读取比消息本身更多的字节,将可能发生以下两种情况:
1.如果信道中没有其他消息,接收者将阻塞等待,同时无法处理接收到的消息;如果发送者也在等待接收端的响应消息,那么就会造成“死锁”

2.如果信道中还有其他消息,则接收者会将后一条的消息的一部分甚至全部读取到第一条消息中,这将会产生一些“协议错误”

因此,在时候流TCP套接字的时候,成帧就是一个非常重要的考虑因素。

对于成帧,主要有两个技术能使接收者能够准确地找到消息的结束位置:
1.消息的结束由一个特殊的标记指明,比如把一个特殊的字节序列0001等显式添加到一个消息的结束位置。这里的限制就在于传输的内容中不能包含和该特殊字节序列中一样的字符。就像HTML中<和>符号不能直接包含在输出中,这时需要转义。

2.显式的告知长度。
在变长字段或消息前面附加一个固定的字段,用来表示该字段或者消息中包含了多少个字节。

我们来写一个网络上常见的投票来作为例子:
这个例子包含了两种类型的请求,一种是“查询”的请求,也就是查询当前的候选人获得的选票情况。
第二种是“投票”请求,服务器保存此次投票信息,并返回投完票后该候选人获得的结果。

在实现一个协议的时候,定义一个专门的类来存放消息中所包含的信息是大有裨益的。类提供了给我们封装的能力,通过属性来公开类中的可变字段,也可以维护一些不变的字段。

我在这里采用的发送消息大小的方式来确定一条完整的消息。

项目结构和功能说明如下:

IFramer接口的定义:
namespace VoteForMyProtocol
{
    public interface IFramer
    {
        void frameMsg(byte[] message);
        byte[] nextMsg();
    }
}

基于长度成帧的实现

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.IO;

namespace VoteForMyProtocol
{

    public class LengthFramer : IFramer {
        public static readonly int MAXMESSAGELENGTH = 65535;
        Socket s = null;

        public LengthFramer(Socket s)
        {
            this.s = s;
        }

        //把消息成帧并发送
        public void frameMsg(byte[] message){
            if (message.Length > MAXMESSAGELENGTH) {
              throw new IOException ("message too long");
            }

            int totalSent = 0;
            int dataLeft = message.Length;     //剩余的消息
            int thisTimeSent;
            //保存消息长度
            byte[] datasize = new byte[4];
            datasize = BitConverter.GetBytes(message.Length);
            //将消息长度发送出去
            thisTimeSent = s.Send(datasize);
            //发送消息剩余的部分
            while (totalSent < message.Length)
            {
                thisTimeSent = s.Send(message, totalSent, dataLeft, SocketFlags.None);
                totalSent += thisTimeSent;
                dataLeft -= thisTimeSent;
            }
        }

        //按帧来解析消息
        public byte[] nextMsg(){
            if (s == null)
                throw new ArgumentNullException("socket null");
            int total = 0;  //已接收的字节数
            int recv;
            //接收4个字节,得到“消息长度”
            byte[] datasize = new byte[4];

            //如果当前使用的是面向连接的 Socket,则 Receive 方法将读取所有可用的数据,直到达到 size 参数指定的字节数。
            //如果远程主机使用 Shutdown 方法关闭了 Socket 连接,并且所有可用数据均已收到,则 Receive 方法将立即完成并返回零字节。 
            recv = s.Receive(datasize, 0, 4, 0);
            if (recv < 4)
                return null;

            int size = BitConverter.ToInt32(datasize, 0);

            //按消息长度接收数据
            int dataleft = size;
            //容器装满了就证明收集到了一条完整的消息。
            byte[] data = new byte[size];
            //直到容器填满再返回
            while (total < size)
            {
                recv = s.Receive(data, total, dataleft, 0);
                total += recv;
                dataleft -= recv;
                if (dataleft == 0)
                {
                    break;
                }
            }
            return data;
        }

    }
}

[转载]Socket=>流,TCP连接,TCP可靠性概述

mikel阅读(856)

[转载]Socket=>流,TCP连接,TCP可靠性概述 – 浪雪 – 博客园.

如前文所说,不同类型的Socket与不同类型的底层协议族以及同一协议族中的不同协议相关联。而我想说的主要就是TCP/IP协议族中的内容。现在TCP /IP协议族中的主要socket类型为”流套接字(stream socket)”和”数据报套接字(datagram socket)”。如果类比到现实中 stream socket类似于打电话沟通,datagram socket类似于写信沟通。当然,其他协议族当然也有相应的stream socket和datagram socket。
我学习的时候喜欢把类似的事物的异同点相比较,因此我就先从相同点开始说起。
一.Client和Server

无论是打电话还是写信,总会有打电话的一 方和写信的一方,相对应的就会有接电话的一方和收信的一方。后者通过回信或者接听电话对请求发出者作出相应。互联网通信也与这个过程类似。客户端 (client)和服务器(server)这两个术语代表了这两种不同的角色:client是通信的发起者,而server则会被动的等待客户端发起 信,并对其做出响应。
这里要提出的一点是,区分client和server的关键点是谁是请求的发出者,谁是请求的响应者。因此,client和 server之间的角色是可以互换的。一旦原先的client变成了请求的响应者,那么client就变成了server,反之亦然。server并不是 高配置的装了oracle等数据的高级服务器,client也并不是就是指个人电脑,也不是浏览器。记住,client和server只是对请求的发出者 和请求的响应者的一种抽象概念。
为什么要区分这两者呢?因为一个程序是作为client和server决定了它在于其对等端(peer)建立通信时所使用的Socket API的形式。什么叫对等端?客户端的对等端就是服务器,反之亦然。更进一步来说,是因为client首先需要知道server的所在地,也就是要知道 server的IP和port,但是反之则不需要。因为如果有必要,server可以通过相应的API来获取其client的地址。这与打电话非常类似, 被呼叫者不需要知道拨电话者的号码就可以接听电话,如果有需要,办个来电显示就可以知道打电话方的电话号码。只要通信建立成功,server和 client就没有什么区别了。(什么,接听免费?打电话收费?好吧,我承认这个是区别之一)
二.TCP Socket(stream socket)
在TCP/IP协议族中,Stream socket对应是以TCP作为其端对端协议,提供了一个基于连接的可信赖的“字节流”服务。
以下就是我第一次读到类似的话时产生的疑问,一直记录在我的书上:
1.什么是流?
2.什么是基于连接?连接又是什么?
3.什么是可信赖?到底可信赖到什么程度?难道发送端这边断电了接收端仍然还能接到数据?


我相信也会有人产生和我类似的疑问
1. 流简单来说就是一个简单有序的字节序列。注意它是有序的。很容易理解输入流就是以有序的方式写入新的字节,而输出流则是读取字节。因为TCP Socke是基于流的,所以我们可以猜到Socket实例中都会维护着相应的InputStream对象和OutputStream对象。当我们通过 Socket的OutputStream输出数据后,这些字节最终能在连接的另一端通过InputStream读取出来。
明白TCP传送的是一个没有记录边界概念的字节流。这一点很重要,可以总结为TCP中没有用户可见的”分组”概念,它只是传送了一个字节流,我们无法准确地预测在一个特定的读操作中会返回多少字节。最最简单的例子来说,你通过OutputStream send 3次的数据可能OutputStream read 2次就读取完了,而这中间又可能有多种情况存在。这里涉及到TCP的可靠性这一性质,这一点将在接下来的文章里细说。

2.和字节序列在网络的环境中称作报文一样,这里的“连接”也是一个基于特定上下文所使用的词。要说连接先从”无连接”来说更容易明白。很显然,如果不同的主机间需要通信,那么肯定需要以某种方式连接起来,无论是有线的还是无线的方式。那么无连接通信指的到底是什么?
回答就是,基于连接和无连接指的是一种协议。也就是说,在这里的连接并不指的是物理介质,而是一种传输的方式,说明了如何在物理介质上来传输数据。无论是基于连接还是无连接,都有可能在同一条网线上传送着数据。
对TCP来说,连接完全是”想象”的。它是由端点(client 和 server)所记忆的状态组成的,并不存在”物理”连接。虽然我们前面用打电话来类比TCP,但打电话的时候是有物理连接的。这 里的连接是指,只要连接的双方认为 TCP 连接存在,并且可以互相发送 IP packet,那么 TCP 连接就一直存在。简单来说,就是一端认为另一端能够接受数据,就记录数据的发送状态并把数据发送出去,除非知道另一端不再能接收到数据了。也就是说,所谓 的连接,就是client认为我能把数据传送到server,server是存在的。而server认为我应该等待client把数据传送过来。而 我们认为连接存在是通过三次握手来实现的。这其中又会产生问题,问题在于连接是想象的,因此并不是实时的。也就是说并不是像你拔掉网线后在右下角的连接提 示就会有个红叉叉出现。当一端出问题的时候,另一端可能仍然会认为连接是存在的。


[转载]分组报文,协议和Socket的概念

mikel阅读(971)

[转载]分组报文,协议和Socket的概念 – 浪雪 – 博客园.

这篇随笔是我接着上篇《字节和字符,对信息进行编码》继续写的内容,看过上篇随笔能更好的理解这篇内容。我想从基础的开始说起,一直说到ASP.NET,WCF为止。然后再转战数据库和数据结构。

信息是指由程序创建和建设的“字节序列”。在网络环境中,这些字节序列被称作“分组报文”。一组报文包括了网络用来完成工作的控制信息,还包括了数据信息。


协议相当于互相通信的程序(进程间通信)间达成的一种约定,它规定了分组报文的交换方式和它们包含的含义。一组协议规定了以下信息:
1.结构。 比如报文中哪一部分表明了其目的地址。
2.解析。如何对报文中所包含的信息进行解析。


设计一组协议,通常是为了在一定约束条件下解决某一特定问题。比如,超文本传输协议http是为了解决在服务期间传递超文本对象的问题。

网络层:网络层完成将分组报文传输到它们的目的地址的工作。注意,目的地只精确到网络接口,并不精确到应用程序。IP属于网络层。

传 输层:TCP协议和IP协议都有一个共同的功能,即寻址。回顾一下,IP协议只是将分组报文分发到了不同主机(更准确的说是网络接口,因为一台主机可能装 了多个网卡,而网卡才是目的地。cpu,主板等其他组成一台主机的其他组成部分并不是目的地)。很明显,还需要其他更细粒度的寻址将报文发送到主机中指定 的应用程序,因为同一台主机上可能有多个应用程序在使用网络。TCP协议和UDP协议使用的地址叫做“端口号”,这就是用来区分同一台主机中不同应用程序 的。TCP和UDP协议有时候也称作端到端传输协议(end-to-end transport protocol),因为它们将数据”从一个应用程序传输到另一个应用程序”,而IP协议只是将数据从”一台主机传输到另一台主机”

TCP 协议能够检测和恢复IP层提供的主机到主机的信道中可能发生的报文丢失,重复以及其他错误。TCP协议提供了一个可信赖的字节流(reilable byte-stream)信道。(注意流的概念,下面在提到NIO的时候会和buffer进行比较).使用TCP协议在很多方面都与文件的输入输出 (I/O)相似。实际上,由一个程序写入的文件再由另一个程序读取就是一个TCP连接的适当模型。

UDP协议并不尝试对IP层产生的错误进行修复,它仅仅简单的扩展了IP协议“尽力而为best effort”的数据报服务,使得数据能在应用程序之间工作,而不是在主机之间工作。因此,使用了UDP协议的应用程序必须为处理报文丢失,顺序混乱等问题做好准备。
下面来谈下Socket。
Socket:只是一个抽象层。用来表示程序已经加入到网络中。
这句话的意思就是一旦一个程序中有了一个Socket实例对象,那么这个程序就被加入到了网络当中,可以和网络中的其他应用程序进行通信。

现在来关注Socket是抽象层这段话。既然Socket是抽象的,那么它肯定有很多不同具体的实现,比如说以TCP为基础的TCPSocekt和以UDP为基础的UDPSocket。

不恰当的比方来说,拥有一个Sokcet就像狱警对犯人说你有和外界沟通的权力。和外界沟通这个概念是抽象的,具体是坐在桌子前面谈,还是通过 电话机隔着玻璃窗谈话,(请想象美剧中的场景)还是允许写信,单从“你可以和外界沟通“这句话来说都是不可知的。我们唯一能知道的是,我们可以和外界沟通 了。
接下来的随笔中我会继续说明这几种实现方式的异同点。

[转载]字节和字符,对信息进行编码

mikel阅读(859)

[转载]字节和字符,对信息进行编码 – 浪雪 – 博客园.

TCP/IP 协议以字节的方式传输用户数据,并没有对其进行检查和修改。这个特点使得应用程序可以非常灵活地对其中传输的信息进行编码。TCP/IP的唯一约束是,信 息必须在块(chunk)中发送和接收,而块的长度必须是8位的倍数。而字节正好是8位的,因此我们可以认为在TCP/IP协议中传输的信息是字节序列。 鉴于此,我们可以进一步把传输的信息看做数字序列或数组,每个数字的取值范围是0~255(8位)


应用程序协议:明确定义了信息的发送者应该怎样排列和解释这些位序列(bit sequence),同时还要定义接受者应该怎样解析,这样才使得信息的接受者和发送者能够抽取每个字段的意义。

在Java和C#程序中,Int数据都由32位表示(都映射到Int32上),因此,我们可以用4个字节来传输任意的Int变量或者常量。这时要注意的是,对于需要超过一个字节来表示的数据类型,我们必须知道这些字节的发送顺序。显然有2种选择,从右往左或者从左往右。


字符串和文本:
因 为人们习惯于处理各种各样以字符串形式表示的信息,如书本中和电脑显示器上显示的信息。因此,只要我们指定如何对要传输的文本进行编码,我们就几乎能发送 其他“任何类型”的数据:先将其表示成文本形式,再对文本进行编码转换成相应类型。用文本表示的类型和二进制表示的类型相比优势之一就是人可以读懂,第二 就是可以跨平台。显然,我们可以将数字和boolean类型的数据表示成String类型,如 “123456”,“1.23e33”,”true”等。也可以表示 成”<int>123</int>”,”<boolean>true</boolean>”等。我们也 知道,通过调用getBytes()方法,可以将一个字符串转换成字节数组。当然,还有其他方法实现这个功能。

为了更好的理解这个过程,我们首先得将文本视为由符号(如感叹号”!”,问号”?”)和字符组成。实际上每个String实例内部都对应了一个char[]类型。一个char在java内部表示为一个整数。如字符 “a” 与整数97对应,”!”则对应了33.

在一组符号与一组整数之间的映射称为编码字符集(coded character set)。你应该听说过ASCII编码字符集(美国标准信息交换码)。ASCII码将英语字母,数字,标点符号以及一些特殊符号映射成0~127的整数。
可以看出,它忽略了许多英语以外的其他语言所使用的符号,因此用ASCII码来开发应用程序和协议显然并不适合我们的情况。

128个数字明显不够用,可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。因此,Unicode字符集出现了,就像它的名字都表示的,这是一种所有符号的编码。将世界上大部分的语言和符号映射到0~65535之间。Unicode包含了ASCII,也就是说,原来在ASCII中字符所对应的数字在Unicode中也是用同样的数字来表示,这就提供了一定的兼容性。需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

发送者和接收者在字符映射这件事上达成共识就完了吗?这还得看情况而定。对于每个整数值都比255小的一小组字符,则不需要其他信息,因为它可以用一个字节 来表示,不会涉及到多个字节的排序问题。对于可能使用超过一个字节的大整数的编码方式,就需要对这些整数如何表示成字节序列统一意见,这个意见也叫做“编码规范”(encoding scheme).当然,只要你喜欢,你可以定义自己的编码规范。但是就如同前面所说得,这需要发送者和接收者沟通并达成一致。这挺费事,而且世界上已经有 大量不同的标准,我们只要选择一种标准来共同遵守就可以了。

说来说去,其实就是一个映射问题,拿String实例的getBytes()方法来举例,该方法返回一个[]byte也就是字节数组。通过调用该方法,就完成一个“由字符串到字节的映射过程”.
比如调用”Test!”.getBytes(),你将获得按照UTF-8字符集编码的数组。
84 101 115 116 33

如果按照”Test!”.getBytes(“IBM037”),返回的结果将会是
227 133 162 163 90
上面的例子说明,发送者和接收者必须在文本字符串的表示方式上达成共识。
上面的是java代码,C#中可以明显的看出字符集的定义,由相应的类来表示:

classProgram
{
staticvoidMain(string[] args)
{
UnicodeEncoding unicodeEncoding = newSystem.Text.UnicodeEncoding();
UTF8Encoding utf8Encoding = newUTF8Encoding();
byte[] arr = utf8Encoding.GetBytes("Test!");
foreach(byteb inarr)
{
Console.WriteLine(b);
}
}
}