Java 网络编程 之 socket 的用法:本地代码实现、打包、引入到http协议的项目中、发布到linux服务器运行、常见的报错描述

1 篇文章 0 订阅
1 篇文章 0 订阅

一、概念

TCP

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内 另一个重要的传输协议。在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。

应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元( MTU)的限制)。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。

JAVA Socket

所谓socket 通常也称作”套接字“,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常通过”套接字”向网络发出请求或者应答网络请求。

以J2SDK-1.3为例,Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务器端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。对于一个网络连接来说,套接字是平等的,并没有差别,不因为在服务器端或在客户端而产生不同级别。不管是Socket还是ServerSocket它们的工作都是通过SocketImpl类及其子类完成的。

重要的Socket API

java.net.Socket继承于java.lang.Object,有八个构造器,其方法并不多,下面介绍使用最频繁的三个方法,其它方法大家可以见JDK-1.3文档。

Accept方法用于产生”阻塞”,直到接受到一个连接,并且返回一个客户端的Socket对象实例。”阻塞”是一个术语,它使程序运行暂时”停留”在这个地方,直到一个会话产生,然后程序继续;通常”阻塞”是由循环产生的。
getInputStream方法获得网络连接输入,同时返回一个InputStream对象实例。
getOutputStream方法连接的另一端将得到输入,同时返回一个OutputStream对象实例。
注意:其中getInputStream和getOutputStream方法均会产生一个IOException,它必须被捕获,因为它们返回的流对象,通常都会被另一个流对象使用。

二、TCP 编程

服务器端套路

1、创建ServerSocket对象,绑定监听端口。
2、通过accept()方法监听客户端请求。
3、连接建立后,通过输入流读取客户端发送的请求信息。
4、通过输出流向客户端发送响应信息。
5、关闭响应的资源。

客户端套路

1、创建Socket对象,指明需要连接的服务器的地址和端口号。
2、连接建立后,通过输出流向服务器发送请求信息。
3、通过输入流获取服务器响应的信息。
4、关闭相应资源。

多线程实现服务器与多客户端之间通信步骤

1、服务器端创建ServerSocket,循环调用accept()等待客户端连接。
2、客户端创建一个socket并请求和服务器端连接。
3、服务器端接受客户端请求,创建socket与该客户建立专线连接。
4、建立连接的两个socket在一个单独的线程上对话。
5、服务器端继续等待新的连接。

三、Socket通信基本示例

多客户端代码
package com.cpic.socket;

import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.Socket;

/**
 * 客户端
 */
public class ClientSocketUtils {
    private final Logger log = LoggerFactory.getLogger(ClientSocketUtils.class);
    @Test
    public void test1(){
        String data="    <dependency><a>ddd</a></dependency>";
        /**
        *这里封装的方法,别人可以送地址、和端口号、socket里面要传输的内容
        */
        ClientScoketUtils("127.0.0.1",31002,data);
    }
    public void ClientScoketUtils(String address,int port,String data){
        try {
            //1、创建客户端socket,指定服务器地址和端口
            Socket socket = new Socket(address,port);
            //2、获取输出流,向服务器发送信息
            OutputStream os = socket.getOutputStream();//字节输出流
            PrintWriter pw = new PrintWriter(os);//将输出流包装为打印流
            pw.write(data);//发送的信息
            System.out.println("*******我是Socket客户端,调用的地址为:*******"+address+":"+port+"=========发送的内容为:"+data);
            pw.flush();//去刷新缓存,向服务端发送信息
            socket.shutdownOutput();//关闭输出流
            //3、获取输入流,并读取服务器端的响应信息
            InputStream is = socket.getInputStream();
            InputStreamReader isr = new InputStreamReader(is);
            BufferedReader br = new BufferedReader(isr);
            String info=null;
            while((info=br.readLine())!=null){
                System.out.println("*******我是Socket客户端,服务器输入的内容为:*******"+info);
            }
			socket.shutdownInput();
            is.close();
            isr.close();
            br.close();
            //4、关闭资源
            if(pw!=null){
                pw.close();
            }
            if(os!=null){
                os.close();
            }
            if(socket!=null){
                socket.close();
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
多线程实现服务器
package com.cpic.socket;

import com.cpic.interflow.core.exception.CastaliaException;
import com.cpic.interflow.core.web.model.ErrorMessage;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 基于TCP协议的socket通信,使用用户登录
 * 服务器端
 */
public class ServerSocketUtils {

    private final Logger log = LoggerFactory.getLogger(ServerSocketUtils.class);
    public static void main(String[] args){
        //1、创建服务端的socket ,即SocketService,指定绑定的端口,并侦听此端口
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(31002);
            Socket socket =null;
            //记录客户端的访问数量
            int count=0;
            System.out.println("*******服务器即将启动,等待客户端的连接*******");
            //循环监听等待客户端的连接
            while (true){
                //2、调用accept()方法,开始监听,等待客户端的链接
                socket = serverSocket.accept();
                //创建一个新的线程
                ServerSocketThread serverSocketThread = new ServerSocketThread(socket);
                if("127.0.0.1:31002".equals(serverSocketThread.getInetAddress())){
                    //启动线程
                    serverSocketThread.start();
                    count++;//统计客户端的数量
                    System.out.println("客户端的数量:"+count);
                }else{
                    throw new CastaliaException(ErrorMessage.SYS_1006.getErrorCode(), String.format(ErrorMessage.SYS_1006.getErrorMessage(), "不是在白名单范围内"));
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }


    }
    @Test
    public void test1(){
        String data="投保单号存在";
        ServerSocketUtils(31002,"UTF-8",data);
    }
    @Test
    public void test2(){
        System.out.println(ErrorMessage.SYS_1006.getErrorMessage());
        System.out.println(ErrorMessage.SYS_1006.getStateCode());
        System.out.println(ErrorMessage.SYS_1006.getErrorCode());
    }
    /**
     * socket通信的过程中,通过输出流进行响应服务器 OutputStream,通过输入流进行读取客户信息 InputStream
     */
    public  void ServerSocketUtils(int port, String charseName,String data){

        try {
            //1、创建服务端的socket ,即SocketService,指定绑定的端口,并侦听此端口
            ServerSocket serverSocket = new ServerSocket(port);
            //2、调用accept()方法,开始监听,等待客户端的链接
            System.out.println("*******服务器即将启动,等待客户端的连接*******");
            Socket socket = serverSocket.accept();
            //3.获取输入流,并读取客户信息
            InputStream is = socket.getInputStream();//字节输入流
            InputStreamReader isr = new InputStreamReader(is,charseName);//将字节流转换为字符流
            BufferedReader br = new BufferedReader(isr);//将字符流添加缓冲
            String info = null;
            while ((info=br.readLine())!=null){//循环读取客户端的信息
                System.out.println("*******我是Socket服务器,客户端输入的内容为:*******"+info);
            };
            socket.shutdownInput();//关闭输入流
            //4、获取字节输出流,响应客户端请求
            OutputStream os = socket.getOutputStream();
            PrintWriter pw = new PrintWriter(os);//将字节输出流包装为打印流
            pw.write(data);
            System.out.println(socket.getInetAddress()+":"+socket.getLocalPort());
            System.out.println(serverSocket.getLocalSocketAddress());
            System.out.println("*******我是Socket服务器,响应客户端的内容为:*******"+data);
            pw.flush();//调用flush()将方法缓冲输出

            //5、关闭资源
            os.close();
            pw.close();

            if(br!=null){
                br.close();
            }
            if(isr!=null){
                isr.close();
            }
            if(is!=null){
                is.close();
            }
            if(socket!=null){
                socket.close();
            }
            if(serverSocket!=null){
                serverSocket.close();
            }
           /* String info=br.readLine();
            while (info!=null){//循环读取客户端的信息
                info=br.readLine();
                //            log.info("*******我是Socket服务器,客户端输入的内容为:*******fo);
            }*/


        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

package com.cpic.socket;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

import com.cpic.interflow.entrance.service.AccidentRoutingService;
import com.cpic.interflow.entrance.service.GrpcClientService;
import com.cpic.interflow.entrance.web.rest.AccidentServiceResource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

/**
 * 服务器线程处理
 */
public class ServerSocketThread extends Thread {
     GrpcClientService clientService;  //远程微服务调用客户端
     AccidentRoutingService accidentRoutingService; //人意险路由服务

    private  AccidentServiceResource accidentServiceResource = new AccidentServiceResource(clientService,accidentRoutingService);
    // 和本线程相关的Socket
    Socket socket = null;

    public ServerSocketThread(Socket socket) {
        this.socket = socket;
    }

    //线程执行的操作,响应客户端的请求
    public void run(){
        InputStream is=null;
        InputStreamReader isr=null;
        BufferedReader br=null;
        OutputStream os=null;
        PrintWriter pw=null;
        try {
            //获取客户端请求路径
            String reqeustUrl=getInetAddressPath(socket);
            //获取客户端请求内容
            String requestContext = getSocketRequestContext(is,isr,br,socket);
            socket.shutdownInput();//关闭输入流
            /**
            *将socket服务端代码引入到自己项目中
            1、调用通过重定向或转发到自己项目的controler 内容
            2、/通过new Controller()的类然后调用controler里面的方法,
            */
            //1、调用通过重定向或转发到自己项目的controler 内容
//            redirect(reqeustUrl,requestContext);
            //2、通过new Controller()的类然后调用controler里面的方法,返回最终的业务处理,比如我的是投保单号存在
//            String reseponseContext = (String) accidentServiceResource.accidentService(requestContext);
            String reseponseContext = "投保单号存在";
            System.out.println("333"+reseponseContext);
            //获取输出流,响应客户端的请求
            getSocketResponseContext(os,pw,socket,reseponseContext);
			socket.shutdownOutput();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{
            //关闭资源
            try {
                if(pw!=null)
                    pw.close();
                if(os!=null)
                    os.close();
                if(br!=null)
                    br.close();
                if(isr!=null)
                    isr.close();
                if(is!=null)
                    is.close();
                if(socket!=null)
                    socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void  redirect(String reqeustUrl,String requestContext){
        ServletRequestAttributes servletAttribute = (ServletRequestAttributes) RequestContextHolder
                .getRequestAttributes(); // 获取ServletRequest
        HttpServletResponse  response = servletAttribute.getResponse();
        try {
            response.sendRedirect(reqeustUrl+"?requestContext="+requestContext);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public String getSocketRequestContext(InputStream is,InputStreamReader isr,BufferedReader br,Socket socket){
        StringBuffer sb = new StringBuffer();
        try {
            //获取输入流,并读取客户端信息
            is = socket.getInputStream();
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);
            String info=null;
            while((info=br.readLine())!=null){//循环读取客户端的信息
                System.out.println("我是服务器,客户端说:"+info);
                sb.append(info);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
        }
        return sb.toString();
    }
    public void getSocketResponseContext(OutputStream os, PrintWriter pw,Socket socket,String responseConext){
        try {
            //响应socket客户端内容
            os = socket.getOutputStream();
            pw = new PrintWriter(os);
            pw.write(responseConext);
            pw.flush();//调用flush()方法将缓冲输出

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
        }
    }
    public String  getInetAddress(){
        InetAddress inetAddress = socket.getInetAddress();
        System.out.println("当前客户端调用的ip"+inetAddress.getHostAddress()+inetAddress.getAddress()+inetAddress.getCanonicalHostName());
//        System.out.println(socket.getLocalAddress()+":"+socket.getLocalPort());
//        System.out.println(socket.getInetAddress()+":"+socket.getPort());
//        System.out.println(socket.getChannel());
//        System.out.println(socket.getLocalSocketAddress());
//        System.out.println(socket.getRemoteSocketAddress());
        return  "127.0.0.1:31002";
    }
    public String getInetAddressPath(Socket socke){
        return "/PropertyInsurance/AccidentService";
    }

}

代码优化

这种一般也是新手写法,但是能够循环处理多个Socket请求,不过当一个请求的处理比较耗时的时候,后面的请求将被阻塞,所以一般都是用多线程的方式来处理Socket,即每有一个Socket请求的时候,就创建一个线程来处理它。

不过在实际生产中,创建的线程会交给线程池来处理,为了:

线程复用,创建线程耗时,回收线程慢。
防止短时间内高并发,指定线程池大小,超过数量将等待,方式短时间创建大量线程导致资源耗尽,服务挂掉。

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class SocketServer {
  public static void main(String args[]) throws Exception {
    // 监听指定的端口
    int port = 55533;
    ServerSocket server = new ServerSocket(port);
    // server将一直等待连接的到来
    System.out.println("server将一直等待连接的到来");
 
    //如果使用多线程,那就需要线程池,防止并发过高时创建过多线程耗尽资源
    ExecutorService threadPool = Executors.newFixedThreadPool(100);
    
    while (true) {
      Socket socket = server.accept();
      
      Runnable runnable=()->{
        try {
          // 建立好连接后,从socket中获取输入流,并建立缓冲区进行读取
          InputStream inputStream = socket.getInputStream();
          byte[] bytes = new byte[1024];
          int len;
          StringBuilder sb = new StringBuilder();
          while ((len = inputStream.read(bytes)) != -1) {
            // 注意指定编码格式,发送方和接收方一定要统一,建议使用UTF-8
            sb.append(new String(bytes, 0, len, "UTF-8"));
          }
          System.out.println("get message from client: " + sb);
          inputStream.close();
          socket.close();
        } catch (Exception e) {
          e.printStackTrace();
        }
      };
      threadPool.submit(runnable);
    }
  }
}

使用线程池的方式,算是一种成熟的方式。可以应用在生产中。

ServerSocket有以下3个属性。

SO_TIMEOUT:表示等待客户连接的超时时间。一般不设置,会持续等待。
SO_REUSEADDR:表示是否允许重用服务器所绑定的地址。一般不设置,经我的测试没必要,下面会进行详解。
SO_RCVBUF:表示接收数据的缓冲区的大小。一般不设置,用系统默认就可以了。

打包socket 服务端,生成jar包

  • 如果是maven项目 ,就执行mvn clean install
  • 如果是java的项目,通过eclipse导出***.jar包

上传到服务器

  • 通过文件上传工具传到服务器上面
    在这里插入图片描述

在服务器上执行socket 服务器端项目

java -jar socket服务端项目.jar 包
有的时候会报如下:
** ‘XX.jar中没有主清单属性’ **
主要原因为:jar 包的这个 MANIFEST.MF 在生成jar包的时候没有指定代码运行入口:
例如如下所示:class 后面主要是程序运行的入口
Main-Class: com.cpic.socket.Server

五、总结

对于同一个socket,如果关闭了输出流比如(pw.close()),则与该输出流关联的socket也会关闭,所以一般不需要关闭输出流,当关闭socket的时候,输出流也会关闭,直接关闭socket就行。

在使用TCP通信传输信息时,更多是使用对象的形式来传输,可以使用ObjectOutputStream对象序列化流来传递对象,比如ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());User user = new User(“admin”,“123”); os.writeObject(user);
参考链接:https://blog.csdn.net/a78270528/article/details/80318571

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java1111115555

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值