日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

2-3:套接字(Socket)编程之UDP通信,sockaddr,sockaddr_in,recvfrom,sendto

發布時間:2025/3/15 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 2-3:套接字(Socket)编程之UDP通信,sockaddr,sockaddr_in,recvfrom,sendto 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 一:回顧
  • 二:徹底了解套接字和struct socket結構
    • (1)一切皆文件-文件描述符-套接字描述符
    • (2)struct socket結構
      • A:struct socket結構體作用
      • B:struct socket結構體詳解
  • 三:socket接口(UDP)和sockaddr結構
    • (1)socket常用API接口1(UDP)
    • (2)sockaddr結構
      • A:struct sockaddr結構
      • B:struct sockaddr_in
  • 四:UDP通信示例
    • (1)UDP通信
    • (1)sendto和recvfrom接口
    • (2)代碼
    • (3)效果

一:回顧

前文講過,套接字分為流式套接字(SOCKET_STREAM)和數據報套接字(SOCKET_DGRAM),他們所采用的協議分別為TCPUDP,相應的對應的Socket編程就是TCP套接字編程UDP套接字編程

相比于UDP而言,TCP保證了數據的可靠傳輸,所以它比UDP就復雜一點,從下面的流程圖中也可以看出來

  • 流式套接字

  • 數據包套接字

二:徹底了解套接字和struct socket結構

(1)一切皆文件-文件描述符-套接字描述符

下圖是Linux內核中關于socket的數據結構還有后續我們再說編程時的一些API接口。

你可能注意到了一個非常熟悉的地方,struct file* file,這不就是文件嗎?是的沒錯,如果再深入理解一點,其實套接字就是使用文件描述符和其它程序進行通訊的一種方式。我們知道,Linux系統在執行任何I/O的時候,都在和文件描述符打交道,而在Linux下,我們一直反復強調“一切皆文件的思想”,之前說過的屏幕都可以作為文件,那么現在接觸的網卡也當然可以做文件

所以以后再進行網絡通訊時,你會利用socket系統調用,它將返回套接字文件描述符,然后你會利用它再通過相應(如下)接口進行通信操作。
既然它是文件描述符,那就意味著你仍然可以使用read()和write()來進行通信,但是“術業有專攻”,網絡的事情還是盡量使用它們對應的接口來操作。

(2)struct socket結構

A:struct socket結構體作用

用戶使用socket系統調用編寫程序時,通過套接字描述符完成相關操作

int socket(int domain, int type, int protocol);

它對應的就是我們在上面說到的struct socket結構體

那么內核中為什么要有struct socket這樣的結構體呢,以及它有什么作用呢?可以看下面這張圖

所以內核中的進程可以通過該結構體來訪問Linux內核中的傳輸層,網絡層和數據鏈路層,也就是說struct socket是內核中的進程與內核中的網絡系統的橋梁

B:struct socket結構體詳解

這是一個基本的BSD socket,我們調用socket系統調用創建的各種不同類型的socket,開始創建的都是它,到后面**,各種不同類型的socket在它的基礎上進行 各種擴展。struct socket是在虛擬文件系統上被創建出來的,可以把它看成一個文件,**是可以被安全地擴展的。下面是其完整定義:

struct socket { socket_state state; unsigned long flags; const struct proto_ops *ops; struct fasync_struct *fasync_list; struct file *file; struct sock *sk; wait_queue_head_t wait; short type; };

1: state用于表示socket所處的狀態,是一個枚舉變量,其類型定義如下:

該成員只對TCP socket有用,因為只有tcp是面向連接的協議,udp跟raw不需要維護socket狀態。

