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) 知道伺服器的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;}