经过令国鸡冻的APEC会之后,北京的冬天终于不冷了,有暖气的日子就是倍儿爽呀~~洗完热水澡,舒服的躺在床上欢乐地敲打着键盘,是件多么幸福的事呀,好了,抒发情感后,正题继续。

上节中已经初步学习了UDP的编程,这次主要是进一步加深对UDP的认识,用它来实现一个简易的聊天室程序,下面首先来看一下该程序的总的逻辑架构图:

下面来将其进行分解:

以上就是聊天程序所涉及的一些消息交互的过程,在正式开始代码前,先来看一下该程序的最后效果,对其有一个更加直观的感觉:

接下来再来登录一个用户,这时还是登录aa,用有什么效果呢?

下面给用户发送消息:

接下来客户端退出:

以上就是聊天程序的效果,下面则正式进入代码的阶段,重在分析其流程:

先看一下代码结构:

其中在上面看到了很多状态消息,都利用宏定义在pub.h头文件中:

接下来定义的一些消息结构,也定义在头文件中:

【说明】:关于这里用到的c++知识可以完全理解既可,稍有一点上层编程语言的都很容易理解,例如java,实际上我也还没学过c++的内容,不过将来会扎实地学习它的,这里只是为了实验需要,重在实验的理解。

下面贴出头文件的具体代码:

pub.h:

#ifndef _PUB_H_
#define _PUB_H_ #include <list>
#include <algorithm>
using namespace std; // C2S
#define C2S_LOGIN 0x01
#define C2S_LOGOUT 0x02
#define C2S_ONLINE_USER 0x03 #define MSG_LEN 512 // S2C
#define S2C_LOGIN_OK 0x01
#define S2C_ALREADY_LOGINED 0x02
#define S2C_SOMEONE_LOGIN 0x03
#define S2C_SOMEONE_LOGOUT 0x04
#define S2C_ONLINE_USER 0x05 // C2C
#define C2C_CHAT 0x06 typedef struct message
{
int cmd;
char body[MSG_LEN];
} MESSAGE; typedef struct user_info
{
char username[];
unsigned int ip;
unsigned short port;
} USER_INFO; typedef struct chat_msg
{
char username[];
char msg[];
}CHAT_MSG; typedef list<USER_INFO> USER_LIST; #endif /* _PUB_H_ */

接着开始分析下服务端的代码:

其main函数代码就不做过多解释了,上节UDP编程中已经详细提到过,下面先贴出来:

下面来具体分析该函数:

对应于逻辑图:

下面各个消息处理进行一一分解:

登录do_login:

void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
{
//从客户端信息中来初使化user结构体
USER_INFO user;
strcpy(user.username, msg.body);
user.ip = cliaddr->sin_addr.s_addr;
user.port = cliaddr->sin_port; }

接下来判断用户是否已经登录过:

void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
{
//从客户端信息中来初使化user结构体
USER_INFO user;
strcpy(user.username, msg.body);
user.ip = cliaddr->sin_addr.s_addr;
user.port = cliaddr->sin_port; /* 查找用户 */
USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
{
break;
}
} if (it == client_list.end()) /* 没找到用户 */
{
printf("has a user login : %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
client_list.push_back(user);
}
else /* 找到用户 */
{
printf("user %s has already logined\n", msg.body); }
}

如果是没有登录过,那就是登录成功了,接下来会进行一系列处理,由于便于理解流程,所以下面说明时会对照着客户端的代码:

void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
{
//从客户端信息中来初使化user结构体
USER_INFO user;
strcpy(user.username, msg.body);
user.ip = cliaddr->sin_addr.s_addr;
user.port = cliaddr->sin_port; /* 查找用户 */
USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
{
break;
}
} if (it == client_list.end()) /* 没找到用户 */
{
printf("has a user login : %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
client_list.push_back(user);//将新的用户插入到集合中 // 登录成功应答
MESSAGE reply_msg;
memset(&reply_msg, 0, sizeof(reply_msg));
reply_msg.cmd = htonl(S2C_LOGIN_OK);
sendto(sock, &reply_msg, sizeof(msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); }
else /* 找到用户 */
{
printf("user %s has already logined\n", msg.body); }
}

这时看一下客户端的代码,登录成功应答时客户端是怎么处理的:

void chat_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); struct sockaddr_in peeraddr;
socklen_t peerlen; MESSAGE msg;
while ()
{
//输入用户名
memset(username,,sizeof(username));
printf("please inpt your name:");
fflush(stdout);
scanf("%s", username); //准备向服务端发送登录请求
memset(&msg, , sizeof(msg));
msg.cmd = htonl(C2S_LOGIN);
strcpy(msg.body, username); //发送登录请求给服务端
sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)&servaddr, sizeof(servaddr)); memset(&msg, , sizeof(msg));
//接收服务端的消息,其中就是登录请求的应答信息
recvfrom(sock, &msg, sizeof(msg), 0, NULL, NULL);
int cmd = ntohl(msg.cmd);
if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
printf("user %s already logined server, please use another username\n", username);
else if (cmd == S2C_LOGIN_OK)
{//证明用户已经成功登录了
printf("user %s has logined server\n", username);
break;
} }
}

接着服务端向客户端发送在线人数及列表:

