头文件:
#ifndef __TEST_H__
#define __TEST_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__",__LINE__);\
perror(msg);\
}while(0)
typedef struct node
{
struct sockaddr_in cin;
struct node *next;
}linklist;
typedef struct _MSG{
char code;
char name[32];
char text[128];
}msg_t;
//创建链表
int create_node(linklist **phead);
//登录
void login(linklist* phead,int sfd,msg_t msg,struct sockaddr_in cin);
//群聊并且转发消息
void chat(linklist* phead,int sfd,msg_t msg,struct sockaddr_in cin);
//退出
void quit(linklist* phead,int sfd,msg_t msg,struct sockaddr_in cin);
#endif
需要调用的函数:
#include "test.h"
int create_node(linklist **phead){
*phead =(linklist*)malloc(sizeof(linklist));
memset(*phead,0,sizeof(linklist));
}
//登录
void login(linklist* phead,int sfd,msg_t msg,struct sockaddr_in cin){
//遍历链表 发送加入消息
linklist* ptemp=phead;
while(ptemp->next!=NULL){
ptemp=ptemp->next;
if(sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&ptemp->cin, sizeof(ptemp->cin)) < 0)
{
ERR_MSG("sendto");
return ;
}
}
//将新成员客户端加入到服务器链表中
linklist *pnew=NULL;
create_node(&pnew);
pnew->cin=cin;
pnew->next=phead->next;
phead->next=pnew;
}
//群聊并且转发给除了自己以外的人
void chat(linklist* phead,int sfd,msg_t msg,struct sockaddr_in cin){
linklist *ptemp=phead;
while(ptemp->next!=NULL){
ptemp=ptemp->next;
if(memcmp(&(ptemp->cin), &cin, sizeof(cin))){
if(-1 == sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(ptemp->cin), sizeof(ptemp->cin))){
ERR_MSG("send error");
}
}
}
}
void quit(linklist *phead, int sfd, msg_t msg, struct sockaddr_in cin){
//先遍历链表 发送 xxx 离开群聊的消息
linklist *pdel = NULL;
linklist *ptemp = phead;
while(ptemp->next != NULL){
if(memcmp(&(ptemp->next->cin), &cin, sizeof(cin))){
//不是自己
if(-1 == sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&(ptemp->next->cin), sizeof(ptemp->next->cin))){
ERR_MSG("send error");
}
ptemp = ptemp->next;
}else{
//是自己 就将自己在链表中删除
pdel = ptemp->next;
ptemp->next = pdel->next;
free(pdel);
pdel = NULL;
}
}
}
服务器代码:
#include "test.h"
int main(int argc, const char *argv[])
{
if(argc<3){
fprintf(stderr,"请输入IP号和端口:\n");
return -1;
}
//将获取到的端口号字符串转化为整形
int port=atoi(argv[2]);
if(port<1024 || port>49151){
fprintf(stderr,"port %d input error!!(1024~49151)\n",port);
return -1;
}
//创建套接字
int sfd=socket(AF_INET,SOCK_DGRAM,0);
if(sfd<0){
ERR_MSG("socket");
return -1;
}
//允许端口快速重用
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)
{
ERR_MSG("setsockopt");
return -1;
}
//填充服务器的IP地址以及端口
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port =htons(port); //服务器提供的端口
sin.sin_addr.s_addr =inet_addr(argv[1]); //服务器的IP
socklen_t sddrlen=sizeof(sin);
//绑定服务器IP地址和端口
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
//客户端网络结构体初始化
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
msg_t msg;
memset(&msg,0,sizeof(msg));
//创建父子进程
pid_t pid=fork();
if(pid<0){
ERR_MSG("fork");
return -1;
}else if(pid == 0){
//创建链表存储客户端IP地址和端口
linklist *phead=NULL;
create_node(&phead);
//子进程逻辑:接受并转发数据
while(1){
//必须保存客户端网络信息结构体,要群发数据,所以要确定端口和IP
if(recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr*)&cin, &addrlen) < 0){
ERR_MSG("recvfrom");
return -1;
}
printf("%s :%s\n",msg.name,msg.text);
//根据收到的数据包进行选择操作
switch(msg.code){
case 'L':
login(phead,sfd,msg,cin);
break;
case 'C':
chat(phead,sfd,msg,cin);
break;
case 'Q':
quit(phead,sfd,msg,cin);
break;
}
}
}else if(pid>0){
//父进程逻辑:用来发送系统消息
//将父进程最为一个客户端 把系统消息以群聊的方式 发给子进程
strcpy(msg.name, "server");
msg.code = 'C';
while(1){
fgets(msg.text, 128, stdin);
msg.text[strlen(msg.text) - 1] = '\0';
if(sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr *)&sin, sddrlen)<0){
ERR_MSG("send error");
}
memset(msg.text, 0, 128);
}
}
//关闭套接字
close(sfd);
return 0;
}
客户端代码实现:
#include "test.h"
int main(int argc, const char *argv[])
{
if(argc < 3)
{
fprintf(stderr, "请输入IP port\n");
return -1;
}
//将获取到的端口号字符串,转换成整形
int port = atoi(argv[2]);
if(port < 1024 || port > 49151)
{
fprintf(stderr, "port %d input error!! 1024~49151\n", port);
return -1;
}
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//绑定客户端自身的地址信息结构体 ---> 非必须绑定
//填充要连接的服务器的地址信息
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port =htons(port);
sin.sin_addr.s_addr =inet_addr(argv[1]);
struct sockaddr_in cin; //存储接收到的数据包来自哪里
socklen_t addrlen=sizeof(cin);
msg_t msg;
memset(&msg,0,sizeof(msg_t));
printf("请输入用户名:");
fgets(msg.name,32,stdin);
msg.name[strlen(msg.name)-1]=0;
msg.code='L';
strcpy(msg.text,"加入群聊");
//发送加入群聊消息到服务器
if(sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
printf("发送成功\n");
//创建进程
pid_t pid=fork();
if(pid<0){
ERR_MSG("fork");
}else if(pid==0){
//子进程接收系统消息
while(1){
if(recvfrom(sfd,&msg,sizeof(msg),0,NULL,NULL)<0){
ERR_MSG("recvform");
return -1;
}
printf(" %s : %s\n",msg.name,msg.text);
}
}else if(pid>0){
//父进程群聊
while(1)
{
bzero(msg.text, sizeof(msg.text));
fgets(msg.text,128, stdin);
msg.text[strlen(msg.text)-1] = 0;
if(strcasecmp(msg.text,"quit")==0){ //判断内容是退出还是群聊
msg.code='Q';
strcpy(msg.text,"退出群聊");
}else{
msg.code='C';
}
//将数据包发送给服务器,所以地址信息结构体需要填服务器的信息
if(sendto(sfd, &msg, sizeof(msg_t), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
if(strcmp(msg.text,"退出群聊")==0){
break;
}
}
}
//父进程退出后杀死子进程,回收僵尸进程
kill(pid,SIGKILL);
wait(NULL);
//关闭套接字
close(sfd);
return 0;
}
实现功能: