longlong001 · 程式 ·

網絡編程-2、TCP&UDP編程

1、UDP編程

1.1、UDP編程-創建套接字

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

功能

創建一個用於網絡通信的socket套接字(描述符)

參數

family:協議族(AF_INET、AF_INET6、PF_PACKET等)

type:套接字類(SOCK_STREAM、SOCK_DGRAM、SOCK_RAW等)

protocol:協議類別(0、IPPROTO_TCP、IPPROTO_UDP等

返回值:

套接字

特點

創建套接字時,系統不會分配埠

創建的套接字默認屬性是主動的,即主動發起服務的請求;當作為伺服器時,往往需要修改為被動的

1.2、UDP編程-發送數據

ssize_t sendto(int sockfd,const void *buf,size_t nbytes,int flags,const struct sockaddr *to,socklen_t addrlen);

功能:

向to結構體指針中指定的ip,發送UDP數據

參數:

sockfd:套接字

buf: 發送數據緩衝區

nbytes: 發送數據緩衝區的大小

flags:一般為0

to: 指向目的主機地址結構體的指針

addrlen:to所指向內容的長度

返回值:

成功:發送數據的字符數

失敗: -1

注意:

通過to和addrlen確定目的地址

可以發送0長度的UDP數據包


1.3、UDP編程-綁定埠

int bind(int sockfd,const struct sockaddr *myaddr,socklen_t addrlen);

功能:將本地協議地址與sockfd綁定

參數:

sockfd: socket套接字

myaddr: 指向特定協議的地址結構指針

addrlen:該地址結構的長度

返回值 :

成功:返回0

失敗:其他

1.4、UDP編程-接收數據

ssize_t recvfrom(int sockfd, void *buf,size_t nbytes,int flags,struct sockaddr *from,socklen_t *addrlen);

功能 :

接收UDP數據,並將源地址信息保存在from指向的結構中

參數 :

sockfd:套接字

buf: 接收數據緩衝區

nbytes:接收數據緩衝區的大小

flags: 套接字標誌(常為0)

from: 源地址結構體指針,用來保存數據的來源

addrlen: from所指內容的長度

返回值:

成功:接收到的字符數

失敗: -1

注意:

通過from和addrlen參數存放數據來源信息

from和addrlen可以為NULL, 表示不保存數據來源


1.5、UDP編程-客戶端示例

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <ARPa/inet.h>#define SERVER_IP "192.168.0.108"#define SERVER_PORT 8080int main(int argc, char *argv[]){ int sockfd; sockfd = socket(AF_INET, SOCK_DGRAM, 0); //創建UDP套接字 if(sockfd < 0) { perror("socket"); exit(-1); } struct sockaddr_in dest_addr; bzero(&dest_addr, sizeof(dest_addr)); dest_addr.sin_family = AF_INET; dest_addr.sin_port = htons(SERVER_PORT); inet_pton(AF_INET, SERVER_IP, &dest_addr.sin_addr); printf("UDP server %s:%d\n", SERVER_IP, SERVER_PORT); while(1) { char send_buf[512] = ""; fgets(send_buf, sizeof(send_buf), stdin);//獲取輸入 send_buf[strlen(send_buf)-1] = '\0'; sendto(sockfd, send_buf, strlen(send_buf), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));//發送數據 } close(sockfd); return 0;}

1.6、UDP編程-服務端示例

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#define SERVER_IP "192.168.0.104"#define SERVER_PORT 8080int main(int argc, char *argv[]){ int sockfd; sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(sockfd < 0) { perror("socket"); exit(-1); } struct sockaddr_in my_addr; bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(SERVER_PORT); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); printf("server bind port: %d\n", SERVER_PORT); int ret = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); if(ret != 0) { perror("bind"); close(sockfd); exit(-1); } printf("receive data:\n"); while(1) { int recv_len; char recv_buf[512] = ""; struct sockaddr_in client_addr; char client_ip[INET_ADDRSTRLEN] = "";//INET_ADDRSTRLEN=16 socklen_t cliaddr_len = sizeof(client_addr); recv_len = recvfrom(sockfd, recv_buf, sizeof(recv_buf), 0, (struct sockaddr*)&client_addr, &cliaddr_len); inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN); //char *client_ip_pr=inet_ntoa(client_addr.sin_addr); printf("client_ip:%s ,client_port:%d\n",client_ip, ntohs(client_addr.sin_port)); printf("data(%d):%s\n",recv_len,recv_buf); } close(sockfd); return 0;}

1.7、UDP廣播

廣播:由一台主機向該主機所在區域網內的所有主機發送數據的方式 ,廣播只能用UDP或原始IP實現,不能用TCP

廣播的用途:

單個伺服器與多個客戶主機通信時減少分組流通

地址解析協議(ARP)

動態主機配置協議(DHCP)

網絡時間協議(NTP)

廣播的特點:

處於同一子網的所有主機都必須處理數據

UDP數據包會沿協議棧向上一直到UDP層

運行音影片等較高速率工作的應用,會帶來大負

局限於區域網內使用


UDP廣播地址

{網絡ID,主機ID}

網絡ID表示由子網掩碼中1覆蓋的連續位

主機ID表示由子網掩碼中0覆蓋的連續位

定向廣播地址:主機ID全1

例:對於192.168.220.0/24,其定向廣播地址為 192.168.220.255

通常路由器不會轉發該廣播

受限廣播地址:255.255.255.255

路由器從不轉發該廣播


套接字選項

int setsockopt(int sockfd, int level,int optname,const void *optval,socklen_t optlen);

功能:

設置套接字選項值

參數:

sockfd:套接字

level: 被設置的選項的級別,如果想要在套接字級別上設置選項,就必須把level設置為 SOL_SOCKET

optname:準備設置的選項,option_name可以有哪些取值,這取決於level

optval:類型

optlen:

返回值:

成功:0

失敗: -1

示例

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#define SERVER_IP "192.168.0.104"#define SERVER_PORT 8080int main(int argc, char *argv[]){ int sockfd=0; sockfd = socket(AF_INET, SOCK_DGRAM, 0); if(sockfd < 0) { perror("socket"); exit(-1); } struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); printf("server bind port: %d\n", SERVER_PORT); int opt=1; setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt)); char send_buf[1024]={0}; strcpy(send_buf,"this is broadbast msg\n"); int len = sendto(sockfd,send_buf,strlen(send_buf),0,(struct sockaddr *)&server_addr,sizeof(server_addr)); if(len<0) { printf("send error\n"); close(sockfd); return -1; } printf("send success\n"); close(sockfd); return 0;}

1.8、UDP多播(組播)

多播: 數據的收發僅僅在同一分組中進行多播的特點:

多播地址標示一組接口

多播可以用於廣域網使用

在IPv4中,多播是可選的

UDP多播地址

IPv4的D類地址是多播地址

十進位:224.0.0.1 - 239.255.255.254

十六進位:E0.00.00.01 - EF.FF.FF.FE 特殊的IP位址多播地址:224.0.0.1:是所有主機組,子網上所有具有多播能力的節點(主機、路由器、印表機)必須在所有具有多播能力的接口上加入該組。224.0.0.2:是所有路由器組,子網上所有多播路由器必須在所有具有多播能力的接口上加入該組。

多播地址分類

224.0.0.0 -- 224.0.255 鏈路局部的多播地址,是低級拓撲和維護協議保留的,多播路由器不轉發以這些地址為目的地址的數據報

224.0.1.0 -- 224.0.1.255: 為用戶可用的組播地址(臨時組地址),可以用於 Internet 上的。224.0.2.0 -- 238.255.255.255: 用戶可用的組播地址(臨時組地址),全網範圍內有效239.0.0.0 -- 239.255.255.255: 為本地管理組播地址,僅在特定的本地範圍內有效


  • 在IPv4網際網路域(AF_INET)中,多播地址結構體用如下結構體ip_mreq表示
struct in_addr{ in_addr_t s_addr; }; struct ip_mreq{ struct in_addr imr_multiaddr;//多播組ip struct in_addr imr_interface;//將要添加到的多播組ip};

套接口選項

int setsockopt(int sockfd, int level,int optname,const void *optval, socklen_t optlen);

功能:

設置套接字選項值

參數:

sockfd:套接字

level: 被設置的選項的級別,IPPROTO_IP

optname: IP_ADD_MEMBERSHIP :加入多播組 ,IP_DROP_MEMBERSHIP :離開多播組

optval:類型 ip_mreq{}

optlen:

返回值:

成功:0

失敗: -1


為什麼要用組播

單播和組播是尋址方案的兩個極端(要麼單個、要麼全部),多播則是兩者之間的一種折中的方案,多播數據報只應該由對它感興趣的接口接收。另外,廣播一般局限於區域網內使用,多播則既可用於區域網,也可跨廣域網使用。

2、TCP編程

2.1、TCP介紹

1、UDP編程11、UDP編程-創建套接字
  • 作為客戶端需要具備的條件

(1) 知道伺服器的ip、port
(2) 主動連接 伺服器

  • 需要用到的函數

socket : 創建TCP套接字(主動)

connect:連接伺服器

send:發送數據到伺服器

recv: 接受伺服器的響應

close:關閉連接

2.2、TCP客戶端相關函數

2.2.1 創建TCP套接字函數

 int sockfd; sockfd = socket(AF_INET, SOCK_STREAM, 0);// 創建通信端點:套接字

2.2.2 連接伺服器函數

int connect(int sockfd,const struct sockaddr *addr,socklen_t len);

功能:主動跟伺服器建立連結
參數:

sockfd:socket套接字
addr: 連接的伺服器地址結構
len: 地址結構體長度

返回值:

成功:0 失敗:其他

注意:

• connect建立連接之後不會產生新的套接字
• 連接成功後才可以開始傳輸TCP數據

2.2.3 TCP發送數據

#include <sys/socket.h>ssize_t send(int sockfd, const void* buf,size_t nbytes, int flags);

功能:用於發送數據
參數:

sockfd: 已建立連接的套接字
buf: 發送數據的地址
nbytes: 發送緩數據的大小(以字節為單位)
flags: 套接字標誌(常為0)

注意:不能用TCP協議發送0長度的數據包

2.2.4 TCP接收數據

#include <sys/socket.h>ssize_t recv(int sockfd, void *buf,size_t nbytes, int flags);

功能:用於接收網絡數據
參數:

• sockfd:套接字
• buf: 接收網絡數據的緩衝區的地址
• nbytes:接收緩衝區的大小(以字節為單位)
• flags: 套接字標誌(常為0)

返回值:成功接收到字節數

2.2.5 客戶端代碼

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#define SERVER_IP "192.168.0.107"#define SERVER_PORT 8080int main(int argc, char *argv[]){ int sockfd; sockfd = socket(AF_INET, SOCK_STREAM, 0);// 創建通信端點:套接字 if(sockfd < 0) { perror("socket"); exit(-1); } struct sockaddr_in server_addr; bzero(&server_addr,sizeof(server_addr)); // 初始化伺服器地址 server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr); int err_log = connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)); // 主動連接伺服器 if(err_log != 0) { perror("connect"); close(sockfd); exit(-1); } char send_buf[1024] = ""; printf("send data to [%s:%d]\n",SERVER_IP,SERVER_PORT); while(1) { printf("send:"); fgets(send_buf,sizeof(send_buf),stdin); send_buf[strlen(send_buf)-1]='\0'; send(sockfd, send_buf, strlen(send_buf), 0); // 向伺服器發送信息 } close(sockfd); return 0;}

2.3、TCP服務端相關函數

做為TCP伺服器需要具備的條件

• 具備一個可以確知的地址
• 讓作業系統知道是一個伺服器,而不是客戶端
• 等待連接的到來

對於面向連接的TCP協議來說,連接的建立才真正代表著數據通信的開始

2.3.1、bind函數

bind用法和UDP服務端使用一樣,

 struct sockaddr_in my_addr; bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(port); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); if( err_log != 0) { perror("binding"); close(sockfd); exit(-1); }

2.3.2、listen函數

#include <sys/socket.h>int listen(int sockfd, int backlog);

功能:

• 將套接字由主動修改為被動
• 使作業系統為該套接字設置一個連接隊列,用來記錄所有連接到該套接字的連接

參數:

• sockfd: socket監聽套接字
• backlog:連接隊列的長度

返回值:

• 成功:返回0
• 失敗:其他

2.3.3、accept 函數

#include <sys/socket.h>int accept(int sockfd,struct sockaddr *cliaddr,socklen_t *addrlen);

功能:從已連接隊列中取出一個已經建立的連接,如果沒有任何連接可用,則進入睡眠等待(阻塞)

參數:

• sockfd: socket監聽套接字

• cliaddr: 用於存放客戶端套接字地址結構

• addrlen:套接字地址結構體長度的地址

返回值:已連接套接字

注意:返回的是一個已連接套接字,這個套接字代表當前這個連接

2.3.4、服務端的代碼

#include <stdio.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#define SERVER_PORT 8080int main(int argc, char *argv[]){ //創建套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if(sockfd < 0) { perror("socket"); exit(-1); } struct sockaddr_in my_addr; bzero(&my_addr, sizeof(my_addr)); my_addr.sin_family = AF_INET; my_addr.sin_port = htons(SERVER_PORT); my_addr.sin_addr.s_addr = htonl(INADDR_ANY); int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)); if( err_log != 0) { perror("binding"); close(sockfd); exit(-1); } err_log = listen(sockfd, 10); if(err_log != 0) { perror("listen"); close(sockfd); exit(-1); } printf("listen client port=%d...\n",SERVER_PORT); while(1) { struct sockaddr_in client_addr; char cli_ip[INET_ADDRSTRLEN] = ""; socklen_t cliaddr_len = sizeof(client_addr); int connfd; connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len); if(connfd < 0) { perror("accept"); continue; } inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN); printf("----------------------------------------------\n"); printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port)); char recv_buf[2048] = ""; while( recv(connfd, recv_buf, sizeof(recv_buf), 0) > 0 ) { printf("\nrecv data:\n"); printf("%s\n",recv_buf); } close(connfd); //關閉已連接套接字 printf("client closed!\n"); } close(sockfd); //關閉監聽套接字 return 0;}
聲明:文章觀點僅代表作者本人,PTTZH僅提供信息發布平台存儲空間服務。
喔!快樂的時光竟然這麼快就過⋯
繼續其他精彩內容吧!
more