chatsrv.cpp:

void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
{
//从客户端信息中来初使化user结构体
USER_INFO user;
strcpy(user.username, msg.body);
user.ip = cliaddr->sin_addr.s_addr;
user.port = cliaddr->sin_port; /* 查找用户 */
USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
{
break;
}
} if (it == client_list.end()) /* 没找到用户 */
{
printf("has a user login : %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
client_list.push_back(user);//将新的用户插入到集合中 // 登录成功应答
MESSAGE reply_msg;
memset(&reply_msg, , sizeof(reply_msg));
reply_msg.cmd = htonl(S2C_LOGIN_OK);
sendto(sock, &reply_msg, sizeof(msg), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); int count = htonl((int)client_list.size());
// 发送在线人数
sendto(sock, &count, sizeof(int), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); printf("sending user list information to: %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
// 发送在线列表
for (it=client_list.begin(); it != client_list.end(); ++it)
{
sendto(sock, &*it/* *it表示USER_INFO */, sizeof(USER_INFO), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
} }
else /* 找到用户 */
{
printf("user %s has already logined\n", msg.body);
}
}

客户端收到在线列表的处理代码:

chatcli.cpp:

void chat_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); struct sockaddr_in peeraddr;
socklen_t peerlen; MESSAGE msg;
while ()
{
//输入用户名
memset(username,,sizeof(username));
printf("please inpt your name:");
fflush(stdout);
scanf("%s", username); //准备向服务端发送登录请求
memset(&msg, , sizeof(msg));
msg.cmd = htonl(C2S_LOGIN);
strcpy(msg.body, username); //发送登录请求给服务端
sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)&servaddr, sizeof(servaddr)); memset(&msg, , sizeof(msg));
//接收服务端的消息,其中就是登录请求的应答信息
recvfrom(sock, &msg, sizeof(msg), , NULL, NULL);
int cmd = ntohl(msg.cmd);
if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
printf("user %s already logined server, please use another username\n", username);
else if (cmd == S2C_LOGIN_OK)
{//证明用户已经成功登录了
printf("user %s has logined server\n", username);
break;
} }
int count;
recvfrom(sock, &count, sizeof(int), 0, NULL, NULL); int n = ntohl(count);
printf("has %d users logined server\n", n); for (int i=0; i<n; i++)
{
USER_INFO user;
recvfrom(sock, &user, sizeof(USER_INFO), 0, NULL, NULL);
client_list.push_back(user);//每接收到一个用户,则插入到聊天成员列表中
in_addr tmp;
tmp.s_addr = user.ip; printf("%d %s <-> %s:%d\n", i, user.username, inet_ntoa(tmp), ntohs(user.port));
} }

下面则向其它用户通知有新用户登录:

void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
{
//从客户端信息中来初使化user结构体
USER_INFO user;
strcpy(user.username, msg.body);
user.ip = cliaddr->sin_addr.s_addr;
user.port = cliaddr->sin_port; /* 查找用户 */
USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
{
break;
}
} if (it == client_list.end()) /* 没找到用户 */
{
printf("has a user login : %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
client_list.push_back(user);//将新的用户插入到集合中 // 登录成功应答
MESSAGE reply_msg;
memset(&reply_msg, , sizeof(reply_msg));
reply_msg.cmd = htonl(S2C_LOGIN_OK);
sendto(sock, &reply_msg, sizeof(msg), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); int count = htonl((int)client_list.size());
// 发送在线人数
sendto(sock, &count, sizeof(int), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); printf("sending user list information to: %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
// 发送在线列表
for (it=client_list.begin(); it != client_list.end(); ++it)
{
sendto(sock, &*it, sizeof(USER_INFO), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
} // 向其他用户通知有新用户登录
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == 0)
continue; struct sockaddr_in peeraddr;
memset(&peeraddr, 0, sizeof(peeraddr));
peeraddr.sin_family = AF_INET;
peeraddr.sin_port = it->port;
peeraddr.sin_addr.s_addr = it->ip; msg.cmd = htonl(S2C_SOMEONE_LOGIN);
memcpy(msg.body, &user, sizeof(user)); if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < 0)
ERR_EXIT("sendto");
}
}
else /* 找到用户 */
{
printf("user %s has already logined\n", msg.body);
}
}

如果发现该用户已经登录了,则给出已登录的提示:

