项目需求:
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发送信息,其他用户可以收到这个人的群聊信息
- 如果有人下线,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息
服务器:
#include <myhead.h>
//定义存储信息结构体
typedef struct _MSG
{
char code; //操作码:'L'表示登录 'C表示群聊 'Q'表示退出
char name[20];
char txt[256];
}msg_t;
//定义保存客户端网络信息的链表
typedef struct _ADDR
{
struct sockaddr_in cin;
struct _ADDR* next;
}addrlist_t;
//登录操作的函数
void do_login(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
//先遍历链表 将新用户加入群聊的消息发送给所有客户端
addrlist_t* tmp = addr; //记录链表头结点
while(tmp->next != NULL)
{
tmp = tmp->next;
if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(tmp->cin),sizeof(tmp->cin)) == -1)
{
perror("sendto error");
return;
}
}
//将新用户的网络信息结构体头插入链表
addrlist_t* pnew = NULL;
if((pnew = (addrlist_t*)malloc(sizeof(addrlist_t)))==NULL){
printf("malloc error\n");
return;
}
pnew->cin = cin;
pnew->next = addr->next;
addr->next = pnew;
printf("--%s已上线--\n",msg.name);
return;
}
//群聊操作函数
void do_chat(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
//遍历链表,将群聊消息发给除自己以外的其他客户端
addrlist_t* ptmp = addr;
while(ptmp->next != NULL)
{
ptmp = ptmp->next;
if(memcmp(&cin, &(ptmp->cin), sizeof(cin))){
//判断哪个客户发的信息
if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin)) == -1)
{
perror("sendto error");
return;
}
}
}
return;
}
//退出操作的函数
void do_quit(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
addrlist_t* ptmp = addr;
addrlist_t* del = NULL;
while(ptmp->next != NULL)
{
if(memcmp(&(ptmp->next->cin), &cin, sizeof(cin)))
{
//判断是否是其他客户发送的消息
ptmp = ptmp->next;
if((sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin))) == -1)
{
perror("sendto error");
return;
}
}else
{
del = ptmp->next;
ptmp->next = del->next;
free(del);
del=NULL;
}
}
printf("--%s已下线--\n",msg.name);
return;
}
int main(int argc, const char *argv[])
{
if(argc != 3){ //输入ip地址及端口号,进行判断
printf("input error\n");
printf("usage: %s <IP> <PORT>\n",argv[0]);
return -1;
}
//定义用于接收等待套接字
int sfd;
if((sfd = socket(AF_INET,SOCK_DGRAM,0)) == -1)
{
perror("socket error");
return -1;
}
printf("socket sfd success\n");
//设置端口号快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1){
perror("setsockopt error");
return -1;
}
//绑定
//填充服务器信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t seraddr_len = sizeof(sin);
if((bind(sfd, (struct sockaddr*)&sin, seraddr_len)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//定义客户端网络信息结构体
struct sockaddr_in cin;
socklen_t cliaddr_len = sizeof(cin);
msg_t msg; //定义接收信息的变量msg
pid_t pid; //进程号
pid = fork(); //创建多进程
if(pid < 0){
perror("fork error");
return -1;
}else if(pid == 0)
{
//子进程,用来收发数据
//创建保存客户端信息的链表头结点
addrlist_t* addr;
if((addr = (addrlist_t*)malloc(sizeof(addrlist_t)))==NULL)
{
printf("malloc error\n");
return -1;
}
bzero(addr, sizeof(addr));
addr->next = NULL;
while(1)
{ //循环收发数据
bzero(&msg,sizeof(msg)); //每次接收新用户数据清空
bzero(&cin,sizeof(cin));
//接收客户端发送的消息,存放在msg中
if((recvfrom(sfd, &msg,sizeof(msg), 0,(struct sockaddr*)&cin, &cliaddr_len)) == -1)
{
perror("recvfrom error");
return -1;
}
switch(msg.code){ //判断消息中的操作码,根据操作码执行对应操作
case 'L': //登录操作
do_login(sfd,msg,addr,cin);
break;
case 'C': //群聊操作
do_chat(sfd,msg,addr,cin);
break;
case 'Q': //退出操作
do_quit(sfd,msg,addr,cin);
break;
}
}
exit(0);
}else{
//父进程,用来发送系统消息
//向子进程发送群聊消息
strcpy(msg.name, "系统消息");
msg.code = 'C';
while(1)
{
bzero(msg.txt,sizeof(msg.txt));
fgets(msg.txt, 256,stdin); //终端获取接收消息
msg.txt[strlen(msg.txt)-1] = '\0';
if((sendto(sfd,&msg,sizeof(msg),0 ,(struct sockaddr*)&sin,seraddr_len)) == -1)
{
perror("sendto error");
return -1;
}
}
}
close(sfd);
return 0;
}
客户端:
#include <myhead.h>
//定义存储信息结构体
typedef struct _MSG
{
char code; //操作码:'L'表示登录 'C表示群聊 'Q'表示退出
char name[20];
char txt[256];
}msg_t;
//定义保存客户端网络信息的链表
typedef struct _ADDR
{
struct sockaddr_in cin;
struct _ADDR* next;
}addrlist_t;
//登录操作的函数
void do_login(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
//先遍历链表 将新用户加入群聊的消息发送给所有客户端
addrlist_t* tmp = addr; //记录链表头结点
while(tmp->next != NULL)
{
tmp = tmp->next;
if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(tmp->cin),sizeof(tmp->cin)) == -1)
{
perror("sendto error");
return;
}
}
//将新用户的网络信息结构体头插入链表
addrlist_t* pnew = NULL;
if((pnew = (addrlist_t*)malloc(sizeof(addrlist_t)))==NULL){
printf("malloc error\n");
return;
}
pnew->cin = cin;
pnew->next = addr->next;
addr->next = pnew;
printf("--%s已上线--\n",msg.name);
return;
}
//群聊操作函数
void do_chat(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
//遍历链表,将群聊消息发给除自己以外的其他客户端
addrlist_t* ptmp = addr;
while(ptmp->next != NULL)
{
ptmp = ptmp->next;
if(memcmp(&cin, &(ptmp->cin), sizeof(cin))){
//判断哪个客户发的信息
if(sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin)) == -1)
{
perror("sendto error");
return;
}
}
}
return;
}
//退出操作的函数
void do_quit(int sfd,msg_t msg,addrlist_t*addr,struct sockaddr_in cin)
{
addrlist_t* ptmp = addr;
addrlist_t* del = NULL;
while(ptmp->next != NULL)
{
if(memcmp(&(ptmp->next->cin), &cin, sizeof(cin)))
{
//判断是否是其他客户发送的消息
ptmp = ptmp->next;
if((sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(ptmp->cin),sizeof(ptmp->cin))) == -1)
{
perror("sendto error");
return;
}
}else
{
del = ptmp->next;
ptmp->next = del->next;
free(del);
del=NULL;
}
}
printf("--%s已下线--\n",msg.name);
return;
}
int main(int argc, const char *argv[])
{
if(argc != 3){ //输入ip地址及端口号,进行判断
printf("input error\n");
printf("usage: %s <IP> <PORT>\n",argv[0]);
return -1;
}
//定义用于接收等待套接字
int sfd;
if((sfd = socket(AF_INET,SOCK_DGRAM,0)) == -1)
{
perror("socket error");
return -1;
}
printf("socket sfd success\n");
//设置端口号快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1){
perror("setsockopt error");
return -1;
}
//绑定
//填充服务器信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t seraddr_len = sizeof(sin);
if((bind(sfd, (struct sockaddr*)&sin, seraddr_len)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success\n");
//定义客户端网络信息结构体
struct sockaddr_in cin;
socklen_t cliaddr_len = sizeof(cin);
msg_t msg; //定义接收信息的变量msg
pid_t pid; //进程号
pid = fork(); //创建多进程
if(pid < 0){
perror("fork error");
return -1;
}else if(pid == 0)
{
//子进程,用来收发数据
//创建保存客户端信息的链表头结点
addrlist_t* addr;
if((addr = (addrlist_t*)malloc(sizeof(addrlist_t)))==NULL)
{
printf("malloc error\n");
return -1;
}
bzero(addr, sizeof(addr));
addr->next = NULL;
while(1)
{ //循环收发数据
bzero(&msg,sizeof(msg)); //每次接收新用户数据清空
bzero(&cin,sizeof(cin));
//接收客户端发送的消息,存放在msg中
if((recvfrom(sfd, &msg,sizeof(msg), 0,(struct sockaddr*)&cin, &cliaddr_len)) == -1)
{
perror("recvfrom error");
return -1;
}
switch(msg.code){ //判断消息中的操作码,根据操作码执行对应操作
case 'L': //登录操作
do_login(sfd,msg,addr,cin);
break;
case 'C': //群聊操作
do_chat(sfd,msg,addr,cin);
break;
case 'Q': //退出操作
do_quit(sfd,msg,addr,cin);
break;
}
}
exit(0);
}else{
//父进程,用来发送系统消息
//向子进程发送群聊消息
strcpy(msg.name, "系统消息");
msg.code = 'C';
while(1)
{
bzero(msg.txt,sizeof(msg.txt));
fgets(msg.txt, 256,stdin); //终端获取接收消息
msg.txt[strlen(msg.txt)-1] = '\0';
if((sendto(sfd,&msg,sizeof(msg),0 ,(struct sockaddr*)&sin,seraddr_len)) == -1)
{
perror("sendto error");
return -1;
}
}
}
close(sfd);
return 0;
}
ubuntu@ubuntu:test$ ^C
ubuntu@ubuntu:test$ cat cli.c
#include <myhead.h>
typedef struct _MSG
{
char code; //'L'表示登录 'C'表示群聊 'Q'表示退出群聊
char name[20];
char txt[256];
}msg_t; //定义消息结构体类型
int main(int argc, const char *argv[])
{
if(3 != argc)
{
printf("input error!\n");
printf("usage:%s <IP> <PORT>\n", argv[0]);
return -1;
}
//定义通信的套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sfd)
{
perror("sockfd error");
return -1;
}
//定义服务器地址信息结构体
struct sockaddr_in sin;
memset(&sin, 0,sizeof(sin));
sin.sin_family = AF_INET;
sin.sin_port = htons(atoi(argv[2]));
sin.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t seraddr_len = sizeof(sin);
msg_t msg;
memset(&msg,0,sizeof(msg));
//输入用户名
printf("请输入用户名:");
fgets(msg.name,45,stdin);
msg.name[strlen(msg.name)-1] = '\0';
msg.code = 'L';
strcpy(msg.txt,"加入群聊");
//给服务器发送登录信息
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,seraddr_len) == -1)
{
perror("sendto error");
return -1;
}
//定义父子进程并创建
pid_t pid = 0;
pid = fork();
if(pid < 0)
{
printf("fork error\n");
return -1;
}else if(pid == 0)
{
//子进程,循环接收并打印接收的数据
while(1){
if(recvfrom(sfd,&msg,sizeof(msg),0,NULL,NULL) == -1)
{
perror("sendto error");
return -1;
}
//打印收到的数据
printf("[%s]:%s\n",msg.name, msg.txt);
}
}else{ //父进程循环接收终端数据并发送给客户端
while(1){
bzero(msg.txt,sizeof(msg.txt));
fgets(msg.txt,128,stdin); //终端获取聊天消息
msg.txt[strlen(msg.txt)-1] = '\0';
if(strcmp(msg.txt, "quit") == 0)
{
msg.code = 'Q';
strcpy(msg.txt, "退出群聊");
}else{
msg.code = 'C';
}
if(sendto(sfd,&msg,sizeof(msg), 0,(struct sockaddr*)&sin,seraddr_len) == -1)
{
perror("sendto error");
return -1;
}
if(strcmp(msg.txt, "退出群聊") == 0){
break;
}
}
kill(pid,SIGKILL); //杀死子进程
wait(NULL); //等待回收子进程资源
}
close(sfd);
return 0;
}
头文件:
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/wait.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <poll.h>
#include <sqlite3.h>