首页 > 编程语言 > 详细

java Socket

时间:2020-05-31 17:53:59      阅读:27      评论:0      收藏:0      [点我收藏+]

一、socket通信基本原理

首先socket 通信是基于TCP/IP 网络层上的一种传送方式,我们通常把TCP和UDP称为传输层。

技术分享图片

 

 

如上图,在七个层级关系中,我们讲的socket属于传输层,

其中UDP是一种面向无连接的传输层协议。UDP不关心对端是否真正收到了传送过去的数据。

如果需要检查对端是否收到分组数据包,或者对端是否连接到网络,则需要在应用程序中实现。

UDP常用在分组数据较少或多播、广播通信以及视频通信等多媒体领域。

在这里我们不进行详细讨论,这里主要讲解的是基于TCP/IP协议下的socket通信。

 

socket是基于应用服务与TCP/IP通信之间的一个抽象,他将TCP/IP协议里面复杂的通信逻辑进行分装,

对用户来说,只要通过一组简单的API就可以实现网络的连接

技术分享图片

 

 

首先,服务端初始化ServerSocket,然后对指定的端口进行绑定,接着对端口及进行监听,通过调用accept方法阻塞,

此时,如果客户端有一个socket连接到服务端,那么服务端通过监听和accept方法可以与客户端进行连接。

 

二  基本示例 

服务端

 1 package socket.socket1.socket;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStreamReader;
 6 import java.net.ServerSocket;
 7 import java.net.Socket;
 8 
 9 public class ServerSocketTest {
10 
11     public static void main(String[] args) {
12         try {
13             //初始化服务端socket并且绑定9999端口
14             ServerSocket serverSocket = new ServerSocket(9999);
15             //等待客户端的连接
16             Socket socket = serverSocket.accept();
17             //获取输入流
18             BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
19             //读取一行数据
20             String str = bufferedReader.readLine();
21             //输出打印
22             System.out.println(str);
23         } catch (IOException e) {
24             e.printStackTrace();
25         }
26     }
27 }

 

客户端

 1 package socket.socket1.socket;
 2 
 3 import java.io.BufferedWriter;
 4 import java.io.IOException;
 5 import java.io.OutputStreamWriter;
 6 import java.net.Socket;
 7 
 8 public class ClientSocket {
 9     public static void main(String[] args) {
10         try {
11             Socket socket = new Socket("127.0.0.1", 9999);
12             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
13             String str = "你好,这是我的第一个socket";
14             bufferedWriter.write(str);
15         } catch (IOException e) {
16             e.printStackTrace();
17         }
18     }
19 }

 

先启动服务端

再启动客户端

发现客户端启动正常后,马上执行完后关闭。同时服务端控制台报错:

服务端报错

技术分享图片

 

 

然后好多童鞋,就拷贝这个java.net.SocketException: Connection reset上王查异常,查询解决方案,搞了半天都不知道怎么回事。

解决这个问题我们首先要明白,socket通信是阻塞的,他会在以下几个地方进行阻塞。

第一个是accept方法,调用这个方法后,服务端一直阻塞在哪里,直到有客户端连接进来。

第二个是read方法,调用read方法也会进行阻塞。通过上面的示例我们可以发现,该问题发生在read方法中。

有朋友说是Client没有发送成功,其实不是的,我们可以通debug跟踪一下,发现客户端发送了,并且没有问题。

而是发生在服务端中,当服务端调用read方法后,他一直阻塞在哪里,因为客户端没有给他一个标识,告诉是否消息发送完成,

所以服务端还在一直等待接受客户端的数据,结果客户端此时已经关闭了,就是在服务端报错:java.net.SocketException: Connection reset

 

那么理解上面的原理后,我们就能明白,客户端发送完消息后,需要给服务端一个标识,告诉服务端,我已经发送完成了,服务端就可以将接受的消息打印出来。

 

通常大家会用以下方法进行进行结束:

调用socket.close() 或者socket.shutdownOutput()方法。

调用这俩个方法,都会结束客户端socket。但是有本质的区别。

socket.close() 将socket关闭连接,那边如果有服务端给客户端反馈信息,此时客户端是收不到的。

socket.shutdownOutput()是将输出流关闭,此时,如果服务端有信息返回,则客户端是可以正常接受的。

 

现在我们将上面的客户端示例修改一下啊,增加一个标识告诉流已经输出完毕:

 

客户端

 1 package socket.socket1.socket;
 2 
 3 import java.io.BufferedWriter;
 4 import java.io.IOException;
 5 import java.io.OutputStreamWriter;
 6 import java.net.Socket;
 7 
 8 public class ClientSocket {
 9     public static void main(String[] args) {
10         try {
11             Socket socket = new Socket("127.0.0.1", 9999);
12             BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
13             String str = "你好,这是我的第一个socket";
14             bufferedWriter.write(str);
15             //刷新输入流
16             bufferedWriter.flush();
17             //关闭socket的输出流
18             socket.shutdownOutput();
19         } catch (IOException e) {
20             e.printStackTrace();
21         }
22     }
23 }

 

在看服务端控制台:

技术分享图片

通过上面示例,我们可以基本了解socket通信原理,掌握了一些socket通信的基本api和方法,实际应用中,都是通过此处进行实现变通的。

 

