-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsmall-server.c
266 lines (243 loc) · 9.49 KB
/
small-server.c
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
/** server.c -- SmallChat Server
*
* 实现聊天室主要逻辑:
* - 接收新的客户端连接
* - 接收客户端发送的消息
* - 将消息转发给其他客户端
*/
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <errno.h>
#include <sys/select.h>
#include "chatlib.h"
#include "log.h"
// 最大客户端连接数
#define MAX_CLIENTS 1000
// 服务端口
#define SERVER_PORT 7711
// 客户端关闭指令
#define EXIT "exit\n"
// 服务端全局状态数据,启动时由`initChat`函数初始化
struct chatState *Chat;
/* 表示一个已连接的客户端 */
struct client{
// client socket fd
int fd;
// client name
char *nick_name;
};
/* 全局状态体 */
struct chatState{
// server socket fd
int server_sock;
// 当前已建立连接的客户端数量
int num_clients;
// 当前最大客户端连接 fd
int max_client;
// client list
struct client* clients[MAX_CLIENTS];
};
/**
* 将新建立的连接(fd),封装为一个客户端实例
*/
struct client* create_client(int client_fd){
// 初始昵称: "user-fd"
char nick[32];
int nick_len = snprintf(nick, sizeof(nick),"user:%d", client_fd);
// 初始化客户端
struct client* client = chatMalloc(sizeof(*client));
socketSetNonBlockNoDelay(client_fd);
client->fd = client_fd;
// 设置昵称
client->nick_name = chatMalloc(nick_len + 1);
memcpy(client->nick_name, nick, nick_len);
// 将连接放入客户端列表
assert(Chat->clients[client->fd] == NULL);
Chat->clients[client->fd] = client;
// 更新当前最大客户端fd
if (client->fd > Chat->max_client)
Chat->max_client = client->fd;
Chat->num_clients++;
return client;
}
/**
* 将消息发送给所有客户端(发送者除外)
*/
void sendMessageToAllClientsBut(int sender, char* msg, size_t msg_len){
for (int j = 0; j <= Chat->max_client; j++){
if (Chat->clients[j] == NULL || Chat->clients[j]->fd == sender) continue;
write(Chat->clients[j]->fd, msg, msg_len);
}
}
/**
* 关闭客户端,释放资源.
*/
void closeClient(struct client* client){
Info("Disconnected client fd = %d, nick = %s", client->fd, client->nick_name);
// 广播退出通知消息
char notify_message[sizeof(client->nick_name) + 24];
int notify_len = snprintf(notify_message, sizeof(notify_message), "Player [%s] Quit Chat!\n", client->nick_name);
sendMessageToAllClientsBut(client->fd, notify_message, notify_len);
free(client->nick_name);
close(client->fd);
Chat->clients[client->fd] = NULL;
Chat->num_clients--;
// 如果关闭的是最大客户端,则找出新的最大客户端并且更新
if (Chat->max_client == client->fd){
int j;
for (j = Chat->max_client - 1; j >= 0; j--){
if (Chat->clients[j] != NULL){
Chat->max_client = j;
break;
}
}
if (j == -1)
// 已经没有客户端
Chat->max_client = -1;
}
free(client);
}
/**
* 初始化服务端全局状态数据
*/
void initChat(void){
// alloc memory
Chat = chatMalloc(sizeof(*Chat));
memset(Chat, 0, sizeof(*Chat));
Chat->max_client = -1;
Chat->num_clients = 0;
// Create server listening socket
Chat->server_sock = createTCPServer(SERVER_PORT);
if (Chat->server_sock == -1){
perror("Creating listening socket");
exit(1);
}
}
/**
* Chat Server
*
*/
int main(void){
// 初始化服务端
initChat();
// event loop
while (1){
// 需要被 select 监听的描述符集合
fd_set listen_fds;
// 设置 select 监听超时时间为 1s
struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;
// 每次 select 的结果
int retval;
// 清除集合,并将 服务监听socket放入集合
FD_ZERO(&listen_fds);
FD_SET(Chat->server_sock, &listen_fds);
// 将所有客户端连接 socket 放入集合
for (int j = 0; j <= Chat->max_client; j++){
if (Chat->clients[j])
FD_SET(j, &listen_fds);
}
// 本次要监听最大fd
int listen_max_fd = Chat->max_client;
if (listen_max_fd < Chat->server_sock) listen_max_fd = Chat->server_sock;
// select listen
retval = select(listen_max_fd + 1, &listen_fds, NULL, NULL, &timeout);
if (retval == -1){
// 错误处理
if (errno == EINTR) {
// 可能由于信号而被打断
Error("server listening interrupt dut to signal");
continue;
};
perror("select () error");
exit(1);
}else if (retval){
// 服务端 socket 就绪
if (FD_ISSET(Chat->server_sock, &listen_fds)){
int fd = acceptClient(Chat->server_sock);
struct client* client = create_client(fd);
// 回复欢迎消息
char *welcome_message =
"Welcome to Small Chat! \n"
"Use /nike <nick> to set your nick. \n";
write(client->fd, welcome_message, strlen(welcome_message));
Info("Connected client fd = %d", fd);
// 广播玩家进入通知消息
char notify_msg[sizeof(client->nick_name) + 24];
int notify_len = snprintf(notify_msg, sizeof(notify_msg), "Player [%s] enter Chat!\n", client->nick_name);
sendMessageToAllClientsBut(fd, notify_msg, notify_len);
}
char buf[256];
// 遍历检查是否有客户端发送数据
for (int j = 0; j <= Chat->max_client; j++){
if (Chat->clients[j] == NULL)
continue;
// 处理事件就绪的客户端
if (FD_ISSET(j, &listen_fds)){
struct client* client = Chat->clients[j];
int nread = read(j, buf, sizeof(buf) - 1);
if (nread <= 0){
// 客户端关闭
closeClient(client);
}else{
buf[nread] = 0;
// 发送的是命令,处理命令,目前只支持修改昵称 '/nike <>'
if (buf[0] == '/'){
// 清除尾行的换行符等
char *p;
p = strchr(buf, '\r'); if (p) *p = 0;
p = strchr(buf, '\n'); if (p) *p = 0;
// 获取客户端要修改的新名称
char *new_nick = strchr(buf, ' ');
if (new_nick){
*new_nick = 0;
new_nick++;
}
if (!strcmp(buf, "/nick") && new_nick){
// 构建通知消息
ssize_t old_len = strlen(client->nick_name);
ssize_t new_len = strlen(new_nick);
char notify_msg[30 + old_len + new_len];
int msg_len = snprintf(notify_msg, sizeof(notify_msg), "Player [%s] rename [%s]\n", client->nick_name, new_nick);
// 修改客户端昵称
free(client->nick_name);
client->nick_name = chatMalloc(new_len + 1);
memcpy(client->nick_name, new_nick, new_len + 1);
char succmsg[] = "\n Rename success.\n\n";
write(client->fd, succmsg, sizeof(succmsg));
sendMessageToAllClientsBut(client->fd, notify_msg, msg_len);
}else{
// 不支持的命令
char *errmsg = "\n Sorry Unsupported Command.\n\n";
write(client->fd, errmsg, strlen(errmsg));
}
}else{
if (strlen(buf) == strlen(EXIT) && strncmp(buf, EXIT, strlen(EXIT)) == 0) { // 客户端关闭
closeClient(client);
continue;
}
// 发送的是消息,广播给其他客户端
// 消息格式: 发送者> 消息内容
// 总消息大小:消息长度 + 昵称长度 + 1(换行符)
char message[nread + sizeof(client->nick_name) + 1];
int message_len = snprintf(message, sizeof(message), "%s> %s", client->nick_name, buf);
// snprintf 返回值可能大于 sizeof(message)
if (message_len >= (int)sizeof(message)){
message_len = sizeof(message) - 1;
}
printf("%s", message);
sendMessageToAllClientsBut(j, message, message_len);
}
}
}
}
}else{
// select 监听超时
// Debug("server listen timeout...");
}
}
return 0;
}