Java4Android之socket网络通信基础

简介:

本节主要介绍Socket编程,发现Java里面的socket编程和C语言的还是有一些不一样,比如TCP socket ,在Java中区分了serverSocket。不过原理都一样,在流程处理上也非常相似,所以,理解起来并不难。我们会先从基础说起,从如何建立socket连接,到如何实现一个合理的设计例如在android中,我们发送一条消息,然后监听一个回复,如何做到不卡死UI,本文将会由浅入深的为大家呈现一个相对完整的android socket编程。

在进入Java 的socket编程之前,我们从原理上切入,然后提及相应的代码说明。

Socket可以说是一种针对网络的抽象,应用通过它可以来针对网络读写数据。就像通过一个文件的file handler就可以都写数据到存储设备上一样。根据TCP协议和UDP协议的不同,在网络编程方面就有面向两个协议的不同socket,一个是面向字节流的一个是面向报文的


TCP

  TCP主要是面向连接的协议,它包含有建立和拆除连接,保证数据流的顺序和正确性等功能。每次对TCP中间的数据操作相当于对一个数据流进行访问。它最典型的特征就是那三次握手的建立连接过程。TCP的连接建立和撤销过程如下图:

tcp

Server端

Server端所要做的事情主要是建立一个通信的端点,然后等待客户端发送的请求。典型的处理步骤如下:
1. 构建一个ServerSocket实例,指定本地的端口。这个socket就是用来监听指定端口的连接请求的。
2.重复如下几个步骤:
a. 调用socket的accept()方法来获得下面客户端的连接请求。通过accept()方法返回的socket实例,建立了一个和客户端的新连接。
b.通过这个返回的socket实例获取InputStream和OutputStream,可以通过这两个stream来分别读和写数据。
c.结束的时候调用socket实例的close()方法关闭socket连接。
 

单线程版本


这个流程的典型示例代码如下:

//1. 构造ServerSocket实例,指定服务端口。
ServerSocket servSock = new ServerSocket(servPort);


while(true)
{
	   // 2.调用accept方法,建立和客户端的连接
           Socket clntSock = servSock.accept();
           SocketAddress clientAddress =    
                clntSock.getRemoteSocketAddress();
           System.out.println("Handling client at " + clientAddress);

	    // 3. 获取连接的InputStream,OutputStream来进行数据读写
            InputStream in = clntSock.getInputStream();
            OutputStream out = clntSock.getOutputStream();

            while((recvMsgSize = in.read(receiveBuf)) != -1)
            {
                out.write(receiveBuf, 0, recvMsgSize);
            }   
	    // 4.操作结束,关闭socket.
            clntSock.close();
}  

多线程版本

上述是单线程版本的服务器流程,也可是是多线程的。大体代码如下:

try   
{ file://建立服务器    
 ServerSocket server = new ServerSocket(9998);    
 int i=1;    
 for(;;)    
 {    
<span style="white-space:pre">	</span>Socket incoming = server.accept();    
<span style="white-space:pre">	</span>new ServerThread(incoming,i).start();    //开启线程来处理客户端的请求
<span style="white-space:pre">	</span>i++;    
 }    
}catch (IOException ex){ ex.printStackTrace(); }   

Client端

客户端的请求过程稍微有点不一样:
1.构建Socket实例,通过指定的远程服务器地址和端口来建立连接。
2.通过Socket实例包含的InputStream和OutputStream来进行数据的读写。
3.操作结束后调用socket实例的close方法,关闭。
示例代码如下;

// 1.根据指定的server地址和端口,建立socket连接。
Socket socket = new Socket(server, servPort);

// 2. 根据socket实例获取InputStream, OutputStream进行数据读写。
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
out.write(data);

//3.操作结束,关闭socket.
socket.close();


上述代码就是一个完整的客户端/服务器结构的socket通信代码。



不卡死UI的socket设计

