1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
| #include <my_header.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <arpa/inet.h> /* Usage: * 第一步实现双人通信 * 第二步实现通过服务器的双人通信 * 第三步实现通过服务器的多人聊天 * 第四步实现30秒不通讯退出 * 第五步实现私聊 */
//存储accept信息的链表,头尾节点, typedef struct Node{ int client_fd; uint32_t addr; //客户的网址 int port; //客户的端口 int chat_fd; //私聊 int sec; struct Node *next; }Node; typedef struct List{ Node *head; Node *tail; int size; }List; void CreateClientFd(List *list,int cli_sockfd,struct sockaddr_in client_addr,fd_set *monitorset){ Node* newNode = (Node*)malloc(sizeof(Node)); ERROR_CHECK(newNode, NULL, "malloc error");
newNode->client_fd = cli_sockfd; newNode->addr = client_addr.sin_addr.s_addr; newNode->port = ntohs(client_addr.sin_port); newNode->chat_fd = -1; newNode->sec=time(NULL); newNode->next = NULL;
if(list->head == NULL){ list->head = newNode; list->tail = newNode; }else{ list->tail->next = newNode; list->tail = newNode; } list->size++; FD_SET(cli_sockfd,monitorset); } void DelectClientFd(List *list, int cli_sockfd, fd_set *monitorset) { if (list->head == NULL) return;
Node *prev = NULL; Node *curr = list->head; FD_CLR(cli_sockfd, monitorset);
// 查找要删除的节点 while (curr != NULL && curr->client_fd != cli_sockfd) { prev = curr; curr = curr->next; }
// 未找到节点 if (curr == NULL) { return; }
// 解除与该客户端相关的私聊关系 Node* temp = list->head; while (temp != NULL) { if (temp->chat_fd == cli_sockfd) { temp->chat_fd = -1; send(temp->client_fd, "错误:私聊对象已下线,自动结束私聊\n", 34, 0); } temp = temp->next; }
// 删除当前节点 if (prev == NULL) { list->head = curr->next; if (list->head == NULL) list->tail = NULL; } else { prev->next = curr->next; if (curr == list->tail) list->tail = prev; } // 关键:彻底清理 FD_CLR(cli_sockfd, monitorset); // 从监听集合中移除 close(cli_sockfd); // 确保套接字关闭(双重保险) free(curr); // 释放当前节点(关键修复:在删除节点后释放,而非NULL) list->size--; } void TimeNow(){ time_t now = time(NULL); struct tm *time_now = localtime(&now); printf("[%02d:%02d:%02d]",time_now->tm_hour,time_now->tm_min,time_now->tm_sec); } void PrintfList(List *list){ Node*p = list->head; printf("当前在线客户端列表:\n\n"); while(p != NULL){ printf("客户端描述符:%d 端口号是:%d\n\n",p->client_fd,p->port); p = p->next; } } void AcceptClient(int sockfd,fd_set *monitorset,List* list){ //接收新连接 struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int cli_sockfd=accept(sockfd,(struct sockaddr*)&client_addr,&client_len); ERROR_CHECK(cli_sockfd,-1,"error accept");
printf("客户端%d已连接,端口号:%d\n\n", cli_sockfd, ntohs(client_addr.sin_port));
//发送客户自己的端口 send(cli_sockfd,&client_addr.sin_port,sizeof(client_addr.sin_port),0); //添加到客户列表 CreateClientFd(list,cli_sockfd,client_addr,monitorset); //添加到监听集合
Node* p = list->head; while(p != NULL){ if(p->client_fd != cli_sockfd){ char new_conn_msg[100]; sprintf(new_conn_msg, "新客户端%d (端口号:%d)已连接,通过“*chat num”来私聊\n\n", cli_sockfd, ntohs(client_addr.sin_port)); send(p->client_fd, new_conn_msg, strlen(new_conn_msg), 0); } p = p->next; } PrintfList(list); } //p发出给new_p的信息 void SendMsg(int client_fd, Node* p, char* buf) { char msg[4096] = {0}; // 扩大缓冲区,避免溢出 if (p == NULL) { // 服务器消息:拼接前缀和内容 snprintf(msg, sizeof(msg), "服务器消息:%s", buf); } else { // 客户端消息:拼接客户端ID和内容 snprintf(msg, sizeof(msg), "%d号客户端:%s", p->client_fd, buf); } send(client_fd, msg, strlen(msg), 0); // 一次发送完整消息 }
void SendClinetMes(List *list,char* buf,Node *p){ if(p != NULL && p->chat_fd != -1){ SendMsg(p->chat_fd, p,buf); }else{ // 广播消息给其他客户端 Node* new_p = list->head; while(new_p != NULL){ if(new_p != p){ SendMsg(new_p->client_fd,p, buf); } new_p = new_p->next; } } memset(buf,0,1024); } void RecvAndSendClient(List* list,Node *p,char* buf,fd_set *monitorset){ ssize_t ret = recv(p->client_fd, buf,1024, 0); if(ret <= 0 || strcmp(buf, "exit\n") == 0){ TimeNow(); printf("客户端%d下线了,端口号:%d\n", p->client_fd,p->port);
// 从监听集合中移除该客户端 FD_CLR(p->client_fd,monitorset);
// 通知其他客户端 char offline_msg[1024]; sprintf(offline_msg, "端口号%d的客户端下线了\n", p->port); SendClinetMes(list,offline_msg,p);
// 关闭客户端连接并从链表中删除 close(p->client_fd); DelectClientFd(list, p->client_fd,monitorset); memset(buf,0,1024); return; } TimeNow(); printf("客户端 %d:%s\n",p->client_fd,buf); if(strncmp(buf,"*chat ",6) == 0){ //输入想聊天的客户端 int num_fd = atoi(buf+6); Node *new_p = list->head; while(new_p != NULL){ if(num_fd == new_p->client_fd){ if(new_p->chat_fd != -1){ send(p->client_fd,"BUSY",4,0); return; }else{ p->chat_fd = new_p->client_fd; new_p->chat_fd = p->client_fd; memset(buf,0,1024); printf("客户端%d想跟客户端%d私聊\n",p->client_fd,p->chat_fd); char server_msg[1024]; sprintf(server_msg,"有客户端%d私聊短消息,输入 “*endchat“ 断开连接\n",p->client_fd); send(p->chat_fd,server_msg,strlen(server_msg),0); return; } } new_p = new_p->next; } } // 处理结束私聊指令:"*endchat" if (strcmp(buf, "*endchat\n") == 0) { if (p->chat_fd == -1) { send(p->client_fd, "你未在私聊中\n", 14, 0); memset(buf, 0, 1024); return; }
// 查找私聊对象并解除关系 Node* target = list->head; while (target != NULL && target->client_fd != p->chat_fd) { target = target->next; } if (target != NULL) { target->chat_fd = -1; send(target->client_fd, "对方已结束私聊\n", 30, 0); }
p->chat_fd = -1; send(p->client_fd, "已结束私聊\n", 30, 0); memset(buf, 0, 1024); return; }
if(p->chat_fd != -1){ char chat_msg[1024]; sprintf(chat_msg,"[私聊%d]%s\n",p->client_fd,buf); send(p->chat_fd,chat_msg,strlen(chat_msg),0); memset(buf,0,1024); return; } SendClinetMes(list,buf,p); memset(buf,0,1024); } int main(int argc, char *argv[]){ ARGS_CHECK(argc,3); int sockfd = socket(AF_INET,SOCK_STREAM,0); ERROR_CHECK(sockfd,-1,"error socket"); struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(atoi(argv[2])); server_addr.sin_addr.s_addr = inet_addr(argv[1]); //n十进制转二进制 int res_addr = 1; setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&res_addr,sizeof(int)); //可以随时重连
int b_ret = bind(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr)); ERROR_CHECK(b_ret,-1,"bind error");
int lis = listen(sockfd,50); ERROR_CHECK(lis,-1,"listen error"); TimeNow(); printf("服务器开机,正在监听网络:%s:%d\n",inet_ntoa(server_addr.sin_addr),ntohs(server_addr.sin_port));
List *list = (List*)calloc(1,sizeof(List)); //存储客户端信息的表 fd_set monitorset; //监听集合 fd_set readyset; //就绪集合 FD_ZERO(&monitorset); FD_SET(sockfd,&monitorset); char buf[4068] = {0}; struct timeval timeout; timeout.tv_sec=1; timeout.tv_usec=0; while(1){ memcpy(&readyset,&monitorset,sizeof(fd_set)); FD_SET(STDIN_FILENO,&readyset); select(1024,&readyset,NULL,NULL,&timeout); if(FD_ISSET(sockfd,&readyset)){ AcceptClient(sockfd,&monitorset,list); }
if(FD_ISSET(STDIN_FILENO,&readyset)){ // 服务器向所有客户端广播消息 memset(buf,0,1024); Node *p = list->head; int ret = read(STDIN_FILENO,buf,sizeof(buf)); ERROR_CHECK(ret,-1, "error read");
if(ret == 0 || strcmp(buf, "exit\n") == 0){ TimeNow(); printf("服务器准备关闭\n");
// 通知所有客户端服务器即将关闭 while(p != NULL){ send(p->client_fd, "serverexit\n", 11, 0); close(p->client_fd); p = p->next; }
free(list); close(sockfd); return 0; } SendClinetMes(list,buf,NULL); memset(buf, 0, sizeof(buf)); }
Node *list_p = list->head; while(list_p != NULL){ Node *p=list_p->next; if((time(NULL)-list_p->sec)>100){ char server_msg[1024]; sprintf(server_msg,"客户端%d潜水太久,强制退出",list_p->port); SendClinetMes(list,server_msg,NULL); close(list_p->client_fd); DelectClientFd(list, list_p->client_fd,&monitorset); list_p=p; continue; } if(FD_ISSET(list_p->client_fd,&readyset)){ list_p->sec=(time(NULL)); RecvAndSendClient(list,list_p,buf,&monitorset); } list_p=p; } } free(list); close(sockfd); return 0; }
|