網絡編程之 UDP ?用戶數據報
1、特性: 無鏈接 ?不可靠 ?大數據 ??
2、框架: C/S模式?
? ?server:socket() ===>bind()===>recvfrom()===>close()
? ?client:socket() ===>bind()===>sendto() ===>close()
注意:socket()的參數需要調整。
? ? ? socket(PF_INET,SOCK_DGRAM,0);
?? ? ?bind() 客戶端是可選的,服務器端是比選的。
1、數據有邊界,
2、發送數據和接受數據的次數使一致的,
同時長度要匹配,不然會產生數據丟棄的情況。
3、recvfrom會阻塞
4、? 沒有recvfrim 不會導致sendto阻塞?
一、發送接收函數:
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
?? ? ? ? ? ? const struct sockaddr *dest_addr, socklen_t addrlen);
功能:用于UDP協議中向對方發送數據。
參數:sockfd ?本地的套接字id
?? ? ?buff ? ?本地的數據存儲,一般是要發送的數據。
?? ? ?len ? ? 要發送的數據長度
?? ? ?flags ? 要發送數據方式,0 表示阻塞發送。
?? ? ?dest_addr: 必選,表示要發送到的目標主機信息結構體。
?? ? ?addrlen :目標地址長度。
返回值:成功 ?發送的數據長度
?? ? ? ?失敗 ? -1;
二ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
? ? ? ? ? ? ? ? ? struct sockaddr *src_addr, socklen_t *addrlen);
功能:用于UDP協議中獲取對方發送的數據。
參數:sockfd 本地的套接字id
?? ? ?buff ? 要存儲數據的內存區,一般是數組或者動態內存。
?? ? ?len ? ?要獲取的數據長度,一般是buff的大小。
?? ? ?flags ?獲取方式,0 阻塞
?? ? ?src_addr 可選,表示對方的地址信息結構體,
?? ? ??? ??? ??? ?如果為NULL,表示不關心對方地址。
?? ? ?addrlen ?對方地址信息結構體大小。
?? ? ??? ??? ??? ?如果對方地址是NULL,則該值也為NULL。
返回值:成功 接收到的數據長度
?? ??? ?失敗 ?-1;
sever.c
#include <stdio.h> // 標準輸入輸出庫
#include <stdlib.h> // 標準庫,包含exit等函數
#include <unistd.h> // UNIX標準函數定義,如sleep(但這里未使用)
#include <string.h> // 字符串操作函數,如bzero(注意:bzero已被棄用,建議使用memset)
#include <sys/types.h> // 基本數據類型定義
#include <sys/socket.h> // 套接字編程接口
#include <netinet/in.h> // 定義IPv4地址
#include <netinet/ip.h> // 雖然包含了,但在本程序中未直接使用到IP協議頭
#include <arpa/inet.h> // 定義IP地址轉換函數
#include <time.h> // 包含時間處理函數 typedef struct sockaddr * (SA); int main(int argc, char *argv[])
{ // 創建一個UDP套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == sockfd) { // 如果創建套接字失敗,則打印錯誤信息并退出 perror("socket"); exit(1); } // 定義服務器和客戶端地址結構體變量,并清零 struct sockaddr_in ser, cli; bzero(&ser, sizeof(ser)); // 使用bzero函數清零結構體,注意bzero函數已被廢棄,建議使用memset bzero(&cli, sizeof(cli)); // 同樣,建議使用memset // 設置服務器地址信息 ser.sin_family = AF_INET; ser.sin_port = htons(50000); // 設置服務器端口號,進行網絡字節序轉換 ser.sin_addr.s_addr = inet_addr("192.168.203.128"); // 設置服務器IP地址 // 將套接字綁定到服務器地址和端口上 // 注意:這通常是在服務器程序中完成的,而不是客戶端 int ret = bind(sockfd, (SA)&ser, sizeof(ser)); if (-1 == ret) { perror("bind"); exit(1); } // 初始化客戶端地址長度變量 socklen_t len = sizeof(cli); // 進入無限循環,接收數據并發送響應 while (1) { char buf[512] = {0}; // 定義一個接收緩沖區,并初始化為空 // 接收數據,注意這里也更新了cli的地址和長度信息 recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len); // 獲取當前時間 time_t tm; time(&tm); // 將接收到的數據和當前時間追加到buf中(注意:這可能會導致緩沖區溢出) // 更安全的做法是先檢查buf中剩余的空間 sprintf(buf, "%s %s", buf, ctime(&tm)); // 發送響應給客戶端 // 注意:這里使用了之前從recvfrom獲取的cli地址和長度信息 sendto(sockfd, buf, strlen(buf), 0, (SA)&cli, len); // 但注意:strlen(buf)可能不包含ctime返回的字符串的結束符'\n' // 更安全的做法是使用snprintf來確保不會超出buf的大小 // 這里沒有添加延時,程序會盡可能快地處理每一個接收到的數據包 } // 注意:由于存在無限循環,這里的close和return語句實際上不會被執行 // 但為了代碼的完整性,我還是保留了它們 // 如果需要退出程序,應該在循環內部添加適當的退出條件 // 關閉套接字(這行代碼實際上不會被執行) close(sockfd); return 0; // 程序正常結束(這行代碼實際上也不會被執行)
}
?client.c
#include <stdio.h> // 標準輸入輸出庫
#include <stdlib.h> // 標準庫,包含exit等函數
#include <unistd.h> // UNIX標準函數定義,如sleep
#include <string.h> // 字符串操作函數,如strlen, bzero
#include <sys/types.h> // 基本數據類型定義
#include <sys/socket.h> // 套接字編程接口
#include <netinet/in.h> // 定義IPv4地址
#include <netinet/ip.h> // IP協議頭
#include <arpa/inet.h> // 定義IP地址轉換函數
#include <time.h> // 包含時間處理函數,但本程序中未使用 // 注意:這里的typedef使用方式不正確,它應該是一個類型別名,而不是一個函數指針
// 正確的寫法應該是:typedef struct sockaddr *SA; 去掉了括號和多余的空格
typedef struct sockaddr *SA; int main(int argc, char *argv[])
{ // 創建一個UDP套接字 int sockfd = socket(AF_INET, SOCK_DGRAM, 0); if (-1 == sockfd) { // 如果創建套接字失敗,則打印錯誤信息并退出 perror("socket"); exit(1); } // 定義服務器地址結構體變量,并清零 struct sockaddr_in ser; bzero(&ser, sizeof(ser)); // 使用bzero函數清零結構體// 設置服務器地址族為IPv4 ser.sin_family = AF_INET; // 設置服務器端口號,使用htons函數進行網絡字節序轉換 ser.sin_port = htons(50000); // 設置服務器IP地址,通過inet_addr函數將點分十進制字符串轉換為網絡字節序 ser.sin_addr.s_addr = inet_addr("192.168.203.128"); // 進入無限循環,持續發送數據并接收響應 while (1) { // 定義一個發送緩沖區,并初始化為測試消息 char buf[512] = "hello,this is udp test"; // 向服務器發送數據 sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser)); // 清零接收緩沖區,準備接收數據 bzero(buf, sizeof(buf)); // 接收服務器響應的數據,但注意這里傳遞了NULL作為地址和長度的參數,這是錯誤的 // 正確的做法應該是提供一個有效的sockaddr_in結構體變量和socklen_t類型的變量來接收客戶端地址和長度 // 這里我們假設服務器和客戶端在同一臺機器上,且端口固定,因此可以省略這部分信息 // 但為了代碼的健壯性和可移植性,建議總是檢查來源地址 recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL); // 這里需要修正 // 打印接收到的數據 printf("buf is %s\n", buf); // 暫停一秒后再次發送 sleep(1); } // 注意:由于存在無限循環,這里的close和return語句實際上不會被執行 // 但為了代碼的完整性,我還是保留了它們 // 如果需要退出程序,應該在循環內部添加適當的退出條件 close(sockfd); // 關閉套接字 return 0; // 程序正常結束
}
練習:
1、根據以上知識點編寫UDP測試程序,驗證UDP協議的無鏈接性質。
1、將照片傳輸到客戶端:
注意要將ip地址改成想要發送的ip地址
??? ?ip: ifconfig
server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr * (SA);
int main(int argc, char *argv[])
{int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(-1 == sockfd){perror("socket");exit(1);}// man 7 ip struct sockaddr_in ser,cli;bzero(&ser,sizeof(ser));bzero(&cli,sizeof(cli));ser.sin_family = AF_INET;// 大小端轉化 host to net short ser.sin_port = htons(50000);//ser.sin_addr.s_addr = inet_addr("127.0.0.1");ser.sin_addr.s_addr = INADDR_ANY;int ret = bind(sockfd,(SA)&ser,sizeof(ser));if(-1 == ret){perror("bind");exit(1);}socklen_t len = sizeof(cli);int fd = open("2.png",O_WRONLY|O_CREAT|O_TRUNC,0666);if(-1 == fd){perror("open");exit(1);}while(1){char buf[512]={0};int rd_ret = recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);if(0 == strcmp(buf,"^_^")){break;}write(fd,buf,rd_ret);bzero(buf,sizeof(buf));strcpy(buf,"go on");sendto(sockfd,buf,strlen(buf),0,(SA)&cli,len);}close(sockfd);close(fd);return 0;
}
clink.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr * (SA);int main(int argc, char *argv[])
{int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(-1 == sockfd){perror("socket");exit(1);}struct sockaddr_in ser;bzero(&ser,sizeof(ser));ser.sin_family = AF_INET;// 大小端轉化 host to net short ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("192.168.203.128");int fd = open("/home/linux/1.png",O_RDONLY);if(-1 == fd){perror("open");exit(1);}char buf[512]={0};while(1){bzero(buf,sizeof(buf));int rd_ret = read(fd,buf,sizeof(buf));if(0==rd_ret){break;}sendto(sockfd,buf,rd_ret,0,(SA)&ser,sizeof(ser));bzero(buf,sizeof(buf));recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);}bzero(buf,sizeof(buf));strcpy(buf,"^_^");sendto(sockfd,buf,3,0,(SA)&ser,sizeof(ser));close(sockfd);close(fd);return 0;
}
3、將以上知識點融合,考慮如何實現一個基于UDP的聊天室程序。
?? ?要求如下:
?? ?1、要有注冊過程,每個客戶端必須在服務器端有注冊信息。
?? ?2、任意客戶端發送的消息必須由服務器轉發給所有在線客戶端。
?? ?3、任意客戶端下線必須通知其他在線用戶主機。
?? ?
? ? 服務端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * (SA);
typedef enum {CMD_LOGIN,CMD_CHAT,CMD_LOGOUT}TYPE;
typedef struct
{TYPE type;char name[50];char context[128];}MSG;
typedef struct
{struct sockaddr_in cli;int flag; // 0 free 1 occu
}LIST;
#define MAX 10
LIST list[MAX]={0};
int do_login(int sockfd,MSG* msg,struct sockaddr_in* cli)
{int i = 0 ;for(i=0;i<MAX;i++){if(1 == list[i].flag ){sendto(sockfd,msg,sizeof(MSG),0,(SA)&list[i].cli,sizeof(list[i].cli));}}for(i=0;i<MAX;i++){if(0 == list[i].flag ){list[i].flag =1;//list[i].cli = *cli;memcpy(&list[i].cli,cli,sizeof(*cli));break;}}return 0;
}int do_chat(int sockfd, MSG* msg,struct sockaddr_in*cli)
{int i = 0 ;for(i=0;i<MAX;i++){if(1 == list[i].flag && 0!=memcmp(&list[i].cli,cli,sizeof(*cli)) ){sendto(sockfd,msg,sizeof(MSG),0,(SA)&list[i].cli,sizeof(list[i].cli));}}
}
int do_logout(int sockfd, MSG* msg,struct sockaddr_in*cli)
{return 0;
}
int main(int argc, char *argv[])
{int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(-1 == sockfd){perror("socket");exit(1);}// man 7 ip struct sockaddr_in ser,cli;bzero(&ser,sizeof(ser));bzero(&cli,sizeof(cli));ser.sin_family = AF_INET;// 大小端轉化 host to net short ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("127.0.0.1");int ret = bind(sockfd,(SA)&ser,sizeof(ser));if(-1 == ret){perror("bind");exit(1);}socklen_t len = sizeof(cli);MSG msg;while(1){bzero(&msg,sizeof(msg));recvfrom(sockfd,&msg,sizeof(msg),0,(SA)&cli,&len);switch(msg.type){case CMD_LOGIN:do_login(sockfd,&msg,&cli);break;case CMD_LOGOUT:do_logout(sockfd,&msg,&cli);break;case CMD_CHAT:do_chat(sockfd,&msg,&cli);break;}}close(sockfd);return 0;
}
? ? 客戶端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr * (SA);
typedef enum {CMD_LOGIN,CMD_CHAT,CMD_LOGOUT}TYPE;
typedef struct
{TYPE type;char name[50];char context[128];}MSG;
int main(int argc, char *argv[])
{int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(-1 == sockfd){perror("socket");exit(1);}// man 7 ip struct sockaddr_in ser,cli;bzero(&ser,sizeof(ser));ser.sin_family = AF_INET;// 大小端轉化 host to net short ser.sin_port = htons(50000);ser.sin_addr.s_addr = inet_addr("127.0.0.1");socklen_t len = sizeof(cli);MSG msg;char name[50]={0};printf("input name:");fgets(name,sizeof(name),stdin);name[strlen(name)-1]='\0';msg.type = CMD_LOGIN;strcpy(msg.name ,name);strcpy(msg.context,"login");sendto(sockfd,&msg,sizeof(msg),0,(SA)&ser,sizeof(ser));pid_t pid = fork();if(pid>0){while(1){bzero(&msg,sizeof(msg));recvfrom(sockfd,&msg,sizeof(msg),0,NULL,NULL); printf("%s:%s\n",msg.name,msg.context);}}else if(0==pid){while(1){printf("to all"); char buf[128]={0};strcpy(msg.name,name);msg.type = CMD_CHAT;fgets(msg.context,sizeof(msg.context),stdin);//#quitmsg.context[strlen(msg.context)-1]='\0';if(0==strcmp(msg.context,"#quit")){msg.type = CMD_LOGOUT;strcpy(msg.context,"CMD_LOGOUT");}sendto(sockfd,&msg,sizeof(msg),0,(SA)&ser,sizeof(ser));}}else {perror("fork");exit(1);}close(sockfd);return 0;
}
?? ?
?? ?