网络编程,又称为Socket编程,简单来讲就是通讯的两个端点都是Socket服务,而Socket之间的数据传输本质上都是IO流。而由于网际层中不同的传输协议,主要指TCP与UDP协议,导致不同的Socket编程的方式,Java为它们提供的API实现是不同的类,但是它们都属于Socket编程的一种,所以都遵循Socket编程的基本特点。
虽然关于TCP协议的基本特性与UDP协议的差异,想必都比较清楚,但是这里还是要简单回顾一下,因为有助于理解Java中关于TCP协议编程的一些知识点。
主要的一点:TCP协议在数据发送前,需要通过三次握手建立连接,形成稳定的数据通道,所以TCP数据的发送与UDP不同,UDP是通过Socket服务对象来发送包数据,从而实现数据的传递,而TCP由于事先建立了连接,形成数据通道,这数据通道本质上就是IO流,而且是输入和输出的双向流。
1、Java API为TCP协议提供的类分为Socket和ServerSocket,可以简单的理解为客户端Socket和服务端Socket,更具体的讲Socket对象是主动发起连接请求的,而ServerSocket对象是等待接收连接请求的。
2、那么客户端什么时候发起连接请求呢?
Java API中提供了两种方式,第一种是最简单和最常用的,就是通过Socket(InetAddress address,int port)或Socket(String host,int port)构造函数创建Socket对象时,Socket服务将向指定的IP地址和端口号发送连接请求;第二种,是通过无参构造函数创建Socket对象,这是面向无连接的,也就是不会发起连接请求,然后再调用voidconnect(SocketAddress endpoint)或者void connect(SocketAddress endpoint,int timeout)方法返回后,将发送连接请求。那么SocketAddress类是什么类呢?通过API文档会发现,它有一个子类为InetAddressSocket,该类其实提供了InetAddress(InetAddress addr,int port)和InetAddress(String host,int port)两个构造函数,可以完成对IP地址和端口的封装,而第二个void connect(SocketAddress endpoint,int timeout)方法,第二个参数是用来指定连接超时的时间,也就是说,当向服务端发出连接请求,超出指定时间服务端无响应,则抛出SocketTimeoutException异常,可以捕获这个异常,做针对处理。
3、服务端怎么接收连接请求?
首先需要通过ServerSocket(int port)构造函数创建服务端Socket对象,参数接收一个端口号,表示该服务绑定该端口号;但此时该Socket服务并不会接收客户端的请求,而是要通过调用Socket accept()方法,该方法执行后,将侦听端口的连接请求,如果侦听不到连接请求,该方法是阻塞式方法,程序挂起;如果侦听到,它会创建一个Socket对象,并指定该Socket对象与客户端Socket通迅,并且accept()方法会返回这个Socket对象。
4、怎么实现数据的传输?
由于客户端和服务端建立连接以后,形式了IO流通道,而Socket类提供了OutputStream getOutputStream()和InputStream getInputStream()方法可以返回输入和输出流,拿到了输入和输出流,就可以充分的使用各种IO流对象实现数据的输入和输出了。
5、Socket和ServerSocket提供了close()方法关闭资源。
6、其他一些常用方法。
InetAddress getInetAddress(),返回Socket连接的地址。
void shutdownOutput(),给Socket输出流添加结束标志,可以让读取该流的read()方法结束。
setSoTimeout(int timeout),设置当通过read()方法读取该Socket流时,如果读取的时间超过指定时间,则报SocketTimeoutException异常。
1、最简单的代码。
客户端:
package com.example.network;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) throws IOException {
//1、创建Socket服务,指定连接的地址和端口号,创建成功将发出连接请求
Socket s = new Socket("127.0.0.1",4000);
//2、连接成功后,将形成数据通道,即输入和输出IO流,可以通过getInpuStream()和getOutputStream()方法获取IO流对象。
OutputStream out = s.getOutputStream();
//3、获取IO对象以后,就可以通过操作IO流来对数据进行读写。
out.write("Hello,TCPServer!".getBytes());
//4.关闭流
s.close();
}
}
服务端:
package com.example.network;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) throws IOException {
//1、创建服务端Socket对象,绑定端口号。
ServerSocket ss = new ServerSocket(4000);
//2、调用accept()方法来侦听端口号是否有连接请求,有,则返回一个socket对象,该socket将会与客户端的socket形成数据通道;如果没有,则该方法一直处于阻塞状态。
Socket s = ss.accept();
//3、有了与客户端连接的socket,同样我们可以返回输入和输出流,而客户端socket服务的输出流对应是服务端socket对象的输入流。
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
System.out.println(new String(buf,0,len));
//4.关闭socket流
s.close();
ss.close();
}
}
2、手机客户端访问服务端
服务端:
package com.example.network;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class AndroidTCPServer {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(4001);
Socket s = ss.accept();
OutputStream out = s.getOutputStream();
out.write("你好,安卓客户端!".getBytes("utf-8"));
s.close();
ss.close();
}
}
客户端:
package com.noodles.networktest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
public class AndroidTCPClientActivity extends Activity {
public static final int UPDATE_TEXT = 1;
private TextView textView;
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
switch (msg.what){
case UPDATE_TEXT:
String text = (String) msg.obj;
textView.setText(text);
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tcp_client_activity);
textView = (TextView) findViewById(R.id.text_view);
connectToTCPServer();
}
private void connectToTCPServer() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Socket s = new Socket("192.168.1.101",4001);
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len = in.read(buf);
String text = new String(buf,0,len);
Message msg = new Message();
msg.what = UPDATE_TEXT;
msg.obj = text;
handler.sendMessage(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
结果:
注意点:
1、在服务端中,通过IO输出流写入中文时,没有使用包装流,并且通过String方法getBytes()指定了转换字符集utf-8,因为本人是Windows操作系统,当java程序中涉及到字符转换的时候,默认是使用的平台的字符集,而Windows默认字符集是GBK,但是Android系统是linux核心,使用的是utf-8,所以如果不指定相同的字符集,就会出现乱码。而现在字符集一般都是使用utf-8或者utf-16。
2、Android使用了多线程机制,因为Android不能在主线程进行网络访问。
3、因为需要对主线程的UI进行更新,所以使用Android平台提供的异步信息处理机制之一Handler。
3、多线程
先思考一个问题,服务端往往是需要处理多个客户端请求的,比如服务端提供了一个2G的游戏程序资源让去用户下载,在下载资源的过程中其实服务端在向输出流中写入数据,但是这个过程可能是漫长,可能需要半个小时,那么在下载过程中,别的用户是没办法下载的,因为服务端程序在执行IO流的操作,没办法执行accept()方法,就不能接收其它客户端的请求,所以我们需要把耗时IO流操作放入子线程中,这样主线程用来接收请求,而一旦有客户端请求,就开辟一个线程去做耗时IO流处理。
简答的手机聊天程序
服务端:
package com.example.network;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
public class ChatServer {
static List list = new ArrayList();
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(4002);
while(true){
Socket s = ss.accept();
String ip = s.getInetAddress().getHostAddress();
System.out.println(ip+" is connected......");
list.add(s);
new Thread(new Chat(s)).start();
}
}
}
服务端流操作:
package com.example.network;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Iterator;
public class Chat implements Runnable{
private Socket s;
Chat(Socket s){
this.s = s;
}
@Override
public void run() {
try {
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int len;
while((len = in.read(buf))!= -1){
String str = new String(buf,0,len,"utf-8");
for(Iterator iterator =ChatServer.list.iterator();iterator.hasNext();){
try{
Socket s = iterator.next();
String ip = s.getInetAddress().getHostAddress();
System.out.println("size:"+ChatServer.list.size() +"," +ip+":"+str);
OutputStream out = s.getOutputStream();
out.write((ip+":"+str).getBytes("utf-8"));
}catch(IOException e){
iterator.remove();
System.out.println(ChatServer.list);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
手机端:
package com.noodles.networktest;
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.IOException;
public class ChatActivity extends Activity {
static final int SEND_TEXT = 1;
static final int UPDATE_TEXT = 2;
private TextView textView;
private EditText inputText;
private Button send;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case UPDATE_TEXT:
textView.append(System.getProperty("line.separator")+(String)(msg.obj));
break;
default:
break;
}
}
};
private ChatClient chatClient;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.chat_activity);
textView = (TextView) findViewById(R.id.text_view);
inputText = (EditText) findViewById(R.id.input_text);
send = (Button) findViewById(R.id.send);
chatClient = new ChatClient(handler);
new Thread(chatClient).start();
send.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String text = inputText.getText().toString();
if (!TextUtils.isEmpty(text)) {
Message message = new Message();
message.what = SEND_TEXT;
message.obj = text;
chatClient.mHandler.sendMessage(message);
inputText.setText("");
}
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
try {
chatClient.s.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
package com.noodles.networktest;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
class ChatClient implements Runnable {
Handler mHandler;
private Handler uiHandler;
private InputStream in;
private OutputStream out;
Socket s;
ChatClient(Handler uiHandler) {
this.uiHandler = uiHandler;
}
@Override
public void run() {
try {
s = new Socket();
s.connect(new InetSocketAddress("192.168.1.101", 4002), 5000);
in = s.getInputStream();
out = s.getOutputStream();
new Thread() {
@Override
public void run() {
byte[] buf = new byte[1024];
int len;
try {
while ((len = in.read(buf)) != -1) {
String text = new String(buf, 0, len, "utf-8");
Log.d("ChatActivity", text);
Message msg = new Message();
msg.what = ChatActivity.UPDATE_TEXT;
msg.obj = text;
uiHandler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
Looper.prepare();
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case ChatActivity.SEND_TEXT:
String text = (String)(msg.obj);
try {
out.write(text.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
};
Looper.loop();
} catch (IOException e) {
e.printStackTrace();
}
}
}
结果: