15.基於UDP協議的聊天室程式
使用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
相關文章
- 基於TCP/UDP的Socket程式設計,HTTP/HTTPS協議TCPUDP程式設計HTTP協議
- 網路協議之:基於UDP的高速資料傳輸協議UDT協議UDP
- 基於SpringBoot+STOMP協議實現的web聊天室Spring Boot協議Web
- 15. SPI通訊協議協議
- 基於UDP協議的乙太網資料收發控制器UDP協議
- 網路程式設計UDP協議方式程式設計UDP協議
- TCP和UDP協議TCPUDP協議
- Socket:UDP協議小白UDP協議
- UDP協議詳解UDP協議
- Java基礎 UDP協議下,收發資料的程式碼實現JavaUDP協議
- 終於懂了TCP和UDP協議區別TCPUDP協議
- 基於UDP程式設計UDP程式設計
- TCP/IP、UDP/IP協議TCPUDP協議
- 基於TCP協議的Socket網路程式設計( )TCP協議程式設計
- UDP協議抓包分析 -- wiresharkUDP協議
- 系列TCP/IP協議-UDP(009)TCP協議UDP
- netty系列之:使用UDP協議NettyUDP協議
- TCP 和 UDP 協議簡介TCPUDP協議
- TCP對應的協議和UDP對應的協議(簡單概述)TCP協議UDP
- 基於HTTP的功能追加協議HTTP協議
- Qt 基於QTcpSocket的ModbusTCP協議QTTCP協議
- 淺談TCP和UDP協議的區別TCPUDP協議
- 傳輸層協議 TCP 和 UDP協議TCPUDP
- 網路程式設計協議(TCP和UDP協議,黏包問題)以及socketserver模組程式設計協議TCPUDPServer
- 為什麼 DNS 協議使用 UDP?只使用了 UDP 嗎?DNS協議UDP
- 基於netty的聊天室Netty
- 基於golang的聊天室Golang
- netty系列之:protobuf在UDP協議中的使用NettyUDP協議
- DNS何時使用TCP與UDP協議?DNSTCPUDP協議
- UDP 和 TCP 兩種協議簡介UDPTCP協議
- iOS中基於協議的路由設計iOS協議路由
- STOMP協議——基於Websocket實現協議Web
- 計算機網路之六:UDP協議計算機網路UDP協議
- UDP 協議簡單瞭解及應用UDP協議
- Python教程之udp和tcp協議介紹PythonUDPTCP協議
- 基於QUIC協議的HTTP/3正式釋出!UI協議HTTP
- DNS 協議為什麼使用 UDP?你確定只使用UDP了嗎?DNS協議UDP
- Node TCP /UDP 簡易聊天室TCPUDP
- 好程式設計師Python培訓分享udp和tcp協議介紹程式設計師PythonUDPTCP協議