void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
{
//从客户端信息中来初使化user结构体
USER_INFO user;
strcpy(user.username, msg.body);
user.ip = cliaddr->sin_addr.s_addr;
user.port = cliaddr->sin_port; /* 查找用户 */
USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
{
break;
}
} if (it == client_list.end()) /* 没找到用户 */
{
printf("has a user login : %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
client_list.push_back(user);//将新的用户插入到集合中 // 登录成功应答
MESSAGE reply_msg;
memset(&reply_msg, , sizeof(reply_msg));
reply_msg.cmd = htonl(S2C_LOGIN_OK);
sendto(sock, &reply_msg, sizeof(msg), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); int count = htonl((int)client_list.size());
// 发送在线人数
sendto(sock, &count, sizeof(int), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); printf("sending user list information to: %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
// 发送在线列表
for (it=client_list.begin(); it != client_list.end(); ++it)
{
sendto(sock, &*it, sizeof(USER_INFO), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
} // 向其他用户通知有新用户登录
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
continue; struct sockaddr_in peeraddr;
memset(&peeraddr, , sizeof(peeraddr));
peeraddr.sin_family = AF_INET;
peeraddr.sin_port = it->port;
peeraddr.sin_addr.s_addr = it->ip; msg.cmd = htonl(S2C_SOMEONE_LOGIN);
memcpy(msg.body, &user, sizeof(user)); if (sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < )
ERR_EXIT("sendto"); }
}
else /* 找到用户 */
{
printf("user %s has already logined\n", msg.body); MESSAGE reply_msg;
memset(&reply_msg, 0, sizeof(reply_msg));
reply_msg.cmd = htonl(S2C_ALREADY_LOGINED);
sendto(sock, &reply_msg, sizeof(reply_msg), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
}
}

接下来,回到客户端这边来,当登录成功之后,会列出该客户端能用到的命令:

chatcli.cpp:

void chat_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); struct sockaddr_in peeraddr;
socklen_t peerlen; MESSAGE msg;
while ()
{
//输入用户名
memset(username,,sizeof(username));
printf("please inpt your name:");
fflush(stdout);
scanf("%s", username); //准备向服务端发送登录请求
memset(&msg, , sizeof(msg));
msg.cmd = htonl(C2S_LOGIN);
strcpy(msg.body, username); //发送登录请求给服务端
sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)&servaddr, sizeof(servaddr)); memset(&msg, , sizeof(msg));
//接收服务端的消息,其中就是登录请求的应答信息
recvfrom(sock, &msg, sizeof(msg), , NULL, NULL);
int cmd = ntohl(msg.cmd);
if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
printf("user %s already logined server, please use another username\n", username);
else if (cmd == S2C_LOGIN_OK)
{//证明用户已经成功登录了
printf("user %s has logined server\n", username);
break;
} }
int count;
recvfrom(sock, &count, sizeof(int), , NULL, NULL); int n = ntohl(count);
printf("has %d users logined server\n", n); for (int i=; i<n; i++)
{
USER_INFO user;
recvfrom(sock, &user, sizeof(USER_INFO), , NULL, NULL);
client_list.push_back(user);
in_addr tmp;
tmp.s_addr = user.ip; printf("%d %s <-> %s:%d\n", i, user.username, inet_ntoa(tmp), ntohs(user.port));
} printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n"); }

接下来用I/O复用模型select函数,来并发处理I/O套接字,因为既有可能产生键盘套接字,也有sock,所以需要用I/O复用模型,如下:

chatcli.cpp:

void chat_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); struct sockaddr_in peeraddr;
socklen_t peerlen; MESSAGE msg;
while ()
{
//输入用户名
memset(username,,sizeof(username));
printf("please inpt your name:");
fflush(stdout);
scanf("%s", username); //准备向服务端发送登录请求
memset(&msg, , sizeof(msg));
msg.cmd = htonl(C2S_LOGIN);
strcpy(msg.body, username); //发送登录请求给服务端
sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)&servaddr, sizeof(servaddr)); memset(&msg, , sizeof(msg));
//接收服务端的消息,其中就是登录请求的应答信息
recvfrom(sock, &msg, sizeof(msg), , NULL, NULL);
int cmd = ntohl(msg.cmd);
if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
printf("user %s already logined server, please use another username\n", username);
else if (cmd == S2C_LOGIN_OK)
{//证明用户已经成功登录了
printf("user %s has logined server\n", username);
break;
} }
int count;
recvfrom(sock, &count, sizeof(int), , NULL, NULL); int n = ntohl(count);
printf("has %d users logined server\n", n); for (int i=; i<n; i++)
{
USER_INFO user;
recvfrom(sock, &user, sizeof(USER_INFO), , NULL, NULL);
client_list.push_back(user);
in_addr tmp;
tmp.s_addr = user.ip; printf("%d %s <-> %s:%d\n", i, user.username, inet_ntoa(tmp), ntohs(user.port));
} printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n"); fd_set rset;
FD_ZERO(&rset);
int nready;
while (1)
{
FD_SET(STDIN_FILENO, &rset);//将标准输入加入到集合中
FD_SET(sock, &rset);//将sock套接字加入集合中
nready = select(sock+1, &rset, NULL, NULL, NULL);
if (nready == -1)
ERR_EXIT("select"); if (nready == 0)
continue; if (FD_ISSET(sock, &rset))
{
peerlen = sizeof(peeraddr);
memset(&msg,0,sizeof(msg));
recvfrom(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&peeraddr, &peerlen);
int cmd = ntohl(msg.cmd);
//将服务端发过来的消息进行分发
switch (cmd)
{
case S2C_SOMEONE_LOGIN:
do_someone_login(msg);
break;
case S2C_SOMEONE_LOGOUT:
do_someone_logout(msg);
break;
case S2C_ONLINE_USER:
do_getlist(sock);
break;
case C2C_CHAT:
do_chat(msg);
break;
default:
break; }
}
if (FD_ISSET(STDIN_FILENO, &rset))
{//标准输入产生了事件
char cmdline[100] = {0};
if (fgets(cmdline, sizeof(cmdline), stdin) == NULL)
break; if (cmdline[0] == '\n')
continue;
cmdline[strlen(cmdline) - 1] = '\0';
//对用户敲的命令进行解析处理
parse_cmd(cmdline, sock, &servaddr);
}
}
}

