服務器端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <25061head.h>
#define SER_IP "192.168.144.128"
#define SER_PORT 8888// 鏈表節點結構定義
typedef struct Node
{char usrName[30]; // 用戶名struct sockaddr_in cin; // 用戶的地址信息struct Node *next; // 指針域
}*linklist;// 消息結構定義
struct msgTyp
{char type;char usrName[30];char msgText[50];
};int main(int argc, const char *argv[])
{// 前期配置int sfd = socket(AF_INET, SOCK_DGRAM, 0);if(sfd == -1){ERR_MSG("socket error");return -1;}struct sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(SER_PORT);sin.sin_addr.s_addr = inet_addr(SER_IP);if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1){ERR_MSG("bind error");close(sfd);return -1;}printf("綁定成功\n");struct sockaddr_in cin;socklen_t addrlen = sizeof(cin);struct msgTyp recv_msg; // 接收的信息// 創建頭節點并初始化linklist head = (linklist)malloc(sizeof(struct Node));if(head == NULL){ERR_MSG("malloc error");close(sfd);return -1;}head->next = NULL; // 初始化頭節點pid_t pid=fork();if(pid>0){// 核心操作while(1){// 清空接收緩沖區memset(&recv_msg, 0, sizeof(recv_msg));// 接收消息ssize_t recv_len = recvfrom(sfd, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr*)&cin, &addrlen);if(recv_len == -1){ERR_MSG("recvfrom error");continue;}switch(recv_msg.type){case 'L': // login登錄printf("%s登錄成功\n", recv_msg.usrName);// 創建新用戶的節點并初始化linklist temp = (linklist)malloc(sizeof(struct Node));if(temp == NULL){ERR_MSG("malloc error");break;} //存儲新用戶結點的相關信息strcpy(temp->usrName, recv_msg.usrName);temp->cin = cin;//頭插temp->next = head->next;head->next = temp;// 廣播登錄消息linklist s = head->next;char sbuf[128] = "";snprintf(sbuf, sizeof(sbuf)-1, "%s已上號!", recv_msg.usrName);strcpy(recv_msg.msgText,sbuf);//放入recv.msg中,發給其他人//recv_msg.msgText[sizeof(recv_msg.msgText)-1] = '\0';while(s != NULL){// 跳過發送者自己(用IP和端口區分,后面的廣播都是這種方法排除自己)if (s->cin.sin_port == cin.sin_port && s->cin.sin_addr.s_addr == cin.sin_addr.s_addr){s = s->next;continue;}sendto(sfd, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr*)&(s->cin), sizeof(s->cin));s = s->next;}break;case 'C': // chat聊天if(strcmp(recv_msg.usrName,"系統")!=0){printf("%s:chat成功\n", recv_msg.usrName);}char cbuf[128] = "";snprintf(cbuf, sizeof(cbuf)-1, "%s說:%s", recv_msg.usrName, recv_msg.msgText);strcpy(recv_msg.msgText, cbuf);//recv_msg.msgText[sizeof(recv_msg.msgText)-1] = '\0';s = head->next;//從第一個用戶開始while(s != NULL){// 跳過發送者自己if (s->cin.sin_port == cin.sin_port && s->cin.sin_addr.s_addr == cin.sin_addr.s_addr){s = s->next;continue;}sendto(sfd, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr*)&(s->cin), sizeof(s->cin));s = s->next;}break;case 'Q': // quit退出printf("%s退出聊天室\n", recv_msg.usrName);char qbuf[128] = "";snprintf(qbuf, sizeof(qbuf)-1, "%s退出聊天室", recv_msg.usrName);strcpy(recv_msg.msgText, qbuf);//recv_msg.msgText[sizeof(recv_msg.msgText)-1] = '\0';// 先廣播退出消息s = head->next;while(s != NULL){// 跳過發送者自己if (s->cin.sin_port == cin.sin_port && s->cin.sin_addr.s_addr == cin.sin_addr.s_addr){s = s->next;continue;}sendto(sfd, &recv_msg, sizeof(recv_msg), 0, (struct sockaddr*)&(s->cin), sizeof(s->cin));s = s->next;} // 然后從鏈表中刪除該用戶節點linklist prev = head;linklist curr = head->next;int found = 0;//判斷找沒找到結點while(curr != NULL){if(strcmp(curr->usrName, recv_msg.usrName) == 0){//頭刪prev->next = curr->next;free(curr);curr=NULL;found = 1;printf("已從鏈表中移除用戶: %s\n", recv_msg.usrName);break;}prev = curr;curr = curr->next;}if(found==0){printf("警告: 未在鏈表中找到用戶 %s\n", recv_msg.usrName);}break;default:printf("發送格式錯誤\n");}}}//服務器廣播系統信息else if(pid==0){struct msgTyp sys_msg;sys_msg.type='C';strcpy(sys_msg.usrName, "系統");while(1){bzero(sys_msg.msgText,50);fgets(sys_msg.msgText,50, stdin);sys_msg.msgText[strlen(sys_msg.msgText)-1] = 0;//核心操作,因為是進程的原因,鏈表在子進程用不了,不能循環廣播//那我直接向主進程發消息,讓主進程廣播sendto(sfd, &sys_msg, sizeof(sys_msg), 0, (struct sockaddr*)&sin, sizeof(sin));}printf("系統消息發送成功");}close(sfd);return 0;
}
客戶端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>
#include <pthread.h>
#include <25061head.h>
#define SER_IP "192.168.144.128"
#define SER_PORT 8888struct msgTyp
{char type;char usrName[30];char msgText[50];
};int cfd; // 客戶端socket
struct sockaddr_in sin; // 服務器的相關配置
char usrName[30] = ""; // 用戶名
//雖然是線程,但有多個阻塞函數,所以用多線程
//該線程主要功能是接收消息
void *recv_msg_thread(void *arg)
{struct msgTyp recv_msg;socklen_t addrlen = sizeof(sin);while(1){// 清空接收緩沖區memset(&recv_msg, 0, sizeof(recv_msg));// 接收服務器發送的消息ssize_t recv_len = recvfrom(cfd, &recv_msg,sizeof(recv_msg),0,(struct sockaddr*)&sin, &addrlen);// 打印接收的消息printf("%s\n", recv_msg.msgText);fflush(stdout); // 刷新輸出緩沖區}// 接收線程退出時關閉socketclose(cfd);pthread_exit(0);return NULL;
}int main(int argc, const char *argv[])
{// 獲取用戶名printf("請輸入你的用戶名: ");fgets(usrName, sizeof(usrName)-1, stdin);// 去除換行符usrName[strcspn(usrName, "\n")] = '\0';// 創建socketcfd = socket(AF_INET, SOCK_DGRAM, 0);if(cfd == -1){ERR_MSG("socket error");return -1;} // 初始化服務器相關配置sin.sin_family = AF_INET;sin.sin_port = htons(SER_PORT);sin.sin_addr.s_addr = inet_addr(SER_IP);// 配置登錄消息struct msgTyp send_msg;send_msg.type = 'L';strcpy(send_msg.usrName,usrName);//strcpy(send_msg.msgText, "");if(sendto(cfd, &send_msg, sizeof(send_msg), 0,(struct sockaddr*)&sin, sizeof(sin)) == -1){ERR_MSG("sendto error");close(cfd);return -1;}printf("登錄成功!輸入'quit'退出聊天室...\n");// 創建接收消息的線程pthread_t recv_tid;if(pthread_create(&recv_tid, NULL, recv_msg_thread, NULL) != 0){ERR_MSG("pthread_create error");close(cfd);return -1;}// 分離線程,系統自動回收pthread_detach(recv_tid);// 主線程用于發送消息while(1){char input[50] = "";fgets(input, sizeof(input)-1, stdin);// 去除換行符input[strcspn(input, "\n")] = '\0';//輸入quit,消息類型則設置從Q類型if(strcmp(input, "quit") == 0){send_msg.type = 'Q';strcpy(send_msg.usrName, usrName); sendto(cfd, &send_msg, sizeof(send_msg),0,(struct sockaddr*)&sin, sizeof(sin));printf("已退出聊天室\n");close(cfd);return 0;}// 發送聊天消息send_msg.type = 'C';strcpy(send_msg.usrName, usrName);strcpy(send_msg.msgText, input);if(sendto(cfd, &send_msg, sizeof(send_msg),0,(struct sockaddr*)&sin, sizeof(sin)) == -1){ERR_MSG("sendto error");close(cfd);return -1;}}//關閉socketclose(cfd);return 0;
}