RT,使用消息队列,信号量和命名管道实现的多人群聊系统。

本学期Linux、unix网络编程的第三个作业。

先上实验要求:

实验三  多进程服务器

【实验目的】

1、熟练掌握进程的创建与终止方法;

2、熟练掌握进程间通信方法;

2、应用套接字函数完成多进程服务器,实现服务器与客户端的信息交互。

【实验学时】

4学时

【实验内容】

通过一个服务器实现最多5个客户之间的信息群发。

服务器显示客户的登录与退出;

客户连接后首先发送客户名称,之后发送群聊信息;

客户输入bye代表退出,在线客户能显示其他客户的登录于退出。

任务分析:

实现提示:

1、服务器端:

服务器进程称之为主进程,主进程创建一个转发子进程和最多5个通信子进程。

主进程与转发子进程之间:

信号量(初值5,主进程接受一个客户连接后执行P操作判断是否超过5,转发子进程有一个客户退出后执行V操作,并发消息队列标识符)

命名管道SERVER(转发子进程将可用的消息队列标识符写入管道,主进程从管道中读取消息队列标识符)

转发子进程与通信子进程之间:

命名管道CLIENT(通信子进程向命名管道写入客户端发来的消息,转发子进程从管道中读取消息并发送给对应的客户端)

消息队列(转发子进程将客户发来的信息通过消息队列发送给每个通信子进程)

(1)主进程:

从转发子进程获取一个可用的消息队列标识符;

接收客户连接请求,如果连接数超过最大连接数,向客户发送退出标志,否则发送OK标志;

每接受一个连接,创建一个通信子进程并将连接socket、消息队列标识符、客户地址传递给通信子进程。

(2)通信子进程:

创建一个子进程负责从消息队列中读取消息,发送给客户。

通信子进程负责接收客户发来信息,通过命名管道CLIENT发送给转发子进程;

若信息为用户名,附带消息队列、客户地址发送给转发子进程;

若信息为退出,终止子进程,程序结束

(3)转发子进程:

创建5个消息队列;

维护客户信息表:消息队列、客户名、客户IP、客户端口、状态。

从命名管道CLIENT中读取通信子进程发来的消息,消息类型为:用户名、退出及一般信息;

若为用户名,依据消息队列在更新客户信息表,状态为可用;

若为一般信息,将信息转换后写入可用客户的消息队列,等待其他通信子进程读取;

若为退出,在客户信息表中状态设为不可用,执行信号量V操作,并将可用客户的消息队列标识符写入到命名管道SERVER;

2、客户端:

根据用户从终端输入的服务器IP地址及端口号连接到相应的服务器;

连接成功后,先发送客户名称;

创建一个子进程负责接收服务器发来的信息,并显示;

主进程循环从终端输入信息,并将信息发送给服务器;

当发送给服务器为bye后,关闭子进程,程序退出。

架构看起来很复杂,我们可以绘制一下流程图方便理清思路。

在word里面截图不是很清晰啊。。。

开始写代码吧:首先clientmsg.h,它定义了一些消息的操作符(OP)和CLIENTMSG这个结构体(用于服务器和客户端之间传递消息)

  1. //CLIENTMSG between server and client
  2. #ifndef _clientmsg
  3. #define _clientmsg
  4.  
  5. //USER MSG EXIT for OP of CLIENTMSG
  6. #define EXIT -1
  7. #define USER 1
  8. #define MSG 2
  9. #define OK 3
  10.  
  11. #ifndef CMSGLEN
  12. #define CMSGLEN 100
  13. #endif
  14.  
  15. struct CLIENTMSG{
  16. int OP;
  17. char username[];
  18. char buf[CMSGLEN];
  19. };
  20.  
  21. #endif

然后实现一下servermsg.h,用于服务器内部的转发子进程和通信子进程之间的消息传递。

  1. //SERVERMSG for communicate to translate
  2. //MESSAGE for translate to communicate
  3. #ifndef _servermsg
  4. #define _servermsg
  5.  
  6. #include <netinet/in.h>
  7. #include "clientmsg.h"
  8.  
  9. #ifndef CMSGLEN
  10. #define CMSGLEN 100
  11. #endif
  12.  
  13. struct SERVERMSG{
  14. int OP;
  15. char username[];
  16. char buf[CMSGLEN];
  17. struct sockaddr_in client;
  18. int stat;
  19. int qid;
  20. };
  21.  
  22. struct MESSAGE{
  23. long msgtype;
  24. struct SERVERMSG msg;
  25. };
  26.  
  27. #endif

