1.简述:
Java Socket编程时对于TCP/IP 协议层的通信进行封装,简化了相关的一些操作。//待续
2.Socket 通信时序图
3.Socket 数据流的交互
4.单客户端和服务器通信的简单例子:
服务器端代码:
- package com.lou.socket;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.InetSocketAddress;
- import java.net.ServerSocket;
- import java.net.Socket;
- public class TestServer {
- public static void main(String[] args) throws IOException {
- //1.创建一个Server Socket
- ServerSocket server = new ServerSocket();
- // 2.绑定监听指定的端口
- InetSocketAddress address = new InetSocketAddress("localhost",18824);
- server.bind(address);
- // 3.接受此端口的通信请求
- Socket socket = server.accept();
- // 在没有客户端对其进行相应前,下面的代码不会执行,将一直阻塞
- //服务器端的输出流和输入流获取
- BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- PrintWriter writer = new PrintWriter(socket.getOutputStream(),true);
- //来自键盘的输入数据
- BufferedReader keyword=new BufferedReader(new InputStreamReader(System.in));
- while(true)
- {
- if(reader.ready())
- {
- // 捕捉来自客户端发来的消息 客户端没有发消息过来时,reader.ready() 为false, 循环检测是否有数据,有测打印出来
- String info = reader.readLine();
- System.out.println("Client:"+info);
- }
- if(keyword.ready())
- {
- //捕获来自服务器端另外一个输入流 : 键盘的数据。如果有数据,则发送给客户端
- String test = keyword.readLine();
- writer.println(test);
- System.out.println("Server:"+test);
- }
- }
- }
- }
客户端代码:
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.InetSocketAddress;
- import java.net.Socket;
- import java.net.UnknownHostException;
- public class ClientSocket {
- public static void main(String[] args) throws UnknownHostException, IOException {
- //1.创建一个Server Socket
- Socket socket = new Socket();
- // 2.连接到指定的 server socket,指定IP 和端口号
- InetSocketAddress address = new InetSocketAddress("localhost",18824);
- socket.connect(address);
- // 3.连接成功后,获取相应的输入输出流,进行数据交互
- BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- PrintWriter pw = new PrintWriter(socket.getOutputStream(),true);
- BufferedReader keyword=new BufferedReader(new InputStreamReader(System.in));
- while(true)
- {
- // 捕捉来自服务器端发来的消息 服务器端没有发消息过来时,br.ready() 为false, 循环检测是否有数据,有测打印出来
- if(br.ready())
- {
- String info = br.readLine();
- System.out.println("Server:"+info);
- }
- //捕获来自服务器端另外一个输入流 : 键盘的数据。如果有数据,则发送给服务器端
- if(keyword.ready())
- {
- String test = keyword.readLine();
- pw.println(test);
- System.out.println("Client:"+test);
- }
- }
- }
- }
运行结果:
5.Socket 多客户端单服务器之间的通信:
上面的这个例子只能支持一个服务器端和一个客户端的通信,因为 客户端内 server.accept() 方法只执行了一次,只能返回一个Socket 连接。可以在服务端接受多个Socket,这时候的Socket应当放在一个线程里,让它有生命周期,来使用客户端和服务端的自由通信。
- package com.lou.socket;
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.net.ServerSocket;
- import java.net.Socket;
- public class TestServer {
- public static void main(String[] args) throws IOException {
- //1.创建一个Server Socket
- ServerSocket server = new ServerSocket();
- // 2.绑定监听指定的端口
- InetSocketAddress address = new InetSocketAddress("localhost",18824);
- server.bind(address);
- // 3.接受此端口的通信请求
- while(true)
- {
- //循环调用accept方法,返回相应的Socket
- Socket socket = server.accept();
- //使用线程,将每一个Socket都封装到线程内,这个每个接受的Socket可以自由的跟服务器通信了
- new Thread(new SocketHandler(socket)).start();
- }
- }
- }
- package com.lou.socket;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.Socket;
- public class SocketHandler implements Runnable {
- private Socket socket;
- public SocketHandler(Socket socket)
- {
- this.socket =socket;
- }
- @Override
- public void run() {
- try {
- BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- PrintWriter writer = new PrintWriter(socket.getOutputStream(),true);
- //来自键盘的输入数据
- BufferedReader keyword=new BufferedReader(new InputStreamReader(System.in));
- boolean flag = true;
- while(flag)
- {
- if(reader.ready())
- {
- // 捕捉来自客户端发来的消息 客户端没有发消息过来时,reader.ready() 为false, 循环检测是否有数据,有测打印出来
- String info = reader.readLine();
- System.out.println("Client "+socket.getPort() +":"+info);
- //如果对方输入BYE,关闭回话
- if("BYE" == info)
- {
- socket.close();
- flag = false;
- }
- }
- if(keyword.ready())
- {
- //捕获来自服务器端另外一个输入流 : 键盘的数据。如果有数据,则发送给客户端
- String test = keyword.readLine();
- writer.println(test);
- System.out.println("Server:"+test);
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.InetSocketAddress;
- import java.net.Socket;
- import java.net.UnknownHostException;
- public class ClientSocket {
- public static void main(String[] args) throws UnknownHostException, IOException {
- //1.创建一个Server Socket
- Socket socket = new Socket();
- // 2.连接到指定的 server socket,指定IP 和端口号
- InetSocketAddress address = new InetSocketAddress("localhost",18824);
- socket.connect(address);
- // 3.连接成功后,获取相应的输入输出流,进行数据交互
- BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- PrintWriter pw = new PrintWriter(socket.getOutputStream(),true);
- BufferedReader keyword=new BufferedReader(new InputStreamReader(System.in));
- while(true)
- {
- // 捕捉来自服务器端发来的消息 服务器端没有发消息过来时,br.ready() 为false, 循环检测是否有数据,有测打印出来
- if(br.ready())
- {
- String info = br.readLine();
- System.out.println("Server:"+info);
- }
- //捕获来自服务器端另外一个输入流 : 键盘的数据。如果有数据,则发送给服务器端
- if(keyword.ready())
- {
- String test = keyword.readLine();
- if("BYE" == test)
- {
- br.close();
- pw.close();
- socket.close();
- }
- pw.println(test);
- System.out.println("Client:"+test);
- }
- }
- }
- }
6. 简易聊天工具的实现:
实现的主要思路:
a.在服务器端设置一个主线程,监听特定的一个接口,为每一个socket请求创建一个对话框和相应的处理。
主线程MainThread代码:
- package com.lou.socket;
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.ArrayList;
- import java.util.List;
- /*
- * server的主线程,监听socket端口,为每一个Socket 创建一个对话框
- */
- public class MainThread extends Thread{
- public List<Socket> sockets = new ArrayList<Socket>();
- @Override
- public void run() {
- //1.创建一个Server Socket
- ServerSocket server =null;
- try {
- server = new ServerSocket();
- InetSocketAddress address = new InetSocketAddress("localhost",18824);
- server.bind(address);
- while(true)
- {
- //循环调用accept方法,返回相应的Socket
- Socket socket = server.accept();
- sockets.add(socket);
- // 为每一个请求的Socket提供 界面 "会话"
- ServerGUI serverGUI = new ServerGUI(socket);
- //创建监听socket 数据流输入信息,有数据输入,则更新到GUI
- new Thread(new SocketInfoUpdater(serverGUI)).start();
- }
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- public static void main(String[] args) {
- new MainThread().start();
- }
- }
b.定义一个监听线程,用来处理 socket发送过来的数据信息,及时更新到GUI上,和 server GUI 上的发送按钮的事件相应,来发送数据。
事件和输入流监听线程SocketInfoUpdater.java:
- package com.lou.socket;
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.Socket;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- /*
- * 此线程监听serverGUI所拥有的socket是否有数据输入,如果有更新,则更新到GUI上
- */
- public class SocketInfoUpdater implements Runnable,ActionListener{
- ServerGUI server;
- SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- public SocketInfoUpdater(ServerGUI server)
- {
- this.server = server;
- }
- @Override
- public void run() {
- Socket socket = server.socket;
- while(true)
- {
- try {
- BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- if(reader.ready())
- {
- String info = reader.readLine();
- server.outputArea.append("Client-" +socket.getPort()+" at " + f.format(new Date()) + "\n");
- server.outputArea.append(" "+info +"\n");
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- //设置发送按钮监听,事件相应
- @Override
- public void actionPerformed(ActionEvent e) {
- String temp = server.inputArea.getText();
- SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- server.outputArea.append("Client "+server.socket.getLocalPort() +" at " + f.format(new Date()) + "\n");
- server.outputArea.append(" " + temp + "\n");
- this.sendInfo(temp);
- server.inputArea.setText("");
- }
- private void sendInfo(String s) {
- try {
- PrintWriter pw = new PrintWriter(server.socket.getOutputStream(),
- true);
- pw.println(s);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
c. 服务器端界面的设计:
服务器端界面设计 ServerGUI.java:
- package com.lou.socket;
- import java.awt.BorderLayout;
- import java.awt.Container;
- import java.awt.GridLayout;
- import java.io.IOException;
- import java.net.Socket;
- import javax.swing.JButton;
- import javax.swing.JFrame;
- import javax.swing.JPanel;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- public class ServerGUI extends JFrame {
- public final Socket socket;
- //交互对话框中接收数据显示区
- final JTextArea outputArea = new JTextArea(70, 70);
- //输入区域
- final JTextArea inputArea = new JTextArea(70, 70);
- final JScrollPane outputScroll = new JScrollPane(outputArea);
- final JScrollPane inputScroll = new JScrollPane(inputArea);
- public ServerGUI(Socket socket1) throws IOException {
- //传入特定的socket
- this.socket = socket1;
- this.setTitle("Server");
- Container container = getContentPane();
- JPanel pane = new JPanel();
- container.setLayout(new BorderLayout());
- pane.setLayout(new GridLayout(2, 1, 5, 5));
- inputScroll.setAutoscrolls(true);
- outputScroll.setAutoscrolls(true);
- pane.add(outputScroll);
- pane.add(inputScroll);
- setSize(300, 300);
- container.add(pane, BorderLayout.CENTER);
- JButton send = new JButton("Send");
- //为发送按钮设置发送事件
- SocketInfoUpdater updater = new SocketInfoUpdater(this);
- send.addActionListener(updater);
- container.add(send, BorderLayout.SOUTH);
- setDefaultCloseOperation(3);
- setVisible(true);
- }
- }
客户端的设计:
客户端的实现比较简单,创建一个界面,然后配一个监听输入流和处理事件的监听线程就可以了。
a.客户端GUI,ClientGUI代码:
- import java.awt.BorderLayout;
- import java.awt.Container;
- import java.awt.GridLayout;
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.net.Socket;
- import javax.swing.JButton;
- import javax.swing.JFrame;
- import javax.swing.JPanel;
- import javax.swing.JScrollPane;
- import javax.swing.JTextArea;
- public class ClientGUI extends JFrame {
- Socket socket = null;
- final JTextArea outputArea = new JTextArea(70, 70);
- final JTextArea inputArea = new JTextArea(70, 70);
- final JScrollPane outputScroll = new JScrollPane(outputArea);
- final JScrollPane inputScroll = new JScrollPane(inputArea);
- public ClientGUI() throws IOException {
- this.setTitle("Client");
- this.initClientSocket();
- SocketInfoUpdater updater = new SocketInfoUpdater(this);
- new Thread(updater).start();
- Container container = getContentPane();
- JPanel pane = new JPanel();
- container.setLayout(new BorderLayout());
- pane.setLayout(new GridLayout(2, 1, 5, 5));
- inputScroll.setAutoscrolls(true);
- outputScroll.setAutoscrolls(true);
- pane.add(outputScroll);
- pane.add(inputScroll);
- setSize(300, 300);
- container.add(pane, BorderLayout.CENTER);
- JButton send = new JButton("Send");
- send.addActionListener(updater);
- container.add(send, BorderLayout.SOUTH);
- setDefaultCloseOperation(3);
- setVisible(true);
- }
- /*
- * 初始化socket
- */
- private void initClientSocket() {
- // 1.创建一个Server Socket
- socket = new Socket();
- // 2.连接到指定的 server socket,指定IP 和端口号
- InetSocketAddress address = new InetSocketAddress("localhost", 18824);
- try {
- socket.connect(address);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- public static void main(String[] args) throws IOException {
- new ClientGUI();
- }
- }
b. 输入流监听和发送数据的监听线程SocketInfoUpdater.java (这个类其实和服务器端上的基本上一样,之所以把它贴出来是考虑到在后续的开发设计中,Server 端和Client端的机制有所不同):
- import java.awt.event.ActionEvent;
- import java.awt.event.ActionListener;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.Socket;
- import java.text.SimpleDateFormat;
- import java.util.Date;
- public class SocketInfoUpdater implements Runnable ,ActionListener{
- ClientGUI client;
- public SocketInfoUpdater(ClientGUI client)
- {
- this.client = client;
- }
- @Override
- public void run() {
- Socket socket = client.socket;
- // 循环检测输入流有没有数据传入,有则显示出来
- while(true)
- {
- try {
- BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- if(reader.ready())
- {
- String info = reader.readLine();
- SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- client.outputArea.append("Server "+socket.getPort() +" at " + f.format(new Date()) + "\n");
- client.outputArea.append(" "+info +"\n");
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- //设置发送按钮监听,事件相应
- @Override
- public void actionPerformed(ActionEvent e) {
- String temp = client.inputArea.getText();
- SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
- client.outputArea.append("Client "+client.socket.getLocalPort() +" at " + f.format(new Date()) + "\n");
- client.outputArea.append(" " + temp + "\n");
- this.sendInfo(temp);
- client.inputArea.setText("");
- }
- private void sendInfo(String s) {
- try {
- PrintWriter pw = new PrintWriter(client.socket.getOutputStream(),
- true);
- pw.println(s);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
执行结果:
截图1:
截图2: