歡迎關(guān)注微信公眾號:羽林君,或者添加作者個人微信:become_me
主要原理介紹
下圖是基于 UDP 的 Socket 函數(shù)調(diào)用過程:
只有接收的時候需要bind ip和端口
socket 監(jiān)聽所有ip 特定端口代碼:
#define?PORT?6000
bzero(&adr_inet,?sizeof(adr_inet));
adr_inet.sin_family?=?AF_INET;
adr_inet.sin_addr.s_addr?=?htonl(INADDR_ANY);
adr_inet.sin_port?=?htons(port);
ret?=?bind(cfd,?(struct?sockaddr?*)&addr,?sizeof(addr));
socket綁定的ip為INADDR_ANY 的說明:
socket INADDR_ANY 監(jiān)聽0.0.0.0地址 socket只綁定端口讓路由表決定傳到哪個ip
其中INADDR_ANY就是指定地址為0.0.0.0的地址,這個地址事實上表示不確定地址,或“所有地址”、“任意地址”。
如果指定ip地址為通配地址(INADDR_ANY),那么內(nèi)核將等到套接字已連接(TCP)或已在套接字上發(fā)出數(shù)據(jù)報時才選擇一個本地IP地址。
一般情況下,如果你要建立網(wǎng)絡(luò)服務(wù)器,則你要通知服務(wù)器操作系統(tǒng):請在某地址 xxx.xxx.xxx.xxx上的某端口 yyyy上進行偵聽,并且把偵聽到的數(shù)據(jù)包發(fā)送給我。這個過程,你是通過bind()系統(tǒng)調(diào)用完成的?!簿褪钦f,你的程序要綁定服務(wù)器的某地址,或者說:把服務(wù)器的某地址上的某端口占為已用。服務(wù)器操作系統(tǒng)可以給你這個指定的地址,也可以不給你。
如果你的服務(wù)器有多個網(wǎng)卡,而你的服務(wù)(不管是在udp端口上偵聽,還是在tcp端口上偵聽),出于某種原因:可能是你的服務(wù)器操作系統(tǒng)可能隨時增減IP地址,也有可能是為了省去確定服務(wù)器上有什么網(wǎng)絡(luò)端口(網(wǎng)卡)的麻煩 —— 可以要在調(diào)用bind()的時候,告訴操作系統(tǒng):“我需要在 yyyy 端口上偵聽,所以發(fā)送到服務(wù)器的這個端口,不管是哪個網(wǎng)卡/哪個IP地址接收到的數(shù)據(jù),都是我處理的?!边@時候,服務(wù)器則在0.0.0.0這個地址上進行偵聽。無論連接哪個ip都可以連上的,只要是往這個端口發(fā)送的所有ip都能連上。
示例代碼:
data_send.c 在端口9001進行ip地址的udp廣播以及讀取終端數(shù)據(jù)廣播到7000端口
#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?"127.0.0.1"
#define?PORT?6000
#define?DATA_PORT?9001
//?gcc?data_send.c?-o?data_send?-pthread
int?cfd?=?-1;
//接收線程函數(shù)
void?*receive(void?*pth_arg)
{
????int?ret?=?0;
????char?name_data[3]?=?{0};
????struct?sockaddr_in?addr0?=?{0};
????int?addr0_size?=?sizeof(addr0);
????//從對端ip和端口號中接收消息,指定addr0用于存放消息
????while?(1)
????{
????????bzero(name_data,?sizeof(name_data));
????????ret?=?recvfrom(cfd,?name_data,?sizeof(name_data),?0,?(struct?sockaddr?*)&addr0,?&addr0_size);
????????if?(-1?==?ret)
????????{
????????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"recv?failed",?strerror(errno));
????????????exit(-1);
????????}
????????else?if?(ret?>?0)
????????{
????????????printf("nname?=?%s?",?name_data);?//打印對方的消息和端口號
????????????printf("ip?%s,port?%d?n",?inet_ntoa(addr0.sin_addr),?ntohs(addr0.sin_port));
????????}
????}
}
void?*data_send(void?*pth_arg)
{
????int?ret?=?0;
????char?data[]?=?"IP?address";
????struct?sockaddr_in?addr0?=?{0};
????addr0.sin_family?=?AF_INET;????????????//設(shè)置tcp協(xié)議族
????addr0.sin_port?=?htons(DATA_PORT);??????????//設(shè)置端口號
????addr0.sin_addr.s_addr?=?htonl(INADDR_ANY);?//設(shè)置ip地址
????//發(fā)送消息
????while?(1)
????{
????????ret?=?sendto(cfd,?(void?*)data,?sizeof(data),?0,?(struct?sockaddr?*)&addr0,?sizeof(addr0));
????????sleep(1);
????????if?(-1?==?ret)
????????{
????????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"sendto?failed",?strerror(errno));
????????????exit(-1);
????????}
????}
}
int?main()
{
????int?ret?=?-1;
????//創(chuàng)建tcp/ip協(xié)議族,指定通信方式為無鏈接不可靠的通信
????cfd?=?socket(AF_INET,?SOCK_DGRAM,?0);
????if?(-1?==?cfd)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"socket?failed",?strerror(errno));
????????exit(-1);
????}
????//進行端口號和ip的綁定
????struct?sockaddr_in?addr;
????addr.sin_family?=?AF_INET;???//設(shè)置tcp協(xié)議族
????addr.sin_port?=?htons(PORT);?//設(shè)置端口號
????addr.sin_addr.s_addr?=?inet_addr(IP);?//設(shè)置ip地址
????ret?=?bind(cfd,?(struct?sockaddr?*)&addr,?sizeof(addr));
????if?(-1?==?ret)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"bind?failed",?strerror(errno));
????????exit(-1);
????}
????//創(chuàng)建線程函數(shù),用于處理數(shù)據(jù)接收
????pthread_t?id,data_send_id;
????ret?=?pthread_create(&id,?NULL,?receive,?NULL);
????if?(-1?==?ret)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"pthread_create?failed",?strerror(errno));
????????exit(-1);
????}
????//?pthread_join(id,NULL);
????ret?=?pthread_create(&data_send_id,?NULL,?data_send,?NULL);
????if?(-1?==?ret)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"pthread_create?failed",?strerror(errno));
????????exit(-1);
????}
????struct?sockaddr_in?addr0;
????addr0.sin_family?=?AF_INET;????????????//設(shè)置tcp協(xié)議族
????addr0.sin_port?=?htons(7000);??????????//設(shè)置端口號
????addr0.sin_addr.s_addr?=?inet_addr(IP);?//設(shè)置ip地址
????char?name_send[3]?=?{0};
????//發(fā)送消息
????while?(1)
????{
????????bzero(name_send,?sizeof(name_send));
????????printf("send?name:");
????????scanf("%s",?name_send);
????????//發(fā)送消息時需要綁定對方的ip和端口號
????????ret?=?sendto(cfd,?(void?*)name_send,?sizeof(name_send),?0,?(struct?sockaddr?*)&addr0,?sizeof(addr0));
????????if?(-1?==?ret)
????????{
????????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"accept?failed",?strerror(errno));
????????????exit(-1);
????????}
????}
????return?0;
}
data_process.c 進行端口9001的ip數(shù)據(jù)的捕獲,當接收到ip數(shù)據(jù)后,綁定廣播的ip地址進行數(shù)據(jù)的收發(fā),這里用的是udp接收大家也可以試試tcp交互。
#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?"127.0.0.1"
#define?PORT?7000
#define?DATA_PORT?9001
//?typedef?uint32_t?in_addr_t;
//?gcc?data_process.c?-o?data_process?-pthread
int?cfd?=?-1,data_fd?=?-1;
uint32_t?receive_ip?=?-1;
void?*receive(void?*pth_arg)
{
????int?ret?=?0;
????char?name_data[3]?=?{0};
????struct?sockaddr_in?addr0?=?{0};
????int?addr0_size?=?sizeof(addr0);
????while?(1)
????{
????????printf("receive:");
????????bzero(name_data,?sizeof(name_data));
????????ret?=?recvfrom(cfd,?name_data,?sizeof(name_data),?0,?(struct?sockaddr?*)&addr0,?&addr0_size);
????????if?(-1?==?ret)
????????{
????????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"recv?failed",?strerror(errno));
????????????exit(-1);
????????}
????????else?if?(ret?>?0)
????????{
????????????printf("nname?=?%s?",?name_data);
????????????printf("ip?%s,port?%d?n",?inet_ntoa(addr0.sin_addr),?ntohs(addr0.sin_port));
????????}
????}
}
void?*data_receive(void?*pth_arg)
{
????int?ret?=?0;
????char?name_data[10]?=?{0};
????struct?sockaddr_in?addr0?=?{0};
????int?addr0_size?=?sizeof(addr0);
????while?(1)
????{
????????bzero(name_data,?sizeof(name_data));
????????ret?=?recvfrom(data_fd,?name_data,?sizeof(name_data),?0,?(struct?sockaddr?*)&addr0,?&addr0_size);
????????if?(-1?==?ret)
????????{
????????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"recv?failed",?strerror(errno));
????????????exit(-1);
????????}
????????else?if?(ret?>?0)
????????{
????????????printf("nname?=?%s?",?name_data);
????????????printf("ip?%s,port?%d?n",?inet_ntoa(addr0.sin_addr),?ntohs(addr0.sin_port));
????????????receive_ip?=?addr0.sin_addr.s_addr;
????????????char?buf[20]?=?{?0?};
????????????inet_ntop(AF_INET,?&receive_ip,?buf,?sizeof(buf));
????????????printf("receive_ip?ip?=?%s?",?buf);
????????????//?printf("receive_ip?ip?=?%s?",?inet_ntop(receive_ip));
????????????break;
????????}
????}
}
int?main()
{
????int?ret?=?-1;
????data_fd?=?socket(AF_INET,?SOCK_DGRAM,?0);
????if?(-1?==?data_fd)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"socket?failed",?strerror(errno));
????????exit(-1);
????}
????struct?sockaddr_in?addr;
????addr.sin_family?=?AF_INET;????????????//設(shè)置tcp協(xié)議族
????addr.sin_port?=?htons(DATA_PORT);??????????//設(shè)置端口號
????addr.sin_addr.s_addr?=?inet_addr(IP);?//設(shè)置ip地址
????ret?=?bind(data_fd,?(struct?sockaddr?*)&addr,?sizeof(addr));
????if?(-1?==?ret)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"bind?failed",?strerror(errno));
????????exit(-1);
????}????
????pthread_t?receive_id;
????ret?=?pthread_create(&receive_id,?NULL,?data_receive,?NULL);
????if?(-1?==?ret)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"pthread_create?failed",?strerror(errno));
????????exit(-1);
????}
????pthread_join(receive_id,NULL);
????cfd?=?socket(AF_INET,?SOCK_DGRAM,?0);
????if?(-1?==?cfd)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"socket?failed",?strerror(errno));
????????exit(-1);
????}
????struct?sockaddr_in?addr1;
????addr1.sin_family?=?AF_INET;????????????//設(shè)置tcp協(xié)議族
????addr1.sin_port?=?htons(PORT);??????????//設(shè)置端口號
????addr1.sin_addr.s_addr?=?receive_ip;?//設(shè)置ip地址
????char?buf[20]?=?{?0?};
????inet_ntop(AF_INET,?&receive_ip,?buf,?sizeof(buf));
????printf("ip?=?%s?",?buf);
????ret?=?bind(cfd,?(struct?sockaddr?*)&addr1,?sizeof(addr1));
????if?(-1?==?ret)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"bind?failed",?strerror(errno));
????????exit(-1);
????}
????pthread_t?id;
????ret?=?pthread_create(&id,?NULL,?receive,?NULL);
????if?(-1?==?ret)
????{
????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"pthread_create?failed",?strerror(errno));
????????exit(-1);
????}
????pthread_join(id,NULL);
????struct?sockaddr_in?addr0;
????addr0.sin_family?=?AF_INET;????????????//設(shè)置tcp協(xié)議族
????addr0.sin_port?=?htons(6000);??????????//設(shè)置端口號
????addr0.sin_addr.s_addr?=?inet_addr(IP);?//設(shè)置ip地址
????char?name_send[3]?=?{0};
????while?(1)
????{
????????bzero(name_send,?sizeof(name_send));
????????printf("send?name:");
????????scanf("%s",?name_send);
????????ret?=?sendto(cfd,?(void?*)name_send,?sizeof(name_send),?0,?(struct?sockaddr?*)&addr0,?sizeof(addr0));
????????if?(-1?==?ret)
????????{
????????????fprintf(stderr,?"%d,?%s?:%s",?__LINE__,?"accept?failed",?strerror(errno));
????????????exit(-1);
????????}
????}
????return?0;
}
一個終端捕獲數(shù)據(jù),sudo tcpdump -i lo portrange 5000-8000 -vv -XX -nn
,另外兩個終端進行數(shù)據(jù)交互
結(jié)語
這就是我自己的一些udp設(shè)計思路的分享。如果大家有更好的想法和需求,也歡迎大家加我好友交流分享哈。
作者:良知猶存,白天努力工作,晚上原創(chuàng)公號號主。公眾號內(nèi)容除了技術(shù)還有些人生感悟,一個認真輸出內(nèi)容的職場老司機,也是一個技術(shù)之外豐富生活的人,攝影、音樂 and 籃球。關(guān)注我,與我一起同行。