java Socket 实践

基于TCP链接的Socket通讯

TCP协议提供可靠的数据传输服务是经过创建TCP链接实现的。一条“TCP链接”链接的两端是Internet上分别在两台主机运行的两个进程,一个是发送进程,一个是接收进程,每一个进程用一个Socket(IP地址和端口)惟一肯定。一对Socket惟一标识一条TCP链接。TCP链接是全双工和点对点的,全双工指数据可双向传输,点对点是指每条TCP链接只有两个端点。

image.png

使用一对socket进行tcp链接,链接成功后,就能够经过socket发送或接收字节流数据,完成点到点、全双工的通讯。segmentfault

Java的TCP Socket通讯

clipboard.png

服务端经过建立指定端口号的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);

使用Socket对象输出/读取字节流数据

从控制台获取输入的字符串,字符串数据将从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();
        }

    }
}

在终端运行的效果图:
image.png
image.png
image.pngserver

相关文章
相关标签/搜索