项目说明:
运行程序,获取好友列表。
客户端可以选择发送单播,组播,广播信息。显示当前聊天室在线成员。
发送单播,输入好友IP,发送单播数据;
发送组播,输入组播数据,进行发送;
发送广播,输入广播数据,进行发送。
==》 拓展,实现发送文件功能。(TCP协议)
==> 要求: 程序运行流畅,无明显bug.
提示:好友列表如何获取,如何更新?
==> 程序上线时,给所有的在线用户发送广播数据 ==> “ON_LINE!”,每一个已经在运行的终端对接收到的广播进行判断,如果是特定的数据”ON_LINE”,那就认为新用户上线,那就把新用户的IP,端口等信息保存 --> 保存到链表。
==> 已在线的用户如果接收到新用户上线的广播信息,那就给这个新用户单播发送一个我也在线的信息。新用户在单播接收到老用户回复的在线信息,那就把老用户的信息添加到链表。
==> 下线通知: 如果有一个已在线的用户下线了,那就广播一个下线通知 “DIS_ON_LINE!”,每一个接收到下线通知的客户端就把自己的好友列表删除这个下线的人的信息!
/*********************************************************************
** @文件名 : MyFeiQ.c
** @功能 :
*********************************************************************/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/input.h>
#define SIGLE_PORT 30501 //单播端口号
#define MUTIL_PORT 30502 //组播端口号
#define BOARD_PORT 30503 //广播端口号
#define TCP_PORT 40504 //tcp端口号
#define MUTIL_IP "224.0.0.100" //组播IP
#define BOARD_IP "255.255.255.255" //广播IP
//#define LOCAL_IP "192.168.15.144" //本机IP
char * LOCAL_IP; //本机IP,以全局变量保存
typedef struct d_node{
char picpath[64]; //存放IP名
struct d_node *prev; //前继指针
struct d_node *next; //后继指针
}D_NODE;
char buf_x[] = "ON_LINE!"; //上线
char buf_y[] = "DIS_ON_LINE!"; //下线
char buf_z[] = "ABC!"; //上线转发
int k = 0;
char ip_addr_gb[20] = {0}; //广播
int sockfd_x;
struct sockaddr_in sigle_addr_x;
socklen_t addrlen_x;
/***********************************链表***************************************/
//创建链表
D_NODE *pic_list;
//创建双向循环链表
D_NODE *D_Loop_List_Create(void)
{
//1)申请链表头结点堆空间
D_NODE *d_list = (D_NODE *)malloc(sizeof(D_NODE));
if (NULL == d_list)
{
perror("malloc failed");
return NULL;
}
//2)对头结点进行赋值
d_list->prev = d_list;
d_list->next = d_list;
//3)返回头结点地址
return d_list;
}
//2,添加链表节点 -->头插法
bool D_Loop_List_Insert_Head(D_NODE *head, char *pic_name)
{
//1,新节点申请堆空间
D_NODE *newnode = (D_NODE *)malloc(sizeof(D_NODE));
if (NULL == newnode)
{
perror("malloc newnode failed");
return false;
}
//2,对新节点进行赋值 --> 字符串赋值不能直接用 == strcpy
strcpy(newnode->picpath, pic_name); //把传入的字符串拷贝到新的节点
newnode->prev = newnode;
newnode->next = newnode;
//3,头插法插入链表
newnode->next = head->next;
head->next = newnode;
newnode->next->prev = newnode;
newnode->prev = head;
return true;
}
//3,链表显示
void D_Loop_List_Display(D_NODE *head)
{
D_NODE *p = head->next;
while( p != head)
{
printf("%s\n", p->picpath);
p = p->next;
}
}
//4,链表节点查找
int D_Lool_List_Search(D_NODE *head, char *data)
{
int i = 1;
D_NODE *p = head->next;
while(p != head)
{
if (p->picpath == data)
{
printf("找到这个节点!,节点序号[%d]\n", i);
return 1;
}
p = p->next;
i++;
}
return 0;
printf("链表中没有这个节点!\n");
}
/*
strcmp(buf, "ON_LINE!") == 0
*/
//5,链表节点删除
bool D_Loop_List_Remove(D_NODE *head, char *pic_name)
{
D_NODE *p = head->next;
while(p != head)
{
if (strcmp(p->picpath,pic_name) == 0 ) //strcmp(p->picpath,pic_name) == 0
{
p->prev->next = p->next;
p->next->prev = p->prev;
free(p);
return true;
}
p = p->next;
}
printf("链表中没有这个节点!\n");
}
/***********************************链表***************************************/
/******************************单播、组播、广播********************************/
/*接收单播信息线程*/
void *Sigle_Recv(void *arg)
{
//1,获取一个UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd)
{
perror("sockfd sigle_recv failed");
return NULL;
}
//2,bind自己的IP和单播端口
struct sockaddr_in sigle_addr, send_addr;
socklen_t addrlen = sizeof(sigle_addr);
sigle_addr.sin_family = AF_INET;
sigle_addr.sin_port = htons(SIGLE_PORT); //绑定单播端口
//server_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
sigle_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本机的IP
if(bind(sockfd, (struct sockaddr *)&sigle_addr, addrlen) < 0)
{
perror("bind failed");
return NULL;
}
//3,循环接收UDP数据
char ip_addr[20] = {0};
char buf[1024];
char buftcp[1024]; //聊天记录
FILE *fp = fopen("sigle_a.txt", "w"); //创建聊天记录文件
if(NULL == fp)
{
perror("fopen failed");
}
while(1)
{
memset(buf, 0, sizeof(buf));
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&send_addr, &addrlen);
inet_ntop(AF_INET, &(send_addr.sin_addr), ip_addr, sizeof(ip_addr));
printf("<接收单播信息>IP<%s>:Port<%d>: %s\n",ip_addr, ntohs(send_addr.sin_port), buf);
sprintf( buftcp, "%s", buf );//聊天记录数据转字符串
fputs( buftcp, fp ); //聊天记录数据存储
if( strcmp(buf, "quit\n") == 0 )
break;
if(strcmp(buf, "ABC!") == 0)
{
printf("你有好友<%s>在线\n",ip_addr);
if( strcmp(ip_addr, LOCAL_IP) != 0 )
D_Loop_List_Insert_Head(pic_list, ip_addr);
}
}
close(sockfd);
}
/*接收组播信息线程*/
void *Mutil_Recv(void *arg)
{
//1,获取一个UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd)
{
perror("sockfd sigle_recv failed");
return NULL;
}
//2,bind自己的IP和单播端口
struct sockaddr_in sigle_addr, send_addr;
socklen_t addrlen = sizeof(sigle_addr);
sigle_addr.sin_family = AF_INET;
sigle_addr.sin_port = htons(MUTIL_PORT); //绑定组播端口
//server_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
sigle_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本机的IP
if(bind(sockfd, (struct sockaddr *)&sigle_addr, addrlen) < 0)
{
perror("bind failed");
return NULL;
}
//3,加入组播
struct ip_mreq mutil_ip;
socklen_t optlen = sizeof(mutil_ip);
inet_pton(AF_INET, MUTIL_IP, &mutil_ip.imr_multiaddr); //组播地址IP
inet_pton(AF_INET, LOCAL_IP, &mutil_ip.imr_interface); //主机地址IP
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mutil_ip, optlen); //加入多播组
//4,循环接收UDP数据
char ip_addr[20] = {0};
char buf[1024];
char buftcp[1024]; //聊天记录
FILE *fp = fopen("mutil_b.txt", "w"); //创建聊天记录文件
while(1)
{
memset(buf, 0, sizeof(buf));
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&send_addr, &addrlen);
inet_ntop(AF_INET, &(send_addr.sin_addr), ip_addr, sizeof(ip_addr));
printf("<接收组播信息>IP<%s>:Port<%d>: %s\n",ip_addr, ntohs(send_addr.sin_port), buf);
sprintf( buftcp, "%s", buf );//聊天记录数据转字符串
fputs( buftcp, fp ); //聊天记录数据存储
if( strcmp(buf, "quit\n") == 0 )
break;
}
close(sockfd);
}
/*接收广播信息线程*/
void *Board_Recv(void *arg)
{
//1,获取一个UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd)
{
perror("sockfd sigle_recv failed");
return NULL;
}
//2,bind自己的IP和单播端口
struct sockaddr_in sigle_addr, send_addr;
socklen_t addrlen = sizeof(sigle_addr);
sigle_addr.sin_family = AF_INET;
sigle_addr.sin_port = htons(BOARD_PORT); //绑定广播端口
//server_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
sigle_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本机的IP
if(bind(sockfd, (struct sockaddr *)&sigle_addr, addrlen) < 0)
{
perror("bind failed");
return NULL;
}
//3,循环接收UDP数据
char buf[1024];
char buftcp[1024]; //聊天记录
FILE *fp = fopen("board_c.txt", "w"); //创建聊天记录文件
while(1)
{
memset(buf, 0, sizeof(buf));
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&send_addr, &addrlen);
inet_ntop(AF_INET, &(send_addr.sin_addr), ip_addr_gb, sizeof(ip_addr_gb));
printf("<接收广播信息>IP<%s>:Port<%d>: %s\n",ip_addr_gb, ntohs(send_addr.sin_port), buf);
sprintf( buftcp, "%s", buf );//聊天记录数据转字符串
fputs( buftcp, fp ); //聊天记录数据存储
if( strcmp(buf, "quit\n") == 0 )
break;
if(strcmp(buf, "ON_LINE!") == 0)
{
printf("你的好友<%s>上线了\n",ip_addr_gb);
if( strcmp(ip_addr_gb, LOCAL_IP) != 0 )
D_Loop_List_Insert_Head(pic_list, ip_addr_gb);
//k=1;
//if(D_Lool_List_Search(pic_list,ip_addr) == 0)
// {
// sendto(sockfd_x, buf_x, sizeof(buf_x), 0,(struct sockaddr *)&board_addr_x, addrlen_x);
// }
//sendto(sockfd, buf, sizeof(buf), 0,(struct sockaddr *)&sigle_addr, addrlen);
sigle_addr_x.sin_addr.s_addr = inet_addr(ip_addr_gb);
sendto(sockfd_x, buf_z, sizeof(buf_z), 0,(struct sockaddr *)&sigle_addr_x, addrlen_x);
}
if(strcmp(buf, "DIS_ON_LINE!") == 0)
{
printf("你的好友<%s>下线了\n",ip_addr_gb);
D_Loop_List_Remove(pic_list, ip_addr_gb);
}
}
//DIS_ON_LINE!
close(sockfd);
}
/******************************单播、组播、广播********************************/
/*********************************TCP接收文件线程**********************************/
void *TCP_file(void *arg)
{
//1,获取TCP套接字 -- socket()
int sockfd_tcp = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd_tcp)
{
perror("socket failed");
}
//2,绑定自己的IP和端口号 -- bind()
struct sockaddr_in server_addr, client_addr;
socklen_t addrlen = sizeof(server_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(TCP_PORT); //把字符串的端口转换成网络字节序端口号
//server_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本机的IP
if(bind(sockfd_tcp, (struct sockaddr *)&server_addr, addrlen) < 0)
{
perror("bind failed");
}
//3,设置监听套接字 -- listen()
listen(sockfd_tcp, 1);
int talkfd;
long filesize, recvsize = 0;
size_t ret;
char filename[32] = {0};
char ip_addr[20] = {0};
char MsgBuf[1024] = {0};
talkfd = accept(sockfd_tcp, (struct sockaddr *)&client_addr, &addrlen);
inet_ntop(AF_INET, &(client_addr.sin_addr), ip_addr, sizeof(ip_addr));
printf("发送方 ip[%s], port[%d]\n", ip_addr, ntohs(client_addr.sin_port));
//5,接收第一个数据包 --> 解析文件名,文件大小
recv(talkfd, MsgBuf, sizeof(MsgBuf), 0);
sscanf(MsgBuf, "%ld:%s", &filesize, filename);
printf("接收的文件名:%s, 文件大小:%ld\n",filename, filesize);
//6,在本地创建一个同名文件 --> 打开文件
FILE *fp = fopen(filename, "w");
if(NULL == fp)
{
perror("fopen failed");
}
//7,循环接收数据,记录接收的数据总大小 --> 如果接收的总数据大小大于等于文件大小 --> 结束
while(1)
{
memset(MsgBuf, 0, sizeof(MsgBuf));
ret = recv(talkfd, MsgBuf, sizeof(MsgBuf), 0);
fwrite(MsgBuf, 1, ret, fp);
recvsize += ret; //当前接收的文件大小
if(recvsize >= filesize)
break;
}
//8,关闭套接字,关闭文件描述符
fclose(fp);
close(talkfd);
close(sockfd_tcp);
}
/*********************************TCP接收文件线程**********************************/
/******************************主线程UDP套接字初始化*******************************/
int UDP_Init()
{
//获取UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd)
{
perror("sockfd sigle_recv failed");
return -1;
}
//加入多播组
struct ip_mreq mutil_ip;
socklen_t optlen = sizeof(mutil_ip);
inet_pton(AF_INET, MUTIL_IP, &mutil_ip.imr_multiaddr); //组播地址IP
inet_pton(AF_INET, LOCAL_IP, &mutil_ip.imr_interface); //主机地址IP
setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP,&mutil_ip, optlen); //加入多播组
//使能广播属性
int on = 1;
setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on));
//返回初始化的UDP套接字
return sockfd;
}
/****************************主线程UDP套接字初始化****************************/
/*********************************TCP传输文件函数**********************************/
int TCP_Init()
{
char IP[32];
char tcp_file[32];
printf("输入对方的IP:");
memset(IP, 0, sizeof(IP));
scanf("%s", IP);
while(getchar() != '\n'); //清除输入缓冲区
printf("输入发送文件名:");
memset(tcp_file, 0, sizeof(tcp_file));
scanf("%s", tcp_file);
while(getchar() != '\n'); //清除输入缓冲区
if( access(tcp_file, F_OK) )//access函数用来判断指定的文件或目录是否存在(F_OK)
{
printf("发送的文件不存在!\n");
}
struct stat buf;
stat(tcp_file, &buf);//获取文件信息
printf("发送的文件:%s, 大小:%ld\n", tcp_file, buf.st_size);
//2,获取套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket failed");
}
//3,连接到接收方
struct sockaddr_in recv_addr;
socklen_t addrlen = sizeof(recv_addr);
recv_addr.sin_family = AF_INET;//
recv_addr.sin_port = htons(TCP_PORT); //tcp
recv_addr.sin_addr.s_addr = inet_addr(IP);
if(connect(sockfd, (struct sockaddr *)&recv_addr, addrlen) < 0 )
{
perror("connect failed");
}
//4,发送第一个数据包 文件名:文件大小
char MsgBuf[1024];
memset(MsgBuf, 0, sizeof(MsgBuf));
sprintf(MsgBuf, "%ld:%s", buf.st_size, tcp_file);
send(sockfd, MsgBuf, sizeof(MsgBuf), 0);
//5,循环发送数据 --> 循环读取文件内容 --> 循环发送 --> 发送完毕,结束
FILE *fp = fopen(tcp_file, "r");
if(NULL == fp)
{
perror("fopen failed");
}
size_t ret;
while(1)
{
ret = fread(MsgBuf, 1, sizeof(MsgBuf), fp); //读取文件信息
send(sockfd, MsgBuf, ret, 0); //读取的文件信息发送给对方
if(ret <= 0)
break;
}
printf("文件发送结束!\n");
//6,关闭套接字,关闭文件描述符
fclose(fp);
close(sockfd);
return 0;
}
/*********************************TCP传输文件函数**********************************/
int main(int argc, char *argv[])
{
if(argc != 2)
{
printf("请按照格式执行:%s <filename>\n", argv[0]);//接收本机IP
return -1;
}
LOCAL_IP = argv[1]; //把本机IP赋值为全局变量,方便调用
pic_list = D_Loop_List_Create(); //创建链表
//1,创建三条线程,接收单播,组播,广播
pthread_t tid[4];
pthread_create(&tid[0], NULL, Sigle_Recv, NULL); //单播
pthread_create(&tid[1], NULL, Mutil_Recv, NULL); //组播
pthread_create(&tid[2], NULL, Board_Recv, NULL); //广播
pthread_create(&tid[3], NULL, TCP_file , NULL); //传输文件
//2,初始化主线程UDP套接字 --> 加入组播,使能广播.
int sockfd = UDP_Init();
//3,循环发送数据 --> 选择发送的数据类型 --> 单播,组播,广播 ...
int cmd;
char IP[32];
char buf[1024];
struct sockaddr_in sigle_addr, mutil_addr, board_addr,recv_addr;
socklen_t addrlen = sizeof(sigle_addr);
socklen_t addrlen_tcp = sizeof(recv_addr);//tcp
sigle_addr.sin_family = AF_INET;
sigle_addr.sin_port = htons(SIGLE_PORT); //单播端口号
//sigle_addr.sin_addr.s_addr = inet_addr("192.168.15.3");
mutil_addr.sin_family = AF_INET;
mutil_addr.sin_port = htons(MUTIL_PORT); //组播端口号
mutil_addr.sin_addr.s_addr = inet_addr(MUTIL_IP); //组播IP
board_addr.sin_family = AF_INET;
board_addr.sin_port = htons(BOARD_PORT); //广播播端口号
board_addr.sin_addr.s_addr = inet_addr(BOARD_IP); //广播IP
sockfd_x = sockfd; //把局部变量赋值全局变量
sigle_addr_x = sigle_addr; //把局部变量赋值全局变量
addrlen_x = addrlen; //把局部变量赋值全局变量
//通过广播发送上线信息
sendto(sockfd, buf_x, sizeof(buf_x), 0,(struct sockaddr *)&board_addr, addrlen);
//sleep(1);
while(1)
{
printf("\n***************************************************************\n");
printf("*1,发送单播, 2,发送组播, 3,发送广播\n");
printf("*4,好友列表, 5,发送文件, 6,退出\n");
printf("***************************************************************\n");
scanf("%d", &cmd);
if(6 == cmd) break;
switch(cmd)
{
case 1: //发送单播
printf("输入对方的IP:");
memset(IP, 0, sizeof(IP));
scanf("%s", IP);
while(getchar() != '\n'); //清除输入缓冲区
sigle_addr.sin_addr.s_addr = inet_addr(IP);
printf("发送单播信息:");
while(1)
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
printf("发送单播信息:%s",buf);
sendto(sockfd, buf, sizeof(buf), 0,(struct sockaddr *)&sigle_addr, addrlen);
if( strcmp(buf, "quit\n") == 0 )
break;
}
break;
case 2: //发送组播
printf("发送组播信息:");
while(1)
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
printf("发送组播信息:%s",buf);
sendto(sockfd, buf, sizeof(buf), 0,(struct sockaddr *)&mutil_addr, addrlen);
if( strcmp(buf, "quit\n") == 0 )
break;
}
break;
case 3: //发送广播
while(1)
{
memset(buf, 0, sizeof(buf));
fgets(buf, sizeof(buf), stdin);
printf("发送广播信息:%s",buf);
sendto(sockfd, buf, sizeof(buf), 0,(struct sockaddr *)&board_addr, addrlen);
if( strcmp(buf, "quit\n") == 0 )
break;
}
break;
case 4: //获取在线好友
printf("在线好友:\n");
D_Loop_List_Display(pic_list);
break;
case 5: //TCP文件传输
TCP_Init();
break;
default:
printf("输入的数据有误!请重新输入!\n");
break;
}
}
//广播发送下线信息
sendto(sockfd, buf_y, sizeof(buf), 0,(struct sockaddr *)&board_addr, addrlen);
return 0;
}
1、在两个不同IP的虚拟机上运行程序,后运行的程序会通过广播发送一个上线信息。先上线程序接收到上线信息后,会给后上线程序发送一个我也在线的信息,从而确保两个程序的好友列表能接收到。
2、单播通信,选择1后,输入对方IP,然后可以进行通信
3、输入quit退出单播功能后,选择组播功能2,就可以进行组播通信
4、输入quit退出组播功能后,选择广播功能3,就可以进行广播通信
5、输入quit退出组播功能后,选择好友列表4,就可以获取在线的好友信息
6、选择发送文件6,输入对方IP,输入发送文件名,就可以发送对应的文件