Socket聊天程序——服务端
写在前面:
昨天在博客记录自己抽空写的一个Socket聊天程序的初始设计,那是这个程序的整体设计,为了完整性,今天把服务端的设计细化记录一下,首页贴出Socket聊天程序的服务端大体设计图,如下图:
功能说明:
服务端主要有两个操作,一是阻塞接收客户端的socket并做响应处理,二是检测客户端的心跳,如果客户端一段时间内没有发送心跳则移除该客户端,由Server创建ServerSocket,然后启动两个线程池去处理这两件事(newFixedThreadPool,newScheduledThreadPool),对应的处理类分别是SocketDispatcher、SocketSchedule,其中SocketDispatcher根据socket不同的请求分发给不同SocketHandler去处理,而SocketWrapper则是对socket加了一层外壳包装,用lastAliveTime记录socket最新的交互时间,SocketHolder存储当前跟服务端交互的socket集合。
具体实现:
[Server.java]
Server是服务端的入口,由Server的start()方法启动ServerSocket,然后阻塞接收客户端的请求,交由SocketDispatcher去分发,SocketDispatcher由newFixedThread类型的线程池启动,当连接数超过最大数据时将被队列处理,使用scheduleAtFixedRate启动SocketSchedule定时循环去监听客户端的心跳包,这两个类型都实现了Runnable接口,下面给出服务端的代码:
package yaolin.chat.server; import java.io.IOException;
import java.net.ServerSocket;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit; import yaolin.chat.common.ConstantValue;
import yaolin.chat.util.LoggerUtil; /**
* 服务器
* @author yaolin
*/
public class Server { private final ServerSocket server;
private final ExecutorService pool; public Server() throws IOException {
server = new ServerSocket(ConstantValue.SERVER_PORT);
pool = Executors.newFixedThreadPool(ConstantValue.MAX_POOL_SIZE);
} public void start() {
try {
ScheduledExecutorService schedule = Executors.newScheduledThreadPool(1);
// Watch dog. Exception??
schedule.scheduleAtFixedRate(new SocketSchedule(), 10, ConstantValue.TIME_OUT, TimeUnit.SECONDS); while (true) {
pool.execute(new SocketDispatcher(server.accept()));
LoggerUtil.info("ACCEPT A CLIENT AT " + new Date());
}
} catch (IOException e) {
pool.shutdown();
}
} public static void main(String[] args) {
try {
new Server().start();
} catch (IOException e) {
LoggerUtil.error("Server start failed! -> " + e.getMessage(), e);
}
}
}
[SocketDispatcher.java]
Server只是服务端的入口,并指挥中心,SocketDispatcher才是服务端的指挥中心,对客户端不同的消息类型请求进行分发,让不同的SocketHandler去处理对应的消息请求,这里服务端和客户端的消息交互都是用JSON数据,所有消息类都继承BaseMessage,所以将接收到数据转换成BaseMessage类型,再判断其类型,(数据类型模块属于common模块),这里需要提一下的是当消息类型是文件类型的时候会睡眠配置执行的间隔时间,这样FileHandler才能有时间对文件流进行读取和重新发送给指定的客户端,而不会立即进入下一次循环对消息类型的判断(可能这里设计有点问题,不过暂时先这样做),下面给出SocketDispatcher的代码:
/**
* SocketDispatcher
*
* @author yaolin
*/
public class SocketDispatcher implements Runnable { private final Socket socket; public SocketDispatcher(Socket socket) {
this.socket = socket;
} @Override
public void run() {
if (socket != null) {
while (!socket.isClosed()) {
try {
InputStream is = socket.getInputStream();
String line = null;
StringBuffer sb = null; if (is.available() > 0) { BufferedReader bufr = new BufferedReader(new InputStreamReader(is));
sb = new StringBuffer();
while (is.available() > 0 && (line = bufr.readLine()) != null) {
sb.append(line);
}
LoggerUtil.trach("RECEIVE [" + sb.toString() + "] AT " + new Date()); BaseMessage message = JSON.parseObject(sb.toString(), BaseMessage.class); switch (message.getType()) {
case MessageType.ALIVE:
HandlerFactory.getHandler(MessageType.ALIVE).handle(socket, sb.toString());
break;
case MessageType.CHAT:
HandlerFactory.getHandler(MessageType.CHAT).handle(socket, sb.toString());
break;
case MessageType.FILE:
HandlerFactory.getHandler(MessageType.FILE).handle(socket, sb.toString());
LoggerUtil.trach("SEVER:PAUSE TO RECEIVE FILE");
Thread.sleep(ConstantValue.MESSAGE_PERIOD);
break;
case MessageType.LOGIN:
HandlerFactory.getHandler(MessageType.LOGIN).handle(socket, sb.toString());
break;
case MessageType.LOGOUT:
break;
case MessageType.REGISTER:
HandlerFactory.getHandler(MessageType.REGISTER).handle(socket, sb.toString());
break;
}
} else {
Thread.sleep(ConstantValue.MESSAGE_PERIOD);
}
} catch (Exception e) { // catch all handler exception
LoggerUtil.error("SocketDispatcher Error!" + e.getMessage(), e);
}
}
}
}
}
[SocketSchedule.java]
跟Server有直接关系的另一个类(组件)是SocketSchedule,SocketSchedule主要负责检测客户端的最新一次跟服务端的交互时间是否超过系统配置允许最大的时间,如果超过了,则将该客户端socket从服务端移除,否则更新客户端的最新一次跟服务端的交互时间。下面是具体的实现:
/**
* Remove socket from SocketHolder if lastAliveTime > TIME_OUT
* @author yaolin
*
*/
public class SocketSchedule implements Runnable { @Override
public void run() {
for (String key : SocketHolder.keySet()) {
SocketWrapper wrapper = SocketHolder.get(key);
if (wrapper != null && wrapper.getLastAliveTime() != null) {
if (((new Date().getTime() - wrapper.getLastAliveTime().getTime()) / 1000) > ConstantValue.TIME_OUT) {
// remove socket if timeout
SocketHolder.remove(key);
}
}
}
}
}
[SocketHolder.java、SocketWrapper.java]
从上面的代码可以看出,SocketSchedule#run()只是简单的对时间进行一次判断,真正有意义的其实是SocketHolder和SocketWrapper,SocketWrapper则是对socket加了一层外壳包装,SocketHolder的存储了当前有效时间内所有跟服务端有交互的客户端,SocketHolder以客户端的唯一标识(这里使用用户名),作为KEY,客户端所在的socket作为VALUE的键值对形式存储,其中SocketHolder#flushClientStatus()的处理逻辑是用于通知其他客户端当前客户端的上线/离线状态,下面给出这两个类的具体实现:
/**
* Wrap Socket, SocketSchedule remove socket if lastAliveTime > TIME_OUT
* @author yaolin
*
*/
public class SocketWrapper { private Socket socket;
private Date lastAliveTime; // full constructor
public SocketWrapper(Socket socket, Date lastAliveTime) {
this.socket = socket;
this.lastAliveTime = lastAliveTime;
}
public Socket getSocket() {
return socket;
}
public void setSocket(Socket socket) {
this.socket = socket;
}
public Date getLastAliveTime() {
return lastAliveTime;
}
public void setLastAliveTime(Date lastAliveTime) {
this.lastAliveTime = lastAliveTime;
}
}
/**
* SocketHolder
* @author yaolin
*/
public class SocketHolder { private static ConcurrentMap<String, SocketWrapper> listSocketWrap = new ConcurrentHashMap<String, SocketWrapper>(); public static Set<String> keySet() {
return listSocketWrap.keySet();
} public static SocketWrapper get(String key) {
return listSocketWrap.get(key);
} public static void put(String key, SocketWrapper value) {
listSocketWrap.put(key, value);
flushClientStatus(key, true);
} public static SocketWrapper remove(String key) {
flushClientStatus(key, false);
return listSocketWrap.remove(key);
} public static void clear() {
listSocketWrap.clear();
} /**
* <pre>content:{username:"",flag:false}</pre>
* @param flag true:put,false:remove;
*/
private static void flushClientStatus(String key, boolean flag) {
ClientNotifyDTO dto = new ClientNotifyDTO(flag, key);
ReturnMessage rm = new ReturnMessage().setKey(Key.NOTIFY).setSuccess(true).setContent(dto);
rm.setFrom(ConstantValue.SERVER_NAME);
for (String toKey : listSocketWrap.keySet()) {
if (!toKey.equals(key)) { // not send to self
rm.setTo(toKey);
SocketWrapper wrap = listSocketWrap.get(toKey);
if (wrap != null) {
SendHelper.send(wrap.getSocket(), rm);
}
}
}
}
}
[SocketHandler.java、HandlerFactory.java、OtherHandlerImpl.java]
SocketDispatcher让不同的SocketHandler去处理对应的消息请求,SocketHandler的设计其实就是一套简单的工厂组件吧(其中ReturnHandler暂时由SendHelper实现信息传送,暂时没有用到,已经@Deprecated ,这里还是给出),完整类图如下:
下面给出这一块的代码,为了缩小篇幅,将所有Handler实现的代码收起来。
/**
* SocketHandler
* @author yaolin
*/
public interface SocketHandler {
/**
* Handle Client Socket
*/
public Object handle(Socket client,Object data);
}
/**
* SocketHandlerFactory
* @author yaolin
*/
public class HandlerFactory { // can not create instance
private HandlerFactory(){} public static SocketHandler getHandler(int type) {
switch (type) {
case MessageType.ALIVE: // usually use
return new AliveHandler();
case MessageType.CHAT:
return new ChatHandler();
case MessageType.LOGIN:
return new LoginHandler();
// case MessageType.RETURN:
// return new ReturnHandler();
case MessageType.LOGOUT:
return new LogoutHandler();
case MessageType.REGISTER:
return new RegisterHandler();
case MessageType.FILE:
return new FileHandler();
}
return null; // NullPointException
}
}
/**
* AliveSocketHandler
* @author yaolin
*/
public class AliveHandler implements SocketHandler { /**
* @return null
*/
@Override
public Object handle(Socket client, Object data) {
if (data != null) {
BaseMessage message = JSON.parseObject(data.toString(), BaseMessage.class);
if (StringUtil.isNotEmpty(message.getFrom())) {
SocketWrapper wrapper = SocketHolder.get(message.getFrom());
if (wrapper != null) {
wrapper.setLastAliveTime(new Date()); // KEEP SOCKET ...
SocketHolder.put(message.getFrom(), wrapper);
}
}
}
return null;
} }
/**
* ChatHandler
*
* @author yaolin
*/
public class ChatHandler implements SocketHandler { @Override
public Object handle(Socket client, Object data) {
if (data != null) {
ChatMessage message = JSON.parseObject(data.toString(), ChatMessage.class); if (StringUtil.isNotEmpty(message.getFrom()) && StringUtil.isNotEmpty(message.getTo())) {
// exist & send
if (SocketHolder.keySet().contains(message.getFrom())) {
String owner = message.getFrom();
message.setOwner(owner); // owner will be display
if (ConstantValue.TO_ALL.equals(message.getTo())) { // one-to-all
// TO_ALL TAB will be select;
message.setFrom(ConstantValue.TO_ALL);
for (String key : SocketHolder.keySet()) {
// also send to self
SocketWrapper wrapper = SocketHolder.get(key);
if (wrapper != null) {
SendHelper.send(wrapper.getSocket(), message);
}
}
} else {// one-to-one
SocketWrapper wrapper = SocketHolder.get(message.getTo());
if (wrapper != null) {
// owner = from
SendHelper.send(wrapper.getSocket(), message);
// also send to self
// TO TAB will be select;
message.setFrom(message.getTo()).setTo(owner);
SendHelper.send(client, message);
}
}
}
}
}
return null;
}
}
public class FileHandler implements SocketHandler { @Override
public Object handle(Socket client, Object data) {
if (client != null) {
FileMessage message = JSON.parseObject(data.toString(), FileMessage.class);
if (StringUtil.isNotEmpty(message.getFrom()) && StringUtil.isNotEmpty(message.getTo())) {
// exist & send
if (SocketHolder.keySet().contains(message.getFrom())) {
if (!ConstantValue.TO_ALL.equals(message.getTo())) { // one-to-all
SocketWrapper wrapper = SocketHolder.get(message.getTo());
if (wrapper != null) {
SendHelper.send(wrapper.getSocket(), message);
try {
if (client != null && wrapper.getSocket() != null && message.getSize() > 0) {
InputStream is = client.getInputStream();
OutputStream os = wrapper.getSocket().getOutputStream();
int total = 0;
while (!client.isClosed() && !wrapper.getSocket().isClosed()) {
if (is.available() > 0) {
byte[] buff = new byte[ConstantValue.BUFF_SIZE];
int len = -1;
while (is.available() > 0 && (len = is.read(buff)) != -1) {
os.write(buff, 0, len);
total += len;
LoggerUtil.debug("SEND BUFF [" + len + "]");
}
os.flush();
if (total >= message.getSize()) {
LoggerUtil.info("SEND BUFF [OK]");
break;
}
}
}
// AFTER SEND FILE
// SEND SUCCESSFULLY
ReturnMessage result = new ReturnMessage().setKey(Key.TIP)
.setSuccess(true)
.setContent(I18N.INFO_FILE_SEND_SUCCESSFULLY);
result.setFrom(message.getTo()).setTo(message.getFrom())
.setOwner(ConstantValue.SERVER_NAME);
SendHelper.send(client, result);
// RECEIVE SUCCESSFULLY
result.setContent(I18N.INFO_FILE_RECEIVE_SUCCESSFULLY)
.setFrom(message.getFrom())
.setTo(message.getTo());
SendHelper.send(wrapper.getSocket(), result);
}
} catch (Exception e) {
LoggerUtil.error("Handle file failed !" + e.getMessage(), e);
}
}
}
}
}
}
return null;
}
}
/**
* LoginHandler
*
* @author yaolin
*
*/
public class LoginHandler implements SocketHandler { private UsrService usrService = new UsrService(); @Override
public Object handle(Socket client, Object data) {
ReturnMessage result = new ReturnMessage();
result.setSuccess(false);
if (data != null) {
LoginMessage message = JSON.parseObject(data.toString(), LoginMessage.class);
if (StringUtil.isNotEmpty(message.getUsername()) && StringUtil.isNotEmpty(message.getPassword())) {
if (usrService.login(message.getUsername(), message.getPassword()) != null) {
result.setSuccess(true);
} else {
result.setMessage(I18N.INFO_LOGIN_ERROR_DATA);
}
result.setFrom(ConstantValue.SERVER_NAME).setTo(message.getUsername());
} else {
result.setMessage(I18N.INFO_LOGIN_EMPTY_DATA);
}
// AFTER LOGIN
result.setKey(Key.LOGIN);
if (result.isSuccess()) { // HOLD SOCKET
SocketHolder.put(result.getTo(), new SocketWrapper(client, new Date()));
}
SendHelper.send(client, result);
if (result.isSuccess()) { // SEND LIST USER
ClientListUserDTO dto = new ClientListUserDTO();
dto.setListUser(SocketHolder.keySet());
result.setContent(dto).setKey(Key.LISTUSER);
SendHelper.send(client, result);
}
}
return null;
} }
public class LogoutHandler implements SocketHandler { @Override
public Object handle(Socket client, Object data) {
if (data != null) {
LogoutMessage message = JSON.parseObject(data.toString(), LogoutMessage.class);
if (message != null && StringUtil.isNotEmpty(message.getFrom())) {
SocketWrapper wrapper = SocketHolder.get(message.getFrom());
Socket socket = wrapper.getSocket();
if (socket != null) {
try {
socket.close();
socket = null;
} catch (Exception ignore) {
}
}
SocketHolder.remove(message.getFrom());
}
}
return null;
} }
public class RegisterHandler implements SocketHandler { private UsrService usrService = new UsrService(); @Override
public Object handle(Socket client, Object data) {
ReturnMessage result = new ReturnMessage();
result.setSuccess(false).setFrom(ConstantValue.SERVER_NAME);
if (data != null) {
RegisterMessage message = JSON.parseObject(data.toString(), RegisterMessage.class);
if (StringUtil.isNotEmpty(message.getUsername()) && StringUtil.isNotEmpty(message.getPassword())) {
if (usrService.register(message.getUsername(), message.getPassword()) != null) {
result.setSuccess(true).setContent(I18N.INFO_REGISTER_OK);
} else {
result.setMessage(I18N.INFO_REGISTER_CLIENT_EXIST);
}
} else {
result.setMessage(I18N.INFO_REGISTER_EMPTY_DATA);
} if (StringUtil.isNotEmpty(message.getUsername())) {
result.setTo(message.getUsername());
}
// AFTER REGISTER
result.setKey(Key.REGISTER);
SendHelper.send(client, result);
}
return null;
} }
/**
* Use SendHelper to send ReturnMessage,
* @see yaolin.chat.server.SocketDispatcher#run()
* @author yaolin
*/
@Deprecated
public class ReturnHandler implements SocketHandler { /**
* @param data ReturnMessage
*/
@Override
public Object handle(Socket client, Object data) {
if (data != null) {
ReturnMessage message = (ReturnMessage) data;
if(StringUtil.isNotEmpty(message.getFrom()) && StringUtil.isNotEmpty(message.getTo())) {
SocketWrapper wrap = SocketHolder.get(message.getTo());
if (wrap != null) {
SendHelper.send(wrap.getSocket(), message);
}
}
}
return null;
} }
用户业务:
服务端除了socket之外,还有一点点具体的业务,那就是用户的注册、登陆等,这里简单的列出Usr和UsrService这两个类,这些业务暂时没有怎么实现,我并不打算在这个程序中引入ORM框架,所以自己写一套DBUtil(待改善),在这里也一并贴出来。
这里只进行了简单的校验,没有持久化存储到DB中,下面是Usr和UsrService:
public class Usr { private long id;
private String username;
private String password;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
/**
* // TODO
* @see yaolin.chat.server.usr.repository.UsrRepository
* @author yaolin
*
*/
public class UsrService {
// TODO db
private static Map<String,Usr> db = new HashMap<String,Usr>(); public Usr register(String username, String password) {
if (StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) {
return null;
}
if (db.containsKey(username)) {
return null; // exist;
}
Usr usr = new Usr();
usr.setUsername(username);
usr.setPassword(MD5Util.getMD5Code(password));
db.put(username, usr);
return usr;
} public Usr login(String username, String password) {
if (StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) {
return null;
}
if (db.containsKey(username)) {
Usr usr = db.get(username);
if (MD5Util.getMD5Code(password).equals(usr.getPassword())) {
return usr;
}
}
return null;
}
}
下面是DBUtil工具:
/**
* DBUtils // TODO 有待调整&优化!!
* @author yaolin
*/
public class DBUtil {
// make connection used repeatedly
private static final List<Connection> cache = new LinkedList<Connection>();
private static String url;
private static String driver;
private static String user;
private static String password;
private static Boolean debug; static {
InputStream is = DBUtil.class.getResourceAsStream("/db.properties");
try {
Properties p = new Properties();
p.load(is);
url = p.getProperty("url");
driver = p.getProperty("driver");
user = p.getProperty("user");
password = p.getProperty("password");
// just for debug
try {
debug = Boolean.valueOf(p.getProperty("debug"));
} catch (Exception ignore) {
debug = false;
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (is != null) {
try {
is.close();
is = null;
} catch (Exception ignore) {
}
}
}
} public synchronized static Connection getConnection() {
if (cache.isEmpty()) {
cache.add(makeConnection());
}
Connection conn = null;
int i = 0;
try {
do {
conn = cache.remove(i);
} while (conn != null && conn.isClosed() && i < cache.size());
} catch (Exception ignore) {
} try {
if (conn == null || conn.isClosed()) {
cache.add(makeConnection());
conn = cache.remove(0);
}
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
} public synchronized static void close(Connection connection) {
try {
if (connection != null && !connection.isClosed()) {
if (debug)
debug("release connection!");
cache.add(connection);
}
} catch (SQLException ignore) {
}
} public static Object query(String sql, ResultSetMapper mapper, Object... args) {
if (debug)
debug(sql);
Connection conn = getConnection();
PreparedStatement ps = null;
ResultSet rs = null;
Object result = null;
try {
ps = conn.prepareStatement(sql);
int i = 1;
for (Object object : args) {
ps.setObject(i++, object);
}
rs = ps.executeQuery();
result = mapper.mapper(rs);
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (rs != null) {
rs.close();
rs = null;
}
if (ps != null) {
ps.close();
ps = null;
}
} catch (Exception ignore) {
}
}
close(conn);
return result;
} public static int modify(String sql, Object... args) {
if (debug)
debug(sql);
Connection conn = getConnection();
PreparedStatement ps = null;
int row = 0;
try {
ps = conn.prepareStatement(sql);
int i = 1;
for (Object object : args) {
ps.setObject(i++, object);
}
row = ps.executeUpdate();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (ps != null) {
ps.close();
ps = null;
}
} catch (Exception ignore) {
}
}
close(conn);
return row;
} public static int[] batch(List<String> sqls) {
if (debug)
debug(sqls.toString());
Connection conn = getConnection();
Statement stmt = null;
int[] row;
try {
stmt = conn.createStatement();
for (String sql : sqls) {
stmt.addBatch(sql);
}
row = stmt.executeBatch();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (stmt != null) {
stmt.close();
stmt = null;
}
} catch (Exception ignore) {
}
}
close(conn);
return row;
} public static int[] batch(String sql, PreparedStatementSetter setter) {
if (debug)
debug(sql);
Connection conn = getConnection();
PreparedStatement ps = null;
int[] row;
try {
ps = conn.prepareStatement(sql);
setter.setter(ps);
row = ps.executeBatch();
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
try {
if (ps != null) {
ps.close();
ps = null;
}
} catch (Exception ignore) {
}
}
close(conn);
return row;
} private static Connection makeConnection() {
try {
Class.forName(driver).newInstance();
Connection conn = DriverManager.getConnection(url, user, password);
if (debug)
debug("create connection!");
return conn;
} catch (Exception e) {
throw new RuntimeException(e);
}
} private static void debug(String sqls) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date())
+ " DEBUG " + Thread.currentThread().getId()
+ " --- [" + Thread.currentThread().getName() + "] " + "excute sqls : " + sqls);
}
}
/**
* PreparedStatementSetter
* @author yaolin
*/
public interface PreparedStatementSetter {
public void setter(PreparedStatement ps);
}
/**
* ResultSetMapper
* @author yaolin
*/
public interface ResultSetMapper {
public Object mapper(ResultSet rs);
}
对于DBUtil的使用参考我之前写过了一个小demo,目前挂在github上:https://github.com/jAVA-yaolin/web
未完,待续。
(本文所有的代码已经挂在csdn的仓库中 :地址 https://code.csdn.net/yaoIin/niloay-chat ,完整代码参考这里,有时间将更新)
Socket聊天程序——服务端的更多相关文章
- Socket聊天程序——Common
写在前面: 上一篇记录了Socket聊天程序的客户端设计,为了记录的完整性,这里还是将Socket聊天的最后一个模块--Common模块记录一下.Common的设计如下: 功能说明: Common模块 ...
- 用c++语言socket库函数实现服务端客户端聊天室
客户端 /* * 程序名:client.cpp,此程序用于演示socket的客户端 * 作者:C语言技术网(www.freecplus.net) 日期:20190525 */ #include < ...
- Socket聊天程序——客户端
写在前面: 上周末抽点时间把自己写的一个简单Socket聊天程序的初始设计和服务端细化设计记录了一下,周二终于等来毕业前考的软考证书,然后接下来就是在加班的日子度过了,今天正好周五,打算把客户端的详细 ...
- java实现微信小程序服务端(登录)
微信小程序如今被广泛使用,微信小程序按照微信官网的定义来说就是: 微信小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验. 这就是微信小程序的魅力所在,有 ...
- 简单的同步Socket程序服务端
首先,Socket是.Net提供的 System.Net.Sockets命名空间的Scoket类为网络通信提供了一套丰富的方法和属性 服务器按照Socket的基本流程 先创建Socket 在用Bind ...
- Socket聊天程序——初始设计
写在前面: 可能是临近期末了,各种课程设计接踵而来,最近在csdn上看到2个一样问答(问题A,问题B),那就是编写一个基于socket的聊天程序,正好最近刚用socket做了一些事,出于兴趣,自己抽了 ...
- socket 客户端和服务端通信
客户端要连接服务器:首先要知道服务器的IP地址.而服务器里有很多的应用程序,每一个应用程序对应一个端口号 所以客户端想要与服务器中的某个应用程序进行通信就必须要知道那个应用程序的所在服务器的IP地址, ...
- Socket通信时服务端无响应,客户端超时设置
背景:在写一个客户端的socket程序,服务端没有返回消息,客户端一直在等待. 目标:我需要设置一个时间,如果超过这个时间客户端自动断开连接.最好是在服务端实现,客户端对我来说不可控.
- 基于 IOCP 的通用异步 Windows Socket TCP 高性能服务端组件的设计与实现
设计概述 服务端通信组件的设计是一项非常严谨的工作,其中性能.伸缩性和稳定性是必须考虑的硬性质量指标,若要把组件设计为通用组件提供给多种已知或未知的上层应用使用,则设计的难度更会大大增加,通用性.可用 ...
随机推荐
- JavaScript之父Brendan Eich,Clojure 创建者Rich Hickey,Python创建者Van Rossum等编程大牛对程序员的职业建议
软件开发是现时很火的职业.据美国劳动局发布的一项统计数据显示,从2014年至2024年,美国就业市场对开发人员的需求量将增长17%,而这个增长率比起所有职业的平均需求量高出了7%.很多人年轻人会选择编 ...
- FFmpeg学习6:视音频同步
在上一篇文章中,视频和音频是各自独立播放的,并不同步.本文主要描述了如何以音频的播放时长为基准,将视频同步到音频上以实现视音频的同步播放的.主要有以下几个方面的内容 视音频同步的简单介绍 DTS 和 ...
- 如何一步一步用DDD设计一个电商网站(四)—— 把商品卖给用户
阅读目录 前言 怎么卖 领域服务的使用 回到现实 结语 一.前言 上篇中我们讲述了“把商品卖给用户”中的商品和用户的初步设计.现在把剩余的“卖”这个动作给做了.这里提醒一下,正常情况下,我们的每一步业 ...
- .NET平台开源项目速览(15)文档数据库RavenDB-介绍与初体验
不知不觉,“.NET平台开源项目速览“系列文章已经15篇了,每一篇都非常受欢迎,可能技术水平不高,但足够入门了.虽然工作很忙,但还是会抽空把自己知道的,已经平时遇到的好的开源项目分享出来.今天就给大家 ...
- python通过protobuf实现rpc
由于项目组现在用的rpc是基于google protobuf rpc协议实现的,所以花了点时间了解下protobuf rpc.rpc对于做分布式系统的人来说肯定不陌生,对于rpc不了解的童鞋可以自行g ...
- 免费高效实用的.NET操作Excel组件NPOI(.NET组件介绍之六)
很多的软件项目几乎都包含着对文档的操作,前面已经介绍过两款操作文档的组件,现在介绍一款文档操作的组件NPOI. NPOI可以生成没有安装在您的服务器上的Microsoft Office套件的Excel ...
- Spring之初体验
Spring之初体验 Spring是一个轻量级的Java Web开发框架,以IoC(Inverse of Control 控制反转)和 ...
- linux-centos在VM中的网络配置
1.自动获取IP地址 虚拟机使用桥接模式,相当于连接到物理机的网络里,物理机网络有DHCP服务器自动分配IP地址. #dhclient 自动获取ip地址命令 #ifconfig 查询系统里网卡信息,i ...
- 归并排序的java实现
归并排序的优点不说了. 做归并排序之前,我先试着将两个有序数组进行排序,合并成一个有序数组. 思路:定义好两个有序数组,理解的时候我先思考了数组只有一个数组的排序,然后是两个元素的数组的排序,思路就有 ...
- JS高级前端开发群加群说明及如何晋级
JS高级前端开发群加群说明 一.文章背景: 二. 高级群: 三. 加入方式: 四. 说明: 一.文章背景: 去年年初建了几个群,在不经意间火了,一直排在“前端开发”关键字搜索结果第一名.当然取得这 ...