由于需要操作信号量,所以将一些信号量的操作做成函数

semaphore.h

  1. #ifndef _semaphore
  2. #define _semaphore
  3.  
  4. union semun
  5. {
  6. int val;
  7. struct semid_ds *buf;
  8. unsigned short *array;
  9. };
  10.  
  11. int CreateSem(key_t key,int value);
  12. int Sem_P(int semid);
  13. int Sem_V(int semid);
  14. int GetvalueSem(int semid);
  15. void DestroySem(int semid);
  16.  
  17. #endif

对函数的实现:semaphore.c

  1. #include <stdlib.h>
  2. #include <fcntl.h>
  3. #include <sys/sem.h>
  4. #include "semaphore.h"
  5.  
  6. int CreateSem(key_t key,int value)
  7. {
  8. union semun sem;
  9. int semid;
  10. sem.val=value;
  11. semid=semget(key,,IPC_CREAT);
  12. if (semid==-){
  13. perror("semget error"); exit();
  14. }
  15. semctl(semid,,SETVAL,sem);
  16. return semid;
  17. }
  18.  
  19. int Sem_P(int semid)
  20. {
  21. struct sembuf sops={,-,IPC_NOWAIT};
  22. return (semop(semid,&sops,));
  23. }
  24.  
  25. int Sem_V(int semid)
  26. {
  27. struct sembuf sops={,+,IPC_NOWAIT};
  28. return (semop(semid,&sops,));
  29. }
  30.  
  31. int GetvalueSem(int semid)
  32. {
  33. union semun sem;
  34. return semctl(semid,,GETVAL,sem);
  35. }
  36. void DestroySem(int semid)
  37. {
  38. union semun sem;
  39. sem.val=;
  40.  
  41. semctl(semid,,IPC_RMID,sem);
  42. }

接下来是非常重要的服务器端实现(里面有很多调试信息,比较懒没有删掉,直接在里面注释掉了。)

