最近Linux網絡程序設計課程大作業,要求設計一個簡易的網絡聊天室,功能如下:
- 網絡聊天室
功能要點:
(1)用戶管理:註冊、修改密碼;
(2)聊天室管理:用戶登錄、創建聊天室、設置聊天室密碼;
(3)聊天管理:在同一聊天室裏,用戶所發送的消息每位在線用戶都可以收到,也可以單獨給某位在線用戶發消息;可以查詢聊天室在線用戶信息;
(4)系統管理:顯示所有在線用戶;顯示所有聊天室;給所有在線用戶羣發消息;提供命令幫助,讓用戶瞭解命令的格式:
例如 send user1 message1表示給用戶user1發送消息message1等。
本人編程學的比較爛,但是還是勉強湊出了這些功能,在這也分享一下我的代碼,代碼是一小部分非原創,因爲是老師上課講的一部分代碼,剩下絕大部分均爲自己寫的,在這裏分享一下。
首先server.c
#include <stdio.h>
#include <stdlib.h> // exit
#include <string.h>
#include <unistd.h> // bind listen
#include <time.h> // time(NULL) ctime(&ticks)
#include <netinet/in.h>
#include <arpa/inet.h> // 必須包含,用於inet_ntop
#include <pthread.h>
#define PORT 8000
#define MAXMEM 10
#define BUFFSIZE 128
//#define DEBUG_PRINT 1 // 宏定義 調試開關
#ifdef DEBUG_PRINT
#define DEBUG(format, ...) printf("FILE: "__FILE__", LINE: %d: "format"\n", __LINE__, ##__VA_ARGS__)
#else
#define DEBUG(format, ...)
#endif
FILE *fp;
int listenfd, connfd[MAXMEM];
struct client{ /* 結構體存儲一組用戶名和對應的套接字 */
char name[20]; /* 用戶名 */
int socket_name; /* 套接字 */
char chatroom[20]; /* 加入的聊天室 */
};
struct client info_class[MAXMEM]; /* 定義結構體數組 */
void quit(); /*輸入quit時退出服務器*/
void rcv_snd(int n);
void regist(int n); /* 註冊賬戶 */
void login(int n); /* 登錄 */
void create_chatroom(int n); /* 創建聊天室函數 */
void join_chatroom(int n); /* 加入聊天室函數 */
void show(int n); /* 服務命令處理函數 */
int main()
{
struct sockaddr_in serv_addr, cli_addr;
int i;
time_t ticks;
pthread_t thread;
char buff[BUFFSIZE];
printf("running...\n(Prompt: enter command ""quit"" to exit server)\n");
DEBUG("=== initialize..."); // 初始化填充服務端地址結構
/*將server_addr指向內存的前sizeof(struct sockaddr_in)字節清零*/
bzero(&serv_addr, sizeof(struct sockaddr_in));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
DEBUG("=== socket...");
/*socket 創建服務器端的監聽套接字*/
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd < 0)
{
perror("fail to socket");
exit(-1);
}
DEBUG("=== bind...");
/*bind 將套接字與填充好的地址結構進行綁定*/
if(bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
perror("fail to bind");
exit(-2);
}
DEBUG("=== listening...");
/*listen 將主動連接套接字變爲被動傾聽套接字*/
listen(listenfd, MAXMEM);
/* === 創建一個線程,對服務器程序進行管理,調用quit函數 === */
pthread_create(&thread, NULL, (void *)(quit), NULL);
// 將套接字描述符數組初始化爲-1,表示空閒
for(i=0; i<MAXMEM; i++)
connfd[i] = -1;
while(1)
{
int len; // = sizeof(cli_addr);
for(i=0; i<MAXMEM; i++)
{
if(connfd[i] == -1)
break;
}
// accept 從listen接受的連接隊列中取得一個連接
connfd[i] = accept(listenfd, (struct sockaddr *)&cli_addr, &len);
if(connfd[i] < 0)
{
perror("fail to accept");
// continue; // 此句可以不用,accept會阻塞等待
}
ticks = time(NULL);
//sprintf(buff, "%.24s\r\n", ctime(&ticks));
printf("%.24s\n\tconnect from: %s, port %d\n",
ctime(&ticks), inet_ntop(AF_INET, &(cli_addr.sin_addr), buff, BUFFSIZE),
ntohs(cli_addr.sin_port)); // 注意 inet_ntop的使用,#include <arpa/inet.h>
/* === 針對當前套接字創建一個線程,對當前套接字的消息進行處理 === */
pthread_create(malloc(sizeof(pthread_t)), NULL, (void *)(&rcv_snd), (void *)i);
}
return 0;
}
void quit()
{
char msg[10];
while(1)
{
scanf("%s", msg); // scanf 不同於fgets, 它不會讀入最後輸入的換行符
if(strcmp(msg, "quit") == 0)
{
printf("Byebye... \n");
close(listenfd);
exit(0);
}
}
}
void rcv_snd(int n)
{
int len, i;
char buf[BUFFSIZE] ,mytime[32], v[5]; /*v用接受服務命令字符*/
time_t ticks;
int ret;
write(connfd[n], "---Service select(1/2)---\n\t1.User registration\n\t2.User login\n",
strlen("---Service select(1/2)---\n\t1.User registration\n\t2.User login\n"));
len = read(connfd[n], v, 5); /* 讀取客戶端輸入的1或者2命令 */
if(len > 0)
v[len-1] = '\0'; /* 去除換行符 */
if(strcmp(v,"1") == 0){ /*如果選擇註冊服務*/
strcpy(buf,"---User registration---\n");
/*在客戶端顯示當前狀態*/
write(connfd[n],buf,strlen(buf));
regist(n);
}else if(strcmp(v,"2") == 0){ /*如果選擇登錄服務*/
strcpy(buf,"---User login---\n");
/*在客戶端顯示當前狀態*/
write(connfd[n],buf,strlen(buf));
login(n);
}
else{
write(connfd[n], "Invalid command, enter again.\n",
strlen("Invalid command, enter again.\n"));
rcv_snd(n); /* 輸入其他值時重新進入服務選項 */
}
/* 登陸成功後服務提示,這裏就是大廳 */
write(connfd[n], "---Service select(1/2/3/4)---\n\t1.Show online users\n\t2.Show all chatrooms\n\t3.Create a chatroom\n\t4.Join a chatroom\n",
strlen("---Service select(1/2/3/4)---\n\t1.Show online users\n\t2.Show all chatrooms\n\t3.Create a chatroom\n\t4.Join a chatroom\n"));
show(n);
while(1)
{
char temp[BUFFSIZE];
char *str;
if((len=read(connfd[n], temp, BUFFSIZE)) > 0) // 檢查如果有消息
{
temp[len-1] = '\0';
/* 當用戶輸入bye時,當前用戶退出 */
if(strcmp(temp, "bye") == 0)
{
close(connfd[n]);
connfd[n] = -1;
pthread_exit(&ret);
}
else if(strcmp(temp, "help") == 0){
memset(buf, 0, sizeof(buf));
strcpy(buf, "\tYou can try these commands.\n");
write(connfd[n], buf, strlen(buf));
strcpy(buf, "[send to user1]: Send the following message to user1.\n");
write(connfd[n], buf, strlen(buf));
strcpy(buf, "[back]: Back to hall.\n");
write(connfd[n], buf, strlen(buf));
strcpy(buf, "[bye]: Exit the client.\n");
write(connfd[n], buf, strlen(buf));
}
else if(strcmp(temp, "back") == 0){
write(connfd[n], "---Service select(1/2/3/4)---\n\t1.Show online users\n\t2.Show all chatrooms\n\t3.Create a chatroom\n\t4.Join a chatroom\n",
strlen("---Service select(1/2/3/4)---\n\t1.Show online users\n\t2.Show all chatrooms\n\t3.Create a chatroom\n\t4.Join a chatroom\n"));
memset(info_class[n].chatroom, 0, sizeof(info_class[n].chatroom)); /* 返回大廳後即退出聊天室 */
show(n);
}
/* 如果當前客戶端在聊天室內 */
else if(strlen(info_class[n].chatroom) != 0){
/* 如果發現關鍵字符串"send to",從聊天室轉入私聊程序段 */
if((str = strstr(temp, "send to ")) != 0){
int x; /* 查找send to後跟用戶名的開始下標值 */
char des[10]; /* 用來存儲目標用戶的名稱,從send to user中獲取 */
x = strspn(temp, "send to "); /* send to 後面用戶名字符串開始的索引 */
strncpy(des, temp + x, strlen(temp)-x+1);
strcat(des, "\n");
for(i=0; i<MAXMEM; i++){
if(strcmp(des, info_class[i].name) == 0){ /* 遍歷匹配user1的姓名 */
while(1){
if((len=read(connfd[n], temp, BUFFSIZE)) > 0){
temp[len-1] = '\0';
if(strcmp(temp, "back") == 0){ /* 如果收到back字段,則返回羣聊 */
write(connfd[n], "You have returned the chatroom, send a message or use 'back' again.\n",
strlen("You have returned the chatroom, send a message or use 'back' again.\n"));
break;
}
else{
ticks = time(NULL);
strftime(mytime,sizeof(mytime),"[%H:%M:%S]",localtime(&ticks));
strcpy(buf, mytime); // 顯示時間
strcat(buf, "\t");
strcat(buf, info_class[n].name); // 顯示用戶名
buf[strlen(buf)-1] = '\0';
strcat(buf, ":\t");
strcat(buf, temp); // 客戶端消息內容
strcat(buf, "\n");
write(connfd[i], buf, strlen(buf));
write(connfd[n], buf, strlen(buf));
}
}
}
}
}
}
else if(strcmp(temp, "show member") == 0){ /* show member顯示聊天室內成員 */
char member[50];
strcpy(member, "---Current members---\n");
for(int i=0; i<MAXMEM; i++)
{
if(strcmp(info_class[i].chatroom, info_class[n].chatroom) == 0){
strcat(member, info_class[i].name); /* 將在線用戶名添加到info */
}
}
write(connfd[n], member, strlen(member));
}
else{ /* 不是私聊那就是在聊天室內羣發 */
for(i=0; i<MAXMEM; i++){
/* 尋找結構體中聊天室名相同的發送信息 */
if(strcmp(info_class[i].chatroom, info_class[n].chatroom) == 0){
char chatroom[20];
strcpy(chatroom, info_class[n].chatroom);
chatroom[strlen(chatroom)-1] = '\0';
ticks = time(NULL);
strftime(mytime,sizeof(mytime),"[%H:%M:%S] [",localtime(&ticks));
strcpy(buf, mytime); // 顯示時間
strcat(buf, chatroom); // 顯示羣聊名稱
strcat(buf, "]\t");
strcat(buf, info_class[n].name); // 顯示用戶名
buf[strlen(buf)-1] = '\0';
strcat(buf, ":\t");
strcat(buf, temp); // 客戶端消息內容
strcat(buf, "\n");
write(connfd[i], buf, strlen(buf)); /* 將消息發送給聊天室內的每一個人 */
}
}
}
}
/* 如果發現關鍵字符串"send to",轉入私聊程序段 */
else if((str = strstr(temp, "send to ")) != 0){
int x; /* 查找send to後跟用戶名的開始下標值 */
char des[10]; /* 用來存儲目標用戶的名稱,從send to user中獲取 */
x = strspn(temp, "send to "); /* send to 後面用戶名字符串開始的索引 */
strncpy(des, temp + x, strlen(temp)-x+1);
strcat(des, "\n");
for(i=0; i<MAXMEM; i++){
if(strcmp(des, info_class[i].name) == 0){ /* 遍歷匹配user1的姓名 */
while(1){
if((len=read(connfd[n], temp, BUFFSIZE)) > 0){
temp[len-1] = '\0';
if(strcmp(temp, "back") == 0){ /* 如果收到back字段,則返回大廳 */
write(connfd[n], "You are in the hall now, send to everyone or 'back' to hall.\n",
strlen("You are in the hall now, send to everyone or 'back' to hall.\n"));
break;
}
else{
ticks = time(NULL);
strftime(mytime,sizeof(mytime),"[%H:%M:%S]",localtime(&ticks));
strcpy(buf, mytime); // 顯示時間
strcat(buf, "\t");
strcat(buf, info_class[n].name); // 顯示用戶名
buf[strlen(buf)-1] = '\0';
strcat(buf, ":\t");
strcat(buf, temp); // 客戶端消息內容
strcat(buf, "\n");
write(connfd[i], buf, strlen(buf));
write(connfd[n], buf, strlen(buf));
}
}
}
}
}
}
/* 默認狀態下在大廳消息將發送給所有在線用戶 */
else{
ticks = time(NULL);
strftime(mytime,sizeof(mytime),"[%H:%M:%S]",localtime(&ticks));
strcpy(buf, mytime); // 顯示時間
strcat(buf, "\t");
strcat(buf, info_class[n].name); // 顯示用戶名
buf[strlen(buf)-1] = '\0';
strcat(buf, ":[Global message]\t");
strcat(buf, temp); // 客戶端消息內容
strcat(buf, "\n");
/* 發給在線所有用戶 */
for(i=0; i<MAXMEM; i++)
{
if(connfd[i] != -1)
write(connfd[i], buf, strlen(buf));
}
}
}
}
}
/* 註冊函數 */
void regist(int n){
char buf[BUFFSIZE],temp[50],info[20];
int len,i;
strcpy(buf,"Enter User name:");
write(connfd[n],buf,strlen(buf));
if((len = read(connfd[n], info, 20)) > 0) /* 讀註冊時從客戶端傳回來的用戶名 */
{
fp = fopen("UserInfo.txt","a+");
// strcpy(temp, info);
while(!feof(fp)){ /* 一直讀到文件結束 */
fgets(temp,50,fp); /* 讀取一行 */
if(strcmp(temp, info) == 0){ /* 如果存在某一行的值與輸入用戶名相同,則提示已註冊 */
strcpy(buf,"User exist! Please select another name!\n");
write(connfd[n], buf, strlen(buf));
regist(n); /* 如果用戶已存在就跳轉到函數開始重新註冊 */
}
}
strcpy(buf,"Enter User password:");
write(connfd[n], buf, strlen(buf));
if((len = read(connfd[n], temp, 50)) > 0) /* 讀註冊時從客戶端傳回來的密碼 */
{
strcat(info, temp);
if(fp = fopen("UserInfo.txt","a+")) /* 以追加方式打開用戶信息文件,第一次不存在會創建 */
{
fputs(info, fp);
fclose(fp); /* 寫入後關閉文件內容才能顯示 */
}
}
}
strcpy(buf,"Success, Go to login\n");
write(connfd[n],buf,strlen(buf));
login(n); /* 註冊成功後跳轉到登錄 */
}
/* 登錄函數 */
void login(int n){
char buf[BUFFSIZE],temp[50],info[50];
int len;
strcpy(buf,"User name:");
write(connfd[n], buf, strlen(buf));
if((len = read(connfd[n], temp, 50)) > 0){
//temp[len-1] = '\0';
fp = fopen("UserInfo.txt","r");
while(!feof(fp)){ /* 一直讀到文件結束 */
fgets(info, 50, fp); /* 讀取一行 */
if(strcmp(temp, info) == 0){ /* 如果存在某一行的值與輸入用戶名相同,則用戶已經註冊,可以正常登錄*/
char str[50];
/* 如果賬戶存在,那麼下一行就是本賬戶的密碼 */
fgets(str, 50, fp); /* 再讀一行,將密碼讀取出來 */
strcat(info, str);
fclose(fp);
break;
}
}
strcpy(buf,"Enter User password:");
write(connfd[n],buf,strlen(buf));
char str[50];
if((len = read(connfd[n], str, 50)) > 0) /* 讀註冊時從客戶端傳回來的密碼 */
{
//temp[len-1] = '\0';
strcpy(info_class[n].name, temp); /* 將客戶名賦給結構體name */
strcat(temp, str);
if(strcmp(temp, info) == 0){
info_class[n].socket_name = connfd[n]; /* 登陸成功後將客戶套接字賦給結構體的套接字成員變量 */
strcpy(buf,"Login success.\n");
write(connfd[n],buf,strlen(buf));
}
else{
strcpy(buf,"User not exist or Password error! Enter again.\n");
write(connfd[n],buf,strlen(buf));
login(n);
}
}
}
}
/* 服務端接收處理大廳相應客戶端命令 */
void show(int n){
char buf[BUFFSIZE], temp[5], info[50];
int len;
if((len = read(connfd[n], temp, 5)) > 0){
temp[len-1] = '\0'; /* 去除換行符 */
if(strcmp(temp, "1") == 0){ /* 顯示在線用戶 */
strcpy(info, "---Current online users---\n");
for(int i=0; i<MAXMEM; i++)
{
if(connfd[i] != -1){
strcat(info, info_class[i].name); /* 將在線用戶名添加到info */
}
}
write(connfd[n], info, strlen(info));
write(connfd[n], "Not sure what to do next, try 'help' command.\n",strlen("Not sure what to do next, try 'help' command.\n"));
}
else if(strcmp(temp, "2") == 0){ /* 顯示所有聊天室 */
strcpy(info, "---Current chatrooms---\n");
for(int i=0; i<MAXMEM; i++)
{
if(connfd[i] != -1){
for(int j=0; j<i; j++){
if(strcmp(info_class[i].chatroom, info_class[j].chatroom) == 0){/* 同一個聊天室的名稱只算作一次 */
strcat(info, info_class[i].chatroom);/* 將正在使用的聊天室名附加到info */
}
}
}
}
write(connfd[n], info, strlen(info));
write(connfd[n], "Not sure what to do next, try 'help' command.\n",strlen("Not sure what to do next, try 'help' command.\n"));
}
else if(strcmp(temp, "3") == 0){ /* 創建聊天室 */
create_chatroom(n);
}
else if(strcmp(temp, "4") == 0){ /* 加入聊天室 */
join_chatroom(n);
}
else{
strcpy(buf,"Invalid command, input again.\n");
write(connfd[n], buf, strlen(buf));
show(n); /* 無效命令碼,重新輸入 */
}
}
}
/* 創建聊天室函數 */
void create_chatroom(int n){
char buf[BUFFSIZE], temp[50], info[20];
int len;
strcpy(buf, "Give the chatroom a name:");
write(connfd[n], buf, strlen(buf));
if((len = read(connfd[n], info, 20)) > 0){ /* 接收聊天室名字 */
fp = fopen("ChatInfo.txt", "a+");
while(!feof(fp)){ /* 一直讀到文件結束 */
fgets(temp,50,fp); /* 讀取一行 */
if(strcmp(temp, info) == 0){ /* 如果存在某一行的值與輸入用戶名相同,則提示聊天室已被創建 */
strcpy(buf,"Same name chatroom exist! Please select another name!\n");
write(connfd[n], buf, strlen(buf));
create_chatroom(n); /* 如果聊天室已存在就跳轉到函數開始重新創建 */
}
}
strcpy(buf, "Set a password for the chatroom:");
write(connfd[n], buf, strlen(buf));
if((len = read(connfd[n], temp, 50)) > 0){ /* 接收聊天室密碼 */
strcpy(info_class[n].chatroom, info); /* 創建完成後自動加入聊天室 */
strcat(info, temp);
if(fp = fopen("ChatInfo.txt","a+")) /* 將聊天室信息添加到文件中保存 */
{
fputs(info, fp);
fclose(fp); /* 寫入後關閉文件內容纔會最終保存 */
}
}
}
strcpy(buf, "Success, now you are in the chatroom.\n");
write(connfd[n], buf, strlen(buf));
write(connfd[n], "You can send message in the chatroom or 'back' to hall.\n",strlen("You can send message in the chatroom or 'back' to hall.\n"));
}
/* 加入聊天室函數 */
void join_chatroom(int n){
char buf[BUFFSIZE], temp[50], info[20];
int len;
strcpy(buf, "Enter the name of chatroom you want to join:");
write(connfd[n], buf, strlen(buf));
if((len = read(connfd[n], temp, 50)) > 0){
fp = fopen("ChatInfo.txt", "r");
while(!feof(fp)){
fgets(info, 20, fp); /* 讀取一行 */
if(strcmp(temp, info) == 0){ /* 如果存在某一行的值與輸入相同,則存在聊天室,可以正常加入 */
char str[50];
/* 如果聊天室存在,那麼下一行就是本聊天室的密碼 */
fgets(str, 50, fp); /* 再讀一行,將密碼讀取出來 */
strcat(info, str);
fclose(fp);
break;
}
}
strcpy(buf, "Enter the password before you join in:");
write(connfd[n], buf, strlen(buf));
char str[50];
if((len = read(connfd[n], str, 50)) > 0) /* 讀創建時從客戶端傳回來的聊天室密碼 */
{
strcpy(info_class[n].chatroom, temp); /* 將聊天室名賦給結構體chatroom成員變量 */
strcat(temp, str);
if(strcmp(temp, info) == 0){
strcpy(buf, "Joined, now you can send a message in the chatroom.\n");
write(connfd[n], buf, strlen(buf));
}
else{
strcpy(buf, "Chatroom not exist or Password error! Enter again.\n");
write(connfd[n],buf,strlen(buf));
join_chatroom(n);
}
}
}
}
下面爲client.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#define BUFFSIZE 128
#define HOST_IP "127.0.0.1"
#define PORT 8000
int sockfd;
void snd();
int main()
{
pthread_t thread; /*pthread_t 線程,gcc編譯時需加上-lpthread*/
struct sockaddr_in serv_addr; // struct sockaddr_in
char buf[BUFFSIZE];
/*初始化服務端地址結構*/
bzero(&serv_addr, sizeof(struct sockaddr_in)); // bzero 清零
serv_addr.sin_family = AF_INET; // sin_family AF_INET
serv_addr.sin_port = htons(PORT); // sin_port htons(PORT)
inet_pton(HOST_IP, &serv_addr.sin_addr); // inet_pton
// 創建客戶端套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0); // socket 創建套接字
if(sockfd < 0)
{
perror("fail to socket");
exit(-1);
}
// 與服務器建立連接
printf("connecting... \n");
if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) // connect
{
perror("fail to connect");
exit(-2);
}
/* === 主進程接收數據,從此處開始 程序分做兩個線程 === */
// 創建發送消息的線程,調用發送消息的函數snd
pthread_create(&thread, NULL, (void *)(&snd), NULL); // pthread_create
// 接收消息的線程
while(1)
{
int len;
if((len=read(sockfd, buf, BUFFSIZE)) > 0) // read 讀取通信套接字
{
buf[len] = '\0'; // 添加結束符,避免顯示緩衝區中殘留的內容
printf("\n%s", buf);
fflush(stdout); // fflush 沖洗標準輸出,確保內容及時顯示
}
}
return 0;
}
/*發送消息的函數*/
void snd()
{
char temp[32], buf[BUFFSIZE];
fgets(temp, 32, stdin); // fgets 會讀取輸入字符串後的換行符
write(sockfd, temp, strlen(temp)); // write 寫入通信套接字
while(1)
{
fgets(buf, BUFFSIZE, stdin);
write(sockfd, buf, strlen(buf));
/* 客戶端輸入bye則退出 */
if(strcmp(buf, "bye\n") == 0) // 注意此處的\n
exit(0);
}
}
命令行編譯時使用如下命令:
gcc -o server server.c -lpthread
gcc -o client client.c -lpthread
運行結果我就不放圖了,大家也可以跟我交流一下。