基于UDP的网络聊天室
流程图。(注意:这里服务器和客户端运行的时候都需要在终端后面紧跟着IP和端口号。)
服务器代码。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
#include <poll.h>
#include <errno.h>
#include <sqlite3.h>
/*
#define SER_PORT 8888
#define SER_IP "192.168.125.122"
*/
typedef struct msg {
char code;//操作码 'L'登录 'C'群聊 'Q'退出
char name[20];
char txt[128];
}msg_t;
typedef struct node_cin {
struct sockaddr_in c_addr;
struct node_cin *next;
}node_t;
//创建结点
int creat_node(node_t **p);
//登录
int do_login(struct sockaddr_in cin,node_t *phead,int sfd,msg_t msg);
//群聊
int do_chat(struct sockaddr_in cin,node_t *phead,int sfd,msg_t msg);
//退出
int do_quit(struct sockaddr_in cin,node_t *phead,int sfd,msg_t msg);
int main(int argc, const char *argv[])
{
//创建套接字,UDP通信
int sfd=socket(AF_INET,SOCK_DGRAM,0);
if(-1 == sfd)
{
perror("socket error");
return -1;
}
//printf("socket success\n");
//绑定IP地址和端口号
//准备填充地址结构体信息
struct sockaddr_in sin;
sin.sin_family=AF_INET;
//sin.sin_port=htons(SER_PORT);
sin.sin_port=htons(atoi(argv[2]));
//sin.sin_addr.s_addr=inet_addr(SER_IP);
sin.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t sin_len=sizeof(sin);
//绑定工作
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
//printf("bind success\n");
//保存客户端网络信息的结构体
struct sockaddr_in cin;
socklen_t cin_len=sizeof(cin);
msg_t msg;
pid_t pid=fork();
if(pid>0)
{
//父进程发送系统消息
//服务器发系统消息时,把父进程当做一个客户端,以群聊的方式向子进程发送系统消息
msg.code='C';
strcpy(msg.name,"server");
while(1)
{
fgets(msg.txt,128,stdin);
msg.txt[strlen(msg.txt)-1]='\0';
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&sin,sin_len) == -1)
{
perror("sendto error");
}
}
}else if(0 == pid)
{
//子进程用来接收并处理数据
node_t *phead=NULL;
creat_node(&phead);
phead->next=NULL;
while(1)
{
memset(&msg,0,sizeof(msg));
memset(&cin,0,sizeof(cin));
if(recvfrom(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&cin,&cin_len) == -1)
{
perror("recvfrom error");
return -1;
}
printf("%s发来消息-->%s\n",msg.name,msg.txt);
switch(msg.code)
{
case 'L':
//cin是收到的客户端的网络信息结构体
//phead是链表的头结点
//sfd是转发消息的套接字
//msg是要转发给所有用户的数据
do_login(cin,phead,sfd,msg);
break;
case 'C':
do_chat(cin,phead,sfd,msg);
break;
case 'Q':
do_quit(cin,phead,sfd,msg);
break;
}
}
}else {
perror("fork error");
return -1;
}
close(sfd);
return 0;
}
//创建链表结点
int creat_node(node_t **p)
{
*p=(node_t*)malloc(sizeof(node_t));
}
//登录
int do_login(struct sockaddr_in cin,node_t *phead,int sfd,msg_t msg)
{
//先遍历链表 将 xxx登录 的消息发送给所有人
node_t *ptemp=phead;
while(ptemp->next != NULL)
{
ptemp=ptemp->next;
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(ptemp->c_addr),sizeof(ptemp->c_addr)) == -1)
{
perror("sendto error");
return -1;
}
}
//将新登录的用户头插到链表中
node_t *pnew=NULL;
creat_node(&pnew);
pnew->c_addr=cin;
pnew->next=phead->next;
phead->next=pnew;
return 0;
}
//群聊
int do_chat(struct sockaddr_in cin,node_t *phead,int sfd,msg_t msg)
{
//群聊。服务器只需要转发
//遍历链表,将群聊的消息发送给除了自己之外的所有人
node_t *ptemp=phead;
while(ptemp->next != NULL)
{
ptemp=ptemp->next;
if(memcmp(&cin,&(ptemp->c_addr),sizeof(cin)))
{
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(ptemp->c_addr),sizeof(ptemp->c_addr)) == -1)
{
perror("sendto error");
return -1;
}
}
}
}
//退出
int do_quit(struct sockaddr_in cin,node_t *phead,int sfd,msg_t msg)
{
//把要退出的客户端从链表中删除,并且把退出的消息转发给其他所有人
node_t *ptemp=phead;
while(ptemp->next != NULL)
{
if(memcmp(&cin,&(ptemp->next->c_addr),sizeof(cin)))
{
//不是自己,就发送数据
ptemp=ptemp->next;
if(sendto(sfd,&msg,sizeof(msg),0,(struct sockaddr*)&(ptemp->c_addr),sizeof(ptemp->c_addr)) == -1)
{
perror("sendto error");
return -1;
}
}else {
//如果是自己,就将自己在链表中删除
node_t *pdel=ptemp->next;
ptemp->next=pdel->next;
free(pdel);
pdel=NULL;
}
}
}
客户端代码。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <semaphore.h>
#include <wait.h>
#include <signal.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/un.h>
#include <poll.h>
#include <errno.h>
#include <sqlite3.h>
typedef struct msg {
char code;//操作码 'L'登录 'C'群聊 'Q'退出
char name[20];
char txt[128];
}msg_t;
int main(int argc, const char *argv[])
{
//创建套接字
int cfd=socket(AF_INET,SOCK_DGRAM,0);
if(-1 == cfd)
{
perror("socket error");
return -1;
}
printf("socket success\n");
//准备填充地址结构体信息
struct sockaddr_in cin;
cin.sin_family=AF_INET;
cin.sin_port=htons(atoi(argv[2]));
cin.sin_addr.s_addr=inet_addr(argv[1]);
socklen_t cin_len=sizeof(cin);
msg_t msg;
memset(&msg,0,sizeof(msg));
printf("请输入用户名:");
fgets(msg.name,20,stdin);
msg.name[strlen(msg.name)-1]='\0';
//给服务器发送登录的数据包
msg.code='L';
strcpy(msg.txt,"加入群聊");
if(sendto(cfd,&msg,sizeof(msg),0,(struct sockaddr*)&cin,cin_len) == -1)
{
perror("sendto error");
return -1;
}
pid_t pid=fork();
if(pid>0)
{
//父进程发送数据
while(1)
{
msg.code='C';//先默认一开始的时候都是群聊
fgets(msg.txt,128,stdin);
msg.txt[strlen(msg.txt)-1]='\0';
//判断是否是退出
if(!strcmp(msg.txt,"quit"))
{
msg.code='Q';
strcpy(msg.txt,"退出群聊");
}
if(sendto(cfd,&msg,sizeof(msg),0,(struct sockaddr*)&cin,cin_len) == -1)
{
perror("sendto error");
return -1;
}
if(!strcmp(msg.txt,"退出群聊"))
{
break;
}
}
//父进程结束前,先杀死子进程
kill(pid,SIGKILL);
wait(NULL);
close(cfd);
}else if(0 == pid)
{
//子进程接收服务器发来的数据并打印
while(1)
{
memset(&msg,0,sizeof(msg));
if(recvfrom(cfd,&msg,sizeof(msg),0,NULL,NULL) == -1)
{
perror("recvfrom error");
return -1;
}
printf("%s发来消息-->%s\n",msg.name,msg.txt);
}
}else {
perror("fork error");
return -1;
}
return 0;
}