但是,在android中的socket通信中,我们可以想象一下这样的场景。我在EditText里面输入了一句“你好”,然后我按发送Button按钮,就把消息发送给了远端的服务器,然后服务器给我回了一个“你好,欢迎您!”。我们发送完第一条信息的时候,我们要阻塞在那里等服务器的话,会发生什么?我们 UI是不是会卡死?所以,很明显,这样的设计是不合理的。

客户端


MyClientActivity.java文件,主要是定义了一个继承自Thread类的用于接收数据的类,覆写了其中的run()方法,在这个函数里面接收数据,接收到数据后就通过Handler发送消息,收到消息后在UI线程里更新接收到的数据。完整的内容如下:
package com.nan.client;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;


public class MyClientActivity extends Activity 
{
    private EditText mEditText = null;
    private Button connectButton = null;
    private Button sendButton = null;
    private TextView mTextView = null;
    
    private Socket clientSocket = null;
    private OutputStream outStream = null;
    
    private Handler mHandler = null;
    
    private ReceiveThread mReceiveThread = null;
    private boolean stop = true;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        mEditText = (EditText)this.findViewById(R.id.edittext);
        mTextView = (TextView)this.findViewById(R.id.retextview);
        connectButton = (Button)this.findViewById(R.id.connectbutton);
        sendButton = (Button)this.findViewById(R.id.sendbutton);
        sendButton.setEnabled(false);      
        
        //连接按钮监听
        connectButton.setOnClickListener(new View.OnClickListener() 
        {
            
            @Override
            public void onClick(View v) 
            {
                // TODO Auto-generated method stub
                try 
                {
                    //实例化对象并连接到服务器
                    clientSocket = new Socket("113.114.170.246",8888);
                } 
                catch (UnknownHostException e) 
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } 
                catch (IOException e) 
                {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
                displayToast("连接成功!");                        
                //连接按钮使能
                connectButton.setEnabled(false);
                //发送按钮使能
                sendButton.setEnabled(true);
                
                mReceiveThread = new ReceiveThread(clientSocket);
                stop = false;
                //开启线程
                mReceiveThread.start();
            }
        });
        
