使用UDP协议完成一个聊天室程序的小项目,大部分代码都有注释,一看就能看到的.
实现的功能:
(1)查看/显示已经登陆的用户信息
(2)向已登陆的用户发送消息
(3)输出错误消息,给予提示
(4)退出
共有三个文件:
chat_public.h
#ifndef _CHAT_PUB_H_ #define _CHAT_PUB_H_ //chat_public.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[16]; // 用户名 unsigned int ip; // IP地址 unsigned short port; //端口 } USER_INFO; typedef struct chat_msg //客户与客户之间的消息结构 { char username[16]; char msg[100]; }CHAT_MSG; typedef list<USER_INFO> USER_LIST; // 用list<USER_INFO> 容器 #endif /* _PUB_H_ */
chatser.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 "chat_public.h" #define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0) // 聊天室成员列表 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 (1) { memset(&msg, 0, sizeof(msg)); clilen = sizeof(cliaddr); n = recvfrom(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&cliaddr, &clilen); // 接收客户到服务器的消息 if (n < 0) { 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_INFO user; // 定义用户结构体变量 strcpy(user.username, msg.body); // 取出用户名 user.ip = cliaddr->sin_addr.s_addr; // ip地址 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) == 0) { 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)); 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, sizeof(USER_INFO), 0, (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); 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)); } } 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) == 0) 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) == 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_LOGOUT); if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr)) < 0) 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), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); int count = htonl((int)client_list.size()); /* 发送在线用户数 */ sendto(sock, (const char*)&count, sizeof(int), 0, (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), 0, (struct sockaddr *)cliaddr, sizeof(struct sockaddr_in)); } } int main(void) { int sock; if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) ERR_EXIT("socket"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sock, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0) ERR_EXIT("bind"); chat_srv(sock); return 0; }
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 "chat_public.h" #define ERR_EXIT(m) do { perror(m); exit(EXIT_FAILURE); } while(0) // 当前用户名 char username[16]; // 聊天室成员列表 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[10]={0}; char *p; p = strchr(cmdline, ' '); if (p != NULL) *p = '\0'; strcpy(cmd, cmdline); if (strcmp(cmd, "exit") == 0) // 退出命令 { MESSAGE msg; memset(&msg,0,sizeof(msg)); msg.cmd = htonl(C2S_LOGOUT); strcpy(msg.body, username); if (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(struct sockaddr_in)) < 0) ERR_EXIT("sendto"); printf("user %s has logout server\n", username); exit(EXIT_SUCCESS); } else if (strcmp(cmd, "send") == 0) // send命令 { char peername[16]={0}; char msg[MSG_LEN]={0}; // send user msg while (*p++ == ' ') ; char *p2; //extern char *strchr(const char *s,char c);查找字符串s中首次出现字符c的位置。 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") == 0) // 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"); } } bool sendmsgto(int sock, char* name, char* msg) { if (strcmp(name, username) == 0) // 不能向自己发送消息 { 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) == 0) break; } if (it == client_list.end()) { printf("user %s has not logined server\n", name); return false; } MESSAGE m; memset(&m,0,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,0,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), 0, (struct sockaddr *)&peeraddr, sizeof(peeraddr)); return true; } void do_getlist(int sock) { int count; recvfrom(sock, &count, sizeof(int), 0, NULL, NULL); printf("has %d users logined server\n", ntohl(count));// 打印有在线用户人数 client_list.clear(); // 清空当前用户列表 int n = ntohl(count); 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("%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) == 0) 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, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(5188); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); struct sockaddr_in peeraddr; socklen_t peerlen; MESSAGE msg; while (1) { memset(username,0,sizeof(username)); printf("please inpt your name:"); fflush(stdout); scanf("%s", username); memset(&msg, 0, sizeof(msg)); msg.cmd = htonl(C2S_LOGIN); strcpy(msg.body, username); sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); memset(&msg, 0, 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; } } 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)); } 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); 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); } } memset(&msg,0,sizeof(msg)); msg.cmd = htonl(C2S_LOGOUT); strcpy(msg.body, username); sendto(sock, (const char*)&msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); close(sock); } int main(void) { int sock; if ((sock = socket(PF_INET, SOCK_DGRAM, 0)) < 0) ERR_EXIT("socket"); chat_cli(sock); return 0; }
服务器:
客户1:cjl
客户2:yzq
原文地址:http://blog.csdn.net/u014304293/article/details/45799659