下面来看一下parse_cmd函数的实现:

在看具体代码前,先看一下用户输入命令的几种情况:

下面具体来看一下该命令解析函数的实现:

首先从输入的字符中查找空格,并替换成'\0',如下:

void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
{
char cmd[]={};
char *p;
p = strchr(cmdline, ' ');//检查空格
if (p != NULL)
*p = '\0';//将控格替换成\0 strcpy(cmd, cmdline);
}

然后下面对其输入的命令进行判断:

void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
{
char cmd[]={};
char *p;
p = strchr(cmdline, ' ');
if (p != NULL)
*p = '\0'; strcpy(cmd, cmdline); if (strcmp(cmd, "exit") == )
{//退出 }
else if (strcmp(cmd, "send") == )
{//向用户发送消息 }
else if (strcmp(cmd, "list") == )
{//列出在线用户列表 }
else
{//说明输入命令有误,给出正确命令提示
printf("bad command\n");
printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n");
}
}

当用户敲入了"exit"命令时,会执行下面这段逻辑:

void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
{
char cmd[]={};
char *p;
p = strchr(cmdline, ' ');
if (p != NULL)
*p = '\0'; strcpy(cmd, cmdline); if (strcmp(cmd, "exit") == )
{//退出
MESSAGE msg;
memset(&msg,,sizeof(msg));
msg.cmd = htonl(C2S_LOGOUT);//向服务器发送C2S_LOGOUT消息
strcpy(msg.body, username); if (sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < )
ERR_EXIT("sendto"); printf("user %s has logout server\n", username);
exit(EXIT_SUCCESS); }
else if (strcmp(cmd, "send") == )
{//向用户发送消息 }
else if (strcmp(cmd, "list") == )
{//列出在线用户列表 }
else
{//说明输入命令有误,给出正确命令提示
printf("bad command\n");
printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n");
}
}

当用户向其它用户发送聊天信息的话:

void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
{
char cmd[]={};
char *p;
p = strchr(cmdline, ' ');
if (p != NULL)
*p = '\0'; strcpy(cmd, cmdline); if (strcmp(cmd, "exit") == )
{//退出
MESSAGE msg;
memset(&msg,,sizeof(msg));
msg.cmd = htonl(C2S_LOGOUT);
strcpy(msg.body, username); if (sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < )
ERR_EXIT("sendto"); printf("user %s has logout server\n", username);
exit(EXIT_SUCCESS); }
else if (strcmp(cmd, "send") == )
{//向用户发送消息
char peername[16]={0};//要发送的用户名
char msg[MSG_LEN]={0};//要发送的消息 //下面则开始解析命令
/* send user msg */
/* p p2 */
while (*p++ == ' ') ;
char *p2;
p2 = strchr(p, ' ');
if (p2 == NULL)
{
printf("bad command\n");
printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n");
return;
}
*p2 = '\0';
strcpy(peername, p); while (*p2++ == ' ') ;
strcpy(msg, p2);
//然后将消息发送给对方,这里封装了一个方法
sendmsgto(sock, peername, msg);
}
else if (strcmp(cmd, "list") == )
{//列出在线用户列表
}
else
{//说明输入命令有误,给出正确命令提示
printf("bad command\n");
printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n");
}
}

下面来看一下sendmsgto方法的具体实现:

bool sendmsgto(int sock, char* name, char* msg)
{
if (strcmp(name, username) == )
{//如果向当前用户发送消息,给出错误提示
printf("can't send message to self\n");
return false;
} return true;
}
bool sendmsgto(int sock, char* name, char* msg)
{
if (strcmp(name, username) == )
{//如果向当前用户发送消息,给出错误提示
printf("can't send message to self\n");
return false;
} //下面开始遍历要发送的用户是否已经登录
USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,name) == )
break;
} if (it == client_list.end())
{//说明要发送的用户还没有登录,给出错误提示
printf("user %s has not logined server\n", name);
return false;
}
return true;
}
bool sendmsgto(int sock, char* name, char* msg)
{
if (strcmp(name, username) == )
{//如果向当前用户发送消息,给出错误提示
printf("can't send message to self\n");
return false;
} USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,name) == )
break;
} if (it == client_list.end())
{
printf("user %s has not logined server\n", name);
return false;
} //流程走到这,证明要发送的用户是已经成功登录过的,所以接下来是组拼消息
MESSAGE m;
memset(&m,,sizeof(m));
m.cmd = htonl(C2C_CHAT);//向服务器发送C2C_CHAT命令 CHAT_MSG cm;
strcpy(cm.username, username);
strcpy(cm.msg, msg); memcpy(m.body, &cm, sizeof(cm));
//strcpy(m.body,msg); struct sockaddr_in peeraddr;
memset(&peeraddr,,sizeof(peeraddr));
peeraddr.sin_family = AF_INET;
peeraddr.sin_addr.s_addr = it->ip;
peeraddr.sin_port = it->port; in_addr tmp;
tmp.s_addr = it->ip; printf("sending message [%s] to user [%s] <-> %s:%d\n", msg, name, inet_ntoa(tmp), ntohs(it->port)); //发送消息
sendto(sock, (const char*)&m, sizeof(m), , (struct sockaddr *)&peeraddr, sizeof(peeraddr));
return true;
}

