转载:

http://blog.csdn.net/microtong/article/details/4989902

Linux下select函数实现的聊天服务器  佟强 http://blog.csdn.net/microtong

Windows下的图形客户端:

基于WSAAsyncSelect模型实现的聊天室图形客户端

http://blog.csdn.net/microtong/archive/2009/12/12/4990175.aspx

Window下的命令行客户端:

http://blog.csdn.net/microtong/archive/2009/12/12/4990138.aspx

消息缓冲区类MessageBuffer,接收线程将受到的消息放入缓冲区,发送线程从缓冲区中取出消息

MessageBuffer.h

[cpp] view plain copy
  1. //MessageBuffer.h
  2. #ifndef _MESSAGE_BUF_INCLUDE_
  3. #define _MESSAGE_BUF_INCLUDE_
  4. #include <pthread.h>
  5. #define MESSAGE_COUNT 16
  6. #define MESSAGE_LENGTH 2048
  7. class MessageBuffer{
  8. private:
  9. pthread_mutex_t mutex;//访问缓冲的互斥量
  10. pthread_cond_t condition;//访问缓冲区的条件变量
  11. //消息缓冲区,循环队列
  12. char buf[MESSAGE_COUNT][MESSAGE_LENGTH];
  13. int rear; //循环队列的队尾
  14. int front; //循环队列的队首
  15. public:
  16. bool toStop;
  17. //构造函数
  18. MessageBuffer();
  19. //析构函数
  20. virtual ~MessageBuffer();
  21. //将消息放入消息缓冲区,当缓冲区满时阻塞,toStop=true时返回-1
  22. int PutMessage(const char *message);
  23. //从消息缓冲区中获得消息,当缓冲区空时阻塞,toStop=true时返回-1
  24. int GetMessage(char *mbuf, int buflen);
  25. };
  26. #endif

MessageBuffer.cpp

[cpp] view plain copy
  1. //MessageBuffer.cpp
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <time.h>
  5. #include <pthread.h>
  6. #include "MessageBuffer.h"
  7. MessageBuffer::MessageBuffer() {
  8. toStop = false;
  9. pthread_mutex_init(&mutex,NULL);//初始化互斥量
  10. pthread_cond_init(&condition,NULL);//初始化条件变量
  11. rear = 0; //队尾指针指向0
  12. front = 0; //队首指针指向0
  13. printf("A MessageBuffer intance created./n");
  14. }
  15. MessageBuffer::~MessageBuffer(){
  16. pthread_mutex_destroy(&mutex);
  17. pthread_cond_destroy(&condition);
  18. printf("A MessageBuffer instance destroyed./n");
  19. }
  20. //将消息放入消息缓冲区
  21. int MessageBuffer::PutMessage(const char *message){
  22. struct timespec t;
  23. //等待互斥量
  24. pthread_mutex_lock(&mutex);
  25. while(!toStop && (rear+1)%MESSAGE_COUNT==front){
  26. t.tv_sec = time(NULL)+1;
  27. t.tv_nsec = 0;
  28. pthread_cond_timedwait(&condition,&mutex,&t);
  29. }
  30. if(toStop){
  31. pthread_cond_broadcast(&condition);
  32. pthread_mutex_unlock(&mutex);
  33. return -1;
  34. }
  35. int messageLen = strlen(message);
  36. int copyLen = messageLen>=MESSAGE_LENGTH?MESSAGE_LENGTH-1:messageLen;
  37. memcpy(buf[rear],message,copyLen);
  38. buf[rear][copyLen]='/0';
  39. rear = (rear+1)%MESSAGE_COUNT;
  40. pthread_cond_signal(&condition);
  41. pthread_mutex_unlock(&mutex);
  42. return 0;
  43. }
  44. //从消息缓冲区中获得消息
  45. int MessageBuffer::GetMessage(char *mbuf, int buflen){
  46. struct timespec t;
  47. pthread_mutex_lock(&mutex);
  48. while(!toStop && rear==front){
  49. t.tv_sec = time(NULL)+1;
  50. t.tv_nsec = 0;
  51. pthread_cond_timedwait(&condition,&mutex,&t);
  52. }
  53. if(toStop){
  54. pthread_cond_broadcast(&condition);
  55. pthread_mutex_unlock(&mutex);
  56. return -1;
  57. }
  58. int messageLen = strlen(buf[front]);
  59. int copyLen = messageLen>=buflen ? buflen-1 : messageLen;
  60. memcpy(mbuf,buf[front],copyLen);
  61. mbuf[copyLen]='/0';
  62. front = (front+1)%MESSAGE_COUNT;
  63. pthread_cond_signal(&condition);
  64. pthread_mutex_unlock(&mutex);
  65. return 0;
  66. }

