Socket 通信

socket通信

一、socket通信基本原理

img

socket是基于应用服务与TCP/IP通信之间的一个抽象,他将TCP/IP协议里面复杂的通信逻辑进行分装,对用户来说,只要通过一组简单的API就可以实现网络的连接。

img

服务端初始化ServerSocket,对指定的端口进行绑定,然后对端口及进行监听,通过调用accept方法阻塞端口。此时,客户端有一个socket连接到服务端,那么服务端通过监听和accept方法可以与客户端进行连接。

二:socket通信基本示例:

服务端代码:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerSocket{
    public static void main(String[] args) {
        try {
            // 初始化服务端socket并且绑定9999端口
            ServerSocket serverSocket = new ServerSocket(9999);
            //等待客户端的连接
            Socket socket = serverSocket.accept();
            //获取输入流
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //读取一行数据
            String str = bufferedReader.readLine();
            //输出打印
            System.out.println(str);
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端代码:

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

public class ClientSocket {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost", 9999);   
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            String str = "你好,这是我的第一个socket";
            bufferedWriter.write(str);
            //刷新输入流
            bufferedWriter.flush();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

问题1: 运行上面代码出现 java.net.SocketException: Connection reset 报错

原因分析:由于Client 端发送完消息后,并没有发送一个结束标识给Server端,所以 Server端 在调用 readLine() 方法时阻塞。

解决方法:调用 socket.close() 或者调用**socket.shutdownOutput()方法,但是二者并不同,下面进行二者的比较。socket.close() 是将socket关闭连接,如果有服务端给客户端反馈信息,此时客户端是收不到的。而socket.shutdownOutput() **是将输出流关闭,此时,如果服务端有反馈信息,则客户端是可以正常接收的。

修改代码如下:

public class ClientSocket {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("localhost", 9999);
            BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            String str = "你好,这是我的第一个socket";
            bufferedWriter.write(str);
            //刷新输入流
            bufferedWriter.flush();
            //关闭输出流
            socket.shutdownOutput();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三:while循环连续接受客户端信息:

​ Server端需要指定统一的编码格式如下:

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));

通过while((str=bufferedReader.readLine()) != null) 判断输入流是否结束,此时服务端会一直阻塞,等待客户端输入。如下:

  //通过while循环不断读取信息
            while((str=bufferedReader.readLine()) != null) {
                //输出打印
                System.out.println(str);
            }

服务端:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerSocketTest1 {
    public static void main(String[] args) {
        try {
            // 初始化服务端socket并且绑定9999端口
            ServerSocket serverSocket = new ServerSocket(9999);
            //等待客户端的连接
            Socket socket = serverSocket.accept();
            //获取输入流,并且指定统一的编码格式
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
            //读取一行数据
            String str;
            //通过while循环不断读取信息
            while((str=bufferedReader.readLine()) != null) {
                //输出打印
                System.out.println(str);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端:

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
public class ClientSocket1 {
    public static void main(String[] args) {
        try {
            //初始化一个socket
            Socket socket = new Socket("localhost", 9999);
            //通过socket获取字符流
            BufferedWriter bufferedWriter =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            //通过标准输入流获取字符流
            BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(System.in,"UTF-8"));
            while(true) {
                String str = bufferedReader.readLine();
                bufferedWriter.write(str);
                bufferedWriter.write("\n");
                bufferedWriter.flush();
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

四:多线程下socket编程

由于socket通信是阻塞式的,假设我现在有A和B俩个客户端同时连接到服务端的上,当客户端A发送信息给服务端后,那么服务端将一直阻塞在A的客户端上,不同的通过while循环从A客户端读取信息,此时如果B给服务端发送信息时,将进入阻塞队列,直到A客户端发送完毕,并且退出后,B才可以和服务端进行通信。简单地说,我们现在实现的功能,虽然可以让客户端不间断的和服务端进行通信,与其说是一对一的功能,因为只有当客户端A关闭后,客户端B才可以真正和服务端进行通信,这显然不是我们想要的。(参考)

服务端:

import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
public class ClientSocket2 {
    public static void main(String[] args) {
        try {
            //初始化一个socket
            Socket socket = new Socket("localhost", 9999);
            //通过socket获取字符流
            BufferedWriter bufferedWriter =new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            //通过标准输入流获取字符流
            BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(System.in,"UTF-8"));
            while(true) {
                String str = bufferedReader.readLine();
                bufferedWriter.write(str);
                bufferedWriter.write("\n");
                bufferedWriter.flush();
            }
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerSocketTest2 {
    public static void main(String[] args) {
        try {
            // 初始化服务端socket并且绑定9999端口
            ServerSocket serverSocket = new ServerSocket(9999);
            while(true) {
                //等待客户端的连接
                final Socket socket = serverSocket.accept();
                //每当有一个客户端连接进来后,就启动一个单独的线程进行处理
                new Thread(new Runnable(){
                    @Override
                    public void run() {
                        //获取输入流,并且指定统一的编码格式
                        BufferedReader bufferedReader =null;
                        try {
                            bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
                            //读取一行数据
                            String str;
                            //通过while循环不断读取信息,
                            while ((str = bufferedReader.readLine())!=null){
                                //输出打印
                                System.out.println("客户端说:"+str);
                            }
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

客户端A 发送消息给服务端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1fWnVJaK-1584793377409)(C:\Users\yc\AppData\Roaming\Typora\typora-user-images\image-20200321113059105.png)]

客户端B发送消息给服务端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IkEoFEOl-1584793377410)(C:\Users\yc\AppData\Roaming\Typora\typora-user-images\image-20200321113121396.png)]

服务端能同时接收到两个客户端的消息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sKUGwtxC-1584793377410)(C:\Users\yc\AppData\Roaming\Typora\typora-user-images\image-20200321113158095.png)]

但是以上通过 new Thread(new Runnable()方法 产生新的线程,只有在客户端比较少的时候适用,一旦大批量的客户端连接,会出现大批量创建线程后的,在发送消息后,大量无用线程的占用。所以,通过线程池技术,保证线程复用 。修改后的服务端如下:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ServerSocketTest {
    public static void main(String[] args) {
        try {
            // 初始化服务端socket并且绑定9999端口
            ServerSocket serverSocket = new ServerSocket(9999);
            //创建一个线程池
            ExecutorService executorService = Executors.newFixedThreadPool(100);
            while(true) {
                //等待客户端的连接
                final Socket socket = serverSocket.accept();
                //每当有一个客户端连接进来后,就启动一个单独的线程进行处理
                Thread thread = new Thread(new Runnable(){
                    @Override
                    public void run() {
                        //获取输入流,并且指定统一的编码格式
                        BufferedReader bufferedReader =null;
                        try {
                            bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream(),"UTF-8"));
                            //读取一行数据                            
                            String str;
                            //通过while循环不断读取信息,                            
                            while ((str = bufferedReader.readLine())!=null){
                                //输出打印                                
                                System.out.println("客户端说:"+str);
                            }      
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }                    
                    }
                });
                executorService.execute(thread);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

);
}
}
});
executorService.execute(thread);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}


引用如下:

[1]: https://www.jianshu.com/p/cde27461c226	"Java socket详解,看这一篇就够了"

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值