如果是输入的在线用户列表的命令,则会走如下逻辑:

void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
{
char cmd[]={};
char *p;
p = strchr(cmdline, ' ');
if (p != NULL)
*p = '\0'; strcpy(cmd, cmdline); if (strcmp(cmd, "exit") == )
{//退出
MESSAGE msg;
memset(&msg,,sizeof(msg));
msg.cmd = htonl(C2S_LOGOUT);
strcpy(msg.body, username); if (sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < )
ERR_EXIT("sendto"); printf("user %s has logout server\n", username);
exit(EXIT_SUCCESS); }
else if (strcmp(cmd, "send") == )
{//向用户发送消息
char peername[]={};
char msg[MSG_LEN]={}; /* send user msg */
/* p p2 */
while (*p++ == ' ') ;
char *p2;
p2 = strchr(p, ' ');
if (p2 == NULL)
{
printf("bad command\n");
printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n");
return;
}
*p2 = '\0';
strcpy(peername, p); while (*p2++ == ' ') ;
strcpy(msg, p2);
sendmsgto(sock, peername, msg);
}
else if (strcmp(cmd, "list") == )
{//列出在线用户列表
MESSAGE msg;
memset(&msg, 0, sizeof(msg));
msg.cmd = htonl(C2S_ONLINE_USER); if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < 0)
ERR_EXIT("sendto"
);
}
else
{//说明输入命令有误,给出正确命令提示
printf("bad command\n");
printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n");
}
}

至此parse_cmd函数的实现就分析到这,客户端还有可能会收到服务端发来的网络的消息,所以下面来看一下这些消息的分发:

下面来具体分析一下实现:

当有用户登录了,do_someone_login函数实现如下:

当有用户登出了,do_someone_logout函数实现如下:

当用户要获取当前在线用户列表时,do_getlist函数实现如下:

void do_getlist(int sock)
{
int count;
recvfrom(sock, &count, sizeof(int), , NULL, NULL);//首先得到用户列表的总个数
printf("has %d users logined server\n", ntohl(count));
client_list.clear();//将当前的在线列表清空 int n = ntohl(count);
for (int i=; i<n; i++)//然后再一个个接收用户,插入到在线列表集合中
{
USER_INFO user;
recvfrom(sock,&user, sizeof(USER_INFO), , NULL, NULL);
client_list.push_back(user);
in_addr tmp;
tmp.s_addr = user.ip; printf("%s <-> %s:%d\n", user.username, inet_ntoa(tmp), ntohs(user.port));
}
}

当要发送消息时,do_chat函数消息实现如下:

void do_chat(const MESSAGE& msg)
{
CHAT_MSG *cm = (CHAT_MSG*)msg.body;
printf("recv a msg [%s] from [%s]\n", cm->msg, cm->username);
//recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
}

好了,关于聊天程序的大体流程已经分析完毕,具体的一些细节可以细看代码,下面将完整代码贴出来:

chatsrv.cpp:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #include "pub.h" #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() // 聊天室成员列表
USER_LIST client_list; void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr);
void do_logout(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr);
void do_sendlist(int sock, struct sockaddr_in *cliaddr); void chat_srv(int sock)
{
struct sockaddr_in cliaddr;
socklen_t clilen;
int n;
MESSAGE msg;
while ()
{
memset(&msg, , sizeof(msg));
clilen = sizeof(cliaddr);
n = recvfrom(sock, &msg, sizeof(msg), , (struct sockaddr *)&cliaddr, &clilen);
if (n < )
{
if (errno == EINTR)
continue;
ERR_EXIT("recvfrom");
} int cmd = ntohl(msg.cmd);
switch (cmd)
{
case C2S_LOGIN:
do_login(msg, sock, &cliaddr);
break;
case C2S_LOGOUT:
do_logout(msg, sock, &cliaddr);
break;
case C2S_ONLINE_USER:
do_sendlist(sock, &cliaddr);
break;
default:
break;
}
}
} void do_login(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
{
//从客户端信息中来初使化user结构体
USER_INFO user;
strcpy(user.username, msg.body);
user.ip = cliaddr->sin_addr.s_addr;
user.port = cliaddr->sin_port; /* 查找用户 */
USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
{
break;
}
} if (it == client_list.end()) /* 没找到用户 */
{
printf("has a user login : %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
client_list.push_back(user);//将新的用户插入到集合中 // 登录成功应答
MESSAGE reply_msg;
memset(&reply_msg, , sizeof(reply_msg));
reply_msg.cmd = htonl(S2C_LOGIN_OK);
sendto(sock, &reply_msg, sizeof(msg), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); int count = htonl((int)client_list.size());
// 发送在线人数
sendto(sock, &count, sizeof(int), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); printf("sending user list information to: %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port));
// 发送在线列表
for (it=client_list.begin(); it != client_list.end(); ++it)
{
sendto(sock, &*it, sizeof(USER_INFO), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
} // 向其他用户通知有新用户登录
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
continue; struct sockaddr_in peeraddr;
memset(&peeraddr, , sizeof(peeraddr));
peeraddr.sin_family = AF_INET;
peeraddr.sin_port = it->port;
peeraddr.sin_addr.s_addr = it->ip; msg.cmd = htonl(S2C_SOMEONE_LOGIN);
memcpy(msg.body, &user, sizeof(user)); if (sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < )
ERR_EXIT("sendto"); }
}
else /* 找到用户 */
{
printf("user %s has already logined\n", msg.body); MESSAGE reply_msg;
memset(&reply_msg, , sizeof(reply_msg));
reply_msg.cmd = htonl(S2C_ALREADY_LOGINED);
sendto(sock, &reply_msg, sizeof(reply_msg), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
}
} void do_logout(MESSAGE& msg, int sock, struct sockaddr_in *cliaddr)
{
printf("has a user logout : %s <-> %s:%d\n", msg.body, inet_ntoa(cliaddr->sin_addr), ntohs(cliaddr->sin_port)); USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
break;
} if (it != client_list.end())
client_list.erase(it); // 向其他用户通知有用户登出
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
continue; struct sockaddr_in peeraddr;
memset(&peeraddr, , sizeof(peeraddr));
peeraddr.sin_family = AF_INET;
peeraddr.sin_port = it->port;
peeraddr.sin_addr.s_addr = it->ip; msg.cmd = htonl(S2C_SOMEONE_LOGOUT); if (sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < )
ERR_EXIT("sendto"); }
} void do_sendlist(int sock, struct sockaddr_in *cliaddr)
{
MESSAGE msg;
msg.cmd = htonl(S2C_ONLINE_USER);
sendto(sock, (const char*)&msg, sizeof(msg), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); int count = htonl((int)client_list.size());
/* 发送在线用户数 */
sendto(sock, (const char*)&count, sizeof(int), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
/* 发送在线用户列表 */
for (USER_LIST::iterator it=client_list.begin(); it != client_list.end(); ++it)
{
sendto(sock, &*it, sizeof(USER_INFO), , (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in));
}
} int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, )) < )
ERR_EXIT("socket"); struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < )
ERR_EXIT("bind"); chat_srv(sock);
return ;
}