客户类Clients,用于维护套接字socket和套接字地址struct sockaddr_in之间的对应关系,并维护用户的姓名。

Clients.h

[cpp] view plain copy
  1. //Clients.h
  2. #ifndef _CLIENTS_INCLUDE_
  3. #define _CLIENTS_INCLUDE_
  4. #include <sys/types.h>
  5. #include <netinet/in.h>
  6. #include <pthread.h>
  7. #define NAME_LEN 50
  8. #define MAX_CLIENT 30
  9. typedef struct client_info{
  10. int sock;
  11. struct sockaddr_in clientAddr;
  12. char name[NAME_LEN];
  13. }CLIENT_INFO;
  14. class Clients{
  15. private:
  16. pthread_mutex_t mutex;
  17. CLIENT_INFO client[MAX_CLIENT];
  18. int clientCount;
  19. int IPtoString(unsigned long ip, char *buf, int buflen);
  20. int Search(int sock);
  21. public:
  22. Clients();//构造函数
  23. virtual ~Clients();//析构函数
  24. int GetClientCount();
  25. bool PutClient(int sock,const struct sockaddr_in &clientAddr);
  26. void RemoveClient(int sock);
  27. bool GetAddrBySocket(int sock,struct sockaddr_in *addr);
  28. bool PutName(int sock,const char *name, int namelen);
  29. bool GetName(int sock, char *name, int namelen);
  30. int GetAllSocket(int* sockArray, int arrayLen );
  31. };
  32. #endif

Clients.cpp

[cpp] view plain copy
  1. //Clients.cpp
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <arpa/inet.h>
  5. #include "Clients.h"
  6. Clients::Clients() {
  7. pthread_mutex_init(&mutex, NULL);
  8. clientCount = 0;
  9. printf("Clients created./n");
  10. }
  11. Clients::~Clients() {
  12. pthread_mutex_destroy(&mutex);
  13. printf("Clients destroyed./n");
  14. }
  15. int Clients::Search(int sock){
  16. int index = -1;
  17. for(int i=0; i<clientCount; i++) {
  18. if(client[i].sock==sock){
  19. index = i;
  20. break;
  21. }
  22. }
  23. return index;
  24. }
  25. int Clients::IPtoString(unsigned long ip,char *buf,int buflen){
  26. unsigned char *p = (unsigned char*)&ip;
  27. if(buflen<16){
  28. return -1;
  29. }
  30. sprintf(buf,"%u.%u.%u.%u",*p,*(p+1),*(p+2),*(p+3));
  31. return strlen(buf);
  32. }
  33. int Clients::GetClientCount(){
  34. return clientCount;
  35. }
  36. bool Clients::PutClient(int sock,const struct sockaddr_in &clientAddr) {
  37. if(clientCount==MAX_CLIENT){
  38. return false;
  39. }
  40. pthread_mutex_lock(&mutex);
  41. client[clientCount].sock = sock;
  42. client[clientCount].clientAddr = clientAddr;
  43. int buflen = sizeof(client[clientCount].name);
  44. int pos = IPtoString(clientAddr.sin_addr.s_addr,client[clientCount].name,buflen);
  45. sprintf(&client[clientCount].name[pos],":%d",ntohs(clientAddr.sin_port));
  46. clientCount++;
  47. pthread_mutex_unlock(&mutex);
  48. return true;
  49. }
  50. void Clients::RemoveClient(int sock){
  51. pthread_mutex_lock(&mutex);
  52. int index = Search(sock);
  53. if(index!=-1){
  54. for(int i=index; i<clientCount-1; i++){
  55. client[i] = client[i+1];
  56. }
  57. clientCount--;
  58. }
  59. pthread_mutex_unlock(&mutex);
  60. }
  61. bool Clients::GetAddrBySocket(int sock,struct sockaddr_in *addr){
  62. pthread_mutex_lock(&mutex);
  63. int index = Search(sock);
  64. if(index!=-1){
  65. memcpy(addr,&client[index].clientAddr,sizeof(struct sockaddr_in));
  66. }
  67. pthread_mutex_unlock(&mutex);
  68. return index!=-1;
  69. }
  70. bool Clients::PutName(int sock,const char *name,int namelen) {
  71. pthread_mutex_lock(&mutex);
  72. int index = Search(sock);
  73. if(index!=-1){
  74. int copyLen = namelen>=NAME_LEN ? NAME_LEN-1:namelen;
  75. memcpy(client[index].name,name,copyLen);
  76. client[index].name[copyLen]='/0';
  77. }
  78. pthread_mutex_unlock(&mutex);
  79. return index!=-1;
  80. }
  81. bool Clients::GetName(int sock, char *name, int namelen) {
  82. pthread_mutex_lock(&mutex);
  83. int index = Search(sock);
  84. if(index!=-1){
  85. int msgLen = strlen(client[index].name);
  86. int copyLen = (msgLen<namelen)? msgLen:(namelen-1);
  87. memcpy(name,client[index].name,copyLen);
  88. name[copyLen]='/0';
  89. }
  90. pthread_mutex_unlock(&mutex);
  91. return index!=-1;
  92. }
  93. int Clients::GetAllSocket(int* sockArray, int arrayLen ) {
  94. pthread_mutex_lock(&mutex);
  95. int copyCount = arrayLen>clientCount ? clientCount : arrayLen;
  96. for(int i=0; i<copyCount; i++){
  97. sockArray[i] = client[i].sock;
  98. }
  99. pthread_mutex_unlock(&mutex);
  100. return copyCount;
  101. }


