Java网络编程(Socket API)

今天就来具体的编写代码来介绍UDP/TCP有关的Socket API,希望对你有帮助

目录

网络编程的基本概念

Socket 套接字

UDP的Socket API

UPD回显服务器(服务器)

UDP回显服务器(客户端)

TCP的Socket API 

TCP回显服务器(服务器) 

TCP回显服务器(客户端)

解决BUG

1.不能回显

2.无法多个客户端使用

网络编程的基本概念

1.网络编程:写代码实现两个/多个进程通过网络进行相互通信,网络编程是一种进程间通信的方式,借助公共区域就是“网卡”

2.客户端(client)/服务器(server)

客户端:主动发送网络数据的一方

服务器:被动接收网络数据的一方

3.请求(request)/响应 (response)

请求:客户端给服务器发送数据

响应:服务器给客户端返回数据

4.客户端和服务器之间的交互方式

1)一问一答 比如浏览网页

2)多问一答 比如上传文件

3)一问多答 比如下载文件

4)多问多答 比如远程控制

Socket 套接字

进行网络编程需要使用操作系统中提供的网络编程API

 因为传输层提供了两个非常重要的协议所以对应的socket api也是截然不同的

TCP :有连接,可靠传输,面向字节流,全双工

UDP:无连接,不可靠传输,面向数据报,全双工

UDP的Socket API

DatagramSocket:Socket类,本质上相当于一个文件,构造一个DatagramSocket对象就相当于打开了一个内核中的socket文件,通过send()发送数据,receive()接收数据,close()关闭文件

DatagramPacket:表示一个UDP数据报,传输数据以DatagramSocket为单位

InetSocketAddress:IP地址+端口号

UPD回显服务器(服务器)

public class UdpEchoServer {
    //创建udp服务器首先打开socket文件
    private DatagramSocket socket=null;
    public UdpEchoServer(int port) throws SocketException {
        socket=new DatagramSocket(port);//创建实例指定端口号(绑定端口号)
    }
    //启动服务器
    public void start() throws IOException {
        System.out.println("服务器启动");
        while(true) {
            //构造DatagramPacket对象参数(字节数组,长度)
            DatagramPacket requestPacket=new DatagramPacket(new byte[4096],4096);
            //构造空的DatagramPacket对象作为receive的参数
            //1.读取客户端请求
            socket.receive(requestPacket);//输出型参数无返回但是会修改DatagramPacket对象
            //2.对请求解析转换为字符串 requestPacket本质就是一个字节数组 拿到数组,起始下标,数组长度
            String request =new String(requestPacket.getData(),0,requestPacket.getLength());
            //3.对请求进行响应
            String response =process(request);//通过process方法实现回显
            //4.将响应构造成DatagramPacket对象
            //构造DatagramPacket对象参数(字符串转字节数组,数组长度(字节数),要返回的对象地址(收件地址) )
            DatagramPacket responsePacket=new DatagramPacket(response.getBytes(),response.getBytes().length,
                    requestPacket.getSocketAddress());
            //5.将这个DatagramPacket对象返回给客户端
            socket.send(responsePacket);//将数据报发送回去
            //打印日志 参数(客户端ip,客户端端口号,请求,响应)
            System.out.printf("[%s:%d] req=%s; resp=%s\n",requestPacket.getAddress().toString(),requestPacket.getPort(),
                    request,response);
        }
    }
    public String process(String req) {
        return req;//实现通过请求计算响应
    }

    public static void main(String[] args) throws IOException {
        UdpEchoServer server=new UdpEchoServer(8000);//真正启动服务器 端口号选择1024以上的65535一下
        server.start();
    }
}

回显服务器的服务器实现过程

1.读取客户端请求

2.请求转换为字符串

3.对请求响应

4.将响应构造为DatagramPacket对象

5.将DatagramPacket对象返回给客户端

UDP回显服务器(客户端)

public class UdpEchoClient {
    private DatagramSocket socket=null;
    public UdpEchoClient() throws SocketException {
        socket=new DatagramSocket();//客户端端口号程序自动分配
    }
    public void start() throws IOException {
        Scanner scanner=new Scanner(System.in);
        while(true) {
            //让客户端从控制台中获取请求
            System.out.println(">");
            String request= scanner.next();
            //将获取到的字符串发送给服务器
            DatagramPacket requestPacket=new DatagramPacket(request.getBytes(),request.getBytes().length,
                    InetAddress.getByName("127.0.0.1"),8000);//127.0.0.1环回IP
            socket.send(requestPacket);//数据报发送给服务器
            //从服务器读取响应数据
            DatagramPacket responsePacket=new DatagramPacket(new byte[4096],4096);
            socket.receive(responsePacket);
            //将数据报转换为字符串
            String response =new String(responsePacket.getData(),0,responsePacket.getLength());
            System.out.printf("req=%s,resp=%s",request,response);
        }
    }

    public static void main(String[] args) throws IOException {
        UdpEchoClient client=new UdpEchoClient();
        client.start();
    }
}

回显服务器的客户端实现过程

1.读取用户请求

2.将请求给服务器

3.读取服务器响应

4.将响应转换为字符串进行显示

以上就是UDP实现的回显服务器主要掌握执行过程和有关类的方法调用

