• 10.1 今日任务:select实现服务器并发


    1. #include <myhead.h>
    2. #define ERR_MSG(msg) do{\
    3. fprintf(stderr, "__%d__:", __LINE__); \
    4. perror(msg);\
    5. }while(0)
    6. #define PORT 8888 //端口号,范围1024~49151
    7. #define IP "192.168.112.115" //本机IP,ifconfig
    8. int keybord_events(fd_set readfds);
    9. int cliConnect_events(int, struct sockaddr_in*, fd_set *, int *);
    10. int cliRcvSnd_events(int, struct sockaddr_in*, fd_set *, int* );
    11. int main(int argc, const char *argv[])
    12. {
    13. //创建流式套接字 socket
    14. int sfd = socket(AF_INET, SOCK_STREAM, 0);
    15. if(sfd < 0)
    16. {
    17. ERR_MSG("socket");
    18. return -1;
    19. }
    20. printf("socket create success sfd=%d\n", sfd);
    21. //允许端口快速的被复用
    22. int reuse = 1;
    23. if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
    24. {
    25. ERR_MSG("setsockopt");
    26. return -1;
    27. }
    28. printf("允许端口快速的被复用成功\n");
    29. //填充地址信息结构体给bind函数绑定,
    30. //真实的地址信息结构体根据地址族指定 AF_INET:man 7 ip
    31. struct sockaddr_in sin;
    32. sin.sin_family = AF_INET; //必须填AF_INET;
    33. sin.sin_port = htons(PORT); //端口号的网络字节序
    34. sin.sin_addr.s_addr = inet_addr(IP);//本机IP
    35. //绑定服务器的地址信息---> 必须绑定 bind
    36. if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
    37. {
    38. ERR_MSG("bind");
    39. return -1;
    40. }
    41. printf("bind success\n");
    42. //将套接字设置为被动监听状态 listen
    43. if(listen(sfd, 128) < 0)
    44. {
    45. ERR_MSG("listen");
    46. return -1;
    47. }
    48. printf("listen success\n");
    49. //创建一个读集合
    50. fd_set readfds, tempfds;
    51. //fd_set 本质上是一个结构体,结构体中有一个整形数组。
    52. //若不清空,则会存有随机值,可能会随机到有效的,但是不需要监测的文件描述符
    53. //清空集合
    54. FD_ZERO(&readfds);
    55. //将需要监测的文件描述符添加到集合中
    56. FD_SET(0, &readfds);
    57. FD_SET(sfd, &readfds);
    58. int maxfd = sfd; //存储最大的文件描述符
    59. int s_res = -1;
    60. ssize_t res = -1;
    61. char buf[128] = "";
    62. struct sockaddr_in saveCin[1024]; //备份连接成功的客户端的地址信息,用下标来对应文件描述符
    63. while(1)
    64. {
    65. tempfds = readfds;
    66. //执行IO多路复用函数
    67. s_res = select(maxfd+1, &tempfds, NULL, NULL, NULL);
    68. if(s_res < 0)
    69. {
    70. ERR_MSG("select");
    71. return -1;
    72. }
    73. else if(0 == s_res)
    74. {
    75. printf("time out,,\n");
    76. break;
    77. }
    78. printf("__%d__\n", __LINE__);
    79. //能运行到当前位置,则代表select函数解除阻塞,即代表集合中有文件描述符准备就绪
    80. //此时集合中会只剩下产生事件的文件描述符,例如:
    81. //0号准备就绪,则集合中会只剩下0
    82. //sfd准备就绪,则集合中会只剩下sfd
    83. //0和sfd均准备就绪,则集合中会剩下0和sfd;
    84. //只需要判断集合中剩下哪个文件描述符,走对应处理函数即可;
    85. for(int i=0; i<=maxfd; i++)
    86. {
    87. if(FD_ISSET(i, &tempfds) == 0)
    88. continue;
    89. //能运行到当前位置,则说明i所代表的文件描述符在集合中
    90. if(0 == i) //0在集合中
    91. {
    92. printf("触发键盘输入事件\n");
    93. keybord_events(readfds);
    94. }
    95. else if(sfd == i) //sfd在集合中
    96. {
    97. printf("触发客户端连接事件\n");
    98. cliConnect_events(sfd, saveCin, &readfds, &maxfd);
    99. }
    100. else
    101. {
    102. printf("触发客户端交互事件\n");
    103. cliRcvSnd_events(i, saveCin, &readfds, &maxfd);
    104. }
    105. }
    106. }
    107. if(close(sfd) < 0)
    108. {
    109. ERR_MSG("close");
    110. return -1;
    111. }
    112. return 0;
    113. }
    114. //键盘输入事件
    115. int keybord_events(fd_set readfds)
    116. {
    117. char buf[128] = "";
    118. int sndfd = -1; //从终端获取一个文件描述符,发送数据给该文件描述符对应的客户端
    119. bzero(buf, sizeof(buf));
    120. int res = scanf("%d %s", &sndfd, buf);
    121. while(getchar() != 10);
    122. if(res != 2) //终端输入的数据格式错误
    123. {
    124. printf("输入数据的格式错误,:fd string\n");
    125. return -1;
    126. }
    127. if(sndfd<=2 || FD_ISSET(sndfd, &readfds)==0) //判断文件描述符的合法性
    128. {
    129. printf("非法的文件描述符:sndfd=%d\n", sndfd);
    130. return -1;
    131. }
    132. if(send(sndfd, buf, sizeof(buf), 0) < 0)
    133. {
    134. ERR_MSG("send");
    135. return -1;
    136. }
    137. printf("send success\n");
    138. return 0;
    139. }
    140. //客户端连接事件
    141. int cliConnect_events(int sfd, struct sockaddr_in saveCin[], fd_set *preadfds, int *pmaxfd)
    142. {
    143. int newfd = -1;
    144. struct sockaddr_in cin; //存储客户端的地址信息
    145. socklen_t addrlen = sizeof(cin); //真实的地址信息结构体的大小
    146. newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen);
    147. if(newfd < 0)
    148. {
    149. ERR_MSG("newfd");
    150. return -1;
    151. }
    152. printf("[%s:%d]客户端连接成功 newfd=%d\n", \
    153. inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), newfd);
    154. saveCin[newfd] = cin; //将cin另存到newfd对应的下标位置去
    155. FD_SET(newfd, preadfds); //将newfd添加到集合中
    156. *pmaxfd = *pmaxfd>newfd ? *pmaxfd:newfd; //更新maxfd
    157. return 0;
    158. }
    159. //客户端交互事件
    160. int cliRcvSnd_events(int fd, struct sockaddr_in* saveCin, fd_set *preadfds, int* pmaxfd)
    161. {
    162. char buf[128] = "";
    163. //清空字符串
    164. bzero(buf, sizeof(buf)); //memset
    165. //接收
    166. ssize_t res = recv(fd, buf, sizeof(buf), 0);
    167. if(res < 0)
    168. {
    169. ERR_MSG("recv");
    170. return -1;
    171. }
    172. else if(0 == res)
    173. {
    174. printf("[%s:%d]客户端下线 newfd=%d\n", \
    175. inet_ntoa(saveCin[fd].sin_addr), ntohs(saveCin[fd].sin_port), fd);
    176. close(fd); //关闭文件描述符
    177. FD_CLR(fd, preadfds); //将文件描述符从集合中剔除
    178. //由于剔除的文件描述符可能是最大文件描述符,所以要更新maxfd
    179. //从大往小判断,若在集合中,则该文件描述符就是最大的文件描述符
    180. /*
    181. for(; *pmaxfd>=0; *pmaxfd--)
    182. {
    183. if(FD_ISSET(*pmaxfd, preadfds))
    184. break;
    185. }
    186. */
    187. while(FD_ISSET(*pmaxfd, preadfds)==0 && (*pmaxfd)-->=0);
    188. return 0;
    189. }
    190. printf("[%s:%d] newfd=%d : %s\n", \
    191. inet_ntoa(saveCin[fd].sin_addr), ntohs(saveCin[fd].sin_port), fd, buf);
    192. //发送
    193. strcat(buf, "*_*");
    194. if(send(fd, buf, sizeof(buf), 0) < 0)
    195. {
    196. ERR_MSG("send");
    197. return -1;
    198. }
    199. printf("send success\n");
    200. return 0;
    201. }

  • 相关阅读:
    【17.19消失的两个数字】
    浅谈 Class.forName() 的用法
    将对象集合具有相同属性的分到一组
    Redis实现简易消息队列的三种方式
    2023年第六届先进控制,自动化与机器人国际会议(ICACAR 2023)
    VUE3的 单文件组件 <script setup>理解与实际上手案例
    软件License授权原理
    看 Serverless Task 如何解决任务调度&可观测性中的问题
    【Mysql】关联查询1对多处理
    混合编程python与C++
  • 原文地址:https://blog.csdn.net/JunCool02/article/details/133469006