server.c

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/socket.h>
  4. #include <netinet/in.h>
  5. #include <stdlib.h>
  6. #include <sys/types.h>
  7. #include <sys/wait.h>
  8. #include <sys/stat.h>
  9. #include <unistd.h>
  10. #include <fcntl.h>
  11. #include <sys/ipc.h>
  12. #include "semaphore.h"
  13. #include "servermsg.h"
  14.  
  15. void trans_process(int semid);
  16. void communicate_process(int connetfd,int qid,struct sockaddr_in client);
  17.  
  18. int main(){
  19.  
  20. struct sockaddr_in server;
  21. struct sockaddr_in client;
  22. int listenfd,connetfd;
  23. char ip[];
  24. int port;
  25. int addrlen;
  26. struct CLIENTMSG clientMsg;
  27. int ret,status;
  28. /*---------------------socket-------------------*/
  29. if((listenfd = socket(AF_INET,SOCK_STREAM,))== -){
  30. perror("socket() error\n");
  31. exit();
  32. }
  33.  
  34. /*----------------------IO-----------------------*/
  35. printf("Please input the ip:\n");
  36. scanf("%s",ip);
  37. printf("Please input the port:\n");
  38. scanf("%d",&port);
  39.  
  40. /*---------------------bind----------------------*/
  41. bzero(&server,sizeof(server));
  42. server.sin_family = AF_INET;
  43. server.sin_port = htons(port);
  44. server.sin_addr.s_addr = inet_addr(ip);
  45. if(bind(listenfd,(struct sockaddr *)&server,sizeof(server))== -){
  46. perror("bind() error\n");
  47. exit();
  48. }
  49.  
  50. /*----------------------listen-------------------*/
  51. if (listen(listenfd,)== -){
  52. perror("listen() error\n");
  53. exit();
  54. }
  55.  
  56. //创建命名管道
  57. unlink("SERVER");
  58. mkfifo("SERVER",O_CREAT);
  59. int rd = open("SERVER",O_RDONLY|O_NONBLOCK);
  60. int semid;
  61. key_t k = ftok(".",'b');
  62. semid = CreateSem(k,);
  63. pid_t pid_1,pid_2;
  64. pid_1 = fork();
  65. if(pid_1 == ){
  66. trans_process(semid);
  67. exit();
  68. }
  69. else if(pid_1 > ){
  70. while(){
  71. addrlen = sizeof(client);
  72. if((connetfd = accept(listenfd,(struct sockaddr *)&client,&addrlen))== -){
  73. perror("accept() error\n");
  74. exit();
  75. }
  76. ret = Sem_P(semid);
  77. if(ret == ){
  78. int qid;
  79. read(rd,&qid,sizeof(qid));
  80. //printf("qid1:%d\n",qid );
  81. pid_2 = fork();
  82. if (pid_2 > ){
  83. close(connetfd);
  84. waitpid(pid_2,&status,WNOHANG);
  85. continue;
  86. }
  87. else if(pid_2 == ){
  88. communicate_process(connetfd,qid,client);
  89. exit();
  90. }
  91. else {
  92. perror("the second fork error\n");
  93. }
  94. }
  95. else {
  96. clientMsg.OP = EXIT;
  97. send(connetfd,&clientMsg,sizeof(clientMsg),);
  98. close(connetfd);
  99. }
  100. waitpid(pid_1,&status,WNOHANG);
  101.  
  102. }
  103. }
  104. else {
  105. perror("first time fork error\n");
  106. }
  107. /*----------------------close-------------------*/
  108. close(connetfd);
  109. close(listenfd);
  110.  
  111. return ;
  112. }
  113.  
  114. /*----------------------------函数实现区----------------------------*/
  115. void trans_process(int semid){
  116. struct SERVERMSG ent[];
  117. struct MESSAGE sendMsg;
  118. struct SERVERMSG msg;
  119. int i;
  120. for(i=;i<;i++){
  121. ent[i].stat = ;
  122. }
  123. int wfd = open("SERVER",O_WRONLY|O_NONBLOCK);
  124. for(i=;i<;i++){
  125. key_t key = ftok(".",(char)i+);
  126. ent[i].qid = msgget(key,IPC_CREAT);
  127. write(wfd,&ent[i].qid,sizeof(ent[i].qid));
  128. }
  129. unlink("CLIENT");
  130. mkfifo("CLIENT",O_CREAT);
  131. int rfd = open("CLIENT",O_RDONLY|O_NONBLOCK);
  132. int len;
  133. while(){
  134. bzero(&msg,sizeof(msg));
  135. len = read(rfd,&msg,sizeof(msg));
  136. //printf(" %d,%s ,%s\n",msg.OP,msg.username,msg.buf );
  137. //sleep(3);
  138. if(len > ){
  139. if(msg.OP == USER){
  140. for(i=;i<;i++){
  141. if(ent[i].qid == msg.qid){
  142. bcopy(msg.username,ent[i].username,strlen(msg.username));
  143. ent[i].client = msg.client;
  144. ent[i].stat = ;
  145. break;
  146. }
  147. }
  148. }
  149. else if(msg.OP == EXIT){
  150. for(i=;i<;i++){
  151. if(ent[i].qid == msg.qid){
  152. ent[i].stat = ;
  153. write(wfd,&ent[i].qid,sizeof(ent[i].qid));
  154. Sem_V(semid);
  155. break;
  156. }
  157. }
  158. }
  159. //bzero(&sendMsg,sizeof(sendMsg));
  160. sendMsg.msg = msg;
  161. for(i=;i<;i++){
  162. if(ent[i].stat == ){
  163. printf("stat 1...\n");
  164. int m_len = sizeof(msg);
  165. int sta=msgsnd(ent[i].qid,&sendMsg,len,);
  166. //printf("flag:%d\n",sta );
  167. }
  168. }
  169. }
  170. else {
  171. continue;
  172. }
  173. }
  174.  
  175. }
  176.  
  177. void communicate_process(int connetfd,int qid,struct sockaddr_in client){
  178. struct CLIENTMSG sendMsg;
  179. struct CLIENTMSG recvMsg;
  180. struct MESSAGE server_Msg;
  181. struct SERVERMSG client_sndMsg;
  182. struct SERVERMSG msg;
  183. int status;
  184. int wfd = open("CLIENT",O_WRONLY|O_NONBLOCK);
  185. pid_t pid;
  186. pid = fork();
  187. if(pid < ){
  188. perror("communicate_process fork error\n");
  189. }
  190. else if (pid == ){
  191. bzero(&sendMsg,sizeof(sendMsg));
  192. sendMsg.OP = OK;
  193. send(connetfd,&sendMsg,sizeof(sendMsg),);
  194. while(){
  195. int m_len = sizeof(msg);
  196. bzero(&server_Msg,sizeof(server_Msg));
  197. int sta=msgrcv(qid,&server_Msg,m_len,,);
  198. //printf("flag:%d\n",sta );
  199. //printf("send..%d,%s,%s\n",server_Msg.msg.OP,server_Msg.msg.username,server_Msg.msg.buf );
  200. bzero(&sendMsg,sizeof(sendMsg));
  201. bcopy(server_Msg.msg.username,sendMsg.username,strlen(server_Msg.msg.username));
  202. sendMsg.OP = server_Msg.msg.OP;
  203. bcopy(server_Msg.msg.buf,sendMsg.buf,strlen(server_Msg.msg.buf));
  204. //printf("send..%d,%s,%s\n",sendMsg.OP,sendMsg.username,sendMsg.buf );
  205. send(connetfd,&sendMsg,sizeof(sendMsg),);
  206. }
  207. }
  208. else{
  209. while(){
  210. bzero(&recvMsg,sizeof(recvMsg));
  211. int len =recv(connetfd,&recvMsg,sizeof(recvMsg),);
  212. if(len > ){
  213. if(recvMsg.OP == USER){
  214. printf("user %s login from ip:%s,port:%d\n",recvMsg.username,inet_ntoa(client.sin_addr),ntohs(client.sin_port) );
  215. client_sndMsg.OP = USER;
  216. }
  217. else if(recvMsg.OP == EXIT){
  218. printf("user %s is logout\n",recvMsg.username );
  219. client_sndMsg.OP = EXIT;
  220. write(wfd,&client_sndMsg,sizeof(client_sndMsg));
  221. break;
  222. }
  223. else if(recvMsg.OP == MSG){
  224. client_sndMsg.OP = MSG;
  225. }
  226. bzero(&client_sndMsg,sizeof(client_sndMsg));
  227. bcopy(recvMsg.username,client_sndMsg.username,strlen(recvMsg.username));
  228. bcopy(recvMsg.buf,client_sndMsg.buf,strlen(recvMsg.buf));
  229. client_sndMsg.client = client;
  230. //printf("qid2:%d\n",qid );
  231. client_sndMsg.qid = qid;
  232. client_sndMsg.OP = recvMsg.OP;
  233. write(wfd,&client_sndMsg,sizeof(client_sndMsg));
  234.  
  235. }
  236. else{
  237. continue;
  238. }
  239. }
  240. kill(pid,SIGKILL);
  241. waitpid(pid,&status,WNOHANG);
  242. close(wfd);
  243. close(connetfd);
  244. }
  245. }

