socket接口开发和测试初探
由于接下来有一个聊天室需求的项目,所以在确定完技术方案后,就要开始着手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日志里面了。
来源:51CTO技术博客 https://blog.51cto.com/u_14032861/2986472
页:
[1]