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




相关文章
|
14天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
14天前
|
程序员 开发者 Python
Python网络编程基础(Socket编程) 错误处理和异常处理的最佳实践
【4月更文挑战第11天】在网络编程中,错误处理和异常管理不仅是为了程序的健壮性,也是为了提供清晰的用户反馈以及优雅的故障恢复。在前面的章节中,我们讨论了如何使用`try-except`语句来处理网络错误。现在,我们将深入探讨错误处理和异常处理的最佳实践。
|
13天前
|
存储 算法 Linux
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
【实战项目】网络编程:在Linux环境下基于opencv和socket的人脸识别系统--C++实现
38 6
|
1天前
|
存储 网络协议 关系型数据库
Python从入门到精通:2.3.2数据库操作与网络编程——学习socket编程,实现简单的TCP/UDP通信
Python从入门到精通:2.3.2数据库操作与网络编程——学习socket编程,实现简单的TCP/UDP通信
|
1天前
|
移动开发 Java Android开发
构建高效Android应用:采用Kotlin协程优化网络请求
【4月更文挑战第24天】 在移动开发领域,尤其是对于Android平台而言,网络请求是一个不可或缺的功能。然而,随着用户对应用响应速度和稳定性要求的不断提高,传统的异步处理方式如回调地狱和RxJava已逐渐显示出局限性。本文将探讨如何利用Kotlin协程来简化异步代码,提升网络请求的效率和可读性。我们将深入分析协程的原理,并通过一个实际案例展示如何在Android应用中集成和优化网络请求。
|
6天前
|
监控 Java 开发者
深入理解 Java 网络编程和 NIO
【4月更文挑战第19天】Java网络编程基于Socket,但NIO(非阻塞I/O)提升了效率和性能。NIO特点是非阻塞模式、选择器机制和缓冲区,适合高并发场景。使用NIO涉及通道、选择器和事件处理,优点是高并发、资源利用率和可扩展性,但复杂度、错误处理和性能调优是挑战。开发者应根据需求选择是否使用NIO,并深入理解其原理。
|
8天前
|
网络协议 Java API
深度剖析:Java网络编程中的TCP/IP与HTTP协议实践
【4月更文挑战第17天】Java网络编程重在TCP/IP和HTTP协议的应用。TCP提供可靠数据传输,通过Socket和ServerSocket实现;HTTP用于Web服务,常借助HttpURLConnection或Apache HttpClient。两者结合,构成网络服务基础。Java有多种高级API和框架(如Netty、Spring Boot)简化开发,助力高效、高并发的网络通信。
|
9天前
|
Android开发 开发者
Android网络和数据交互: 请解释Android中的AsyncTask的作用。
Android&#39;s AsyncTask simplifies asynchronous tasks for brief background work, bridging UI and worker threads. It involves execute() for starting tasks, doInBackground() for background execution, publishProgress() for progress updates, and onPostExecute() for returning results to the main thread.
10 0
|
9天前
|
网络协议 安全 API
Android网络和数据交互: 什么是HTTP和HTTPS?在Android中如何进行网络请求?
HTTP和HTTPS是网络数据传输协议,HTTP基于TCP/IP,简单快速,HTTPS则是加密的HTTP,确保数据安全。在Android中,过去常用HttpURLConnection和HttpClient,但HttpClient自Android 6.0起被移除。现在推荐使用支持TLS、流式上传下载、超时配置等特性的HttpsURLConnection进行网络请求。
9 0
|
10天前
|
JavaScript Java 测试技术
基于Java的网络游戏交易系统的设计与实现(源码+lw+部署文档+讲解等)
基于Java的网络游戏交易系统的设计与实现(源码+lw+部署文档+讲解等)
23 0