写出了服务端,就可以非常容易的写出客户端了。

client.c

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/socket.h>
  4. #include <netinet/in.h>
  5. #include <stdlib.h>
  6. #include <sys/types.h>
  7. #include <sys/wait.h>
  8. #include <signal.h>
  9. #include <unistd.h>
  10. #include "clientmsg.h"
  11.  
  12. int main(){
  13. int sockfd;
  14. char ip[];
  15. int port;
  16. int status;
  17. pid_t pid;
  18. struct sockaddr_in server;
  19. struct CLIENTMSG clientMsg;
  20.  
  21. /*---------------------socket---------------------*/
  22. if((sockfd = socket(AF_INET,SOCK_STREAM,))== -){
  23. perror("socket error\n");
  24. exit();
  25. }
  26.  
  27. /*---------------------connect--------------------*/
  28. printf("Please input the ip:\n");
  29. scanf("%s",ip);
  30. printf("Please input the port:\n");
  31. scanf("%d",&port);
  32. bzero(&server,sizeof(server));
  33. server.sin_family = AF_INET;
  34. server.sin_port = htons(port);
  35. inet_aton(ip,&server.sin_addr);
  36. if(connect(sockfd,(struct sockaddr *)&server,sizeof(server))== -){
  37. perror("connect() error\n");
  38. exit();
  39. }
  40. recv(sockfd,&clientMsg,sizeof(clientMsg),);
  41. if(clientMsg.OP == OK){
  42. int len;
  43. pid = fork();
  44. if(pid == ){
  45. while(){
  46. bzero(&clientMsg,sizeof(clientMsg));
  47. len =recv(sockfd,&clientMsg,sizeof(clientMsg),);
  48. if(len > ){
  49. if(clientMsg.OP ==USER){
  50. printf("the user %s is login.\n",clientMsg.username );
  51. }
  52. else if(clientMsg.OP == EXIT){
  53. printf("the user %s is logout.\n",clientMsg.username);
  54. }
  55. else if(clientMsg.OP == MSG){
  56. printf("%s: %s\n",clientMsg.username,clientMsg.buf );
  57. }
  58. }
  59. }
  60. exit(EXIT_SUCCESS);
  61. }
  62. else if(pid > ){
  63. printf("Please input the username:\n");
  64. scanf("%s",clientMsg.username);
  65. clientMsg.OP = USER;
  66. send(sockfd,&clientMsg,sizeof(clientMsg),);
  67. while(){
  68. clientMsg.OP = MSG;
  69. scanf("%s",clientMsg.buf);
  70. if(strcmp("bye",clientMsg.buf) == ){
  71. clientMsg.OP = EXIT;
  72. send(sockfd,&clientMsg,sizeof(clientMsg),);
  73. break;
  74. }
  75. send(sockfd,&clientMsg,sizeof(clientMsg),);
  76.  
  77. }
  78. kill(pid,SIGKILL);
  79. waitpid(pid,&status,WNOHANG);
  80. }
  81. else{
  82. perror("fork error!\n");
  83. }
  84. }
  85. else{
  86. printf("以达到最大连接数!\n");
  87. }
  88. /*------------------------close--------------------------*/
  89. close(sockfd);
  90.  
  91. return ;
  92. }

