TCP协议提供可靠的数据传输服务是经过创建TCP链接实现的。一条“TCP链接”链接的两端是Internet上分别在两台主机运行的两个进程,一个是发送进程,一个是接收进程,每一个进程用一个Socket(IP地址和端口)惟一肯定。一对Socket惟一标识一条TCP链接。TCP链接是全双工和点对点的,全双工指数据可双向传输,点对点是指每条TCP链接只有两个端点。
使用一对socket进行tcp链接,链接成功后,就能够经过socket发送或接收字节流数据,完成点到点、全双工的通讯。segmentfault
服务端经过建立指定端口号的ServerSocket
对象,调用accept()方法等待客户端链接请求,等待期间当前进程处于堵塞状态。当服务端接收到客户端的链接请求时,进程继续运行,并创建一条TCP链接,accept()完成并返回一个Socket对象,经过该Socket对象与客户端的Socket对象实现实时的数据通讯。服务器
ServerSocket serverSocket = new ServerSocket(7000); Socket socket = serverSocket.accept();
客户端建立Socket对象,指定服务器主机的IP和端口,发出TCP链接请求,待服务器接受链接请求后Socket对象建立成功,此时能够经过此Socket对象与服务端Socket对象实时通讯。socket
Socket socket = new Socket("127.0.0.1", 7000);
从控制台获取输入的字符串,字符串数据将从PrintWriter"流入"OutputStreamWriter在"流入"socket的OutputStream中,经过TCP创建的链路,流到与之对应的Socket的InputStream中。注意,Socket老是成对的。tcp
PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); message = scanner.nextLine(); writer.println(message); writer.flush();
在另外一端的Socket中,即可以经过InputStream把发送的数据读取出来,此时数据的流向为InputStream->InputStreamReader->BufferedReader->控制台。spa
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = ""; while ((line = bufferedReader.readLine()) != null) { System.out.println(line); }
整个的数据流向为:客户端控制台->PrintWriter->OutputStreamWriter->客户端Socket的OutputStream->TCP链路->服务端Socekt的InputStream->InputStreamReader->BufferedReader->服务端控制台。经过socket即可以轻易的将数据从客户端发送到服务端了。线程
了解了Socekt通讯后,咱们能够使用Socket仿照QQ群聊效果:当某个用户发送消息时,全部用户都接收到此消息并显示到控制台上。3d
客户端须要两个进程,一个进程不停的从控制台中读取数据,读取到用户输入时即向服务端发送读取到的数据,把它称为写进程。另外一个进程接收服务端发送的数据,并把它显示到控制台上,称为读进程。code
public static void main(String[] args) throws IOException { Scanner scanner = new Scanner(System.in); Socket socket = new Socket("127.0.0.1", 7000); // 获取输入流和输出流 PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream())); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 开启两个线程分别读写 // 写线程 new Thread(() -> { // 循环从控制台读取数据 String message = ""; while (!message.equals("exit")) { message = scanner.nextLine(); writer.println(message); writer.flush(); } writer.close(); }).start(); // 读线程 new Thread(() -> { // 不停从input流获取数据 String line = ""; try { while ((line = bufferedReader.readLine()) != null) { System.out.format("%50s", line); System.out.println(); } bufferedReader.close(); } catch (IOException e) { } }).start(); }
为了响应多个客户端链接,须要在循环中不停的调用accept()方法,每当获取到一个新TCP链接时,把获取到的Socket对象存入set中,对每一个Socket对象都开启一个线程,主要的任务是不停的从InputStream中读取数据,即接收发送客户端数据。获取到数据后,再发送给全部已链接的客户端Socket(除了发送数据的客户端Socket),即遍历set发送数据。orm
public class Server { public static int userCount = 0; public static void main(String[] args) throws IOException { ServerSocket serverSocket = new ServerSocket(7000); Set<Socket> socketSet = new HashSet<>(); while (true) { Socket socket = serverSocket.accept(); socketSet.add(socket); userCount++; // 开启一个新线程 Thread thread = new Thread(() -> { // 不停从input流获取数据 BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String userName = Thread.currentThread().getName(); String line = ""; System.out.println("start Read"); while ((line = bufferedReader.readLine()) != null) { System.out.println(userName + "消息:" + line); for (Socket tem : socketSet) { if (!tem.equals(socket)) { PrintWriter writer = new PrintWriter(new OutputStreamWriter(tem.getOutputStream())); writer.println(line + ":" + userName); writer.flush(); } } } bufferedReader.close(); } catch (IOException e) { } System.out.println("finish Read"); }); thread.setName("用户" + userCount); thread.start(); } } }
在终端运行的效果图:server