        //发送数据按钮监听
        sendButton.setOnClickListener(new View.OnClickListener() 
        {
            
            @Override
            public void onClick(View v) 
            {
                // TODO Auto-generated method stub
                byte[] msgBuffer = null;
                //获得EditTex的内容
                String text = mEditText.getText().toString();
                try {
                    //字符编码转换
                    msgBuffer = text.getBytes("GB2312");
                } catch (UnsupportedEncodingException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                            
                
                try {
                    //获得Socket的输出流
                    outStream = clientSocket.getOutputStream();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }                                                    
                
                
                try {
                    //发送数据
                    outStream.write(msgBuffer);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                //清空内容
                mEditText.setText("");
                displayToast("发送成功!");
            }
        });
              
        //消息处理
        mHandler = new Handler()
        {
            @Override
            public void handleMessage(Message msg)
            {
                //显示接收到的内容
                mTextView.setText((msg.obj).toString());
            }
        };
        
    }
    
    //显示Toast函数
    private void displayToast(String s)
    {
        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
    }
    
    
    private class ReceiveThread extends Thread
    {
        private InputStream inStream = null;
        
        private byte[] buf;  
        private String str = null;
        
        ReceiveThread(Socket s)
        {
            try {
                //获得输入流
                this.inStream = s.getInputStream();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }      
        
        @Override
        public void run()
        {
            while(!stop)
            {
                this.buf = new byte[512];
                
                try {
                    //读取输入数据(阻塞)
                    this.inStream.read(this.buf);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } 
                
                //字符编码转换
                try {
                    this.str = new String(this.buf, "GB2312").trim();
                } catch (UnsupportedEncodingException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
                Message msg = new Message();
                msg.obj = this.str;
                //发送消息
                mHandler.sendMessage(msg);
                
            }
        }
        
        
    }
    
    @Override
    public void onDestroy()
    {
        super.onDestroy();
        
        if(mReceiveThread != null)
        {
            stop = true;
            mReceiveThread.interrupt();
        }
    }
     
}

服务端

MyServerActivity.java文件,定义了两个Thread子类,一 个用于监听客户端的连接,一个用于接收数据,其他地方与MyClientActivity.java差不多。完整的内容如下:
package com.nan.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;


public class MyServerActivity extends Activity 
{
    private TextView ipTextView = null;
    private EditText mEditText = null;
    private Button sendButton = null;
    private TextView mTextView = null;
    
    private OutputStream outStream = null;
    private Socket clientSocket = null;
    private ServerSocket mServerSocket = null;
    
    private Handler mHandler = null;
    
    private AcceptThread mAcceptThread = null;
    private ReceiveThread mReceiveThread = null;
    private boolean stop = true;
    
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) 
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ipTextView = (TextView)this.findViewById(R.id.iptextview);
        mEditText = (EditText)this.findViewById(R.id.sedittext);
        sendButton = (Button)this.findViewById(R.id.sendbutton);
        sendButton.setEnabled(false);
        mTextView = (TextView)this.findViewById(R.id.textview);
        
        //发送数据按钮监听
        sendButton.setOnClickListener(new View.OnClickListener() 
        {
            
            @Override
            public void onClick(View v) 
            {
                // TODO Auto-generated method stub
                byte[] msgBuffer = null;
                //获得EditTex的内容
                String text = mEditText.getText().toString();
                try {
                    //字符编码转换
                    msgBuffer = text.getBytes("GB2312");
                } catch (UnsupportedEncodingException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                            
                
                try {
                    //获得Socket的输出流
                    outStream = clientSocket.getOutputStream();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }                                                    
                
                
                try {
                    //发送数据
                    outStream.write(msgBuffer);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                //清空内容
                mEditText.setText("");
                displayToast("发送成功!");
                
            }
        });
        //消息处理
        mHandler = new Handler()
        {
            @Override
            public void handleMessage(Message msg)
            {
                switch(msg.what)
                {
                    case 0:
                    {
                        //显示客户端IP
                        ipTextView.setText((msg.obj).toString());
                        //使能发送按钮
                        sendButton.setEnabled(true);
                        break;
                    }
                    case 1:
                    {
                        //显示接收到的数据
                        mTextView.setText((msg.obj).toString());
                        break;
                    }                 
                }                                           
                
            }
        };
        
        
        mAcceptThread = new AcceptThread();
        //开启监听线程
        mAcceptThread.start();
              
    }
    
    //显示Toast函数
    private void displayToast(String s)
    {
        Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
    }
    
    
    private class AcceptThread extends Thread
    {
        @Override
        public void run()
        {
            try {
                //实例化ServerSocket对象并设置端口号为8888
                mServerSocket = new ServerSocket(8888);
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            while(1)
{
            try {
                //等待客户端的连接(阻塞)
                clientSocket = mServerSocket.accept();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            
            mReceiveThread = new ReceiveThread(clientSocket);
            stop = false;
            //开启接收线程
            mReceiveThread.start();
            
            Message msg = new Message();
            msg.what = 0;
            //获取客户端IP
            msg.obj = clientSocket.getInetAddress().getHostAddress();
            //发送消息
            mHandler.sendMessage(msg);
  }
        }
        
    }
    
    
    private class ReceiveThread extends Thread
    {
        private InputStream mInputStream = null;
        private byte[] buf ;  
        private String str = null;
        
        ReceiveThread(Socket s)
        {
            try {
                //获得输入流
                this.mInputStream = s.getInputStream();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        
        @Override
        public void run()
        {
            while(!stop)
            {
                this.buf = new byte[512];
                
                //读取输入的数据(阻塞读)
                try {
                    this.mInputStream.read(buf);
                } catch (IOException e1) {
                    // TODO Auto-generated catch block
                    e1.printStackTrace();
                }
                
                //字符编码转换
                try {
                    this.str = new String(this.buf, "GB2312").trim();
                } catch (UnsupportedEncodingException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
                Message msg = new Message();
                msg.what = 1;        
                msg.obj = this.str;
                //发送消息
                mHandler.sendMessage(msg);
                
            }
        }
    }
    
      
    @Override
    public void onDestroy()
    {
        super.onDestroy();
        
        if(mReceiveThread != null)
        {
            stop = true;
            mReceiveThread.interrupt();
        }
    }
    
    
}
上述代码就是实现了真实环境下android的socket通信。

UDP

UDP和TCP有两个典型的区别,一个就是它不需要建立连接,另外就是它在每次收发的报文都保留了消息的边界。

server端

因为UDP协议不需要建立连接,它的过程如下:
1. 构造DatagramSocket实例,指定本地端口。
2. 通过DatagramSocket实例的receive方法接收DatagramPacket.DatagramPacket中间就包含了通信的内容。
3. 通过DatagramSocket的send和receive方法来收和发DatagramPacket.
典型的交互流程代码如下:
// 1. 构建DatagramSocket实例,指定本地端口。
DatagramSocket socket = new DatagramSocket(servPort);

// 2. 构建需要收发的DatagramPacket报文
DatagramPacket packet = new DatagramPacket(new byte[ECHOMAX], ECHOMAX);

while(true)
{
	// 3. 收报文
	socket.receive(packet);
	System.out.println("Handling client at " + packet.getAddress().getHostAddress()
	    + " on port " + packet.getPort());
	// 4. 发报文
	socket.send(packet);
	packet.setLength(ECHOMAX);
}

client端

UDP客户端的步骤也比较简单,主要包括下面3步:
1. 构造DatagramSocket实例。
2.通过DatagramSocket实例的send和receive方法发送DatagramPacket报文。
3.结束后,调用DatagramSocket的close方法关闭。
因为和TCP不同,UDP发送报文的时候可以在同一个本地端口随意发送给不同的服务器,一般不需要在UDP的DatagramSocket的构造函数中指定目的服务器的地址。
另外,UDP客户端还有一个重要的不同就是,TCP客户端发送echo连接消息之后会在调用read方法的时候进入阻塞状态,而UDP这样却不行。因为UDP中间是可以允许报文丢失的。如果报文丢失了,进程一直在阻塞或者挂起的状态,则进程会永远没法往下走了。所以会一般设置一个setSoTimeout方法,指定在多久的时间内没有收到报文就放弃。也可以通过指定一个数字,循环指定的次数来读取报文,读到就返回,否则就放弃。
 
一个典型的UDP Client代码示例如下:

// 1. 构造UDP DatagramSocket对象
DatagramSocket socket = new DatagramSocket();

// 2。指定timeout时间,防止进入无限等待状态
socket.setSoTimeout(TIMEOUT);

// 3. 构造收发的报文对象
DatagramPacket sendPacket = new DatagramPacket(bytesToSend,
    bytesToSend.length, serverAddress, servPort);
DatagramPacket receivePacket =
    new DatagramPacket(new byte[bytesToSend.length], bytesToSend.length);

// 4.指定尝试的次数
int tries = 0;
boolean receivedResponse = false;
 do
{
	socket.send(sendPacket);
	try
	{
		socket.receive(receivePacket);
 
		if(!receivePacket.getAddress().equals(serverAddress))
		{
			throw new IOException("Received packet from an unknown source");
		}
		receivedResponse = true;
	}
	catch(InterruptedIOException e)
	{
		tries += 1;
		System.out.println("Timed out, " + (MAXTRIES - tries) + "");
	}
}while((!receivedResponse) && (tries < MAXTRIES));

// 根据是否接收到报文进行反馈
if(receivedResponse)
{
	System.out.println("Received: " + new String(receivePacket.getData()));
}
else
{
	System.out.println("No response -- giving up.");
}

// 5. 关闭socket
socket.close();

这次的socket编程就说道这里,里面参考和引用转载了不少别人的东西,mark一下。

Reference:

http://shmilyaw-hotmail-com.iteye.com/blog/1556187

http://www.cnblogs.com/lknlfy/archive/2012/03/04/2379628.html




相关文章
|
1月前
|
Java 程序员
JAVA程序员的进阶之路:掌握URL与URLConnection,轻松玩转网络资源!
在Java编程中,网络资源的获取与处理至关重要。本文介绍了如何使用URL与URLConnection高效、准确地获取网络资源。首先,通过`java.net.URL`类定位网络资源;其次,利用`URLConnection`类实现资源的读取与写入。文章还提供了最佳实践,包括异常处理、连接池、超时设置和请求头与响应头的合理配置,帮助Java程序员提升技能,应对复杂网络编程场景。
58 9
|
1月前
|
人工智能 Java 物联网
JAVA网络编程的未来:URL与URLConnection的无限可能,你准备好了吗?
随着技术的发展和互联网的普及,JAVA网络编程迎来新的机遇。本文通过案例分析,探讨URL与URLConnection在智能API调用和实时数据流处理中的关键作用,展望其未来趋势和潜力。
43 7
|
1月前
|
Java 开发者
JAVA高手必备:URL与URLConnection,解锁网络资源的终极秘籍!
在Java网络编程中,URL和URLConnection是两大关键技术,能够帮助开发者轻松处理网络资源。本文通过两个案例,深入解析了如何使用URL和URLConnection从网站抓取数据和发送POST请求上传数据,助力你成为真正的JAVA高手。
54 11
|
1月前
|
安全 Java API
深入探索Java网络编程中的HttpURLConnection:从基础到进阶
本文介绍了Java网络编程中HttpURLConnection的高级特性,包括灵活使用不同HTTP方法、处理重定向、管理Cookie、优化安全性以及处理大文件上传和下载。通过解答五个常见问题,帮助开发者提升网络编程的效率和安全性。
|
1月前
|
JSON 安全 算法
JAVA网络编程中的URL与URLConnection:那些你不知道的秘密!
在Java网络编程中,URL与URLConnection是连接网络资源的两大基石。本文通过问题解答形式,揭示了它们的深层秘密,包括特殊字符处理、请求头设置、响应体读取、支持的HTTP方法及性能优化技巧,帮助你掌握高效、安全的网络编程技能。
56 9
|
1月前
|
JSON Java API
JAVA网络编程新纪元:URL与URLConnection的神级运用,你真的会了吗?
本文深入探讨了Java网络编程中URL和URLConnection的高级应用,通过示例代码展示了如何解析URL、发送GET请求并读取响应内容。文章挑战了传统认知,帮助读者更好地理解和运用这两个基础组件,提升网络编程能力。
48 5
|
1月前
|
Kubernetes 网络协议 Python
Python网络编程:从Socket到Web应用
在信息时代,网络编程是软件开发的重要组成部分。Python作为多用途编程语言,提供了从Socket编程到Web应用开发的强大支持。本文将从基础的Socket编程入手,逐步深入到复杂的Web应用开发,涵盖Flask、Django等框架的应用,以及异步Web编程和微服务架构。通过本文,读者将全面了解Python在网络编程领域的应用。
31 1
|
2月前
|
网络协议 Shell 网络安全
解决两个 Android 模拟器之间无法网络通信的问题
让同一个 PC 上运行的两个 Android 模拟器之间能相互通信,出(qiong)差(ren)的智慧。
28 3
|
2月前
|
Java
[Java]Socket套接字(网络编程入门)
本文介绍了基于Java Socket实现的一对一和多对多聊天模式。一对一模式通过Server和Client类实现简单的消息收发;多对多模式则通过Server类维护客户端集合,并使用多线程实现实时消息广播。文章旨在帮助读者理解Socket的基本原理和应用。
29 1
|
6天前
|
存储 SQL 安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
随着互联网的普及,网络安全问题日益突出。本文将介绍网络安全的重要性,分析常见的网络安全漏洞及其危害,探讨加密技术在保障网络安全中的作用,并强调提高安全意识的必要性。通过本文的学习,读者将了解网络安全的基本概念和应对策略,提升个人和组织的网络安全防护能力。