最后是makefile:

  1. main:server.o client.o semaphore.o
  2. gcc server.o semaphore.o -oserver
  3. gcc client.o -oclient
  4. server.o:server.c semaphore.h clientmsg.h servermsg.h
  5. gcc -c server.c
  6. client.o:client.c clientmsg.h
  7. gcc -c client.c
  8. semaphore.o:semaphore.h semaphore.c
  9. gcc -c semaphore.c
  10. clean:
  11. rm -rf *.o

下面上一下演示过程:(测试环境,Red Hat Enterprise Linux 6 + centos系Linux,ubuntu下可能会有些问题。)

首先先把服务端启动开来,为了方便测试,这里直接使用的是127.0.0.1的localhost。

然后启动两个客户端用来测试,在用户登录的时候客户端会有消息提醒。服务端会有日志打印输出客户端的名字和登录ip、端口。

客户可以发送消息了,如图发送与接收均正常。可以同时启动<=5个客户端进行群聊,这里为了简单演示只是启动了2个。(修改信号量代码可以实现n多个客户的同时登陆):

输入bye以后即可退出聊天并下线。当有客户下线的时候,在线的客户端会收到下线提醒,客户端会有日志打印输出。

这个实验内容前前后后花了我2天才写完,刚开始没有弄清楚这一整套的工作机制与流程,写起来很是吃力,程序就是各种调不通。本来都想放弃了,但是后来还是咬咬牙坚持了一下来,饭要一口一口吃,程序要一点一点的写,万事不能操之过急,写代码一定要心平气和,头脑清晰。由于gdb调试工具用的不是很熟练,只能在程序里面一段一段的print变量来DEBUG,很是辛苦啊。

