一、熟悉Socket
(1)从它的原意来理解
socket的英文原义是“孔”或“插座”。作为4BDS UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过"套接字"向网络发出请求或者应答网络请求。 在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座。
(2)从java的角度来看
应用程序通常通过"套接字"向网络发出请求或者应答网络请求。以J2SDK-1.3为例,Socket和ServerSocket类库位于java .net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。
(3)简单的说,它是一种抽象层,应用程序通过它来发送和接收数据,使用Socket可以将应用程序添加到网络中,与处于同一网络中的其他应用程序进行通信。简单来说,Socket提供了程序内部与外界通信的端口并为通信双方的提供了数据传输通道。
二、socket分类
Socket分为流套接字(streamsocket)和数据报套接字(datagramsocket)。分别对应网络传输控制中的TCP和UDP协议。TCP协议是面向连接的协议,提供稳定的双向通信功能。UDP是无连接诶的,提供不稳定的单向通信功能。我们来认识一下这两种Socket类型的基本实现模型。
(1)TCP通信模型
(2)UDP通信模型
三、重头戏:android实现socket通信
(1)使用socket来进行通信,首先需要声明权限
<!--允许应用程序改变网络状态-->
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<!--允许应用程序完全使用网络-->
<uses-permission android:name="android.permission.INTERNET"/>
(2)服务端的设计
当sevice启动时,会在线程建立TCP服务,这里监听的是8688端口,然后就可以在等待客户端的连接请求,当有客户端连接时,就会生成一个新的socket,通过每次新创建的socket就可以分别和不同的客户端通信了。
public class TCPServiceService extends Service {
private boolean mIsServiceDestroyed=false;
private String[] mDefinedMessages=new String[]{
"hello",
"Are you ok",
"haha",
"yeah"
};
@Override
public void onCreate() {
new Thread(new TcpServer()).start();
super.onCreate();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
mIsServiceDestroyed=true;
super.onDestroy();
}
private class TcpServer implements Runnable{
@SuppressWarnings("resource")
@Override
public void run() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(8688);
} catch (IOException e) {
System.err.println("establish tcp server failed, port:8688");
e.printStackTrace();
return;
}
while (!mIsServiceDestroyed){
try {
//接受客户端的请求
final Socket client=serverSocket.accept();
Log.d("test","accept");
new Thread(){
@Override
public void run() {
try {
responseClient(client);
} catch (IOException e) {
e.printStackTrace();
}
};
}.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void responseClient(Socket client) throws IOException {
//接受客户端消息
BufferedReader in=new BufferedReader(new InputStreamReader(client.getInputStream()));
//发送服务端消息
PrintWriter out=new PrintWriter(new BufferedWriter(new OutputStreamWriter(client.getOutputStream())),true);
out.println("欢迎来到聊天室!");
while (!mIsServiceDestroyed) {
String str = in.readLine();
Log.d("test", "msg from client:" + str);
if (str == null) {
break;
}
int i = new Random().nextInt(mDefinedMessages.length);
String msg = mDefinedMessages[i];
out.println(msg);
Log.d("test:", "send" + msg);
}
Log.d("test:","client quit");
client.close();
}
(2)客户端的设计
为了确认能够连接成功,这里采用了超市重连的策略,每次连接失败后都会重新建立尝试建立连接。等然为了降低重新机制的开销,我们加入休眠机制,即加上了每次重试的时间间隔。
服务端连接成功以后,就可以和服务端进行通信了。
注意:Activity退出时,要关闭当前的socket。
public class MainActivity extends AppCompatActivity implements View.OnClickListener{
private static final int MESSAGE_RECEIVE_NEW_MSG=1;
private static final int MESSAGE_SOCKET_CONNECTED=2;
private Button mSendButton;
private TextView mMessageTextView;
private EditText mMessageEditText;
private PrintWriter mPrintWriter;
private Socket mClientSocket;
Handler mHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case MESSAGE_RECEIVE_NEW_MSG: {
mMessageTextView.setText(mMessageTextView.getText() + (String) msg.obj);
break;
}
case MESSAGE_SOCKET_CONNECTED:
{
mSendButton.setEnabled(true);
break;
}
default:
break;
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mMessageTextView=(TextView) findViewById(R.id.tv_contents);
mSendButton=(Button) findViewById(R.id.send);
mSendButton.setOnClickListener(this);
mMessageEditText=(EditText) findViewById(R.id.ed_news);
Intent service=new Intent(this,TCPServiceService.class);
startService(service);
new Thread() {
@Override
public void run() {
connectTCPService();
}
}.start();
}
@Override
protected void onDestroy() {
if(mClientSocket!=null){
try {
mClientSocket.shutdownInput();
mClientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
super.onDestroy();
}
private void connectTCPService() {
Socket socket = null;
while (socket == null) {
try {
socket = new Socket("localhost", 8688);
mClientSocket = socket;
mPrintWriter = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())), true);
mHandler.sendEmptyMessage(MESSAGE_SOCKET_CONNECTED);
System.out.println("connect server success");
} catch (IOException e) {
SystemClock.sleep(1000);
System.out.println("connect tcp server failed, retry...");
}
}
try {
BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
while (!MainActivity.this.isFinishing()) {
String msg = br.readLine();
if (msg != null) {
String time = formatDateTime(System.currentTimeMillis());
final String showedMsg = "server" + time + ":" + msg + "\n";
mHandler.obtainMessage(MESSAGE_RECEIVE_NEW_MSG, showedMsg).sendToTarget();
}
}
Log.d("text","quit...");
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//把当前时间转化成自定义格式
private String formatDateTime(long time){
return new SimpleDateFormat("(HH:mm:ss)").format(new Date(time));
}
@Override
public void onClick(View view) {
if(view==mSendButton){
final String msg=mMessageEditText.getText().toString();
if(!TextUtils.isEmpty(msg)&&mPrintWriter!=null){
mPrintWriter.println(msg);
mMessageEditText.setText("");
String time=formatDateTime(System.currentTimeMillis());
final String showedMsg="self"+time+":"+msg+"\n";
mMessageTextView.setText(mMessageTextView.getText()+showedMsg);
}
}
}
}
四、小结一下
上述就是通过socket来进行进程间通信的实例,除了采用TCP套接字,还可以采用UDP套接字。实际上通过socket不仅仅能实现进程间通信,还可以实现设备间的通信,当然前提是这些设备之间的IP地址互相可见。