聊天室服务器主程序Server.cpp

[cpp] view plain copy
  1. /*server.c*/
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <sys/types.h>
  6. #include <netinet/in.h>
  7. #include <sys/socket.h>
  8. #include <sys/select.h>
  9. #include <pthread.h>
  10. #include <unistd.h>
  11. #include <netdb.h>
  12. #include <arpa/inet.h>
  13. #include "MessageBuffer.h"
  14. #include "Clients.h"
  15. using namespace std;
  16. #define SERVER_PORT 8000
  17. #define BUFFER_SIZE 4096
  18. #ifndef MAX_CLIENT
  19. #define MAX_CLIENT 30
  20. #endif
  21. #ifndef NAME_LEN
  22. #define NAME_LEN 50
  23. #endif
  24. MessageBuffer messageBuffer;
  25. Clients clients;
  26. void* ListenThread(void*);
  27. void* RecvThread(void*);
  28. void* SendThread(void*);
  29. void ProcessMessage(int sock,char buf[],int bufsize,int bytes);
  30. bool toStop=false;
  31. int main(int argc,char* argv[]) {
  32. if(argc!=2){
  33. printf("Usage: %s PortNumber/n",argv[0]);
  34. return -1;
  35. }
  36. unsigned short port;
  37. if((port = atoi(argv[1]))==0){
  38. printf("incorrect port number./n");
  39. return -1;
  40. }
  41. int s;
  42. struct sockaddr_in serverAddr;
  43. s = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  44. if(s==-1){
  45. fprintf(stderr,"create socket failed./n");
  46. return -1;
  47. }
  48. bzero(&serverAddr,sizeof(struct sockaddr_in));
  49. serverAddr.sin_family = AF_INET;
  50. serverAddr.sin_port = htons(port);
  51. serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  52. if(bind(s,(struct sockaddr*)&serverAddr,sizeof(serverAddr))==-1){
  53. fprintf(stderr,"bind socket to port %d failed./n",port);
  54. return -1;
  55. }
  56. if(listen(s,SOMAXCONN)==-1){
  57. fprintf(stderr,"listen failed./n");
  58. return -1;
  59. }
  60. printf("Server  is listening on ");
  61. char hostname[255];
  62. if(gethostname(hostname,sizeof(hostname))){
  63. printf("gethostname() failed./n");
  64. return -1;
  65. }
  66. struct hostent* pHost = gethostbyname(hostname);
  67. if(pHost){
  68. for(int i=0; pHost->h_addr_list[i]; i++){
  69. printf("%s ",inet_ntoa(*(in_addr*)pHost->h_addr_list[i]));
  70. }
  71. }
  72. printf("/nport: %d/n",port);
  73. pthread_t tListenId;
  74. if(pthread_create(&tListenId,NULL,ListenThread,&s)){
  75. printf("failed to create listen thread./n");
  76. return -1;
  77. }
  78. pthread_t tRecvId;
  79. if(pthread_create(&tRecvId,NULL,RecvThread,NULL)){
  80. printf("failed to create recv thread./n");
  81. return -1;
  82. }
  83. pthread_t tSendId;
  84. if(pthread_create(&tSendId,NULL,SendThread,NULL)){
  85. printf("failed to create send thread./n");
  86. return -1;
  87. }
  88. while(getchar()!='q');
  89. toStop = true;
  90. messageBuffer.toStop = true;
  91. pthread_join(tListenId,NULL);
  92. pthread_join(tRecvId,NULL);
  93. pthread_join(tSendId,NULL);
  94. close(s);
  95. int sock[MAX_CLIENT];
  96. int count = clients.GetAllSocket(sock,MAX_CLIENT);
  97. for(int i=0;i<count;i++){
  98. close(sock[i]);
  99. }
  100. printf("server stopped./n");
  101. return 0;
  102. }
  103. void* ListenThread(void*ps){
  104. int s=*(int*)ps;
  105. fd_set listenSet;
  106. int sock;
  107. struct sockaddr_in clientAddr;
  108. struct timeval timeout;
  109. while(!toStop){
  110. FD_ZERO(&listenSet);
  111. FD_SET(s,&listenSet);
  112. timeout.tv_sec = 5;
  113. timeout.tv_usec = 0;
  114. int ret = select(s+1,&listenSet,NULL,NULL,&timeout);
  115. if(toStop){
  116. printf("ListenThread: exit./n");
  117. return NULL;
  118. }
  119. if(ret==-1){
  120. printf("ListenThread: select() failed!/n");
  121. }else if(ret==0){
  122. printf("ListenThread: select() time out./n");
  123. }else{
  124. if(FD_ISSET(s,&listenSet)){
  125. socklen_t addrlen = sizeof(struct sockaddr_in);
  126. memset(&clientAddr,0,sizeof(struct sockaddr_in));
  127. if((sock=accept(s,(struct sockaddr*)&clientAddr,&addrlen))==-1){
  128. fprintf(stderr,"accept failed./n");
  129. }
  130. if(!clients.PutClient(sock,clientAddr)){
  131. printf("max client limited. MAX_CLIENT=%d/n",MAX_CLIENT);
  132. close(sock);
  133. }
  134. printf("accept a connection from %s:%u/n",
  135. inet_ntoa(*(struct in_addr*)&(clientAddr.sin_addr.s_addr)),
  136. ntohs(clientAddr.sin_port));
  137. printf("new socket is: %u/n",sock);
  138. }
  139. }
  140. }
  141. return NULL;
  142. }
  143. void* RecvThread(void*){
  144. fd_set readSet;
  145. int sock[MAX_CLIENT];
  146. char buf[BUFFER_SIZE];
  147. struct timeval timeout;
  148. while(!toStop){
  149. int count = clients.GetAllSocket(sock,MAX_CLIENT);
  150. if(count==0){
  151. sleep(2);
  152. if(toStop){
  153. printf("RecvThread: exit./n");
  154. return NULL;
  155. }
  156. continue;
  157. }
  158. FD_ZERO(&readSet);
  159. int maxfd=0;
  160. for(int i=0;i<count;i++){
  161. printf("--%d",sock[i]);
  162. FD_SET(sock[i],&readSet);
  163. if(sock[i]>maxfd){
  164. maxfd = sock[i];
  165. }
  166. }
  167. printf("/n");
  168. timeout.tv_sec = 2;
  169. timeout.tv_usec = 0;
  170. int ret = select(maxfd+1,&readSet,NULL,NULL,&timeout);
  171. if(toStop){
  172. printf("RecvThread: exit./n");
  173. return NULL;
  174. }
  175. if(ret==-1){
  176. printf("RecvThread: select() failed!/n");
  177. }else if(ret==0){
  178. printf("RecvThread: select() time out./n");
  179. }else{
  180. for(int i=0; i<count; i++){
  181. if(FD_ISSET(sock[i],&readSet)){
  182. int bytes=recv(sock[i],buf,sizeof(buf)-1,0);
  183. if(bytes==-1){
  184. printf("RecvThread: recv failed./n");
  185. clients.RemoveClient(sock[i]);
  186. close(sock[i]);
  187. }else if(bytes==0){
  188. printf("RecvThread: socket closed by the other side./n");
  189. clients.RemoveClient(sock[i]);
  190. close(sock[i]);
  191. }else{
  192. ProcessMessage(sock[i],buf,sizeof(buf),bytes);
  193. }
  194. }
  195. }
  196. }
  197. }
  198. return NULL;
  199. }
  200. void* SendThread(void*){
  201. fd_set writeSet;
  202. int sock[MAX_CLIENT];
  203. char buf[BUFFER_SIZE];
  204. struct timeval timeout;
  205. while(!toStop){
  206. int ret = messageBuffer.GetMessage(buf,sizeof(buf));
  207. printf("get a message from buffer./n");
  208. if(ret==-1){
  209. printf("SendThread: exit./n");
  210. return NULL;
  211. }
  212. int count = clients.GetAllSocket(sock,MAX_CLIENT);
  213. FD_ZERO(&writeSet);
  214. int maxfd = 0;
  215. for(int i=0;i<count;i++){
  216. FD_SET(sock[i],&writeSet);
  217. if(sock[i]>maxfd){
  218. maxfd = sock[i];
  219. }
  220. }
  221. timeout.tv_sec = 2;
  222. timeout.tv_usec = 0;
  223. ret = select(maxfd+1,NULL,&writeSet,NULL,&timeout);
  224. if(toStop){
  225. printf("SendThread: exit./n");
  226. return NULL;
  227. }
  228. if(ret==-1){
  229. printf("SendThread: select() failed!/n");
  230. }else if(ret==0){
  231. printf("SendThread: select() time out./n");
  232. }else{
  233. for(int i=0;i<count;i++){
  234. if(FD_ISSET(sock[i],&writeSet)){
  235. int messageLen = strlen(buf);
  236. int bytes = send(sock[i],buf,messageLen,0);
  237. if(bytes==-1){
  238. printf("SendThread: send() failed./n");
  239. }else if(bytes!=messageLen){
  240. printf("SendThread: send message trunked.");
  241. }else{
  242. //do nothing
  243. }
  244. }
  245. }
  246. }
  247. }
  248. return NULL;
  249. }
  250. void ProcessMessage(int sock,char buf[],int bufsize,int bytes){
  251. struct sockaddr_in clientAddr;
  252. if(!clients.GetAddrBySocket(sock,&clientAddr)){
  253. printf("ProcessMessage: can not find socket address./n");
  254. return;
  255. }
  256. char ipString[16];
  257. unsigned char *ip = (unsigned char*)&clientAddr.sin_addr.s_addr;
  258. sprintf(ipString,"%u.%u.%u.%u",*ip,*(ip+1),*(ip+2),*(ip+3));
  259. unsigned short port = ntohs(clientAddr.sin_port);
  260. buf[bytes]='/0';
  261. printf("Message from %s:%d: %s/n",ipString,port,buf);
  262. const char* CMD_BYE="bye";
  263. if(strcmp(buf,CMD_BYE)==0){
  264. send(sock,CMD_BYE,strlen(CMD_BYE),0);
  265. clients.RemoveClient(sock);
  266. close(sock);
  267. printf("%s:%u disconnected./n", ipString, port);
  268. return;
  269. }else{
  270. char bufWithName[BUFFER_SIZE+NAME_LEN];
  271. char cmdname[5];
  272. char name[NAME_LEN];
  273. memcpy(cmdname, buf, 4);
  274. cmdname[4] = '/0';
  275. const char* CMD_NAME="name";
  276. if(strcmp(cmdname,CMD_NAME)==0){
  277. char newname[NAME_LEN];
  278. int nameLen = strlen(buf+5);
  279. int copyLen;
  280. if(nameLen>=NAME_LEN){
  281. copyLen = NAME_LEN-1;
  282. }else{
  283. copyLen = nameLen;
  284. }
  285. memcpy(newname,buf+5,copyLen);
  286. newname[copyLen]='/0';
  287. clients.GetName(sock,name,sizeof(name));
  288. sprintf(bufWithName,"%s change name to %s",name,newname);
  289. clients.PutName(sock,newname,strlen(newname));
  290. messageBuffer.PutMessage(bufWithName);
  291. }else{
  292. clients.GetName(sock,name,sizeof(name));
  293. sprintf(bufWithName,"%s: %s",name,buf);
  294. messageBuffer.PutMessage(bufWithName);
  295. }
  296. }
  297. }

