一、基于socket的用法
服务器端:
先启动一个服务器端的socket
开始侦听请求 Socket s = svr.accept();
取得输入和输出 DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());//向客户端发送数据
Socket 的交互通过流来完成,即是说传送的字节流,因此任何文件都可以在上面传送。谁打开的记得要关上。
用DataInputStream/DataOutputStream来进行包装是因为我们想要他们对基本数据类型的读写功能readInt(),writeInt(),readUTF(),writeUTF()等等。
客户端:
发起一个socket连接 Socket s = new Socket("192.168.1.200",8989);
取得输入和输出 DataInputStream dis = new DataInputStream(s.getInputStream());
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
之后就可以相互通信了。谁打开的记得要关上。
TCP/IP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket,从而在两端之间形成网络虚拟链路。
在两个通信实体没有建立虚拟链路之前,必须有一个实体先“主动”,主动接收来自其它实体的通信请求。而这个能接受其它实体连接请求的类是ServerSocket,用于监听来自客户端的Socket连接,如果没有连接则一直处于等待状态。accept():如果接收到一个客户端Socket的连接请求,则返回一个与连接客户端Socket对应的Socket;否则一直处于等待状态,线程也被阻塞。
setSoTimeout()设置读写操作完成的超时时长,即在该时间限制内没有完成读写操作则会抛出SocketTimeoutException。
而若要为Socket连接服务器的指定超时时长,由于Socket的构造器里没有该参数,所以需要先建立一个无连接的Socket,再调用Socket的connect()方法来连接远程服务器。而connect()方法可以接受一个超时时长参数:Socket s=new Socket(); s.connect(new InetSocketAddress(host,port),10000);
阻塞:用传统的BufferedReader的readLine()读取数据时,在该方法成功返回前,线程一直被阻塞,所以服务器应为每个Socket单独启动一条线程,没条线程负责与一个客户端进行通信。
客户端读取服务器数据的线程同样也会被阻塞,所以也应该单独启动一条线程,专门负责读取服务器的数据。
一个C/S聊天室,就是Socket多线程的应用。服务器端包含多条线程,每个Socket对应一条线程,该线程负责读取Socket对应输入流的数据(客户端发来的数据),并将读到的数据向每个Socket输出流发送一遍。
服务器端
MyServer:
public class MyServer {
public static ArrayList<Socket> list=new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocket ss=new ServerSocket(30000);
while (true){
Socket s=ss.accept();//此处会一直阻塞
list.add(s);
//每当客户端连接成功后启动一条ServerThread线程为客户端服务
new Thread(new ServerThread(s)).start();
}
}
}
ServerThread:
public class ServerThread implements Runnable {
Socket s=null;
BufferedReader br=null;
public ServerThread(Socket s) throws IOException {
this.s=s;
br=new BufferedReader(new InputStreamReader(s.getInputStream(),"utf-8"));
}
@Override
public void run() {
String content=null;
try {
while((content=ReadFromClient())!=null){
//将读到的内容向每个Socket发送一次
for (Iterator<Socket> it=MyServer.list.iterator();it.hasNext();){
Socket s=it.next();
OutputStream out=s.getOutputStream();
out.write((content+"\n").getBytes());
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private String ReadFromClient() {
try {
return br.readLine();
} catch (IOException e) { //如果捕获到异常,说明该客户端已关闭
e.printStackTrace();
MyServer.list.remove(s);
}
return null;
}
}
客户端:
Activity:
public class CSActivity extends AppCompatActivity {
EditText input;
TextView show;
Button send;
Handler handler;
//与服务器通讯的子线程
ClientThread clientThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_cs);
show= (TextView) findViewById(R.id.tv_content);
input= (EditText) findViewById(R.id.et_content);
send= (Button) findViewById(R.id.btn_send);
handler=new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what==0x123){ //消息来自子线程
show.append("\n"+msg.obj.toString());
}
}
};
clientThread=new ClientThread(handler);
new Thread(clientThread).start();//客户端启动新线程,读取来自服务器的数据
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//为了避免UI线程被阻塞,所以该程序将建立网络连接、与服务器通讯等工作都交给ClientThread线程完成
//所以要将用户的输入数据封装成Message,然后发送给子线程的Handler
Message msg=new Message();
msg.what=0x345;
msg.obj=input.getText().toString();
clientThread.revHandler.sendMessage(msg);
input.setText("");
}
});
}
public class ClientThread implements Runnable {
public Handler revHandler;
private Handler handler;
private Socket s;
BufferedReader br=null;
OutputStream os=null;
public ClientThread(Handler handler) {
this.handler=handler;
}
@Override
public void run() {
try {
s=new Socket("192.168.1.88",30000);
br=new BufferedReader(new InputStreamReader(s.getInputStream()));
os=s.getOutputStream();
//启动一条子线程读取服务器响应的数据
new Thread(){
@Override
public void run() {
String content=null;
try {
while ((content=br.readLine())!=null){
//从服务器读到的数据之后,发送消息通知UI更改界面
Message msg=new Message();
msg.what=0x123;
msg.obj=content;
handler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
//为当前线程初始化Looper
Looper.prepare();
revHandler=new Handler(){
@Override
public void handleMessage(Message msg) {
if (msg.what==0x345){
try {
os.write((msg.obj.toString()+"\r\n").getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
//启动Looper
Looper.loop();
} catch (IOException e) {
e.printStackTrace();
}
}
}
两条线程:
handler读取Socket对应输入流中的数据,并显示在界面上;而
revHandler负责响应用户动作,将用户输入的数据写入Socket对应的输出流中。
为避免UI线程被阻塞,由ClientThread完成建立网络连接、与服务器通信的工作。
二、基于Http协议
Android中提供的HttpURLConnection和HttpClient接口可以用来开发HTTP程序。
1. HttpURLConnection接口
//以Get方式上传参数
public class Activity03 extends Activity
{
private final String DEBUG_TAG = "Activity03";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.http);
TextView mTextView = (TextView)this.findViewById(R.id.TextView_HTTP);
//http地址"?par=abcdefg"是我们上传的参数
String httpUrl = "http://192.168.1.110:8080/httpget.jsp?par=abcdefg";
//获得的数据
String resultData = "";
URL url = null;
try
{
//构造一个URL对象
url = new URL(httpUrl);
}
catch (MalformedURLException e)
{
Log.e(DEBUG_TAG, "MalformedURLException");
}
if (url != null)
{
try
{
// 使用HttpURLConnection打开连接
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
//得到读取的内容(流)
InputStreamReader in = new InputStreamReader(urlConn.getInputStream());
// 为输出创建BufferedReader
BufferedReader buffer = new BufferedReader(in);
String inputLine = null;
//使用循环来读取获得的数据
while (((inputLine = buffer.readLine()) != null))
{
//我们在每一行后面加上一个"\n"来换行
resultData += inputLine + "\n";
}
//关闭InputStreamReader
in.close();
//关闭http连接
urlConn.disconnect();
//设置显示取得的内容
if ( resultData != null )
{
mTextView.setText(resultData);
}
else
{
mTextView.setText("读取的内容为NULL");
}
}
catch (IOException e)
{
Log.e(DEBUG_TAG, "IOException");
}
}
else
{
Log.e(DEBUG_TAG, "Url NULL");
}
}
如果需要使用POST方式,则需要setRequestMethod设置为POST
//以post方式上传参数
public class Activity04 extends Activity
{
private final String DEBUG_TAG = "Activity04";
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.http);
TextView mTextView = (TextView)this.findViewById(R.id.TextView_HTTP);
//http地址"?par=abcdefg"是我们上传的参数
String httpUrl = "http://192.168.1.110:8080/httpget.jsp";
//获得的数据
String resultData = "";
URL url = null;
try
{
//构造一个URL对象
url = new URL(httpUrl);
}
catch (MalformedURLException e)
{
Log.e(DEBUG_TAG, "MalformedURLException");
}
if (url != null)
{
try
{
// 使用HttpURLConnection打开连接
HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
//因为这个是post请求,设立需要设置为true
urlConn.setDoOutput(true);
urlConn.setDoInput(true);
// 设置以POST方式
urlConn.setRequestMethod("POST");
// Post 请求不能使用缓存
urlConn.setUseCaches(false);
urlConn.setInstanceFollowRedirects(true);
// 配置本次连接的Content-type,配置为application/x-www-form-urlencoded的
urlConn.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
// 连接,从postUrl.openConnection()至此的配置必须要在connect之前完成,
// 要注意的是connection.getOutputStream会隐含的进行connect。
urlConn.connect();
//DataOutputStream流
DataOutputStream out = new DataOutputStream(urlConn.getOutputStream());
//要上传的参数
String content = "par=" + URLEncoder.encode("ABCDEFG", "gb2312");
//将要上传的内容写入流中
out.writeBytes(content);
//刷新、关闭
out.flush();
out.close();
//获取数据
BufferedReader reader = new BufferedReader(new InputStreamReader(urlConn.getInputStream()));
String inputLine = null;
//使用循环来读取获得的数据
while (((inputLine = reader.readLine()) != null))
{
//我们在每一行后面加上一个"\n"来换行
resultData += inputLine + "\n";
}
reader.close();
//关闭http连接
urlConn.disconnect();
//设置显示取得的内容
if ( resultData != null )
{
mTextView.setText(resultData);
}
else
{
mTextView.setText("读取的内容为NULL");
}
}
catch (IOException e)
{
Log.e(DEBUG_TAG, "IOException");
}
}
else
{
Log.e(DEBUG_TAG, "Url NULL");
}
}
}
2. HttpClient接口
public class Activity02 extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.http);
TextView mTextView = (TextView) this.findViewById(R.id.TextView_HTTP);
// http地址
String httpUrl = "http://192.168.1.110:8080/httpget.jsp?par=HttpClient_android_Get";
//HttpGet连接对象
HttpGet httpRequest = new HttpGet(httpUrl);
try
{
//取得HttpClient对象
HttpClient httpclient = new DefaultHttpClient();
//请求HttpClient,取得HttpResponse
HttpResponse httpResponse = httpclient.execute(httpRequest);
//请求成功
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
{
//取得返回的字符串
String strResult = EntityUtils.toString(httpResponse.getEntity());
mTextView.setText(strResult);
}
else
{
mTextView.setText("请求错误!");
}
}
catch (ClientProtocolException e)
{
mTextView.setText(e.getMessage().toString());
}
catch (IOException e)
{
mTextView.setText(e.getMessage().toString());
}
catch (Exception e)
{
mTextView.setText(e.getMessage().toString());
}
}
}
使用POST方法进行参数传递时,需要使用NameValuePair来保存要传递的参数,另外,还需要设置所使用的字符集。进行字符编码
public class Activity03 extends Activity
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.http);
TextView mTextView = (TextView) this.findViewById(R.id.TextView_HTTP);
// http地址
String httpUrl = "http://192.168.1.110:8080/httpget.jsp";
//HttpPost连接对象
HttpPost httpRequest = new HttpPost(httpUrl);
//使用NameValuePair来保存要传递的Post参数
List<NameValuePair> params = new ArrayList<NameValuePair>();
//添加要传递的参数
params.add(new BasicNameValuePair("par", "HttpClient_android_Post"));
try
{
//设置字符集
HttpEntity httpentity = new UrlEncodedFormEntity(params, "gb2312");
//请求httpRequest
httpRequest.setEntity(httpentity);
//取得默认的HttpClient
HttpClient httpclient = new DefaultHttpClient();
//取得HttpResponse
HttpResponse httpResponse = httpclient.execute(httpRequest);
//HttpStatus.SC_OK表示连接成功
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK)
{
//取得返回的字符串
String strResult = EntityUtils.toString(httpResponse.getEntity());
mTextView.setText(strResult);
}
else
{
mTextView.setText("请求错误!");
}
}
catch (ClientProtocolException e)
{
mTextView.setText(e.getMessage().toString());
}
catch (IOException e)
{
mTextView.setText(e.getMessage().toString());
}
catch (Exception e)
{
mTextView.setText(e.getMessage().toString());
}
}
}
HttpClient实际上是对Java提供方法的一些封装,在HttpURLConnection中的输入输出流操作,在这个接口中被统一封装成了HttpPost(HttpGet)和HttpResponse,这样,就减少了操作的繁琐性。
三、Socket和Http的区别