【LINUX/UNIX网络编程】之使用消息队列,信号量和命名管道实现的多进程服务器(多人群聊系统)的更多相关文章

  1. UNIX网络编程5 POSIX 消息队列

    <mqueue.h> mq_open mq_close mq_unlink mq_getattr/mq_setattr mq_send/mq_receive mq_notify sigwa ...

  2. 【LINUX/UNIX网络编程】之简单多线程服务器(多人群聊系统)

    RT,Linux下使用c实现的多线程服务器.这个真是简单的不能再简单的了,有写的不好的地方,还希望大神轻拍.(>﹏<) 本学期Linux.unix网络编程的第四个作业. 先上实验要求: [ ...

  3. 【Linux/unix网络编程】之使用socket进行TCP编程

    实验一 TCP数据发送与接收 [实验目的] 1.熟练掌握套接字函数的使用方法. 2.应用套接字函数完成基本TCP通讯,实现服务器与客户端的信息交互. [实验学时] 4学时 [实验内容] 实现一个服务器 ...

  4. Linux网络编程学习(九) ----- 消息队列(第四章)

    1.System V IPC System V中引入的几种新的进程间通信方式,消息队列,信号量和共享内存,统称为System V IPC,其具体实例在内核中是以对象的形式出现的,称为IPC 对象,每个 ...

  5. 【LINUX/UNIX网络编程】之使用SOCKET进行UDP编程

    先看任务需求: 实验二 UDP数据发送与接收 [实验目的] 1.熟练掌握套接字函数的使用方法. 2.应用套接字函数完成基本UDP通讯,实现服务器与客户端的文件传送 [实验学时] 4学时 [实验内容] ...

  6. Linux进程间通信(七):消息队列 msgget()、msgsend()、msgrcv()、msgctl()

    下面来说说如何用不用消息队列来进行进程间的通信,消息队列与命名管道有很多相似之处.有关命名管道的更多内容可以参阅我的另一篇文章:Linux进程间通信 -- 使用命名管道 一.什么是消息队列 消息队列提 ...

  7. [转载] 读《UNIX网络编程 卷1:套接字联网API》

    原文: http://cstdlib.com/tech/2014/10/09/read-unix-network-programming-1/ 文章写的很清楚, 适合初学者 最近看了<UNIX网 ...

  8. 《Unix 网络编程》14:高级 I/O 函数

    高级 I/O 函数 ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ...

  9. 《Unix 网络编程》15:Unix 域协议

    Unix 域协议 ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ ★ 本 ...

随机推荐

  1. HNU 12827 NASSA’s Robot

    题目链接:http://acm.hnu.cn/online/?action=problem&type=show&id=12827&courseid=268 #include&l ...

  2. Linux 4.6分支已到生命尽头 请尽快升级至Linux 4.7.1

    导读 在Linux Kernel 4.7首个维护版本发布的同时,Greg Kroah-Hartman同时也向社区发布了Linux Kernel 4.6.7版本.作为Linux 4.6分支的第7个维护版 ...

  3. python4delphi 安装

    环境搭建: 目前p4d已经可以支持到XE7,可惜googlecode即将关闭,不知道作者是否会在github上继续更新. 因为此开源项目历史较久远,拿到源代码后可能还需要手动修改相关的文件引用,毕竟需 ...

  4. HDOJ 1312 DFS&BFS

    Red and Black Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) To ...

  5. Mysql增删改

    改 UPDATE tbl_name SET 字段名=值,...[WHERE 条件][ORDER BY 字段名称][LIMIT限制条数] --更新用户名为4位的用户,让其以有年龄-3 UPDATA SE ...

  6. 【SpringMVC】SpringMVC系列11之Restful的CRUD

      11.Restful的CRUD 11.1.需求 11.2.POST转化为PUT.DELETE的fileter 11.3.查询所有 11.4.添加 11.5.删除     优雅的 REST 风格的资 ...

  7. Objective-C 和 C++中指针的格式和.方法 和内存分配

    最近在看cocos2d-x,于是打算复习一下C++,在这里简单对比下,留个念想. 先看看oc中指针的用法 @interface ViewController : UIViewController { ...

  8. 理解和解决MySQL乱码问题

    本文将详细介绍MySQL乱码的成因和具体的解决方案 在阅读本文之前,强烈建议对字符集编码概念还比较模糊的同学 阅读下博主之前对相关概念的一篇科普:十分钟搞清字符集和字符编码 MySQL出现乱码的原因 ...

  9. Java问题排查工具箱[转载]

    转载自:http://hellojava.info/?p=517 作者:阿里毕玄 问题排查除了最重要的解决思路和逻辑推导能力外,工具也是不可缺少的一部分,一个好用的工具可以事半功倍,甚至在某些情况下会 ...

  10. sublime text 3 使用过程总结记录

    自定义的设置: "save_on_focus_lost": true //在文件失去焦点的时候自动保存