chatcli.cpp:

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h> #include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h> #include "pub.h" #define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while() // 当前用户名
char username[]; // 聊天室成员列表
USER_LIST client_list; void do_someone_login(MESSAGE& msg);
void do_someone_logout(MESSAGE& msg);
void do_getlist();
void do_chat(); void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr);
bool sendmsgto(int sock, char* username, char* msg); void parse_cmd(char* cmdline, int sock, struct sockaddr_in *servaddr)
{
char cmd[]={};
char *p;
p = strchr(cmdline, ' ');
if (p != NULL)
*p = '\0'; strcpy(cmd, cmdline); if (strcmp(cmd, "exit") == )
{//退出
MESSAGE msg;
memset(&msg,,sizeof(msg));
msg.cmd = htonl(C2S_LOGOUT);
strcpy(msg.body, username); if (sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < )
ERR_EXIT("sendto"); printf("user %s has logout server\n", username);
exit(EXIT_SUCCESS); }
else if (strcmp(cmd, "send") == )
{//向用户发送消息
char peername[]={};
char msg[MSG_LEN]={}; /* send user msg */
/* p p2 */
while (*p++ == ' ') ;
char *p2;
p2 = strchr(p, ' ');
if (p2 == NULL)
{
printf("bad command\n");
printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n");
return;
}
*p2 = '\0';
strcpy(peername, p); while (*p2++ == ' ') ;
strcpy(msg, p2);
sendmsgto(sock, peername, msg);
}
else if (strcmp(cmd, "list") == )
{//列出在线用户列表
MESSAGE msg;
memset(&msg, , sizeof(msg));
msg.cmd = htonl(C2S_ONLINE_USER); if (sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < )
ERR_EXIT("sendto");
}
else
{//说明输入命令有误,给出正确命令提示
printf("bad command\n");
printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n");
}
} bool sendmsgto(int sock, char* name, char* msg)
{
if (strcmp(name, username) == )
{
printf("can't send message to self\n");
return false;
} USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,name) == )
break;
} if (it == client_list.end())
{
printf("user %s has not logined server\n", name);
return false;
} MESSAGE m;
memset(&m,,sizeof(m));
m.cmd = htonl(C2C_CHAT); CHAT_MSG cm;
strcpy(cm.username, username);
strcpy(cm.msg, msg); memcpy(m.body, &cm, sizeof(cm));
//strcpy(m.body,msg); struct sockaddr_in peeraddr;
memset(&peeraddr,,sizeof(peeraddr));
peeraddr.sin_family = AF_INET;
peeraddr.sin_addr.s_addr = it->ip;
peeraddr.sin_port = it->port; in_addr tmp;
tmp.s_addr = it->ip; printf("sending message [%s] to user [%s] <-> %s:%d\n", msg, name, inet_ntoa(tmp), ntohs(it->port)); sendto(sock, (const char*)&m, sizeof(m), , (struct sockaddr *)&peeraddr, sizeof(peeraddr));
return true;
} void do_getlist(int sock)
{
int count;
recvfrom(sock, &count, sizeof(int), , NULL, NULL);//首先得到用户列表的总个数
printf("has %d users logined server\n", ntohl(count));
client_list.clear();//将当前的在线列表清空 int n = ntohl(count);
for (int i=; i<n; i++)//然后再一个个接收用户,插入到在线列表集合中
{
USER_INFO user;
recvfrom(sock,&user, sizeof(USER_INFO), , NULL, NULL);
client_list.push_back(user);
in_addr tmp;
tmp.s_addr = user.ip; printf("%s <-> %s:%d\n", user.username, inet_ntoa(tmp), ntohs(user.port));
}
} void do_someone_login(MESSAGE& msg)
{
USER_INFO *user = (USER_INFO*)msg.body;
in_addr tmp;
tmp.s_addr = user->ip;
printf("%s <-> %s:%d has logined server\n", user->username, inet_ntoa(tmp), ntohs(user->port));
client_list.push_back(*user);
} void do_someone_logout(MESSAGE& msg)
{
USER_LIST::iterator it;
for (it=client_list.begin(); it != client_list.end(); ++it)
{
if (strcmp(it->username,msg.body) == )
break;
} if (it != client_list.end())
client_list.erase(it); printf("user %s has logout server\n", msg.body);
} void do_chat(const MESSAGE& msg)
{
CHAT_MSG *cm = (CHAT_MSG*)msg.body;
printf("recv a msg [%s] from [%s]\n", cm->msg, cm->username);
//recvfrom(sock, &count, sizeof(int), 0, NULL, NULL);
} void chat_cli(int sock)
{
struct sockaddr_in servaddr;
memset(&servaddr, , sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons();
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); struct sockaddr_in peeraddr;
socklen_t peerlen; MESSAGE msg;
while ()
{
//输入用户名
memset(username,,sizeof(username));
printf("please inpt your name:");
fflush(stdout);
scanf("%s", username); //准备向服务端发送登录请求
memset(&msg, , sizeof(msg));
msg.cmd = htonl(C2S_LOGIN);
strcpy(msg.body, username); //发送登录请求给服务端
sendto(sock, &msg, sizeof(msg), , (struct sockaddr *)&servaddr, sizeof(servaddr)); memset(&msg, , sizeof(msg));
//接收服务端的消息,其中就是登录请求的应答信息
recvfrom(sock, &msg, sizeof(msg), , NULL, NULL);
int cmd = ntohl(msg.cmd);
if (cmd == S2C_ALREADY_LOGINED)//证明用户已经登录过
printf("user %s already logined server, please use another username\n", username);
else if (cmd == S2C_LOGIN_OK)
{//证明用户已经成功登录了
printf("user %s has logined server\n", username);
break;
} }
int count;
recvfrom(sock, &count, sizeof(int), , NULL, NULL); int n = ntohl(count);
printf("has %d users logined server\n", n); for (int i=; i<n; i++)
{
USER_INFO user;
recvfrom(sock, &user, sizeof(USER_INFO), , NULL, NULL);
client_list.push_back(user);
in_addr tmp;
tmp.s_addr = user.ip; printf("%d %s <-> %s:%d\n", i, user.username, inet_ntoa(tmp), ntohs(user.port));
} printf("\nCommands are:\n");
printf("send username msg\n");
printf("list\n");
printf("exit\n");
printf("\n"); fd_set rset;
FD_ZERO(&rset);
int nready;
while ()
{
FD_SET(STDIN_FILENO, &rset);//将标准输入加入到集合中
FD_SET(sock, &rset);//将sock套接字加入集合中
nready = select(sock+, &rset, NULL, NULL, NULL);
if (nready == -)
ERR_EXIT("select"); if (nready == )
continue; if (FD_ISSET(sock, &rset))
{
peerlen = sizeof(peeraddr);
memset(&msg,,sizeof(msg));
recvfrom(sock, &msg, sizeof(msg), , (struct sockaddr *)&peeraddr, &peerlen);
int cmd = ntohl(msg.cmd);
//将服务端发过来的消息进行分发
switch (cmd)
{
case S2C_SOMEONE_LOGIN:
do_someone_login(msg);
break;
case S2C_SOMEONE_LOGOUT:
do_someone_logout(msg);
break;
case S2C_ONLINE_USER:
do_getlist(sock);
break;
case C2C_CHAT:
do_chat(msg);
break;
default:
break; }
}
if (FD_ISSET(STDIN_FILENO, &rset))
{//标准输入产生了事件
char cmdline[] = {};
if (fgets(cmdline, sizeof(cmdline), stdin) == NULL)
break; if (cmdline[] == '\n')
continue;
cmdline[strlen(cmdline) - ] = '\0';
//对用户敲的命令进行解析处理
parse_cmd(cmdline, sock, &servaddr);
}
}
} int main(void)
{
int sock;
if ((sock = socket(PF_INET, SOCK_DGRAM, )) < )
ERR_EXIT("socket"); chat_cli(sock); return ;
}

其中用到的头文件在文章开头已经有了,这次的学习到这,主要是重在体会代码的流程。