编译脚本文件compile

g++ -c MessageBuffer.cpp
g++ -c Clients.cpp
g++ -c Server.cpp
g++ -lpthread -o server MessageBuffer.o Clients.o Server.o

chmod +x compile

./compile 就可以编译并链接

运行服务器

./server 8000

注意Linux下的防火墙iptables服务是否已经启动,如果启动了,需要在/etc/sysconfig/iptables中加入例外端口8000,并重启启动防火墙

/etc/init.d/iptables restart

佟强的高级网络编程课件 http://www.oakcms.cn/network

Linux下select函数实现的聊天服务器相关推荐

  1. linux下select()函数

     1.I/O处理的模型有5种: 1>.阻塞I/O模型 在这种模型下,若所调用的I/O函数没有完成相关的功能,则会使进程挂起,直到相关数据到达才会返回.如常见的对管道.终端.网络设备进行读写时经常 ...

  2. linux 下 select 函数的用法

    一.Select 函数详细介绍 Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect. accept.recv ...

  3. Linux下select函数的使用

    一.Select 函数详细介绍 Select在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect. accept.recv ...

  4. Linux下Select多路复用实现简易聊天室

    前言 和之前的udp聊天室有异曲同工之处,这次我们客户端send的是一个封装好了的数据包,recv的是一个字符串,服务器recv的是一个数据包,send的是一个字符串,在用户连接的时候发送一个logi ...

  5. linux用qt实现聊天,linux下的Qt开发双人聊天服务器编写

    一.定义数据结构,初始化系统环境 1.定义数据结构 struct message{ int type;        //传输类型 long length;  //传输内容的长度 char *valu ...

  6. Linux下connect函数 阻塞 与 非阻塞 问题

    一.概述 linux系统下,connect函数是阻塞的,阻塞时间的长度与系统相关.而如果把套接字设置成非阻塞,调用connect函数时会报错Operation now in progress,且err ...

  7. Linux中select函数的使用 select() Linux linux函数 select

    阻塞式I/O编程有两个特点: 一.如果一个发现I\O有输入,读取的过程中,另外一个也有了输入,这时候不会产生任何反应.这就需要你的程序语句去用到select函数的时候才知道有数据输入. 二.程序去se ...

  8. LINUX下poll函数用法

    LINUX下poll函数用法 文章目录 LINUX下poll函数用法 一.函数介绍 二.使用 1. 一.函数介绍 int poll(struct pollfd *fds, nfds_t nfds, i ...

  9. linux下syscall函数,SYS_gettid,SYS_tgkill

    出处:http://blog.chinaunix.net/uid-28458801-id-4630215.html linux下syscall函数,SYS_gettid,SYS_tgkill 2014 ...

最新文章

  1. 推荐给Android开发者的抢手书单
  2. Oracle 监听器无法启动(TNS-12537,TNS-12560,TNS-00507)
  3. 摘抄--apache工作模式详解
  4. 拼接名字_一个最简单的办法,教你识别原切肉和拼接肉
  5. 使用Xamarin实现跨平台移动应用开发
  6. 分类算法—Performance指标
  7. 多次访问redis造成redis连接总是断开的解决方案
  8. spanner 的前世今生
  9. 苹果六电池_【行业分析】特斯拉“电池日”前夕供应链个股备受关注 溶剂龙头石大胜华DMC等需求量有望大增...
  10. 人脸识别冤枉了98%的好人,伦敦警察局长:我很满意
  11. 南阳理工oj 题目26 孪生素数问题 素数筛选法
  12. WPS设置奇偶页页眉不同
  13. APP上架各大应用市场对比
  14. 【Django】admin的save_modle方法重写-20220803
  15. 什么是数据安全,为什么需要保证数据安全
  16. 17岁少年找黑客攻击航司系统获刑4年
  17. 【干货收藏】数据分析师必备的20种分析思维
  18. jQuery File Upload
  19. 线性模型是否真的能给出一个很好的解释?
  20. 2小时速刷8大项目——上海迪士尼一日游攻略

热门文章

  1. 翻车现场:解决redis使用increment自增方法时报错ERR value is not a valid float
  2. html显示立方米符号,怎么打立方米符号m³ 打出m³立方米符号的方法
  3. MKS SERVO42C 闭环步进电机 使用说明 V1.1 (三) 串口通讯
  4. UC Berkeley在线免费课程
  5. 大雄保姆机器人_作为一个未来保姆机器人,哆啦A梦为什么有那么多因果律道具?...
  6. 计算机网络基础概念ppt,1-1计算机网络基础概念.ppt
  7. 一文搞懂什么是数据湖(data lake)?
  8. 黑马程序员 关于学习毕老师IO流后的一些笔记
  9. 基于PX4、树莓派和Mocap(Optitrack)的室内飞行环境搭建
  10. js实现判断鼠标滚轮的上下滚动