TCP所提供服务的主要特点:
- 面向连接的传输;
- 端到端的通信;
- 高可靠性,确保传输数据的正确性,不出现丢失或乱序;
- 全双工方式传输;
- 采用字节流方式,即以字节为单位传输字节序列;
TCP传输需要建立客户端和服务器端,即Socket和Server Socket,建立连接后,通过Socket中的IO流进行数据的传输。传输结束后关闭Socket。
package com.demo.test;import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.Timer;
import java.util.TimerTask;class TcpDemo {private static String SERVER_ADDR = "127.0.0.1";private static int PORT = 7777;private static String EXIT = "exit";static class TcpServerThread extends Thread {static class TcpClient {private Socket socket;private BufferedReader br;private BufferedWriter bw;public TcpClient(Socket socket) throws IOException {this.socket = socket;br = new BufferedReader(new InputStreamReader(socket.getInputStream()));bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));}public String readLine() throws IOException {if (br != null) {return br.readLine();}return null;}public void write(String str) throws IOException {if (bw != null) {bw.write(str + "\n");// 注意:刷新是关键,否则会等到缓冲区满才将数据实际地发送出去bw.flush();}}public void close() {if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}if (bw != null) {try {bw.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}private ArrayList<TcpClient> clientList = new ArrayList<TcpClient>();private ServerSocket serverSocket;@Overridepublic void run() {try {// backlog=-1表示无限制,默认是50个最大等待队列,如果设置无限制,由于每一个TCP连接都会占用服务器的内存,当服务器无法处理那么多连接时,就会让服务器崩溃。int backlog = -1;serverSocket = new ServerSocket(PORT, backlog);Socket socket = null;while ((socket = serverSocket.accept()) != null) {System.out.println("server accept");final TcpClient tcpClient = new TcpClient(socket);clientList.add(tcpClient);new Thread(new Runnable() {@Overridepublic void run() {try {String line;while ((line = tcpClient.readLine()) != null) {System.out.println("serverSocket received: " + line);tcpClient.write(line);if (EXIT.equals(line)) {break;}}} catch (IOException e) {e.printStackTrace();} finally {close();}}}).start();}} catch (IOException e) {e.printStackTrace();}}public void close() {for (TcpClient client : clientList) {client.close();}try {if (serverSocket != null) {serverSocket.close();}} catch (IOException e) {e.printStackTrace();}}}static class TcpClientThread extends Thread {private Socket socket;private BufferedReader br;private BufferedWriter bw;private Scanner scanner;@Overridepublic void run() {try {socket = new Socket(SERVER_ADDR, PORT);br = new BufferedReader(new InputStreamReader(socket.getInputStream()));bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));scanner = new Scanner(System.in);new Thread(new Runnable() {@Overridepublic void run() {try {String line;while ((line = br.readLine()) != null) {System.out.println("client received: " + line);if (EXIT.equals(line)) {break;}}} catch (IOException e) {e.printStackTrace();} finally {closeSocket();}}}).start();Timer timer = new Timer();timer.schedule(new TimerTask() {@Overridepublic void run() {try {bw.write("hello world\n");// 注意:刷新是关键,否则会等到缓冲区满才将数据实际地发送出去bw.flush();System.out.println("client write");} catch (IOException e) {e.printStackTrace();// 发生错误立刻取消定时器this.cancel();}}}, 0, 5000);String line;while (scanner.hasNext()) {line = scanner.nextLine();bw.write(line + "\n");// 注意:刷新是关键,否则会等到缓冲区满才将数据实际地发送出去bw.flush();if (EXIT.equals(line)) {break;}}} catch (UnknownHostException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} finally {closeSocket();}}private void closeSocket() {if (scanner != null) {scanner.close();}if (br != null) {try {br.close();} catch (IOException e) {e.printStackTrace();}}if (bw != null) {try {bw.close();} catch (IOException e) {e.printStackTrace();}}if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}}public static void main(String[] args) {new TcpServerThread().start();new TcpClientThread().start();}
}
这里需要格外注意一点:如果没有调用flush(),数据只会被写入输出缓冲区,并不会从输出缓冲区发送到输出流中,因此另一方也就不可能接收到数据