1、TCP/IP协议是一种可靠的网络协议,在通信的两端各建立以个Socket,从而在两端之间形成网络虚拟链路。
IP协议只负责在两个客户端之间传输数据,但不能处理数据分组在传输过程中可能出现的问题,所以连上Internet的计算机还是要安装TCP协议来提供可靠的无差错通信服务。
TCP协议重发机制:当一个通信实体给另一个通信实体发送消息后,需要收到另一个通信实体的确认信息,如果没有收到另一个通信实体的确认消息,则会重发刚才发送的消息。
2、使用ServerSocket创建TCP服务器端
两个通信实体没有建立虚拟链路之前,必须要有一个通信实体先做出“主动姿态”,主动接受来自其他通信实体的连接请求。
Java中接受其他通信实体连接请求的类是ServerSocket对象,可以监听来自客户端的Socket连接,如果没有连接,则一直处于等待状态。
ServerSocket包含一个监听来自客户端请求方法:Socket Accpet()
,这个方法返回一个与连接客户端Socket对应的Socket。
ServerSocket的构造方法:
- ServerSocket(int port),用指定端口创建。
- ServerSocket(int port,int backlog),增加一个用来改变连接队列长度的参数backlog
- ServerSocket(int port,int backlog,InetAddress localAddr),在机器存在多个IP地址情况下,使用localAddr这个参数将这个Socket绑定到指定的IP地址。
3、使用Socket进行通信
客户端使用Socket的构造器来连接指定服务器。
Socket构造方法:
- Socket(InetAddress/String remoteAddress,int port),创建连接指定远程主机,远程端口的Socket,默认使用本地主机默认的IP地址,系统动态分配的端口。
- Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort),指定本地IP地址和本地端口。
创建了Socket后 ,就会连接到指定的服务器让服务器端的ServerSocket的accpt()方法向下执行。于是服务器端和客户端就产生了一对互相通信的Socket。
之后服务器端使用输出流输出数据,客户端使用输入流接受客户端输入的流就可以进行数据通信了。
假如程序需要为Socket连接服务器时指定超时时长,程序需要先创建一个无连接的Socket,再调用Socket的connect()方法来连接远程服务器。代码如下:
Socket socket = new socket();
socket.connet(new InetSocketAddress(host,port),10000);
//10000就是设定的超时时间
4、加入多线程
实际应用中的客户端可能需要和服务器端保持长时间的通信,即客户端需要不断的读取客户端数据,并向客户端写入数据,同时客户端也需要持续的做这些动作。而这个过程线程会被阻塞,程序无法继续运行。
所以服务器应该为每个Socket单独启动一条线程,每条线程负责与一个客户端进行通信。
同样的,客户端读取服务器数据的线程同样会被阻塞,所以系统应该单独启动一条线程,这个线程专门负责读取服务器的数据。
服务器端程序如下:
public class Socket_server {
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket socket = new Socket();
socket = serverSocket.accept();
socketList.add(socket);
new Thread(new ServerThread(socket)).start();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
服务器端为每个线程启动吧一个线程,线程类的代码如下:
public class ServerThread implements Runnable {
Socket s = null;
BufferedReader br = null;
public ServerThread(Socket s) {
s = this.s;
try {
br = new BufferedReader(new InputStreamReader(s.getInputStream(), "utf-8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
String content = null;
while ((content = readFromClient()) != null) {
for(int i =0;i<Socket_server.socketList.size();i++){
s = Socket_server.socketList.get(i);
try {
OutputStream outputStream = s.getOutputStream();
outputStream.write((br.readLine()+"\n").getBytes("utf-8"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
private String readFromClient() {
// TODO Auto-generated method stub
try {
String line = br.readLine();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
Socket_server.socketList.remove(s);
}
return null;
}
}
客户端的程序包含两条线程,一条杜泽生成界面,响应用户动作,并将用户输入的数据写入socket对应的输出流中,另一条负责读取从socket输入流中的数据,并将这些数据在界面上显示出来。客户端程序如下:
public class MainActivity extends ActionBarActivity {
private Button send;
private EditText input;
private TextView show;
private Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
send = (Button)findViewById(R.id.send);
input = (EditText)findViewById(R.id.input);
show = (TextView)findViewById(R.id.show);
handler = new Handler(){
public void handleMessage(Message msg) {
if(msg.what == 1){
show.append("\n"+msg.obj.toString()); }
};
};
ClientThread clientThread = new ClientThread(handler);
new Thread(clientThread).start();
send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
String content = input.getText().toString();
Message message = new Message();
message.what = 0;
message.obj = content;
clientThread.revHandler.sendMessage(message);
}
});
}
}
为了避免UI进程被阻塞,所以与网络有关的操作都被封装到clientThread中,通过clientThread中的revhandler来传递消息,同时clientThread中的UI操作都通过UI activity中的handler来处理。
clientThread的代码如下:
public ClientThread(Handler handler) {
// TODO Auto-generated constructor stub
handler = this.handler ;
}
@Override
public void run() {
// TODO Auto-generated method stub
try {
socket = new Socket("10.0.2.2", 8080);
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = socket.getOutputStream();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
new Thread(){
String content = null;
public void run() {
try {
while ((content = br.readLine().toString())!=null){
Message message = new Message();
message.obj = content;
message.what = 1;
handler.sendMessage(message);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
};
}.start();
Looper.prepare();
revHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
while (msg.what == 0) {
try {
out.write(msg.obj.toString().getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
};
Looper.loop();
}
}