linux网络编程之socket编程(十四)的更多相关文章

  1. linux网络编程之socket编程(十六)

    继续学习socket编程,今天的内容会有些难以理解,一步步来分解,也就不难了,正入正题: 实际上sockpair有点像之前linux系统编程中学习的pipe匿名管道,匿名管道它是半双工的,只能用于亲缘 ...

  2. linux网络编程之socket编程(十五)

    今天继续学习socket编程,这次主要是学习UNIX域协议相关的知识,下面开始: [有个大概的认识,它是来干嘛的] ①.UNIX域套接字与TCP套接字相比较,在同一台主机的传输速度前者是后者的两倍. ...

  3. linux网络编程之socket编程(十二)

    今天继续学习socket编程,期待的APEC会议终于在京召开了,听说昨晚鸟巢那灯火通明,遍地礼花,有点08年奥运会的架势,有种冲动想去瞅见一下习大大的真容,"伟大的祖国,我爱你~~~&quo ...

  4. linux网络编程之socket编程(十)

    今天继续socket编程的学习,最近晚上睡觉都没有发热,没有暖气的日子还是种煎熬,快乐的十一也已经走来,幸福有暖气的日子也快啦,好了,回到正题~ ①close终止了数据传送的两个方向. ②shutdo ...

  5. linux网络编程之socket编程(四)

    经过两周的等待,终于可以回归我正常的学习之旅了,表哥来北京了在我这暂住,晚上回家了基本在和他聊天,周末带他在北京城到处乱转,几乎剥夺了我自由学习的时间了,不过,亲人之情还是很难得的,工作学习并不是生活 ...

  6. linux网络编程之socket编程(一)

    今天开始,继续来学习linux编程,这次主要是研究下linux下的网络编程,而网络编程中最基本的需从socket编程开始,下面正式开始学习: 什么是socket: 在学习套接口之前,先要回顾一下Tcp ...

  7. linux网络编程之socket编程(六)

    经过一个国庆长假,又有一段时间没有写博文了,今天继续对linux网络编程进行学习,如今的北京又全面进入雾霾天气了,让我突然想到了一句名句:“真爱生活,珍惜生命”,好了,言归正传. 回顾一下我们之间实现 ...

  8. linux网络编程之socket编程(八)

    学习socket编程继续,今天要学习的内容如下: 先来简单介绍一下这五种模型分别是哪些,偏理论,有个大致的印象就成,做个对比,因为最终只会研究一个I/O模型,也是经常会用到的, 阻塞I/O: 先用一个 ...

  9. linux网络编程之socket编程(七)

    今天继续学习socket编程,北京在持续几天的雾霾天之后久违的太阳终于出来了,心情也特别特别的好,于是乎,在这美好的夜晚,该干点啥事吧,那当然就是继续坚持我的程序学习喽,闲话不多说,进入正题: 通过这 ...

随机推荐

  1. c# stmp邮件发送

    最近用到了邮件发送功能,因为stmp设置.参数传递错误等一些问题走了弯路,虽然代码很简单,这里还是记录一下,方便下次查阅. 用个人163邮箱测试的邮件发送 class Program { static ...

  2. Flask项目中使用mysql数据库启动项目是发出警告

    Flask项目中使用mysql数据库启动项目是发出警告: Warning: (1366, "Incorrect string value: '\xD6\xD0\xB9\xFA\xB1\xEA ...

  3. java properties文件转义字符和中文乱码解决

    properties文件的分隔符是   =或者 : 第一次出现的就是分割符,第二次出现的也不需要转义,也即是(忽略掉[],只是着重描述字符) [\=]     [\:]   或者  [=]  [:] ...

  4. JDK1.8 的 HashMap 源码之文件注释

    文章目录 null 插入,key的位置变化 迭代操作时间 性能因素 负载因子 Comparable 加锁 迭代器修改 null 插入,key的位置变化,迭代操作时间,性能因素,负载因子,Compara ...

  5. Word 文档内超级链接跳转到书签

    1. 前言 在Word文档内如何实现一些跳转的超链接呢?Word中,一些外部链接,我们通常叫作超链接,内部链接我们可以叫书签.如何在文档中如何使用书签,跳转到指定位置? 这里我在网上随便找了一份模拟试 ...

  6. 长乐培训Day5

    T1 圆圈舞蹈 题目 [题目描述] 熊大妈的奶牛在时针的带领下,围成了一个圈跳舞.由于没有严格的教育,奶牛们之间的间隔不一致. 奶牛想知道两只最远的奶牛到底隔了多远.奶牛A到B的距离为A顺时针走和逆时 ...

  7. 以环形角度理解php数组索引

    array_slice ( array $array , int $offset [, int $length = NULL [, bool $preserve_keys = false ]] ) : ...

  8. Synergy 一套键鼠同时控制多台电脑

    百度云(1.5是最后一个免费的版本,linux,mac,windows全有): 链接: https://pan.baidu.com/s/1A_qR8FHd8a0ILQXsSqjfCQ 密码: 9h8c

  9. SQL Server2008 删除重复记录只剩一条(无Uid)

    INSERT INTO 表1   SELECT  *  FROM  视图1 CREATE TABLE  ##TMP01                                    ---创建 ...

  10. wget的url获取方式

    获取方式 每次用wget都是在网上查相应的url,但以前没怎么关注过这个url是怎么获取到的,这里总结一下 这里以下载jekins为例: 打开jekins网站:https://jenkins.io/d ...