linux 下websocket server demo例程
来源:互联网 发布:小型宝塔数控机床编程 编辑:程序博客网 时间:2024/06/05 19:00
websocket的协议,原理参考文档:WebScoket 规范 + WebSocket 协议。
主要步骤:1、创建socket套接字进行监听客户端;
2、握手,当与客户端建立tcp连接后,客户端会发送websocket请求,此时,服务器端需要提取客户端在websocket请求中包含一个握手的唯一Key,服务端在拿到这个Key后,需要加入一个GUID,然后进行sha1的加密,再转换成base64,最后再发回到客户端。这样就完成了一次握手。此种握手方式是针对chrome websocket 13的版本,其他版本的可能会有所不同。握手只需一次即可,握手成功后进行正常的数据通信。
3、后面的过程跟tcp通信一样;
附上代码:
websocket.c
/********************************************************************************* * Copyright: (C) 2017 Yang Zheng<yz2012ww@gmail.com> * All rights reserved. * * Filename: websocket.c * Description: This file * * Version: 1.0.0(08/17/2017~) * Author: Yang Zheng <yz2012ww@gmail.com.com> * ChangeLog: 1, Release initial version on "08/17/2017 02:03:22 PM" * ********************************************************************************/#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include "base64.h"#include "sha1.h"#include "intlib.h"#define REQUEST_LEN_MAX 1024#define DEFEULT_SERVER_PORT 8000#define WEB_SOCKET_KEY_LEN_MAX 256#define RESPONSE_HEADER_LEN_MAX 1024#define PER_LINE_MAX 256/************************************************************************************** * Function Name: extract_client_key * Description: 提取客户端发送的handshake key值 * Input Args: @buffer 客户端发送的握手数据 * Output Args: 输出客户端发来handshake key * Return Value: server_key 客户端发来的handshake key *************************************************************************************/static char *extract_client_key(const char * buffer){ char *key = NULL; char *start = NULL; /* 要提取字符串的起始地址 */ char *flag = "Sec-WebSocket-Key: "; int i = 0; int buf_len = 0; if(NULL == buffer) { printf("buffer is NULL.\n"); return NULL; } key=(char *)malloc(WEB_SOCKET_KEY_LEN_MAX); if (NULL == key) { printf("key alloc failure.\n"); return NULL; } memset(key,0, WEB_SOCKET_KEY_LEN_MAX); start = strstr(buffer, flag); if(NULL == start) { printf("start is NULL.\n"); return NULL; } start += strlen(flag); buf_len = strlen(buffer); for(i=0;i<buf_len;i++) { if(start[i]==0x0A || start[i]==0x0D) /* 回车换行标志 */ break; key[i] = start[i]; } return key;} /* ----- End of extract_client_key() ----- *//************************************************************************************** * Function Name: calculate_accept_key * Description: 计算handshake key值 * Input Args: @buffer 客户端发送的握手数据 * Output Args: 输出服务器发送的handshake key * Return Value: server_key 服务器发送的handshake key *************************************************************************************/static char *calculate_accept_key(const char * buffer){ int i; int n; char *client_key = NULL; char *server_key = NULL; char *sha1_data_tmp = NULL; char *sha1_data = NULL; const char *guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; /* "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 存放于.rodata段 */ if(NULL == buffer) { printf("buffer is NULL.\n"); return NULL; }#if 0 client_key = (char *)malloc(PER_LINE_MAX); if (NULL == client_key) { printf("client_key alloc failure.\n"); return NULL; } memset(client_key, 0, PER_LINE_MAX);#endif client_key = extract_client_key(buffer); if(NULL == client_key) { printf("client_key is NULL."); return NULL; } strcat(client_key, guid); /* 构建新key */ sha1_data_tmp = sha1_hash(client_key); /* hash 加密 */ n = strlen(sha1_data_tmp); sha1_data=(char *)malloc(n/2+1); if (NULL == sha1_data) { printf("sha1_data alloc failure.\n"); return NULL; } memset(sha1_data,0,n/2+1); for(i=0; i<n; i+=2) sha1_data[i/2] = htoi(sha1_data_tmp, i, 2); server_key = base64_encode(sha1_data, strlen(sha1_data)); if (client_key != NULL) { free(client_key); client_key = NULL; } if (sha1_data != NULL) { free(sha1_data); sha1_data = NULL; } if (sha1_data_tmp != NULL) { free(sha1_data_tmp); sha1_data_tmp = NULL; } return server_key;} /* ----- End of calculate_accept_key() ----- *//************************************************************************************** * Function Name: websocket_shakehand * Description: 服务器发送握手消息 * Input Args: @conn_fd 连接句柄 * @server_key 服务器加密后的handshake key * Output Args: 无 * Return Value: 无 *************************************************************************************/static void websocket_shakehand(int conn_fd, const char *server_key){ char response_header[RESPONSE_HEADER_LEN_MAX]; if(!conn_fd) { printf("connfd is error.\n"); return ; } if(NULL == server_key) { printf("server_key is NULL.\n"); return ; } memset(response_header,'\0',RESPONSE_HEADER_LEN_MAX); sprintf(response_header, "HTTP/1.1 101 Switching Protocols\r\n"); sprintf(response_header, "%sUpgrade: websocket\r\n", response_header); sprintf(response_header, "%sConnection: Upgrade\r\n", response_header); sprintf(response_header, "%sSec-WebSocket-Accept: %s\r\n\r\n", response_header, server_key); printf("Response Header:%s\n", response_header); write(conn_fd, response_header, strlen(response_header));} /* ----- End of websocket_shakehand() ----- *//************************************************************************************** * Function Name: deal_data * Description: 处理客户端数据 * Input Args: @buffer 接收的数据 * @buf_len 接收的数据长度 * Output Args: 无 * Return Value: payload_data 返回负载数据 *************************************************************************************/char *deal_data(const char *buffer,const int buf_len){ int i = 0; char fin; char mask_flag; char masks[4]; char *payload_data = NULL; char temp[8]; unsigned long n; unsigned long payloadLen = 0; if (buf_len < 2) { printf("buf_len less than 2.\n"); return NULL; } fin = (buffer[0] & 0x80) == 0x80; // 1bit,1表示最后一帧 if (!fin) { printf("fin is error.\n"); return NULL;// 超过一帧暂不处理 } mask_flag = (buffer[1] & 0x80) == 0x80; // 是否包含掩码 if (!mask_flag) { printf("no mask.\n"); return NULL;// 不包含掩码的暂不处理 } payloadLen = buffer[1] & 0x7F; // 数据长度 if (payloadLen == 126) { memcpy(masks, buffer+4, 4); payloadLen =(buffer[2]&0xFF) << 8 | (buffer[3]&0xFF); payload_data=(char *)malloc(payloadLen); memset(payload_data,0,payloadLen); memcpy(payload_data,buffer+8,payloadLen); } else if (payloadLen == 127) { memcpy(masks,buffer+10,4); for ( i = 0; i < 8; i++) temp[i] = buffer[9 - i]; memcpy(&n,temp,8); payload_data=(char *)malloc(n); memset(payload_data,0,n); memcpy(payload_data,buffer+14,n);//toggle error(core dumped) if data is too long. payloadLen=n; } else { memcpy(masks,buffer+2,4); payload_data=(char *)malloc(payloadLen); memset(payload_data,0,payloadLen); memcpy(payload_data,buffer+6,payloadLen); } for (i = 0; i < payloadLen; i++) payload_data[i] = (char)(payload_data[i] ^ masks[i % 4]); printf("data(%ld):%s\n", payloadLen, payload_data); return payload_data;} /* ----- End of deal_data() ----- *//************************************************************************************** * Function Name: construct_packet_data * Description: 组建websocket数据包 * Input Args: @message 发送的数据 * @len 发送数据长度 * Output Args: 无 * Return Value: data 返回组建后的包 *************************************************************************************/static char *construct_packet_data(const char *message, unsigned long *len){ char *data = NULL; unsigned long n; if (NULL == message) { printf("message is NULL.\n"); return NULL; } n = strlen(message); if (n < 126) { data=(char *)malloc(n+2); if (NULL == data) { printf("data is NNLL.\n"); return NULL; } memset(data,0,n+2); data[0] = 0x81; data[1] = n; memcpy(data+2,message,n); *len=n+2; } else if (n < 0xFFFF) { data=(char *)malloc(n+4); if (NULL == data) { printf("data is NNLL.\n"); return NULL; } memset(data,0,n+4); data[0] = 0x81; data[1] = 126; data[2] = (n>>8 & 0xFF); data[3] = (n & 0xFF); memcpy(data+4,message,n); *len=n+4; } else { // 暂不处理超长内容 *len=0; } return data;} /* ----- End of construct_packet_data() ----- *//************************************************************************************** * Function Name: response * Description: 响应客户端 * Input Args: @conn_fd 连接句柄 * @message 发送的数据 * Output Args: 无 * Return Value: 无 *************************************************************************************/void response(int conn_fd, const char *message){ char *data = NULL; unsigned long n=0; if(!conn_fd) { printf("conn_fd is error.\n"); return ; } if(NULL == message) { printf("message is NULL.\n"); return ; } data = construct_packet_data(message, &n); if(NULL == data || n <= 0) { printf("data is empty!\n"); return ; } write(conn_fd, data, n); if (NULL == data) { free(data); data = NULL; }} /* ----- End of response() ----- *//******************************************************************************** * Function Name: * Description: * Input Args: * Output Args: * Return Value: ********************************************************************************/int main(int argc, char *argv[]){ int listen_fd; int conn_fd; char buf[REQUEST_LEN_MAX]; char *data = NULL; char str[INET_ADDRSTRLEN]; char *sec_websocket_key = NULL; int n; int connected = 0;//0:not connect.1:connected. int port = DEFEULT_SERVER_PORT; struct sockaddr_in servaddr; struct sockaddr_in cliaddr; socklen_t cliaddr_len; if(argc > 1) port = atoi(argv[1]); if(port<=0 || port>0xFFFF) { printf("Port(%d) is out of range(1-%d)\n", port, 0xFFFF); return -1; } listen_fd = socket(AF_INET, SOCK_STREAM, 0); bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); listen(listen_fd, 20); printf("Listen %d\nAccepting connections ...\n",port); cliaddr_len = sizeof(cliaddr); conn_fd = accept(listen_fd, (struct sockaddr *)&cliaddr, &cliaddr_len); printf("From %s at PORT %d\n", \ inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port)); while (1) { memset(buf, 0, REQUEST_LEN_MAX); n = read(conn_fd, buf, REQUEST_LEN_MAX); printf("---------------------\n"); if(!connected) { printf("read:%d\n%s\n", n, buf); sec_websocket_key = calculate_accept_key(buf); websocket_shakehand(conn_fd, sec_websocket_key); if (sec_websocket_key != NULL) { free(sec_websocket_key); sec_websocket_key = NULL; } connected=1; continue; } data = deal_data(buf, n); response(conn_fd, data); } close(conn_fd); return 0;} /* ----- End of main() ----- */
sha1.c
/********************************************************************************* * Copyright: (C) 2017 Yang Zheng<yz2012ww@gmail.com> * All rights reserved. * * Filename: sha1.c * Description: This file * * Version: 1.0.0(08/17/2017~) * Author: Yang Zheng <yz2012ww@gmail.com> * ChangeLog: 1, Release initial version on "08/17/2017 02:08:20 PM" * ********************************************************************************/#include "sha1.h"static void sha1_process_message_block(sha1_context*);static void sha1_pad_message(sha1_context*);static void sha1_reset(sha1_context *context) // 初始化动作{ context->length_low = 0; context->length_high = 0; context->message_block_index = 0; context->message_digest[0] = 0x67452301; context->message_digest[1] = 0xEFCDAB89; context->message_digest[2] = 0x98BADCFE; context->message_digest[3] = 0x10325476; context->message_digest[4] = 0xC3D2E1F0; context->computed = 0; context->corrupted = 0;}static int sha1_result(sha1_context *context) // 成功返回1,失败返回0{ if (context->corrupted) { return 0; } if (!context->computed) { sha1_pad_message(context); context->computed = 1; } return 1;}static void sha1_input(sha1_context *context,const char *message_array,unsigned length){ if (!length) return; if (context->computed || context->corrupted){ context->corrupted = 1; return; } while(length-- && !context->corrupted){ context->message_block[context->message_block_index++] = (*message_array & 0xFF); context->length_low += 8; context->length_low &= 0xFFFFFFFF; if (context->length_low == 0){ context->length_high++; context->length_high &= 0xFFFFFFFF; if (context->length_high == 0) context->corrupted = 1; } if (context->message_block_index == 64){ sha1_process_message_block(context); } message_array++; }}static void sha1_process_message_block(sha1_context *context){ const unsigned K[] = {0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6 }; int t; unsigned temp; unsigned W[80]; unsigned A, B, C, D, E; for(t = 0; t < 16; t++) { W[t] = ((unsigned) context->message_block[t * 4]) << 24; W[t] |= ((unsigned) context->message_block[t * 4 + 1]) << 16; W[t] |= ((unsigned) context->message_block[t * 4 + 2]) << 8; W[t] |= ((unsigned) context->message_block[t * 4 + 3]); } for(t = 16; t < 80; t++) W[t] = SHA1_CIRCULAR_SHIFT(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); A = context->message_digest[0]; B = context->message_digest[1]; C = context->message_digest[2]; D = context->message_digest[3]; E = context->message_digest[4]; for(t = 0; t < 20; t++) { temp = SHA1_CIRCULAR_SHIFT(5,A) + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; temp &= 0xFFFFFFFF; E = D; D = C; C = SHA1_CIRCULAR_SHIFT(30,B); B = A; A = temp; } for(t = 20; t < 40; t++) { temp = SHA1_CIRCULAR_SHIFT(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; temp &= 0xFFFFFFFF; E = D; D = C; C = SHA1_CIRCULAR_SHIFT(30,B); B = A; A = temp; } for(t = 40; t < 60; t++) { temp = SHA1_CIRCULAR_SHIFT(5,A) + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; temp &= 0xFFFFFFFF; E = D; D = C; C = SHA1_CIRCULAR_SHIFT(30,B); B = A; A = temp; } for(t = 60; t < 80; t++) { temp = SHA1_CIRCULAR_SHIFT(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; temp &= 0xFFFFFFFF; E = D; D = C; C = SHA1_CIRCULAR_SHIFT(30,B); B = A; A = temp; } context->message_digest[0] = (context->message_digest[0] + A) & 0xFFFFFFFF; context->message_digest[1] = (context->message_digest[1] + B) & 0xFFFFFFFF; context->message_digest[2] = (context->message_digest[2] + C) & 0xFFFFFFFF; context->message_digest[3] = (context->message_digest[3] + D) & 0xFFFFFFFF; context->message_digest[4] = (context->message_digest[4] + E) & 0xFFFFFFFF; context->message_block_index = 0;}static void sha1_pad_message(sha1_context* context){ if (context->message_block_index > 55) { context->message_block[context->message_block_index++] = 0x80; while(context->message_block_index < 64) context->message_block[context->message_block_index++] = 0; sha1_process_message_block(context); while(context->message_block_index < 56) context->message_block[context->message_block_index++] = 0; } else { context->message_block[context->message_block_index++] = 0x80; while(context->message_block_index < 56) context->message_block[context->message_block_index++] = 0; } context->message_block[56] = (context->length_high >> 24 ) & 0xFF; context->message_block[57] = (context->length_high >> 16 ) & 0xFF; context->message_block[58] = (context->length_high >> 8 ) & 0xFF; context->message_block[59] = (context->length_high) & 0xFF; context->message_block[60] = (context->length_low >> 24 ) & 0xFF; context->message_block[61] = (context->length_low >> 16 ) & 0xFF; context->message_block[62] = (context->length_low >> 8 ) & 0xFF; context->message_block[63] = (context->length_low) & 0xFF; sha1_process_message_block(context);}#define SHA1_SIZE 128char *sha1_hash(const char *source){ sha1_context sha; char *buf = NULL; sha1_reset(&sha); sha1_input(&sha, source, strlen(source)); if (!sha1_result(&sha)){ printf("SHA1 ERROR: Could not compute message digest"); return NULL; } else { buf = (char *)malloc(SHA1_SIZE); if (NULL == buf) { printf("buf is NULL.\n"); return NULL; } memset(buf, 0, sizeof(SHA1_SIZE)); sprintf(buf, "%08X%08X%08X%08X%08X", sha.message_digest[0],sha.message_digest[1], sha.message_digest[2],sha.message_digest[3],sha.message_digest[4]); //lr_save_string(buf, lrvar); //return strlen(buf); return buf; }}
sha1.h
/******************************************************************************** * Copyright: (C) 2017 Yang Zheng<yz2012ww@gmail.com> * All rights reserved. * * Filename: sha1.h * Description: This head file * * Version: 1.0.0(08/17/2017~) * Author: Yang Zheng <yz2012ww@gmail.com> * ChangeLog: 1, Release initial version on "08/17/2017 02:11:08 PM" * ********************************************************************************/#ifndef __SHA1_H__#define __SHA1_H__#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct _sha1_context{unsigned message_digest[5]; unsigned length_low; unsigned length_high; unsigned char message_block[64]; int message_block_index; int computed; int corrupted; }sha1_context;#define SHA1_CIRCULAR_SHIFT(bits,word) ((((word) << (bits)) & 0xFFFFFFFF) | ((word) >> (32-(bits))))char *sha1_hash(const char *source);#endif
intlib.c
/********************************************************************************* * Copyright: (C) 2017 Yang Zheng<yz2012ww@gmail.com> * All rights reserved. * * Filename: intlib.c * Description: This file * * Version: 1.0.0(08/17/2017~) * Author: Yang Zheng <yz2012ww@gmail.com> * ChangeLog: 1, Release initial version on "08/17/2017 02:09:51 PM" * ********************************************************************************/int tolower(int c) { if (c >= 'A' && c <= 'Z') { return c + 'a' - 'A'; } else { return c; } } int htoi(const char s[],int start,int len) { int i,j; int n = 0; if (s[0] == '0' && (s[1]=='x' || s[1]=='X')) //判断是否有前导0x或者0X { i = 2; } else { i = 0; } i+=start; j=0; for (; (s[i] >= '0' && s[i] <= '9') || (s[i] >= 'a' && s[i] <= 'f') || (s[i] >='A' && s[i] <= 'F');++i) { if(j>=len) { break; } if (tolower(s[i]) > '9') { n = 16 * n + (10 + tolower(s[i]) - 'a'); } else { n = 16 * n + (tolower(s[i]) - '0'); } j++; } return n; }
intlib.h
/******************************************************************************** * Copyright: (C) 2017 Yang Zheng<yz2012ww@gmail.com> * All rights reserved. * * Filename: intlib.h * Description: This head file * * Version: 1.0.0(08/17/2017~) * Author: Yang Zheng <yz2012ww@gmail.com> * ChangeLog: 1, Release initial version on "08/17/2017 02:10:46 PM" * ********************************************************************************/#ifndef __INT_LIB_H__#define __INT_LIB_H__int tolower(int c);int htoi(const char s[],int start,int len);#endif
/********************************************************************************* * Copyright: (C) 2017 Yang Zheng<yz2012ww@gmail.com> * All rights reserved. * * Filename: base64.c * Description: This file * * Version: 1.0.0(08/17/2017~) * Author: Yang Zheng <yz2012ww@gmail.com> * ChangeLog: 1, Release initial version on "08/17/2017 02:09:12 PM" * ********************************************************************************/#include "base64.h"const char base[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; char *base64_encode(const char* data, int data_len) { int prepare = 0; int ret_len; int temp = 0; char *ret = NULL; char *f = NULL; int tmp = 0; unsigned char changed[4]; int i = 0; ret_len = data_len / 3; temp = data_len % 3; if (temp > 0) ret_len += 1; ret_len = ret_len*4 + 1; ret = (char *)malloc(ret_len); if ( ret == NULL) { printf("ret alloc failure.\n"); return NULL; } memset(ret, 0, ret_len); f = ret; while (tmp < data_len) { temp = 0; prepare = 0; memset(changed, '\0', 4); while (temp < 3) { if (tmp >= data_len) break; prepare = ((prepare << 8) | (data[tmp] & 0xFF)); tmp++; temp++; } prepare = (prepare<<((3-temp)*8)); for (i=0; i<4 ;i++) { if (temp < i) changed[i] = 0x40; else changed[i] = (prepare>>((3-i)*6)) & 0x3F; *f = base[changed[i]]; f++; } } *f = '\0'; return ret; } static char find_pos(char ch) { char *ptr = (char*)strrchr(base, ch);//the last position (the only) in base[] return (ptr - base); } char *base64_decode(const char *data, int data_len) { int ret_len = (data_len / 4) * 3; int equal_count = 0; char *ret = NULL; char *f = NULL; int tmp = 0; int temp = 0; char need[3]; int prepare = 0; int i = 0; if (*(data + data_len - 1) == '=') equal_count += 1; if (*(data + data_len - 2) == '=') equal_count += 1; if (*(data + data_len - 3) == '=') equal_count += 1; switch (equal_count) { case 0: ret_len += 4;//3 + 1 [1 for NULL] break; case 1: ret_len += 4;//Ceil((6*3)/8)+1 break; case 2: ret_len += 3;//Ceil((6*2)/8)+1 break; case 3: ret_len += 2;//Ceil((6*1)/8)+1 break; } ret = (char *)malloc(ret_len); if (NULL == ret) { printf("ret alloc failure.\n"); return NULL; } memset(ret, 0, ret_len); f = ret; while (tmp < (data_len - equal_count)) { temp = 0; prepare = 0; memset(need, 0, 4); while (temp < 4) { if (tmp >= (data_len - equal_count)) break; prepare = (prepare << 6) | (find_pos(data[tmp])); temp++; tmp++; } prepare = prepare << ((4-temp) * 6); for (i=0; i<3; i++) { if (i == temp) break; *f = (char)((prepare>>((2-i)*8)) & 0xFF); f++; } } *f = '\0'; return ret; }
base64.h
/******************************************************************************** * Copyright: (C) 2017 Yang Zheng<yz2012ww@gmail.com> * All rights reserved. * * Filename: base64.h * Description: This head file * * Version: 1.0.0(08/17/2017~) * Author: Yang Zheng <yz2012ww@gmail.com> * ChangeLog: 1, Release initial version on "08/17/2017 02:11:15 PM" * ********************************************************************************/#ifndef __BASE64_H__#define __BASE64_H__ #include <stdio.h> #include <stdlib.h>#include <string.h>char* base64_encode(const char* data, int data_len); char *base64_decode(const char* data, int data_len); #endif
测试server的方法
下载浏览器websocket插件,我用chrome浏览器测试,具体操作可参考:
阅读全文
0 0
- linux 下websocket server demo例程
- linux 下 tcp server 的 demo
- HTML5 websocket linux/c server
- websocket-demo
- WebSocket Server
- FreeRTOS 队列例程Demo
- linux下让lighttpd支持websocket
- html5 websocket demo
- php websocket 简单DEMO
- webSocket 入门demo
- websocket使用demo
- HTML5 WebSocket DEMO示例
- java websocket demo
- Spring Boot + WebSocket Demo
- java的WebSocket Demo
- Netty的websocket Demo
- Java WebSocket 聊天室Demo
- WebSocket(5)-- WebSocket Server
- 为给俺的小白救星浏览器增加一个调试功能绞尽脑汁搞了2天了,终于搞出点眉目,不容易啊☺
- -0的存储在计算机中到底代表啥?
- 为什么需要项目管理软件
- springboot【23】监控管理之Actuator监控端点实践
- MongoDB学习笔记(入门)
- linux 下websocket server demo例程
- 洛谷1901 发射站
- spring boot启动过程
- windows 下一台服务器多个tomcat服务安装
- pgrouting路径分析(站点与GRID中心点最短距离)
- 剑指offer(3):从尾到头打印链表
- 最大子矩阵问题-悬线法
- 将一个字符串数组中的字符串拼接出来,使得字典序最小
- Day10-34.Java has no “sizeof”