文章目錄
- UDP的特點:
- UDP的用途
- UDP程式設計模型
- UDP通信代碼實作
UDP的特點:
- udp 協定是一種無連結的不可靠傳輸協定,且UDP每次發送到分組資料大小都是固定的,它的主要特點如下:
- 不建立連接配接
- 沒有應答機制
- 不會根據網絡狀況的好壞調整分組資料的大小
UDP之是以為不可靠傳輸協定,主要還是因為UDP沒有連結和應答機制,導緻UDP在發送資料的時候根本不清楚通信線路的連接配接情況,所有通過UDP發送資料的時候無法保證資料一定能夠發送給對方。是以UDP時不可靠的傳輸協定
-
UDP協定每次傳輸資料時,必須指定對方的IP和端口号
因為udp沒有連接配接的特性,所有UDP協定沒有自動記錄對方ip和端口号的特點,因為每次發送資料事,應用程式必須親自指定對方的ip和端口号,隻有這樣才能将資料發送給對方。
如果是跨網通信,指定的ip就是對方的路由器公網ip
UDP的用途
- 資料量打但是允許資料丢失的傳輸服務(音視訊傳輸)
- 開發監控視訊相關得網絡傳輸資料程式時使用
UDP程式設計模型
UDP 程式設計模型非常簡單,通信雙發的實作基本一緻,且沒有伺服器的概念,如下圖
-
建立socket通信
這裡需要注意socket的内幾個參數設定
第一個參數仍然為tcp/ip協定族:
第二個參數為傳輸格式:AF_INET
SOCK_DGRAM
- 建立綁定,使用
函數和自己的ip和端口進行綁定bind
- 使用
函數向對方發送資料,這裡需要注意的是sendto
sendto
函數需要填寫對方ip和端口号的參數,因為udp通信時需要明确對方裝置的ip和端口,是以發送資料包的時候需要對對方的ip和端口号進行指定
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
- 最後接收資訊的時候會接收到從對方的ip和端口發送過來的資訊,因為消息的接收需要知道它的來源,而
struct sockaddr
将會指定消息的來源。
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
PS:兩個通信程序在同一主機不能出現端口沖突,是以以下代碼測試是在同一主機上,此時需要設定不同的端口。
UDP通信代碼實作
UDP_A.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#define IP "192.168.102.174"
#define PORT 6000
typedef struct data {
char name[30];
unsigned int num;
}Data;
void print_err(char *str, int line, int err_no) {
printf("%d, %s :%s\n",line,str,strerror(err_no));
_exit(-1);
}
int cfd = -1;
//接收線程函數
void *receive(void *pth_arg) {
int ret = 0;
Data stu_data = {0};
struct sockaddr_in addr0 = {0};
int addr0_size = sizeof(addr0);
//從對端ip和端口号中接收消息,指定addr0用于存放消息
while(1) {
bzero(&stu_data, sizeof(stu_data));
ret = recvfrom(cfd, &stu_data, sizeof(stu_data),0, (struct sockaddr *)&addr0, &addr0_size);
if (-1 == ret) {
print_err("recv failed",__LINE__,errno);
}
else if (ret > 0){
printf("student number = %d student name = %s \n",ntohl(stu_data.num),stu_data.name);
//列印對方的消息和端口号
printf("ip %s,port %d\n",\
inet_ntoa(addr0.sin_addr),ntohs(addr0.sin_port));
}
}
}
int main()
{
int ret = -1;
//建立tcp/ip協定族,指定通信方式為無連結不可靠的通信
cfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == cfd) {
print_err("socket failed", __LINE__, errno);
}
//進行端口号和ip的綁定
struct sockaddr_in addr;
addr.sin_family = AF_INET; //設定tcp協定族
addr.sin_port = htons(PORT); //設定端口号
addr.sin_addr.s_addr = inet_addr(IP); //設定ip位址
ret = bind(cfd, (struct sockaddr*)&addr, sizeof(addr));
if ( -1 == ret) {
print_err("bind failed",__LINE__,errno);
}
//建立線程函數,用于處理資料接收
pthread_t id;
ret = pthread_create(&id,NULL,receive,NULL);
if (-1 == ret) print_err("pthread_create failed", __LINE__, errno);
struct sockaddr_in addr0;
addr0.sin_family = AF_INET; //設定tcp協定族
addr0.sin_port = htons(7000); //設定端口号
addr0.sin_addr.s_addr = inet_addr(IP); //設定ip位址
Data std_data = {0};
//發送消息
while (1) {
bzero(&std_data, sizeof(std_data));
printf("stu name:\n");
scanf("%s",std_data.name);
printf("stu num:\n");
scanf("%d",&std_data.num);
std_data.num = htonl(std_data.num);
//發送消息時需要綁定對方的ip和端口号
ret = sendto(cfd, (void *)&std_data,sizeof(std_data), 0, (struct sockaddr *)&addr0, sizeof(addr0));
if ( -1 == ret) {
print_err("accept failed", __LINE__, errno);
}
}
return 0;
}
另一端的實作基本和
UDP_A.c
一樣,因為是本機測試,隻有在發送的時候設定的端口号不一樣。如果跨網通信,則需要設定發送時指定的ip為 對方路由器的公網ip
UDP_B.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#define IP "192.168.102.174"
#define PORT 7000
typedef struct data {
char name[30];
unsigned int num;
}Data;
void print_err(char *str, int line, int err_no) {
printf("%d, %s :%s\n",line,str,strerror(err_no));
_exit(-1);
}
int cfd = -1;
void *receive(void *pth_arg) {
int ret = 0;
Data stu_data = {0};
struct sockaddr_in addr0 = {0};
int addr0_size = sizeof(addr0);
while(1) {
bzero(&stu_data, sizeof(stu_data));
ret = recvfrom(cfd, &stu_data, sizeof(stu_data),0, (struct sockaddr *)&addr0, &addr0_size);
if (-1 == ret) {
print_err("recv failed",__LINE__,errno);
}
else if (ret > 0){
printf("student number = %d student name = %s \n",ntohl(stu_data.num),stu_data.name);
printf("ip %s,port %d\n",\
inet_ntoa(addr0.sin_addr),ntohs(addr0.sin_port));
}
}
}
int main()
{
int ret = -1;
cfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == cfd) {
print_err("socket failed", __LINE__, errno);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET; //設定tcp協定族
addr.sin_port = htons(PORT); //設定端口号
addr.sin_addr.s_addr = inet_addr(IP); //設定ip位址
ret = bind(cfd, (struct sockaddr*)&addr, sizeof(addr));
if ( -1 == ret) {
print_err("bind failed",__LINE__,errno);
}
pthread_t id;
ret = pthread_create(&id,NULL,receive,NULL);
if (-1 == ret) print_err("pthread_create failed", __LINE__, errno);
struct sockaddr_in addr0;
addr0.sin_family = AF_INET; //設定tcp協定族
addr0.sin_port = htons(6000); //設定端口号
addr0.sin_addr.s_addr = inet_addr(IP); //設定ip位址
Data std_data = {0};
while (1) {
bzero(&std_data, sizeof(std_data));
printf("stu name:\n");
scanf("%s",std_data.name);
printf("stu num:\n");
scanf("%d",&std_data.num);
std_data.num = htonl(std_data.num);
ret = sendto(cfd, (void *)&std_data,sizeof(std_data), 0, (struct sockaddr *)&addr0, sizeof(addr0));
if ( -1 == ret) {
print_err("accept failed", __LINE__, errno);
}
}
return 0;
}
編譯運作:
gcc UDP_B.c -o B -pthread
gcc UDP_A.c -o A -pthread