typedef enum { SS_FREE = 0, //該socket還未分配 SS_UNCONNECTED, //未連向任何socket SS_CONNECTING, //正在連接過程中 SS_CONNECTED, //已連向一個socket SS_DISCONNECTING //正在斷開連接的過程中 }socket_state;

2:ops是協議相關的一組操作集,結構體struct proto_ops的定義如下:

struct proto_ops { int family; struct module *owner; int (*release)(struct socket *sock); int (*bind)(struct socket *sock, struct sockaddr *myaddr, int sockaddr_len); int (*connect)(struct socket *sock, struct sockaddr *vaddr, int sockaddr_len, int flags); int (*socketpair)(struct socket *sock1, struct socket *sock2); int (*accept)(struct socket *sock,struct socket *newsock, int flags); int (*getname)(struct socket *sock, struct sockaddr *addr,int *sockaddr_len, int peer); unsigned int (*poll)(struct file *file, struct socket *sock, struct poll_table_struct *wait); int (*ioctl)(struct socket *sock, unsigned int cmd, unsigned long arg); int (*listen)(struct socket *sock, int len); int (*shutdown)(struct socket *sock, int flags); int (*setsockopt)(struct socket *sock, int level, int optname, char __user *optval, int optlen); int (*getsockopt)(struct socket *sock, int level, int optname, char __user *optval, int __user *optlen); int (*sendmsg)(struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len); int (*recvmsg)(struct kiocb *iocb, struct socket *sock, struct msghdr *m, size_t total_len, int flags); int (*mmap)(struct file *file, struct socket *sock,struct vm_area_struct * vma); ssize_t (*sendpage)(struct socket *sock, struct page *page, int offset, size_t size, int flags); };

其中協議??偣捕x了了三個strcut proto_ops類型的變量,分別myinet_stream_ops, myinet_dgram_ops, myinet_sockraw_ops,分別對應流式套接字,數據報套接字和原生套接字

3:type是socket的類型,對應的取值如下:

enum sock_type { SOCK_DGRAM = 1, //數據報套接字SOCK_STREAM = 2, //流式套接字SOCK_RAW = 3, //原生套接字SOCK_RDM = 4, SOCK_SEQPACKET = 5, SOCK_DCCP = 6, SOCK_PACKET = 10, };

4:sk是網絡層對于socket的表示,用戶需要進行傳參,讓網絡層采用TCP還是UDP

三:socket接口(UDP)和sockaddr結構

(1)socket常用API接口1(UDP)

所以我們要完成通訊,就要利用sockt結構體提供給我們的一些接口來實現。由于UDP只負責傳送,所以相較于TCP而言它的接口少一點,所以這里只列出UDP中使用到的,但需要注意的是下面的接口對TCP也是通用的,只不過TCP相較于UDP的接口要多一點,還要擴展一點。

本節的模型就是一個服務端接受客戶端發送的消息,然后回應客戶端

1:創建 一個socket 文件描述符

#include <sys/tyeps.h> #include <sys/socket.h> int socket(int domain,int type,int protocol);

他們三個參數的含義及選用如下

對于它的返回值其實前面我們已經說過了,本質是一個文件描述符

2:綁定地址信息

前文說過,IP地址+端口號唯一表示了全網的一個進程。服務端既然想要提供服務,那么必須要讓客戶端知道怎么找到自己。所以對于服務端我們需要填入ip地址和端口號,以便客戶端可以找到自己。 當然客戶端一般是不要綁定的,當數據返回給客戶端時,具體要用哪一個端口,是操作系統決定的,如果我們人為去綁定,可能導致端口號的沖突

#include <sys/types.h> #include <sys/socket.h>int bind(int socket,const struct sockaddr* address,socklen_t addrss_len);

第一個參數很好理解,第二個參數和第三個參數就是我們下面要說到的sockaddr結構

(2)sockaddr結構

所以在綁定時,我們需要將ip和端口號一起封裝在某個結構體中,然后傳參到bind接口里面。它共有三種類型的結構體

你可能發現了,bind接口的形參給的是struct sockaddr*的指針,但是為什么這里有三種類型的結構呢。其實這樣的設計主要是為了用更少的操作完成更多的事情。

在傳參時,這三種結構體的前16位是不相同的,它就是通過這個來區分的,所以只要保證在對齊的情況下,就能用struct sockaddr*來接受不同的結構,這有點像C++中的切片操作

A:struct sockaddr結構

struct sockaddr為許多類型的套接字存儲套接字地址信息,其結構如下

struct sockaddr {unsigned short sa_family; /* 地址家族, AF_xxx */char sa_data[14]; /*14字節協議地址*/ };

這個結構體是一個通用的地址信息結構,它并不是某一個具體的地址信息結構。所以我們不選擇它,而選擇struct sockaddr_int這種結構

B:struct sockaddr_in

該結構體如下

struct sockaddr_in {short int sin_family; /* 通信類型 */unsigned short int sin_port; /* 端口 */struct in_addr sin_addr; /* Internet 地址 */unsigned char sin_zero[8]; /* 與sockaddr結構的長度相同*/};

用這樣結構可以很輕松的處理套接字地址的基本元素。

現在讓我們回到bind接口,看一下它是如何傳參的:當我們使用ipv4版本的洗衣,綁定地址信息的時候,需要填充struct sockadd_in結構體來保存服務器的ip和端口號

四:UDP通信示例

(1)UDP通信

UDP是面向非連接的協議,它不與對方建立連接,而是直接把數據報發給對方。UDP無需建立類如三次握手的連接,使得通信效率很高。因此UDP適用于一次傳輸數據量很少、對可靠性要求不高的或對實時性要求高的應用場景。

UDP服務端流程如下

  • 使用函數socket(),生成套接字文件描述符
  • 通過struct sockaddr_in 結構設置服務器地址和監聽端口
  • 使用bind() 函數綁定監聽端口,將套接字文件描述符和地址類型變量(struct sockaddr_in )進行綁定;
  • 接收客戶端的數據,使用recvfrom() 函數接收客戶端的網絡數據;
  • 向客戶端發送數據,使用sendto() 函數向服務器主機發送數據;
  • 關閉套接字,使用close() 函數釋放資源;
  • UDP客戶端流程如下

  • 使用函數socket(),生成套接字文件描述符
  • 通過struct sockaddr_in 結構設置服務器地址和監聽端口
  • 向服務器發送數據,sendto() ;
  • 接收服務器的數據,recvfrom() ;
  • 關閉套接字,close() ;
  • 需要注意以下幾點

  • 服務器和客戶端地址理應是不一樣的,這里為了測試使用本地環回地址

  • ip地址實際是點分十進制,每個部分算作1個字節,但是我們輸入時往往是字符串,所以下面的接口可以將字符串轉換為正確的IP地址,并且是網絡字節序

  • 如果需要將四字節序列轉為點分十進制,則用char *inet_ntoa (struct in_addr);

  • 本地環回:本地環回地址為127.0.0.1,這是一個測試IP。表示數據會完整的走一遍協議,但是是自己發自己收

  • 對于服務端一般不指定某個ip,因為有可能會有很多個ip,如果指定了ip,服務端只能接受特定ip的數據。所以我們一般把ip設置為一個宏,也即INADDR_ANY表示綁定任意IP

  • (1)sendto和recvfrom接口

    1:UDP發送函數

    #include <sys/types.h> #include <sys/socket.h> int sendto( int sockfd,//套接字描述符 const void* buf,//要發什么東西 size_t len,//期望發多長 ,int flags,//阻塞還是非阻塞,一般設置為0 const struct sockaddr* dest_addr,//指向服務器的struct_in結構體(注意強轉) socklen_t addrlen//指的是上面結構體的長度 )

    2:UDP接受函數

    #include <sys/tyeps.h> #include <sys/socket.h> ssize_t recvfrom( int sockfd,//套接字描述符 void* buf,//讀取到放在哪? size_t len,//期望讀取多長 int flags.//沒有數據讀的時候掛起,默認設置為0表示阻塞等待 struct sockaddr* src_addr, socklen_t* addrlen//保存那個客戶端發給你的(這兩個如果你不關心是誰發給你的,設置為null即可。)

    (2)代碼

    udpServer.h

    #include <iostream> #include <cstdio> #include <string> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> using namespace std;class udpServer { private:int _port;//端口號int _sock;//套接字描述符public:udpServer(int port=8080):_port(port){}void initServer()//初始化服務器{_sock=socket(AF_INET,SOCK_DGRAM,0);//cout<<_sock<<endl;struct sockaddr_in local;//創建sockaddr_in結構//填充local.sin_family=AF_INET;//IPV4協議local.sin_port=htons(_port);//主機字節序轉為網路字節序local.sin_addr.s_addr=INADDR_ANY;//綁定任意IP//綁定if(bind(_sock,(struct sockaddr*)&local,sizeof(local)) < 0){cerr << "綁定失敗" <<endl;exit(1);}}void startServer(){char msg[64];for(;;)//服務器永不停機{msg[0]='\0';//清空緩沖區struct sockaddr_in end_point;//客戶端的信息socklen_t len=sizeof(end_point);//輸出和輸入型參數,recvfrom和sendto都要用ssize_t ret=recvfrom(_sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);//接受,并把客戶端的信息保存在結構體當中if(ret > 0){char bu[16];sprintf(bu,"%d",ntohs(end_point.sin_port));//把網絡字節序轉為主機字節序,端口號string cli=inet_ntoa(end_point.sin_addr);//把客戶端的ip轉為點分十進制cli+=":";cli+=bu;msg[ret]='\0';cout<<"服務端接受到消息:來自->"<<cli<<" "<< msg << endl;string respond="服務端回消息";sendto(_sock,respond.c_str(),respond.size(),0,(struct sockaddr*)&end_point,len);//服務器應答}}}~udpServer(){close(_sock);} };

    udpServer.cpp

    #include "udpServer.h"int main(int argc,char* argv[]) {if(argc!=2)//判斷是否傳入端口號{cout<<"端口號未傳入"<<endl;exit(1);}udpServer* ss=new udpServer(atoi(argv[1]));ss->initServer();ss->startServer(); }

    udpClient.h

    #include <iostream> #include <string> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> using namespace std;class udpClient { private:string _ip;//int _port;//客戶端要保存服務器的IP和端口int _sock;//套接字描述符public:udpClient(string ip="127.0.0.1",int port=8080):_ip(ip),_port(port){//連接服務器,本地環回測試}void initClient()//初始化服務器{_sock=socket(AF_INET,SOCK_DGRAM,0);////客戶端不需要綁定}void startClient(){string msg;//接受用戶輸入//發送給服務器struct sockaddr_in peer;peer.sin_family=AF_INET;peer.sin_port=htons(_port);peer.sin_addr.s_addr=inet_addr(_ip.c_str());for(;;){cout<<"【請輸入:】";cin>>msg;if(msg=="quit")break;//如果用戶輸入退出,下線sendto(_sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));//客戶端給服務端發送消息//服務器接受消息會返回信息char echo[128];ssize_t ret=recvfrom(_sock,echo,sizeof(echo)-1,0,nullptr,nullptr);//不關心服務器的地址if(ret > 0){echo[ret]='\0';cout<<"客戶端受到回應"<< echo<< endl;}}}~udpClient(){close(_sock);}};

    udpClient.cpp

    #include "udpClient.h"int main(int argc,char* argv[]) {if(argc!=3){cout<<"服務器地址沒有傳入"<<endl;exit(1);}udpClient uu(argv[1],atoi(argv[2]));uu.initClient();//初始化客戶端uu.startClient();//啟動客戶端 }

    (3)效果

    使用netstat nlup可以查看網絡進程信息


    1:本地環回測試

    2:局域網IP

    3:公網IP

    總結

    以上是生活随笔為你收集整理的2-3:套接字(Socket)编程之UDP通信,sockaddr,sockaddr_in,recvfrom,sendto的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。