目錄題目解析代碼實現 題目 解析 由於該題需要實現組播通信,所以我們需要將套接字文件句柄設置為組播屬性,並將需要通信的用戶端IP地址,加入組中。 由於組播通信需要實現一對多發送消息,所以還需要將套接字文件句柄的廣播屬性一併開啟。 由於該題實現過程使用到了線程相關函數介面,所以編譯時需要帶上 “-pt ...
目錄
題目
解析
- 由於該題需要實現組播通信,所以我們需要將套接字文件句柄設置為組播屬性,並將需要通信的用戶端IP地址,加入組中。
- 由於組播通信需要實現一對多發送消息,所以還需要將套接字文件句柄的廣播屬性一併開啟。
- 由於該題實現過程使用到了線程相關函數介面,所以編譯時需要帶上 “-pthread“ 選項
代碼實現
/********************************************************************************
*
* file name: udp_group.c
* author : [email protected]
* date : 2024/06/04
* function : 該案例是掌握UDP協議的組播通信方式
* note :
* 想要實現組間通信,只需要對本地地址進行修改後,可實現組間通信,即一個用戶
* 發送的消息,組間所有用戶都能收到
* version :
*
* CopyRight (c) 2023-2024 [email protected] All Right Reseverd
*
* ******************************************************************************/
/****************************頭文件*********************************************/
#include <sys/socket.h>
#include <stdio.h>
#include <errno.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/udp.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <time.h>
/****************************全局變數*********************************************/
int udp_socket; //用於存儲套接字文件的句柄
/****************************巨集*********************************************/
#define PORT 60000 //用戶端運行進程的埠號
#define IP_HOST "192.168.64.94" //用戶端本地IP地址
#define IP_GROUP "226.66.66.66" //組播通信IP地址
/********************************************************************************
*
* name : recv_time
* function : 實現獲取當前系統時間,並對獲取值進行固定格式的處理,獲得更好的顯示效果
* param :
* none
*
* retval : 調用成功返回處理後的當前系統時間
* author : [email protected]
* date : 2024/06/04
* note :
* 輸出時間的格式固定為:"X年 X月 X日 星期X XX:XX:XX",可根據需要進行修改
* version :
*
* *****************************************************************************/
char *recv_time()
{
//定義一個字元指針,用於存儲獲取的當前系統時間值
char *buf =(char *)calloc(1,128);
//定義一個字元數組,用於優化後的星期值
char wekday[10];
//利用time()獲取當前系統時間,並將返回值存儲起來
time_t systime = time(NULL);
//利用localtime()對獲取值進行處理,並將處理後的數據寫入目標文件中
struct tm *systimep = localtime(&systime);
systimep->tm_year += 1900;
systimep->tm_mon += 1;
//對獲取星期數值進行判斷,併進行美觀優化
if(systimep->tm_wday == 1)
{
strcpy(wekday, "一");
}
else if(systimep->tm_wday == 2)
{
strcpy(wekday, "二");
}
else if(systimep->tm_wday == 3)
{
strcpy(wekday, "三");
}
else if(systimep->tm_wday == 4)
{
strcpy(wekday, "四");
}
else if(systimep->tm_wday == 5)
{
strcpy(wekday, "五");
}
else if(systimep->tm_wday == 6)
{
strcpy(wekday, "六");
}
else if(systimep->tm_wday == 7)
{
strcpy(wekday, "日");
}
//將獲取值按固定格式寫入緩衝區中
sprintf(buf, "%d年 %d月 %d日 星期%s %d:%d:%d", systimep->tm_year,
systimep->tm_mon,
systimep->tm_mday,
wekday,
systimep->tm_hour,
systimep->tm_min,
systimep->tm_sec);
return buf;
}
/********************************************************************************
*
* name : recv_msg
* function : 接收信息線程的任務函數
* param :
* none
*
* retval : none
* author : [email protected]
* date : 2024/06/04
* note :
* 對應填寫的IP地址,可以通過程式開頭的巨集定義進行修改
* version :
*
* *****************************************************************************/
void *recv_msg(void *arg)
{
//將該線程的屬性設置為分離屬性,防止線程結束後變為僵屍線程
pthread_detach(pthread_self());
//綁定伺服器的埠和地址
struct sockaddr_in host_addr;
host_addr.sin_family = AF_INET; //協議族,UDP協議固定填寫該巨集定義
host_addr.sin_port = htons(PORT); //想要接收信息進程的埠號
host_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本地地址,填寫該巨集定義代表任意網口都可以接收值
bind(udp_socket,(struct sockaddr *)&host_addr, sizeof(host_addr));
//調用recvfrom等待接收數據,並且接收客戶端的網路信息
char buf[128] = {0};
struct sockaddr_in client;
socklen_t client_len = sizeof(client);
//定義一個字元數組用於存儲系統時間
char *recvtime = (char *)calloc(128,1);
while(1)
{
recvfrom(udp_socket,buf,sizeof(buf), 0 ,(struct sockaddr *)&client,&client_len); //函數標識定義為預設屬性,即會阻塞
recvtime = recv_time();
if(client.sin_addr.s_addr == inet_addr(IP_HOST))
{
bzero(buf,sizeof(buf));
continue;
}
printf("[%s] (%s): %s\n",inet_ntoa(client.sin_addr),recvtime,buf);
bzero(buf,sizeof(buf));
}
}
/********************************************************************************
*
* name : send_msg
* function : 發送信息線程的任務函數
* param :
* none
*
* retval : none
* author : [email protected]
* date : 2024/06/04
* note :
* 對應填寫的IP地址,可以通過程式開頭的巨集定義進行修改
* version :
*
* *****************************************************************************/
void *send_msg(void *arg)
{
//將該線程的屬性設置為分離屬性,防止線程結束後變為僵屍線程
pthread_detach(pthread_self());
//向目標主機發送消息,需要設置目標埠和目標地址
char buf[128] = {0};
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET; //協議族,是固定的
dest_addr.sin_port = htons(PORT); //伺服器運行進程的埠號,必須轉換為網路位元組序
dest_addr.sin_addr.s_addr = inet_addr(IP_GROUP); //要發送對象的IP地址,必須轉換為點分十進位字元串形式
while(1)
{
//阻塞等待獲取用戶從鍵盤輸入的信息
scanf("%s", buf);
//發送獲取到的用戶輸入信息
sendto(udp_socket,buf,strlen(buf),0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
}
}
int main(int argc,char *argv[])
{
//創建UDP套接字,並存儲套接字文件的句柄
udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket == -1)
{
fprintf(stderr, "udp socket error,errno:%d,%s\n",errno,strerror(errno));
exit(1);
}
//將套接字文件的廣播屬性設置為開啟
int flag = 1;
socklen_t flag_len = sizeof(flag);
setsockopt(udp_socket, SOL_SOCKET, SO_BROADCAST, (void *)&flag, flag_len);
//將套接字文件的組播屬性設置為開啟
struct ip_mreqn ip_grop;
ip_grop.imr_multiaddr.s_addr = inet_addr(IP_GROUP);//設置組播地址
ip_grop.imr_address.s_addr = inet_addr(IP_HOST); //設置要加入組的本地地址
ip_grop.imr_ifindex = 0; //設置為預設屬性,由系統分配埠
socklen_t ip_len = sizeof(ip_grop);
setsockopt(udp_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void *)&ip_grop, ip_len);
//創建兩個線程,分別用於發送數據和接受數據
pthread_t send;
pthread_t recv;
pthread_create(&send, NULL, send_msg, NULL);
pthread_create(&recv, NULL, recv_msg, NULL);
//關閉主線程
pthread_exit(NULL);
return 0;
}