评论

收藏

[JavaScript] socket接口开发和测试初探

开发技术 开发技术 发布于:2021-07-06 11:16 | 阅读数:480 | 评论:0

  由于接下来有一个聊天室需求的项目,所以在确定完技术方案后,就要开始着手socket协议的接口测试准备了。
  在简单查阅一些Java实现websocket的案例资料,决定采用org.java_websocket.client.WebSocketClient;这个实现类进行封装,而非采用直接封装Socket这个,原因比较简单,我相信第一眼的感觉。看了好几个不同的socket client实现,就这个最简单。
  大概抄了一下Demo的代码,但是问题来了,手头没有Websocket接口可提供测试的,着实有点尴尬。既然都抄了一个client的实现代码,再抄一个server实现的代码也不会太浪费时间。

  • 这里分享一个VIP群友的问题:学习之初,抄代码的意义何在?
  • 我觉得本次socket协议接口测试的学习这两天,抄代码对我的意义主要两点:1、能够迅速掌握一种解决问题的方案。2、能够迅速掌握该框架的基本功能的使用。
  • 从零开始学习少不了抄代码的过程,抄完之后,再去魔改,不断验证各种API和函数的使用,看看源码和注释,逐步掌握该技能。
server代码  我用的SpringBoot框架写的,下面是socket server实现代码:
@Component@ServerEndpoint("/ws/{username}")public class SocketS {    private static Logger logger = LoggerFactory.getLogger(SocketS.class);    private static Map<String, SocketS> clients = new ConcurrentHashMap<String, SocketS>();    private Session session;    private String username;    @OnOpen    public void onOpen(@PathParam("username") String username, Session session) throws IOException {        this.username = username;        this.session = session;        clients.put(username, this);        logger.info("用户:{}已连接", username);        sendMessageAll("用户:" + username + "已经上线了!");    }    @OnClose    public void onClose() throws IOException {        clients.remove(username);        sendMessageAll("用户:" + username + "已经离线了!");    }    @OnMessage    public void onMessage(String message) throws IOException {        logger.info(message);        sendMessageAll(this.username + ":" + message);    }    @OnError    public void onError(Session session, Throwable error) {        error.printStackTrace();    }    public void sendMessageTo(String message, String To) throws IOException {        // session.getBasicRemote().sendText(message);        //session.getAsyncRemote().sendText(message);        for (SocketS item : clients.values()) {            if (item.username.equals(To))                synchronized (item.session) {                    item.session.getBasicRemote().sendText(message);                }        }    }    public void sendMessageAll(String message) throws IOException {        for (SocketS item : clients.values()) {            synchronized (item.session) {                item.session.getBasicRemote().sendText("世界喊话器     " + message + "   ");            }        }    }}
注解不兼容的坑
  由于要开启WebSocket支持,所以一些教程上直接在启动类加上了注解@EnableWebSocket,然后启动的时候就会报错:
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.ERROR org.springframework.boot.SpringApplication:837  Application run failedorg.springframework.beans.factory.BeanNotOfRequiredTypeException: Bean named 'defaultSockJsTaskScheduler' is expected to be of type 'org.springframework.scheduling.TaskScheduler' but was actually of type 'org.springframework.beans.factory.support.NullBean' at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:395) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:227) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1174) at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveNamedBean(DefaultListableBeanFactory.java:1141) at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.resolveSchedulerBean(ScheduledAnnotationBeanPostProcessor.java:315) at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.finishRegistration(ScheduledAnnotationBeanPostProcessor.java:256) at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:233) at org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor.onApplicationEvent(ScheduledAnnotationBeanPostProcessor.java:105) at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:172) at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:165) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:139) at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:403) at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:360) at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:897) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:553) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:143) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:758) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:750) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1237) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) at com.okay.family.FamilyApplication.main(FamilyApplication.java:25)
  经过查询资料,发现这是开启定时任务注解@EnableScheduling和开启WebSocket注解@EnableWebSocket不能同时在启动类使用的缘故。
  修改方案如下,取消两个注解,然后通过Bean注入的方式完成配置,下面是两个配置项的类代码:

  • 定时任务配置
package com.okay.family.common;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.scheduling.TaskScheduler;import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;@Configurationpublic class ScheduledConfig {    @Bean    public TaskScheduler taskScheduler() {        ThreadPoolTaskScheduler scheduling = new ThreadPoolTaskScheduler();        scheduling.setPoolSize(10);        scheduling.initialize();        return scheduling;    }}

  • WebSocket配置
package com.okay.family.common;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.socket.config.annotation.EnableWebSocket;import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration@EnableWebSocketpublic class WebSocketConfig {    @Bean    public ServerEndpointExporter serverEndpoint() {        return new ServerEndpointExporter();    }}
client代码和测试脚本
client实现
  这里的比较粗糙,仅限于交流使用,等我再学习学习之后,完善一下这个封装类。
  测试脚本我用了三个人在某一个聊天室中里面从进入,发言,到退出聊天室的场景。