但上面示例,其实不够完整,比如我们每次发送都要new 一个socket ,也只支持一次发送消息,所以我们用另外一个例子,实现1个比较完整的demo

 

三  手写完整示例

例用Socket实现客户端和服务端通信,要求客户发送数据后回显相同的数据

 服务端 socket 

package com.differ.jackyun.examples.javabasisc.socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 服务端soccket 测试
 *
 * @author hup
 * @data 2020-05-31 14:30
 **/
public class MyServerSocket implements Runnable {

    @Override
    public void run() {
        //创建一个线程池
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        try {
            ServerSocket server = new ServerSocket(10001);
            while(true)
            {
                //阻塞等待
                Socket socket = server.accept();
                //为了支持并发,所以每来1次消息,都弄个新线程处理
                Runnable runnable = () -> {
                    //字符输入流
                    BufferedReader reader = null;
                    //字符输出流
                    PrintWriter pw = null;
                    try {
                        //读取接收到的内容
                        reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                        //接收到的数据
                        String readResult = reader.readLine();
                        System.out.println("服务端接收到数据=" + readResult);
                        //数据发回客户端
                        pw = new PrintWriter(socket.getOutputStream(), true);
                        pw.println(readResult);
                    } catch (Exception e) {
                    } finally {
                        //关闭流
                        try {
                            if (reader != null) {
                                reader.close();
                            }
                            if (pw != null) {
                                pw.close();
                            }
                        } catch (Exception e) {
                        }
                    }
                };
                //线程池提交线程任务
                executorService.submit(runnable);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

 

客户端 socket

package com.differ.jackyun.examples.javabasisc.socket;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * 客户端socket
 *
 * @author hup
 * @data 2020-05-31 14:30
 **/
public class MySocket implements Runnable {

    @Override
    public void run() {
        //输出字符流
        PrintWriter pw = null;
        //输入字符流
        BufferedReader reader = null;
        try {
            //输出字符流
            Socket socket = new Socket("localhost", 10001);
            pw = new PrintWriter(socket.getOutputStream(), true);
            //向服务端发送消息
            pw.println("我是客户端消息,今天天气真好");
            //等待服务器端的消息
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
            while (true) {
                String result = reader.readLine();
                if (result != null) {
                    System.out.println("客户端接收到服务端消息=" + result);
                    break;
                }
            }
        } catch (Exception ex) {
        } finally {
            //关闭流
            try {
                if (pw != null) {
                    pw.close();
                }
                if (reader != null) {
                    reader.close();
                }
            } catch (Exception e) {
            }
        }
    }
}

 

测试类

 1 package com.differ.jackyun.examples.javabasisc.socket;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * 套接字测试类
 7  *
 8  * @author hup
 9  * @data 2020-05-31 15:14
10  **/
11 public class socketTest {
12     @Test
13     public void test() {
14         //启动服务端
15         MyServerSocket myServerSocket = new MyServerSocket();
16         new Thread(myServerSocket).start();
17 
18         try {
19             Thread.currentThread().sleep(5000);
20         } catch (Exception ex) {
21             System.out.println(ex);
22         }
23 
24         //启动客户端1
25         MySocket mySocket = new MySocket();
26         new Thread(mySocket).start();
27 
28         //启动客户端2
29         MySocket mySocket2 = new MySocket();
30         new Thread(mySocket2).start();
31 
32         try {
33             Thread.currentThread().sleep(10000);
34         } catch (Exception ex) {
35             System.out.println(ex);
36         }
38     }
40 }

测试输出结果

服务端接收到数据=我是客户端消息,今天天气真好
客户端接收到服务端消息=我是客户端消息,今天天气真好
服务端接收到数据=我是客户端消息,今天天气真好
客户端接收到服务端消息=我是客户端消息,今天天气真好

根据结果可以知道: 多个客户端给服务端发消息,服务端都能处理(用到了多线程)

 

四   看完上面例子,可能有同学有疑问了,为什么你输入流(读取)的时候用的是BufferedReader, 输出流(写)的时候用的是PrintWriter 不应该用与BufferedReader 配套的BufferedWriter吗?

           Socket编程中,尽量用PrintWriter取代BufferedWriter,下面是PrintWriter的优点:

1. PrintWriter的print、println方法可以接受任意类型的参数,而BufferedWriter的write方法只能接受字符、字符数组和字符串;

2. PrintWriter的println方法自动添加换行,BufferedWriter需要显示调用newLine方法;

3. PrintWriter的方法不会抛异常,若关心异常,需要调用checkError方法看是否有异常发生;

4. PrintWriter构造方法可指定参数,实现自动刷新缓存(autoflush);

5. PrintWriter的构造方法更广。

        在使用BufferedReader中的readLine方法接收BufferedWriter中的字符流时,由于readLine是在读取到换行符的时候才将整行字符返回,所以BufferedWriter方法在录入一段字符后要使用newLine方法进行一次换行操作,然后再把字符流刷出去。而PrintWriter由于可以开启自动刷新,并且其中的println方法自带换行操作。所以代码实现起来要比BufferedWriter简单一些。
————————————————
版权声明:最后面这部分总结 来源于下面链接
原文链接:https://blog.csdn.net/arno_dzl/java/article/details/76601852

 

java Socket

原文:https://www.cnblogs.com/hup666/p/13019464.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!