文章目錄
Udp編程接口
socket():創建套接字
- 1.使用socket()函數創建一個文件描述符
int socket(AF_INET,SOCK_DGRAM,0);
//參數一:表示改文件描述符使用的是IPV4
//參數二:表示該文件描述符使用的是UDP協議
//參數三:一般不使用
-
參數domain 用來說明網路程序所在的主機採用的是那種通信協議簇,這些協議族再頭文件<sys/socket.h>中定義
-
AF_INET //表示IPv4網絡協議
-
AF_INET6 //IPv6網絡協議
-
參數type 用來指明創建的套接字類型,
-
SOCK_STREAM:流式套接字,面向連接可靠的通信類型
-
SOCK_DGRAM:數據報套接字,非面向連接和不可靠的通信類型
-
SOCK_RAW:原始套接字,用來直接訪問IP協議
-
參數protocol 指定套接字使用的協議,一般採用默認值0,表示讓系統根據地址格式和套接字類型,自動選擇一個合適的協議
-
返回值:
-
調用成功就創建了一個新的套接字並返回它的描述符,在之後對該套接字的操作中都要藉助這個文件描述符,
-
否則返回-1(<0) 表示套接字出錯.應用程序可調用WSAGetLastError() 獲取相應的錯誤代碼
bind ():將套接字綁定到指定的網絡地址
使用這個函數前需要將服務器的ip和端口號賦值到結構體sockaddr_in中
使用bind()函數前的準備工作
- sockaddr 結構:針對各種通信域的套接字,存儲它們的地址信息
struct sockaddr{
u_short sa_family; /*16位協議家族*/
char sa_data[14] /*14字節協議地址*/
};
- sockaddr_in結構:專門針對Internet通信域,存儲套接字相關的網絡地址信息(IP地址、傳輸層端口號等信息)
struct sockadd_in{
short int sin_family; //地址家族
unsigned short int sin_port; //端口號
struct in_addr sin_addr; //IP地址
unsigned char sin_zero[8]; //全爲0
};
- in_addr 結構:專門用來存儲IP地址
struct in_addr{
unsigned long s_addr;
};
#include<netinet/in.h>
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
-
參數sockfd:是未經綁定的套接字文件描述符,是由socket()函數返回的,要將它綁定到指定的網絡地址上
-
參數addr : 是一個指向sockaddr結構變量的指針,所指結構體中保存着特定的網絡地址,就是要把套接字sockfd綁定到這個地址上.
-
參數 addrlen :是結構sockaddr結構的長度,等於sizeof(struct sockadd
-
返回值
返回0:表示已經正確的實現了綁定
如果返回SOCKET_ERROR表示有錯。應用程序可調WSAGetLastError()獲取相應的錯誤代碼
- 最後在函數調用的時候,將這個結構強制轉換成sockaddr類型
本機字節序和網絡字節序
-
本機字節序在不同的計算機中,存放多字節值的順序是不一樣的,有的是從低到高,有的是從高到低.計算機中的多字節數據的存儲順序稱爲本機字節順序.
-
網絡字節序:在網絡協議中,對多字節數據的存儲,有它自己的規定。多字節數據在網絡協議報頭中的存儲順序,稱爲網絡字節序.在套接字中必須使用網絡字節序
所以把IP地址和端口號裝入套接字時,我們需要將本機字節序抓換成網絡字節序,在本機輸出時,我們需要將它們從網絡字節序轉換成本機字節序
-
套接字編程接口專門爲解決這個問題設置了4個函數
-
1.htons():短整數本機順序轉換爲網絡順序,用於端口號
-
2.htonl():長整數本機順序轉換成網絡順序,用於IP地址
-
3.ntohs():短整數網絡順序轉換爲本機順序,用於端口號
-
4.ntohl():長整數網絡順序轉換爲本機順序,用於IP地址
這4個函數將被轉換的數值作爲函數的參數,函數返回值作爲轉換後的結果
- 點分十進制的IP地址的轉換
在Internet中,IP地址常常是用點分十進制的表示方法,但在套接字中,IP地址是無符號的長整型數,套接字編程接口設置了兩個函數,專門用來兩種形式IP地址的轉換
- 1.inet_addr函數
unsigned long inet_addr(const char* cp);
/*
入口參cp:點分十進制形式的IP地址
返回值:網絡字節順序的IP地址,是無符號的長整數
*/
- 2.inet_ntoa函數
char* inet_ntoa(struct in_addr in)
/*
入口參數in: 包含長整數IP地址的in_addr結構變量
返回值:指向點分十進制IP地址的字符串的指針
*/
recvfrom()接收一個數據報並保存源地址,從數據報套接字接收數據
調用格式
int recvfrom(SOCKET s,char *buf,int len,int falgs,
struct sockaddr* from, int* fromlen);
-
參數s:接收端的數據報套接字描述符,包含接收方的網絡地址,從這個套接字接收數據報
-
參數buf:字符串指針,指向用戶進程的接收緩衝區,用來接收從套接字收到的數據報
-
參數len:用戶接收緩衝區的長度,制定了所能接收的最大字節數
-
參數flags:接收的方式,一般置爲0.
-
參數from: 指向sockaddr 結構的指針,實際上是一個出口參數,當本次調用成功執行後,在這個結構中返回了發送方的網絡地址,包括對方的IP地址和端口號
-
參數fromlen:整數型指針,也是一個出口型參數,本調用結束時,返回存在from中的網絡地址長度
-
返回值: 如果正確接收,則返回實際收到的字節數,如果出錯,返回SOCKET_ERROR,應用程序可通過WSAGetLastError()獲取相應的錯誤代碼
函數功能
- 本函數從s套接口 的接收緩衝區隊列中,取出第一個數據報,把它放到用戶進程 的緩衝區buf中,但最多不超過用戶緩衝區的大小。如果數據報大於用戶緩衝區長度,那麼用戶緩衝區中只有數據報的前面部分,後面的數據都會丟失,並且recvfrom()函數WSAEMSGSIZE錯誤
sendto():按照指定目的地向數據報套接字發送數據
調用格式
int sendto(SOCKET s,char* buf,int len,int flags,struct sockaddr* to,
int tolen)
-
參數s:發送方的數據報套接字 描述符,包含發送方的網絡地址,數據報通過這個套接字向對方發送
-
參數buf:指向用戶進程發送緩衝區的字符串指針,該緩衝區包含將要發送的數據,
-
參數len:用戶發送緩衝區中要發送的數據的長度,是可以發送的最大字節數
-
參數flags: 指定函數的執行方式,一般置爲0.
-
參數to:指向sockaddr 結構的指針,指定接收數據報的目的套接字的完整的網絡地址
-
參數tolen: to的地址長度,等於sizeof(struct sockaddr)
-
返回值:如果發送成功,則返回實際發送的字節數,注意這個數字可能小於len中所規定的長度,如果出錯返回錯誤碼
函數功能
本函數專用於數據報套接字,用來向發送端的本地套接字發送一個數據報.套接字會將數據下交給傳輸層的UDP,由它向對方發送.
- 容易看出,這個調用需要決定通信的兩個端點,需要一個全相關的五元組信息,既協議(UDP)、源IP地址、源IP地址、源端口號、目的IP地址和目的端口.通信一端由發送方套接字s指定,通信的另一端由to結構決定
實例代碼
udp_server.cc
//先實現UDP版本的服務器
#include<stdio.h>
#include<sys/socket.h> //socket所用的頭文件
#include<netinet/in.h> //socketaddr_in 結構體的頭文件
#include<arpa/inet.h> //inet.addr的頭文件
#include<cstring>
#include<unistd.h>
int main(){
//1.先創建一個socket
// AF_INET是一個宏 ,表示使用 ipv4 協議
//SOCK_DGRAM 表示使用 UDP 協議
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0){
perror("socket");
return 1;
}
//2.把當前的socket 綁定上一個ip + 端口號
sockaddr_in addr; //是一個結構體
addr.sin_family=AF_INET;
addr.sin_addr.s_addr = inet_addr("0.0.0.0"); //0.0.0.0將本地所有的ip都包含進來
//把點分十進制轉換成32位整數
//設置ip地址
//ip 地址也是一個整數,也需要轉成網絡字節序,只不過
//int_addr 函數自動幫我們轉了
addr.sin_port=htons(8080);
//端口號必須得先轉成網絡字節序
//9090 TCP端口,這個端口不是系統默認的端口,
//屬於自定義的端口
int ret=bind(sock,(sockaddr*)&addr,sizeof(addr));
if(ret<0){
perror("bind");
return 1;
}
printf("server start ok!\n");
//3.處理服務器收到的請求
while(true){
//1.讀取客戶端的請求
//面向數據報的函數
sockaddr_in peer;//獲取客戶端的ip
socklen_t len=sizeof(peer);//表示一個整數
char buf[1024]={0};
ssize_t n= recvfrom(sock,buf,sizeof(buf)-1,0,(sockaddr*)&peer,&len);
printf("客戶端發來消息:%s\n",buf);
printf("請輸入回覆內容:");
if(n<0){
perror("recvfrom");
continue;//不要因爲一次請求的失利就結束整個程序
}
buf[n]='\0';
//double check
//2.根據請求計算響應時間
//[略]此處寫的是一個回顯服務器(echo server)
//3.把響應寫回客戶端
char buf2[1024]="0";
scanf("%s",buf2);
n=sendto(sock,buf2,strlen(buf2),0,(sockaddr*)&peer,len);
if(n<0){
perror("sendto");
continue;
}
printf("[%s:%d] buf: %s\n",inet_ntoa(peer.sin_addr),
ntohs(peer.sin_port), buf2);
}
close(sock);
return 0;
}
ucp_client.cc
//實現UDP版本的客戶端
#include<stdio.h>
#include<sys/socket.h> //socket所用的頭文件
#include<netinet/in.h> //socketaddr_in 結構體的頭文件
#include<arpa/inet.h> //inet.addr的頭文件
#include<cstring>
#include<unistd.h>
//./client [ip]
int main(int argc,char *argv[]){
//1.先創建一個socket
int sock=socket(AF_INET,SOCK_DGRAM,0);
if(sock<0){
perror("socket");
return 1;
}
//2.客戶端一般不需要bind
//bind 意味着和某個具體的端口號關聯在一起
//如果沒有bind,操作系統會隨機分配一個端口號
//如果是服務器程序不去bind ,就會導致每次啓動服務器的端口發生變化
//客戶端就沒法連接了
//如果客戶端bind的話,可能會出現問題
//通常情況下,一個端口號只能被一個進程bind
//如果客戶端bind一個端口號其他客戶端也可能bind同一個端口號
//就會出錯
//客戶端最好還是讓操作系統隨機分配更科學
//2.準備好服務器的sockaddr_in 結構
sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=inet_addr(argv[1]);
server_addr.sin_port=htons(8080);
//3.客戶端直接發送數據即可
while(1){
char buf[1024]={0};
printf("請輸入一段內容:");
fflush(stdout);
scanf("%s",buf);
sendto(sock,buf,strlen(buf),0,
(sockaddr*)&server_addr,sizeof(server_addr));
//從服務器接收一下返回結果
char buf_output[1024]={0};
//後兩個參數填NULL 表示不關注對端的地址
recvfrom(sock,buf_output,sizeof(buf_output)-1,0,
NULL,NULL);
printf("客戶端迴應:%s\n",buf_output);
}
return 0;
}