package com.fun.ztest;import com.fun.frame.SourceCode;import org.java_websocket.client.WebSocketClient;import org.java_websocket.enums.ReadyState;import org.java_websocket.handshake.ServerHandshake;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import java.net.URI;import java.net.URISyntaxException;import java.util.Iterator;import java.util.concurrent.atomic.AtomicInteger;public class SocketTest extends WebSocketClient {    private static Logger logger = LoggerFactory.getLogger(SocketTest.class);    public SocketTest(String url) throws URISyntaxException {        super(new URI(url));    }    static SocketTest getInstance(String url) {        try {            return new SocketTest(url);        } catch (URISyntaxException e) {            logger.error("获取socketclient失败!", e);            return null;        }    }    @Override    public void onOpen(ServerHandshake shake) {        logger.info("开始建立socket连接...");        for (Iterator<String> it = shake.iterateHttpFields(); it.hasNext(); ) {            String key = it.next();            logger.debug(key + ":" + shake.getFieldValue(key));        }    }    @Override    public void onMessage(String paramString) {        logger.warn( paramString);    }    @Override    public void onClose(int paramInt, String paramString, boolean paramBoolean) {        logger.info("socket关闭...");    }    @Override    public void onError(Exception e) {        logger.error("socket异常!", e);    }    public static void main(String[] args) throws InterruptedException {        Thread thread = new Thread(new Message());        Thread thread1 = new Thread(new Message());        Thread thread2 = new Thread(new Message());        thread.start();        thread1.start();        thread2.start();        thread.join();        thread1.join();        thread2.join();    }    static class Message extends SourceCode implements Runnable {        String name;        String url;        static AtomicInteger users = new AtomicInteger(1);        SocketTest socketTest;        Message() {            name = DEFAULT_STRING + users.getAndIncrement();            url = "ws://127.0.0.1:8080/ws/" + name;            socketTest = SocketTest.getInstance(url);            output(name);        }        @Override        public void run() {            try {                socketTest.connect();                sleep(2000);                while (!socketTest.getReadyState().equals(ReadyState.OPEN)) {                    SourceCode.sleep(2000);                    logger.warn("还没有打开");                    socketTest.reconnect();                }                logger.info("建立websocket连接");                socketTest.send("我是" + name);                sleep(getRandomInt(5));                socketTest.send("我有事先走了!!!");                sleep(getRandomInt(5));            } catch (Exception e) {                e.printStackTrace();            } finally {                socketTest.close();            }        }    }}
控制台输出
INFO-> 当前用户:fv,IP:10.60.192.21,工作目录:/Users/fv/Documents/workspace/fun/,系统编码格式:UTF-8,系统Mac OS X版本:10.15.7INFO-> FunTester1INFO-> FunTester2INFO-> FunTester3INFO-> 开始建立socket连接...INFO-> 开始建立socket连接...INFO-> 开始建立socket连接...WARN-> 世界喊话器     用户:FunTester2已经上线了!   WARN-> 世界喊话器     用户:FunTester2已经上线了!   WARN-> 世界喊话器     用户:FunTester2已经上线了!   WARN-> 世界喊话器     用户:FunTester3已经上线了!   WARN-> 世界喊话器     用户:FunTester1已经上线了!   WARN-> 世界喊话器     用户:FunTester1已经上线了!   WARN-> 世界喊话器     用户:FunTester3已经上线了!   WARN-> 世界喊话器     用户:FunTester1已经上线了!   WARN-> 世界喊话器     用户:FunTester3已经上线了!   INFO-> 建立websocket连接INFO-> 建立websocket连接WARN-> 世界喊话器     FunTester2:我是FunTester2   WARN-> 世界喊话器     FunTester2:我是FunTester2   WARN-> 世界喊话器     FunTester2:我是FunTester2   WARN-> 世界喊话器     FunTester1:我是FunTester1   WARN-> 世界喊话器     FunTester1:我是FunTester1   WARN-> 世界喊话器     FunTester1:我是FunTester1   INFO-> 建立websocket连接WARN-> 世界喊话器     FunTester3:我是FunTester3   WARN-> 世界喊话器     FunTester3:我是FunTester3   WARN-> 世界喊话器     FunTester3:我是FunTester3   WARN-> 世界喊话器     FunTester2:我有事先走了!!!   WARN-> 世界喊话器     FunTester2:我有事先走了!!!   WARN-> 世界喊话器     FunTester2:我有事先走了!!!   WARN-> 世界喊话器     FunTester3:我有事先走了!!!   WARN-> 世界喊话器     FunTester3:我有事先走了!!!   WARN-> 世界喊话器     FunTester3:我有事先走了!!!   WARN-> 世界喊话器     用户:FunTester2已经离线了!   INFO-> socket关闭...WARN-> 世界喊话器     用户:FunTester2已经离线了!   WARN-> 世界喊话器     FunTester1:我有事先走了!!!   WARN-> 世界喊话器     FunTester1:我有事先走了!!!   INFO-> socket关闭...WARN-> 世界喊话器     用户:FunTester3已经离线了!   INFO-> socket关闭...Process finished with exit code 0
  纯文字版可能不够直观,下面分享一下截图,我把发言的内容WARN日志里面了。
DSC0000.png


  
关注下面的标签,发现更多相似文章