(IPv4 IPv6 無關版本)
Client端 已UDP傳輸字串~並接收server的回傳
Server端 接收字串並把收到的東西回傳給client
Client端 已UDP傳輸字串~並接收server的回傳
Server端 接收字串並把收到的東西回傳給client
前述:socket定址結構
基本上socket的定址結構是用來儲存IP位址、通訊埠....等訊息來後再依照給的東西建立連線
而現行的定址結構共有sockaddr、sockaddr_in、sockaddr_in6、addrinfo、 sockaddr_storage 等 (請移駕看詳細內容)
以往ipv4 是用sockaddr_in 而 ipv6是用sockaddr_in6很明顯不同~為了以後連線成功~這邊有兩個選擇
1、自己寫判斷ip 屬性再建立對應的
2、丟給getaddrinfo用別人寫好的東西產生~在用sockaddr_storage 接收
用第二種的方法你就也不用管啥 位元排序啦 把ip字串轉成unsigned integer啦.....雜事(關於自己幹該弄些啥請移駕)
你只要把想用的模式填好addrinfo就可以了~
struct addrinfo 結構內容
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol; //port
socklen_t ai_addrlen; //struct sockaddr的大小 must be zero or a null pointer
struct sockaddr *ai_addr; //must be zero or a null pointer 真正拿來用的東西
char *ai_canonname; // must be zero or a null pointer 不好意思不知道幹麼的
struct addrinfo *ai_next; // must be zero or a null pointer 鍊結串列的概念
而現行的定址結構共有sockaddr、sockaddr_in、sockaddr_in6、addrinfo、 sockaddr_storage 等 (請移駕看詳細內容)
以往ipv4 是用sockaddr_in 而 ipv6是用sockaddr_in6很明顯不同~為了以後連線成功~這邊有兩個選擇
1、自己寫判斷ip 屬性再建立對應的
2、丟給getaddrinfo用別人寫好的東西產生~在用sockaddr_storage 接收
用第二種的方法你就也不用管啥 位元排序啦 把ip字串轉成unsigned integer啦.....雜事(關於自己幹該弄些啥請移駕)
你只要把想用的模式填好addrinfo就可以了~
struct addrinfo 結構內容
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol; //port
socklen_t ai_addrlen; //struct sockaddr的大小 must be zero or a null pointer
struct sockaddr *ai_addr; //must be zero or a null pointer 真正拿來用的東西
char *ai_canonname; // must be zero or a null pointer 不好意思不知道幹麼的
struct addrinfo *ai_next; // must be zero or a null pointer 鍊結串列的概念
參數 | 常值 | 常值 | 說明 |
---|---|---|---|
ai_family | AF_INET | 2 | IPv4 |
AF_INET6 | 23 | IPv6 | |
AF_UNSPEC | 0 | 協議無關 | |
ai_protocol | IPPROTO_IP | 0 | IP協議 |
IPPROTO_IPV4 | 4 | IPv4 | |
IPPROTO_IPV6 | 41 | IPv6 | |
IPPROTO_UDP | 17 | UDP | |
IPPROTO_TCP | 6 | TCP | |
ai_socktype | SOCK_STREAM | 1 | 串流(TCP用) |
SOCK_DGRAM | 2 | 數據報(UDP用) | |
ai_flags | AI_PASSIVE | 1 (1) | 被動的,用於bind,通常用於server socket |
AI_CANONNAME | 2 (10) | ||
AI_NUMERICHOST | 4(100) | 地址為數字串 |
上面是可設定的參數內容相關~詳細可參考getaddrinfo函數原型
特別提一下的是ai_flags 其實是依照2進制來設定三個參數 000~111
也就是說你也可以給7代表三個都啟動、或是給0代表全都不啟動的意思
細項請參考剛剛給的網址~
已這次的程式為例~
server端的設定為
特別提一下的是ai_flags 其實是依照2進制來設定三個參數 000~111
也就是說你也可以給7代表三個都啟動、或是給0代表全都不啟動的意思
細項請參考剛剛給的網址~
已這次的程式為例~
server端的設定為
ai_family=AF_UNSPEC;//v6,v4皆可
ai_socktype=SOCK_DGRAM; //UDP
ai_flags=AI_PASSIVE; //用於bind
ai_protocol=0; //IP協議
ai_canonname=NULL;
ai_addr=NULL;
ai_next=NULL;
ai_socktype=SOCK_DGRAM; //UDP
ai_flags=AI_PASSIVE; //用於bind
ai_protocol=0; //IP協議
ai_canonname=NULL;
ai_addr=NULL;
ai_next=NULL;
Client端
程式的功能為:輸入伺服器端的 ip 和port 和想傳送的字串~之後已UDP傳輸並接收伺服器的回傳訊息顯示在營幕上
要做到以上目標的流程為
用輸入的IP、port 創好socket_addr && 呼叫socket() 建好socket -->送出訊息(sendto) -->等待接收訊息(recvfrom)
步驟1
要把IP做轉換~依照上面的介紹填好初始的addrinfo
struct addrinfo hints;
memset(&hints,0,sizeof(struct addrinfo)); //1bit,1bit全部填0
//=========填表=============
hints.ai_family=AF_UNSPEC;//V6,V4
hints.ai_sock;//UDP
hints.ai_flags=0;
//=========================
之後呼叫getaddrinfo
說明:
原型 int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
須引入
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
翻成白話文就是
int(錯誤碼) getaddrinfo( const char *(IP位置) , const char *(PORT號),
const struct addrinfo *(剛剛設定好的設定), struct addrinfo ** (幫你建好的結果) );
回傳值(錯誤碼):0——成功,非0——出錯
為了方便使用要再宣告 struct addrinfo * 來傳進去~讓函數幫你new (malloc)個設定完畢的東西以上
(處理有錯誤的情況~把回傳值丟進 const char *gai_strerror(int errcode); 看錯誤訊息再修正即可)
再來剛剛函數幫你malloc了struct addrinfo現在應該把他free掉 (寫的是c 可沒有垃圾回收機制QAQ)
方法一:手動一個一個free掉
剛剛有提到struct addrinfo 有個成員ai_next ~所以要free的時候當然是比較串列囉w
方法二:直接呼叫 void freeaddrinfo(struct addrinfo *res); 別人寫好的
要做到以上目標的流程為
用輸入的IP、port 創好socket_addr && 呼叫socket() 建好socket -->送出訊息(sendto) -->等待接收訊息(recvfrom)
步驟1
要把IP做轉換~依照上面的介紹填好初始的addrinfo
struct addrinfo hints;
memset(&hints,0,sizeof(struct addrinfo)); //1bit,1bit全部填0
//=========填表=============
hints.ai_family=AF_UNSPEC;//V6,V4
hints.ai_sock;//UDP
hints.ai_flags=0;
//=========================
之後呼叫getaddrinfo
說明:
原型 int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
須引入
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
翻成白話文就是
int(錯誤碼) getaddrinfo( const char *(IP位置) , const char *(PORT號),
const struct addrinfo *(剛剛設定好的設定), struct addrinfo ** (幫你建好的結果) );
回傳值(錯誤碼):0——成功,非0——出錯
為了方便使用要再宣告 struct addrinfo * 來傳進去~讓函數幫你new (malloc)個設定完畢的東西以上
(處理有錯誤的情況~把回傳值丟進 const char *gai_strerror(int errcode); 看錯誤訊息再修正即可)
再來剛剛函數幫你malloc了struct addrinfo現在應該把他free掉 (寫的是c 可沒有垃圾回收機制QAQ)
方法一:手動一個一個free掉
剛剛有提到struct addrinfo 有個成員ai_next ~所以要free的時候當然是比較串列囉w
方法二:直接呼叫 void freeaddrinfo(struct addrinfo *res); 別人寫好的
步驟二
建立socket
~呼叫 int socket(int domain, int type, int protocol); 即可
include <sys/types.h>
include <sys/socket.h>
白話文:int(socket ID)socket(int ai_family(ip類型), int ai_socktype(tcp/udp), int ai_protocol(傳輸協議));
一個程式可以創好幾個socket 故作業系統會給你個id來識別~(他是system call)
所以回傳值很自然是socket ID囉~如果是 -1 代表建立失敗
然後關於傳入的東西~跟你剛剛設定addrinfo 一模一樣~故直接把剛剛getaddrinfo幫你建好的addrinfo裡的成員丟進去就好
用完記得close(socket ID); 關掉
~呼叫 int socket(int domain, int type, int protocol); 即可
include <sys/types.h>
include <sys/socket.h>
白話文:int(socket ID)socket(int ai_family(ip類型), int ai_socktype(tcp/udp), int ai_protocol(傳輸協議));
一個程式可以創好幾個socket 故作業系統會給你個id來識別~(他是system call)
所以回傳值很自然是socket ID囉~如果是 -1 代表建立失敗
然後關於傳入的東西~跟你剛剛設定addrinfo 一模一樣~故直接把剛剛getaddrinfo幫你建好的addrinfo裡的成員丟進去就好
用完記得close(socket ID); 關掉
步驟三
直接sendto資料
對UDP是不確認Server到底可不可連的~就我的理解根本不用connect()
函數原型
size_t sendto(int socket, const void *message, size_t length,
int flags, const struct sockaddr *dest_addr,
socklen_t dest_len);
#include <sys/socket.h>
白話文: (實際傳了幾byte) sendto(int socket(透過哪個socket傳), const void *message (你想傳啥),
size_t length(你想傳多大),int flags(一般設0),
const struct sockaddr *dest_addr(目標的資訊),socklen_t dest_len(上個參數多大));
可以看出要送出的參數為const void * (不定指標)所以你想丟什麼東西過去~其實都沒差
反正他只是從你指定的地址開始把length byte個資料全部傳出~
對方接收時當然也是接到一堆0/1所以根本不會知道他接收到啥型別~
第四個參數又回到一開始講的socket定址結構了~
剛剛弄好的addrinfo 裡面的成員ai_addr 就是勒~而ai_addrlen就是ai_addr 的大小
最後是回傳值~他代表實際上傳了多長~故如果回傳值跟你給的length不相等就是有錯
對UDP是不確認Server到底可不可連的~就我的理解根本不用connect()
函數原型
size_t sendto(int socket, const void *message, size_t length,
int flags, const struct sockaddr *dest_addr,
socklen_t dest_len);
#include <sys/socket.h>
白話文: (實際傳了幾byte) sendto(int socket(透過哪個socket傳), const void *message (你想傳啥),
size_t length(你想傳多大),int flags(一般設0),
const struct sockaddr *dest_addr(目標的資訊),socklen_t dest_len(上個參數多大));
可以看出要送出的參數為const void * (不定指標)所以你想丟什麼東西過去~其實都沒差
反正他只是從你指定的地址開始把length byte個資料全部傳出~
對方接收時當然也是接到一堆0/1所以根本不會知道他接收到啥型別~
第四個參數又回到一開始講的socket定址結構了~
剛剛弄好的addrinfo 裡面的成員ai_addr 就是勒~而ai_addrlen就是ai_addr 的大小
最後是回傳值~他代表實際上傳了多長~故如果回傳值跟你給的length不相等就是有錯
步驟四
用 recvfrom 接收資料
函數原型
ssize_t recvfrom(int socket, void *restrict buffer, size_t length,
int flags, struct sockaddr *restrict address,
socklen_t *restrict address_len);
#include <sys/socket.h>
白話文 接收到的長度 recvfrom(int socket, void * buffer(接收倒的東西放哪), size_t length(能放多大),
int flags(0), struct sockaddr *restrict address,
socklen_t *restrict address_len);
buffer一樣是不定指標~就是從你給的記憶體位置開始依序把收到的東西填入
接下來的struct sockaddr * 是送給你資料的socket結構資料~
很明顯你應該先準備一塊位置來接資料 (或是你也可以忽視掉直接填NULL)
也因為此程式為V4,V6皆可~故這邊的sockaddr 必須要能裝sockaddr_in 或 sockaddr_in6
struct sockaddr_storage address;
sockaddr_storage 大小跟sockaddr_in6一樣大~但是內部成員沒有分割
int address_len <----裝大小而已型別隨便啦
函數原型
ssize_t recvfrom(int socket, void *restrict buffer, size_t length,
int flags, struct sockaddr *restrict address,
socklen_t *restrict address_len);
#include <sys/socket.h>
白話文 接收到的長度 recvfrom(int socket, void * buffer(接收倒的東西放哪), size_t length(能放多大),
int flags(0), struct sockaddr *restrict address,
socklen_t *restrict address_len);
buffer一樣是不定指標~就是從你給的記憶體位置開始依序把收到的東西填入
接下來的struct sockaddr * 是送給你資料的socket結構資料~
很明顯你應該先準備一塊位置來接資料 (或是你也可以忽視掉直接填NULL)
也因為此程式為V4,V6皆可~故這邊的sockaddr 必須要能裝sockaddr_in 或 sockaddr_in6
struct sockaddr_storage address;
sockaddr_storage 大小跟sockaddr_in6一樣大~但是內部成員沒有分割
int address_len <----裝大小而已型別隨便啦
client原始碼全文
Server端
程式的功能為:輸入要透過哪個port 之後已UDP接收訊息再把收到的訊息、傳送端的ip、透過哪個port顯示在營幕上
並傳送一個訊息(直接把對方傳的再回傳回去XD)給傳送端
要做到以上目標的流程為
用輸入的port 創好socket_addr && 呼叫socket() 建好socket -->連結socket設定本地端資訊(bind)
-->等待接收訊息(recvfrom)--->解析收到的socket_addr 把ip、跟實際port號顯示在螢幕(getnameinfo)
-->傳送訊息(sendto)
這邊就不一個步驟一個步驟打了~直接補缺少的部份
首先
這邊使用 getaddrinfo的時候 ip位置要設定為"::"(v6) 或 "0.0.0.0" (v4) 代表 任意 ip皆可連接~推薦使用v6
bind
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
#include <sys/socket.h>
白話文
回傳0代表成功 -1代表錯誤...........socket id ,定址結構,定址結構大小~不用解釋了吧
getnameinfo
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen,
char *serv, size_t servlen, int flags);
#include <sys/socket.h>
#include <netdb.h>
白話文:int(錯誤碼) getnameinfo(const struct sockaddr *sa (要轉換的資料), socklen_t salen,
char *host(轉換出來的IP), size_t hostlen,
char *serv(轉換出來的PORT號), size_t servlen, int flags(不想用就設0));
這邊的錯誤碼也可以用gai_strerror(error)檢查
而該傳入的sockaddr就是剛剛recvfrom接收到的囉
ps.PORT號為作業系統分配實際透過的那個
沒啥好講~就這樣
並傳送一個訊息(直接把對方傳的再回傳回去XD)給傳送端
要做到以上目標的流程為
用輸入的port 創好socket_addr && 呼叫socket() 建好socket -->連結socket設定本地端資訊(bind)
-->等待接收訊息(recvfrom)--->解析收到的socket_addr 把ip、跟實際port號顯示在螢幕(getnameinfo)
-->傳送訊息(sendto)
這邊就不一個步驟一個步驟打了~直接補缺少的部份
首先
這邊使用 getaddrinfo的時候 ip位置要設定為"::"(v6) 或 "0.0.0.0" (v4) 代表 任意 ip皆可連接~推薦使用v6
bind
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);
#include <sys/socket.h>
白話文
回傳0代表成功 -1代表錯誤...........socket id ,定址結構,定址結構大小~不用解釋了吧
getnameinfo
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, size_t hostlen,
char *serv, size_t servlen, int flags);
#include <sys/socket.h>
#include <netdb.h>
白話文:int(錯誤碼) getnameinfo(const struct sockaddr *sa (要轉換的資料), socklen_t salen,
char *host(轉換出來的IP), size_t hostlen,
char *serv(轉換出來的PORT號), size_t servlen, int flags(不想用就設0));
這邊的錯誤碼也可以用gai_strerror(error)檢查
而該傳入的sockaddr就是剛剛recvfrom接收到的囉
ps.PORT號為作業系統分配實際透過的那個
沒啥好講~就這樣
Server原始碼全文
上圖為測試結果~ 附註:addrinfo實際上是個串列所以getaddrinfo後到底哪個是能連線的 實際上應該走訪去試的(通常頭就可以)不過因為某些原因我沒有這麼做 詳細請參照getaddrinfo(3) - Linux man page 話說那裡面的Example我好像在哪看過~是我的幻覺嗎 浪費六個小時就只學這樣~對我來說挺失望的就是~ 下一篇預計會補上TCP版本的傳輸版本 第陸章 憑落し 誰も、憎んでいた訳じゃなかった ただ、ほんのすこしの誤解が、全てを変えてしまった |