TCP的Socket API 

ServerSocket:服务器端使用的socket,这里主要使用accept()方法,没有参数返回值是一个Socket对象,功能是等待有客户端与服务器建立上连接,accept则会将这个连接获取到进程中,进一步的通过返回值的Socket对象来和客户端进行交互
Socket:客户端与服务器都能使用的socket,通过accept()返回的Socket对象进行发送/接收数据,Socket对象提供了 getInputStream() getOutputStream() 来实现输入/输出

TCP回显服务器(服务器) 

public class TcpEchoServer {
    ServerSocket ServerSocket=null;

    public TcpEchoServer(int port) throws IOException {
        this.ServerSocket = new ServerSocket(port);
    }
    public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
            Socket clientSocket = ServerSocket.accept();//accept是建立服务器与客户端之前连接的桥梁
            //在没建立成功连接时就会阻塞等待,直到成功连接
            processConnect(clientSocket);
        }
            
    }

    public void processConnect(Socket clientSocket) throws IOException {
        //通过这个方法来给客户端提供服务
        //在一次连接中会出现多种可能1请1答短连接,N请N答长连接
        System.out.printf("[%s:%d] 建立连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
        try(InputStream inputStream=clientSocket.getInputStream();
            OutputStream outputStream=clientSocket.getOutputStream())
        {
            Scanner scanner=new Scanner(inputStream);
            PrintWriter printWriter=new PrintWriter(outputStream);
            while (true)  {
                //处理长连接
                if(!scanner.hasNext()) {
                    System.out.printf("[%s:%d] 断开连接\n",clientSocket.getInetAddress().toString(),clientSocket.getPort());
                    break;//客户端断开连接
                }
                //读取请求解析为字符串
                String request = scanner.next();
                //根据请求计算响应
                String response = process(request);
                //将响应写回客户端
                printWriter.write(response);
                printWriter.flush();//刷新缓冲区,避免数据没发送
                System.out.printf("[%s:%d]  req:%s resp:%s\n",clientSocket.getInetAddress().toString(),clientSocket.getPort(),
                        request,response);
            }
        } finally {
            clientSocket.close();//防止资源泄露
        }
    }
    public String process(String req) {//计算响应
        return req;
    }

    public static void main(String[] args) throws IOException {
        TcpEchoServer server=new TcpEchoServer(8000);
        server.start();
    }
}

TCP回显服务器(客户端)

public class TcpEchClient {
    public Socket socket=null;
    public TcpEchClient() throws IOException {
        this.socket=new Socket("127.0.0.1",8000);//要与服务器建立连接
    }
    public void start() throws IOException {
        Scanner scanner=new Scanner(System.in);
        try(InputStream inputStream=socket.getInputStream() ;
            OutputStream outputStream=socket.getOutputStream())
        {
            Scanner scannerNet=new Scanner(inputStream);
            PrintWriter printWriter=new PrintWriter(outputStream);
            while(true) {
                //用户在控制台输入
                System.out.print(">");
                String request=scanner.next();
                //请求发送给服务器
                printWriter.write(request);
                printWriter.flush();
                //从服务器读取响应
                String response=scannerNet.next();
                //结果显示回界面
                System.out.printf("req:%s resp:%s\n",request,response);
            }
        }
    }

    public static void main(String[] args) throws IOException {
        TcpEchClient client=new TcpEchClient();
        client.start();
    }
}

代码的逻辑执行顺序与UDP相差不大但是此时的代码有BUG

解决BUG

1.不能回显

 可以看到我们运行起了代码连接建立成功输入数据后并未回显这是为什么呢?有两种可能

1.客户端没将数据发出(没加flush())

2.服务器没收到数据(服务器还在阻塞)

我们为了确认阻塞位置就可以借助工具jconsole开看线程的调用栈

 可以看到确实是发生了阻塞,我们就可以分析一下了,客户端等待服务器响应返回是合理的,服务器等待客户端的请求但是我们已经将请求发给服务器了呀所以问题就在这了

 找到了问题我们就可以解决了使用printWriter.println();发送数据添加换行符

2.无法多个客户端使用

 通过代码测试我们发现只能有·一个客户端与服务器相连接,未解决这个BUG就需要使用多线程

 

 通过使用新线程去调用processConnect(),原来的线程去继续执行accept()

public void start() throws IOException {
        System.out.println("服务器启动");
        while (true) {
            Socket clientSocket = ServerSocket.accept();
           // 多线程方式
            Thread thread=new Thread(()-> {
                try {
                    processConnect(clientSocket);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
        }
}

 此时就解决了只能一个客户端连接服务器的问题,但是我们知道客户端的创建与销毁次数是不确定的所以更推荐使用线程池

public void start() throws IOException {
        System.out.println("服务器启动");
        ExecutorService services = Executors.newCachedThreadPool();
        while (true) {
            Socket clientSocket = ServerSocket.accept();
            //线程池的使用
            services.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        processConnect(clientSocket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });

        }
}

 好的以上就是有关Java网络编程有关Socket API相关的知识点,各种框架的底层就是Socket API的使用,掌握基础就能更好的学习之后的知识,希望对你有所帮助,觉得有帮助的还请点赞 评论 蟹蟹!!!会持续更新的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值