目錄
1. udp
1.1 udp通信流程
- - 服務器端
- 1.創建通信的套接字
- int fd = socket( af_inet, SOCK_DGRAM, 0)
- 2. 綁定 -> 通信的fd 和本地 IP / port 綁定
- struct sockaddr_in addr;
- 3.通信
- 接收數據: recvfrom
- 發送數據: sendto
- 4.關閉通信的fd
- close(fd);
- - 客戶端
- 1.創建一個通信的套接字
- 2.通信
- 接收數據: recvfrom
- 發送數據: sendto
- 3.關閉通信的文件描述符
- close();
1.2 操作函數
-
send、sendto
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> //也可以只用這一個頭文件,包含了上面2個頭文件
// tcp 發送數據的函數 write
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// udp 發送數據
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
參數:
- sockfd: 通信的fd
- buf: 要發送的數據
- len: 要發送的數據的長度
- flags: 0
- dest_addr: 通信的另外一端的地址信息
- addrlen: dest_addr的內存大小
-
recv、recvfrom
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> //也可以只用這一個頭文件,包含了上面2個頭文件
// tcp 接收數據 read
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// udp 接收數據函數
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
參數:
- sockfd: 通信的fd
- buf: 接收數據的一塊內存
- len: 接收數據的內存(第二個參數)大小
- flags: 0
- src_addr: 接收誰的數據, 就寫入了那個終端的地址信息, 如果不要這部分數據 -> NULL
- addrlen: src_addr參數對應內存大小(傳入傳出參數)
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
2. 廣播
向子網中多臺計算機發送消息,並且子網中所有的計算機都可以接收到發送方發送的消息。
- 只能在局域網中使用
- 客戶端只要綁定了服務器廣播使用的端口, 就可以接收到廣播數據
2.1 廣播通信流程
服務器端 -> 廣播的一端:
- - 創建通信的套接字
int fd = socket( af_inet, SOCK_DGRAM, 0);
- - 設置udp廣播屬性
setsockopt();
- - 通信 -> 發送廣播數據
struct sockaddr_in cliaddr;
cliaddr.sin_port(8888); // 廣播數據發送到客戶端的8888端口, 客戶端需要綁定該端口
cliaddr.sin_addr.s_addr -> 初始化一個廣播地址
發送數據: sendto(fd, buf, len, 0, &cliaddr, len);
- - 關閉通信的fd
close(fd);
客戶端
- - 創建一個通信的套接字
int fd = socket( af_inet, SOCK_DGRAM, 0);
- - 如果想接收廣播數據, 需要綁定以固定端口(服務器廣播數據使用的端口)
struct sockaddr_in cliaddr;
cliaddr.sin_port(8888);
bind(fd, cliaddr, len);
- - 通信
接收數據: recvfrom
- - 關閉通信的文件描述符
close();
2.2 設置廣播屬性函數:setsockopt
這個函數有許多功能,這裏只討論設置廣播屬性函數功能
#include <arpa/inet.h>
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
參數:
- sockfd: 文件描述符
- level: SOL_SOCKET
- optname: SO_BROADCAST
- optval: int數值爲1, 允許廣播
- optlen: optval內存大小
2.3 廣播代碼
服務器:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
// 1. 創建通信的套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
// 設置廣播屬性
int op = 1;
setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &op, sizeof(op));
// 將數據發送給客戶端, 使用廣播地址和固定端口
// 初始化客戶端的地址信息
struct sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(8989); // 客戶端也需要綁定這端口
inet_pton(AF_INET, "192.168.247.255", &cliaddr.sin_addr.s_addr);
int num = 0;
// 3. 通信
while(1)
{
// 接收數據
char buf[128];
// 發送數據
sprintf(buf, "hello, client...%d\n ", num++);
sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
printf("廣播的數據: %s\n", buf);
sleep(1);
}
close(fd);
return 0;
}
客戶端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
// 1. 創建通信的套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
// 2. 通信的fd綁定本地的IP和端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8989);
addr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
//inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr.s_addr);
// 3. 通信
while(1)
{
// 接收數據
char buf[128];
recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
printf("server say: %s\n", buf);
}
close(fd);
return 0;
}
3 組播
廣播:無論連接到局域網的客戶端想不想接收該數據,Server都會給客戶端發送該數據。
進而造成客戶端上數據的擁塞,因此引出了組播:Server可以將數據包只發送給指定組內的客戶端,而不發送給指定組外的客戶端。
特點:
- 可以在internet中進行組播
- 加入到廣播的組織中才可以收到數據
3.1 組播地址
IP 組播通信必須依賴於 IP 組播地址,在 IPv4 中它的範圍從 `224.0.0.0` 到 `239.255.255.255`,並被劃分爲局部鏈接多播地址、預留多播地址和管理權限多播地址三類:
3.2 組播通信流程
服務器端 -> 播的一端:
- - 1.創建通信的套接字
int fd = socket( af_inet, SOCK_DGRAM, 0);
- 設置udp組播屬性
setsockopt();
- - 2.通信 -> 發送組播數據
struct sockaddr_in cliaddr;
cliaddr.sin_port(8888); // 廣播數據發送到客戶端的8888端口, 客戶端需要綁定該端口
cliaddr.sin_addr.s_addr -> 初始化一個組播地址
發送數據: sendto(fd, buf, len, 0, &cliaddr, len);
- - 3.關閉通信的fd
close(fd);
客戶端
- - 1.創建一個通信的套接字
int fd = socket( af_inet, SOCK_DGRAM, 0);
- - 2.如果想接收組播數據, 需要綁定以固定端口(服務器組播數據使用的端口)
struct sockaddr_in cliaddr;
cliaddr.sin_port(8888);
bind(fd, cliaddr, len);
- - 3.客戶端加入到組播網絡中
setsockopt():
- - 4.通信
接收數據: recvfrom
- - 5.關閉通信的文件描述符
close();
3.3 設置組播屬性函數:setsockopt
這個函數有許多功能,這裏只討論設置組播屬性函數功能
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
// 服務器端 -> 進程組播
參數:
- sockfd: 通信的文件描述符
- level: IPPROTO_IP
- optname: IP_MULTICAST_IF
- optval: struct in_addr
- optlen: optval 的內存大小
// 客戶端 -> 加入到多播組
參數:
- sockfd: 通信的文件描述符
- level: IPPROTO_IP
- optname: IP_ADD_MEMBERSHIP
- optval: struct ip_mreqn
- optlen: optval 的內存大小
struct ip_mreqn
{
// 組播組的IP地址.
struct in_addr imr_multiaddr;
// 本地某一網絡設備接口的IP地址。
struct in_addr imr_address;
int imr_ifindex; // 網卡編號
};
3.4 組播代碼
服務器:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
// 1. 創建通信的套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
// 設置組播屬性
struct in_addr imr_multiaddr;
// 初始化組播地址
inet_pton(AF_INET, "239.0.0.10", &imr_multiaddr.s_addr);
setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &imr_multiaddr, sizeof(imr_multiaddr));
// 將數據發送給客戶端, 使用廣播地址和固定端口
// 初始化客戶端的地址信息
struct sockaddr_in cliaddr;
cliaddr.sin_family = AF_INET;
cliaddr.sin_port = htons(8989); // 客戶端也需要綁定這端口
inet_pton(AF_INET, "239.0.0.10", &cliaddr.sin_addr.s_addr);
int num = 0;
// 3. 通信
while(1)
{
// 接收數據
char buf[128];
// 發送數據
sprintf(buf, "hello, client...%d\n ", num++);
sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, sizeof(cliaddr));
printf("組播的數據: %s\n", buf);
sleep(1);
}
close(fd);
return 0;
}
客戶端:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <net/if.h>
int main()
{
// 1. 創建通信的套接字
int fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
// 2. 通信的fd綁定本地的IP和端口
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8989);
addr.sin_addr.s_addr = INADDR_ANY;
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
//inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr.s_addr);
// 加入到組播
struct ip_mreqn op;
op.imr_address.s_addr = INADDR_ANY; // 本地地址
inet_pton(AF_INET, "239.0.0.10", &op.imr_multiaddr.s_addr);
op.imr_ifindex = if_nametoindex("ens33");
setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &op, sizeof(op));
// 3. 通信
while(1)
{
// 接收數據
char buf[128];
recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
printf("server say: %s\n", buf);
}
close(fd);
return 0;
}
4. 本地套接字
本地套接字用於進程間通信,有沒有血緣關係都可以。通信流程 -> 一般按照tcp流程處理
4.1 結構體sockaddr_un
結構體 sockaddr、sockaddr_in用於網絡通信
結構體 sockaddr_un用於進程間通信
結構體 sockaddr_in用於ipv6通信
由於結構體sockaddr需要用指針偏移添加IP地址,這樣很麻煩,在網絡通信中我們使用sockaddr_in來添加端口號、IP地址。再強轉成sockaddr類型,因爲這2個結構體大小一樣,後面的服務器—客戶端程序會有具體體現。
在進程間通信中,使用sockaddr_un
#include <sys/un.h>
#define UNIX_PATH_MAX 108
struct sockaddr_un
{
sa_family_t sun_family; // 地址族協議 af_local
char sun_path[UNIX_PATH_MAX]; // 套接字文件的路徑, 這是一個僞文件, 大小永遠=0
};
4.2 本地套接字—進程間通信流程
進程1:服務器端
- 1. 創建監聽的套接字
int lfd = socket(af_local, sock_stream, 0);
第一個參數: AF_UNIX, AF_LOCAL
- 2. 監聽的套接字綁定本地的 套接字文件-> server端
struct sockaddr_un addr;
// 綁定成功之後, 指定的sun_path中的套接字文件會自動生成
bind(lfd, addr, len);
- 3. 監聽
listen(lfd, 100);
- 4. 等待並接受連接請求
struct sockaddr_un cliaddr;
int connfd = accept(lfd, cliaddr, len);
- 5. 通信
接收數據: read/recv
發送數據: write/send
- 6. 關閉連接
close();
進程2:客戶端
- 1. 創建通信的套接字
int fd = socket(af_local, sock_stream, 0);
- 2. 監聽的套接字綁定本地的IP 端口
struct sockaddr_un addr;
// 綁定成功之後, 指定的sun_path中的套接字文件會自動生成
bind(fd, addr, len);
- 3. 連接服務器
struct sockaddr_un serveraddr;
connect(fd, serveraddr, sizeof(serveraddr));
- 4. 通信
接收數據: read/recv
發送數據: write/send
- 5. 關閉連接
close();
4.3 本地套接字—進程間通代碼
進程1:服務器
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/un.h>
int main()
{
unlink("server.sock");
// 1. 創建監聽的套接字
int lfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if(lfd == -1)
{
perror("socket");
exit(0);
}
// 2. 綁定本地套接字文件
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, "server.sock"); //套接字文件是僞文件,會自動生成,名字後綴隨便取
int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
// 3. 監聽
ret = listen(lfd, 100);
if(ret == -1)
{
perror("listen");
exit(0);
}
// 4. 等待並接受連接請求
struct sockaddr_un cliaddr;
int len = sizeof(cliaddr);
int connfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
if(connfd == -1)
{
perror("accept");
exit(0);
}
printf("client socket fileName: %s\n", cliaddr.sun_path);
// 5. 通信
while(1)
{
// 接收數據
char buf[128];
int nums = recv(connfd, buf, sizeof(buf), 0);
if(nums == -1)
{
perror("recv");
exit(0);
}
else if(nums == 0)
{
printf("client disconnect...\n");
break;
}
else
{
printf("client say: %s\n", buf);
send(connfd, buf, nums, 0);
}
}
close(connfd);
close(lfd);
return 0;
}
進程2:客戶端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/un.h>
int main()
{
unlink("client.sock");
// 1. 創建通信的套接字
int cfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if(cfd == -1)
{
perror("socket");
exit(0);
}
// 2. 綁定本地套接字文件
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, "client.sock");
// 綁定成功, client.sock會自動生成
int ret = bind(cfd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("bind");
exit(0);
}
// 3. 連接服務器
struct sockaddr_un seraddr;
seraddr.sun_family = AF_LOCAL;
strcpy(seraddr.sun_path, "server.sock");
ret = connect(cfd, (struct sockaddr*)&seraddr, sizeof(seraddr));
if(ret == -1)
{
perror("connect");
exit(0);
}
int num = 0;
// 5. 通信
while(1)
{
// 發送數據
char buf[128];
sprintf(buf, "hello, everyone... %d\n", num++);
send(cfd, buf, strlen(buf)+1, 0);
printf("client say: %s\n", buf);
// 接收數據
int nums = recv(cfd, buf, sizeof(buf), 0);
if(nums == -1)
{
perror("recv");
exit(0);
}
else if(nums == 0)
{
printf("server disconnect...\n");
break;
}
sleep(1);
}
close(cfd);
return 0;
}