1.socket 通信
1.1 大小端轉(zhuǎn)換
- 主機(jī)字節(jié)序 16 位值 <==> 網(wǎng)絡(luò)字節(jié)序 16 位值
- 主機(jī)字節(jié)序 32 位值 <==> 網(wǎng)絡(luò)字節(jié)序 32 位值
#include <arpa/inet.h> // 主機(jī)字節(jié)序轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序 uint16_t htons(uint16_t hostshort); // host to net unsigned short 可用端口轉(zhuǎn)換 unit32_t htonl(unit32_t hostlong); // host to net unsigned int 可用ip地址轉(zhuǎn)換 // 網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換為主機(jī)字節(jié)序 uint16_t ntohs(uint16_t netshort); unit32_t ntohl(unit32_t netlong);
1.2 IP地址轉(zhuǎn)換
- 主機(jī)字節(jié)序的字符串IP地址? <==> 網(wǎng)絡(luò)字節(jié)序的整形IP地址
#include <arpa/inet.h> // 主機(jī)字節(jié)序IP to 網(wǎng)絡(luò)字節(jié)序(大端)IP int inet_pton(int af, const char* src, void* dst); /* 參數(shù): af: 地址族協(xié)議 AF_INET(ipv4), AF_INET6(ipv6) src: 主機(jī)字節(jié)序的字符串類型的IP地址,被轉(zhuǎn)換的數(shù)據(jù) dst: 傳出參數(shù), 存儲(chǔ)轉(zhuǎn)換之后的大端的IP地址 返回值: 成功0; 失敗-1 */ const char *int_ntop(int af, const void *src, char *dst, socklen_t size); /* 參數(shù): af: 地址族協(xié)議 AF_INET; AF_INET6 src: 傳入?yún)?shù), 要被轉(zhuǎn)換的數(shù)據(jù)指針, 指向內(nèi)存中存儲(chǔ)的大端IP地址(整形數(shù)) dst: 傳出參數(shù), 指針指向主機(jī)字節(jié)序, 字符串類型的IP地址 size: dst指向的內(nèi)存的大小 返回值: 成功: 返回指向 dst 指針指向的內(nèi)存 失敗: NULL */
1.3 套接字相關(guān)函數(shù)
1.3.1 socket 創(chuàng)建
#include <arpa/inet.h> // 該頭文件包括了 <sys/socket.h> int socket(int domain, int type, int protocol); /* 參數(shù): domain: AF_INET; AF_INET6 type: SOCK_STREAM: 流式傳輸協(xié)議 TCP SOCK_DGRAM: 報(bào)式傳輸協(xié)議 UDP protocol: 默認(rèn)寫0 流式傳輸默認(rèn) TCP 報(bào)式傳輸默認(rèn) UDP 返回值: 成功: 返回文件描述符 失敗: 返回-1 */
1.3.2 bind 綁定套接字
將監(jiān)聽的套接字和本地IP和端口進(jìn)行關(guān)聯(lián)
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); /* 參數(shù): sockfd: 用于監(jiān)聽的套接字, 通過socket創(chuàng)建 addr: 將本地ip和端口初始化給該結(jié)構(gòu)體(需要用大端) 綁定的時(shí)候服務(wù)器一般ip使用宏 INADDR_ANY (0) 0 表示綁定該主機(jī)的所有ip地址, 多個(gè)網(wǎng)卡可能有多個(gè)ip addrlen: 記錄第二個(gè)指針指向內(nèi)存的大小, sizeof(struct sockaddr) 返回值: 成功0, 失敗-1 */
1.3.3 listen 監(jiān)聽套接字
給監(jiān)聽的套接字設(shè)置監(jiān)聽,開始檢測客戶端鏈接
int listen(int sockfd, int backlog); /* 參數(shù): sockfd: 監(jiān)聽的套接字, 設(shè)置監(jiān)聽前需要先綁定 backlog: 可以同時(shí)檢測的新的連接個(gè)數(shù), 最大值128 返回值: 成功0, 失敗-1 */
1.3.4 accept 接收客戶端連接
等待并接受客戶端的連接,阻塞函數(shù),沒有客戶端連接就阻塞,監(jiān)聽的文件描述符緩沖區(qū)沒有數(shù)據(jù)就阻塞,有數(shù)據(jù)就解除阻塞建立連接,連接建立成功后,返回一個(gè)通信用的文件描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); /* 參數(shù): sockfd: 監(jiān)聽的文件描述符 addr: 傳出參數(shù), 保存了建立連接的客戶端的地址信息(ip 端口) -> 大端存儲(chǔ) 不需要客戶端信息則填NULL addrlen: 傳入傳出參數(shù), 傳入addr指針指向的內(nèi)存大小, 傳出存儲(chǔ)了客戶端信息的addr內(nèi)存大小 addr為NULL,則該參數(shù)也填NULL 返回值: 文件描述符或-1 */
1.3.5 read、recv 讀數(shù)據(jù)
讀取數(shù)據(jù),如果數(shù)據(jù)區(qū)空會(huì)讀堵塞
ssize_t read(int sockfd, void *buf, size_t size); ssize_t recv(int sockfd, void *buf, size_t size, int flags); /* 參數(shù): sockfd: 通信文件描述符 服務(wù)器端: accept 返回值 客戶端: socket 創(chuàng)建得到, connect 初始化連接 buf: 存儲(chǔ)接收到的數(shù)據(jù), 數(shù)據(jù)來自文件描述符對(duì)應(yīng)的緩沖區(qū) size: buf 的內(nèi)存容量 flag: 默認(rèn)屬性0即可 返回值: >0: 讀到的字節(jié)數(shù) =0: 對(duì)方斷開連接 -1: 讀異常, 失敗 */
1.3.6 write、send 寫數(shù)據(jù)
發(fā)送數(shù)據(jù),如果數(shù)據(jù)區(qū)滿會(huì)寫阻塞
ssize_t write(int fd, const void *buf, size_t len); ssize_t send(int fd, const void *buf, size_t len, int flags); /* 參數(shù): fd: 通信的文件描述符 buf: 要發(fā)送的數(shù)據(jù)緩沖區(qū) len: 緩沖區(qū)大小 flags: 使用默認(rèn)屬性0即可 */
1.3.7 recvfrom / sendto 發(fā)送接收
- 報(bào)式傳輸協(xié)議發(fā)送
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); /* 參數(shù): sockfd: 通信文件描述符 buf: 一塊有效內(nèi)存地址 len: 參數(shù)buf指向的內(nèi)存地址大小 flags: 默認(rèn)屬性0即可 src_addr: 傳出參數(shù), 保存發(fā)送端的IP和端口(網(wǎng)絡(luò)字節(jié)序), 不感興趣可以NULL addrlen: 傳入傳出參數(shù), src_addr指針指向內(nèi)存空間的大小, 如果src_addr為NULL, 則填NULL 返回值: >0: 接收到的字節(jié)數(shù); -1: 失敗 */
- 報(bào)式傳輸協(xié)議接收
ssize_t sendto(int sockfd, void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t *addrlen); /* 參數(shù): sockfd: 通信文件描述符 buf: 待發(fā)送的數(shù)據(jù)地址 len: 參數(shù)buf指向的內(nèi)存地址大小 flags: 默認(rèn)屬性0即可 dest_addr: 傳入?yún)?shù), 接收端的IP和端口信息(網(wǎng)絡(luò)字節(jié)序) addrlen: 傳入?yún)?shù), src_addr指針指向內(nèi)存空間的大小 返回值: >0: 發(fā)送的字節(jié)數(shù); -1: 失敗 */
1.3.8 connect 客戶端連接
客戶端連接服務(wù)器
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); /* 參數(shù): sockfd: 通信文件描述符 addr: 連接服務(wù)器的ip和端口信息(需要使用大端描述) addrlen: 參數(shù)addr指向的內(nèi)存大小 返回值: 成功0; 失敗-1 */
1.4 套接字選項(xiàng)
該函數(shù)用來設(shè)置套接字選項(xiàng),端口復(fù)用、廣播、組播等,下面是端口復(fù)用的參數(shù)解釋
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); /* 參數(shù) sockfd: 監(jiān)聽的套接字 level: SOL_SOCKET optname: SO_REUSEPORT optval: 實(shí)際類型int 0 -> 端口不復(fù)用 1 -> 端口復(fù)用 optlen: optval 指針指向的內(nèi)存大小 sizeof(int) 返回值 成功0, 失敗-1 */
2. IO多路復(fù)用
2.1 select
- 構(gòu)造一個(gè)文件描述符列表,將要監(jiān)聽的文件描述符添加到該列表中(最大支持1024,線性描述) 調(diào)用一個(gè)函數(shù),監(jiān)聽該表中的文件描述符,知道這些描述符中的一個(gè)進(jìn)行IO操作時(shí),函數(shù)返回(該函數(shù)為阻塞函數(shù),檢測由內(nèi)核完成)
- 讀集合:檢測文件描述符列表的讀緩沖區(qū)
- 監(jiān)聽的文件描述符:新客戶端連接
- 通信的文件描述符:新數(shù)據(jù)到達(dá)
-
- 寫集合:內(nèi)核檢測集合中文件描述符是否可寫
- 通信的文件描述符
- 異常集合:檢測文件描述符是否有異常
- 寫集合:內(nèi)核檢測集合中文件描述符是否可寫
- 返回時(shí),告訴進(jìn)程有哪些描述符需要進(jìn)行IO操作
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); /* 參數(shù): nfds: 下面三個(gè)集合中, 最大文件描述符值 + 1 readfds: 傳出傳出參數(shù),讀集合,檢測若干文件描述符的讀緩沖區(qū)(新連接 / 新數(shù)據(jù)) writefds: 傳入傳出參數(shù),寫集合,檢測若干文件描述符的寫緩沖區(qū)(一般都可寫,很少用) execptfds: 傳入傳出參數(shù),異常集合 timeout: 表示時(shí)間段,最長檢測多長時(shí)間,超過這個(gè)時(shí)間還在阻塞就解除阻塞 NULL 一直阻塞等待; 0 函數(shù)調(diào)用后立刻返回 返回值: >0: 檢測完成后,滿足條件的總個(gè)數(shù) =0: 超時(shí)強(qiáng)制返回 - 1: 失敗 */
timeval 結(jié)構(gòu)體
struct timeval { time_t tv-sec; suseconds_t tv_usec; };
fd_set 文件描述符集合(位操作)操作函數(shù)
void FD_CLR(int fd, fd_set *set); // 刪除fd int FD_ISSET(int fd, fd_set *set); // 判斷fd是否在集合 void FD_SET(int fd, fd_set *set); // 添加fd void FD_ZERO(fd_set *set); // 清空fd(初始化)
2.2 epoll
在select/poll時(shí)代,服務(wù)器進(jìn)程每次都把這100萬個(gè)連接告訴操作系統(tǒng)(從用戶態(tài)復(fù)制句柄數(shù)據(jù)結(jié)構(gòu)到內(nèi)核態(tài)),讓操作系統(tǒng)內(nèi)核去查詢這些套接字上是否有事件發(fā)生,輪詢完后,再將句柄數(shù)據(jù)復(fù)制到用戶態(tài),讓服務(wù)器應(yīng)用程序輪詢處理已發(fā)生的網(wǎng)絡(luò)事件,這一過程資源消耗較大,因此,select/poll一般只能處理幾千的并發(fā)連接。
epoll的設(shè)計(jì)和實(shí)現(xiàn)與select完全不同。epoll通過在Linux內(nèi)核中申請(qǐng)一個(gè)簡易的文件系統(tǒng)。把原先的select/poll調(diào)用分成了3個(gè)部分:
1)調(diào)用epoll_create()建立一個(gè)epoll對(duì)象(在epoll文件系統(tǒng)中為這個(gè)句柄對(duì)象分配資源)
2)調(diào)用epoll_ctl向epoll對(duì)象中添加這100萬個(gè)連接的套接字
3)調(diào)用epoll_wait收集發(fā)生的事件的連接
如此一來,要實(shí)現(xiàn)上面說是的場景,只需要在進(jìn)程啟動(dòng)時(shí)建立一個(gè)epoll對(duì)象,然后在需要的時(shí)候向這個(gè)epoll對(duì)象中添加或者刪除連接。同時(shí),epoll_wait的效率也非常高,因?yàn)檎{(diào)用epoll_wait時(shí),并沒有一股腦的向操作系統(tǒng)復(fù)制這100萬個(gè)連接的句柄數(shù)據(jù),內(nèi)核也不需要去遍歷全部的連接。
2.2.1 epoll_create 創(chuàng)建 epoll
#include <sys/epoll.h> int epoll_create(int size); /* 參數(shù): size: 沒有實(shí)際意義, 大于0即可 返回值: 成功: 返回一個(gè)文件描述符 該文件描述符對(duì)應(yīng)的指針存儲(chǔ)了紅黑樹的根節(jié)點(diǎn) 失敗: -1 */
2.2.2 epoll_ctl 操作epoll
實(shí)現(xiàn)對(duì) epoll 樹上節(jié)點(diǎn)的操作(添加、修改、刪除節(jié)點(diǎn))
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); /* 參數(shù): epfd: epoll_create() 函數(shù)的返回值,找到對(duì)應(yīng)的epoll實(shí)例 op: EPOLL_CTL_ADD: 添加新節(jié)點(diǎn) EPOLL_CTL_MOD: 修改已經(jīng)添加到樹上節(jié)點(diǎn)的屬性(讀改寫) EPOLL_CTL_DEL: 刪除節(jié)點(diǎn) fd: 要操作的文件描述符 添加 / 修改 / 刪除(監(jiān)聽、通信) event: 對(duì)應(yīng)的事件(若刪除填NULL) EPOLLIN: 讀事件 EPOLLOUT: 寫事件 */
- epoll_data
typedef union epoll_data{ void *ptr; int fd; // 該聯(lián)合體常用這個(gè) uint32_t u32; uint64_t u64; } epoll_data_t;
-
epoll_event
- event 是位操作,EPOLLIN 檢測寫緩沖區(qū),EPOLLOUT 檢測讀緩沖區(qū)
- data.fd 等于 epoll_ctl 函數(shù)調(diào)用的第三個(gè)參數(shù)
struct epoll_event{ uint32_t event; // Epoll events; epoll_data_t data; // User data variable };
2.2.3 epoll_wait
阻塞函數(shù),委托內(nèi)核檢測epoll樹上文件描述符的狀態(tài),如果沒有狀態(tài)變化,默認(rèn)一直阻塞
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); /* 參數(shù): epfd: epoll_create() 的返回值, 找到epoll實(shí)例 event: 傳出參數(shù),記錄了這輪檢測到epoll模型中有狀態(tài)變化的文件描述符(結(jié)構(gòu)體數(shù)組地址) maxevent: events數(shù)組的容量 timeout: 超時(shí)時(shí)長 ms(-1一直阻塞; 0立即返回) 返回值: 成功: 有多少文件描述符發(fā)生變化 */
2.2.4 Level triggered 水平模式(默認(rèn))
LT(level triggered)是缺省的工作方式,同時(shí)支持 block 和 no-block socket。這種模式下,內(nèi)核會(huì)通知文件描述符是否就緒,如果不進(jìn)行任何操作,內(nèi)核會(huì)一直通知你該文件描述符就緒
2.2.5 Edge triggered 邊沿模式
ET(edge triggered)是高速工作模式,只支持 no-block socket。這種模式下,如果接到通知,但是沒有把數(shù)據(jù)從緩沖區(qū)讀完,epoll_wait不會(huì)再次通知;直到再次接收到新數(shù)據(jù)也一樣通知一次,但是此時(shí)他會(huì)接著上次的緩沖區(qū)數(shù)據(jù)讀。
struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; // 設(shè)置文件描述符為邊沿模式 ev.data.fd = lfd;
使用邊沿模式讀數(shù)據(jù)需要在收到消息后我們一般需要 while(1) 死循環(huán)讀取數(shù)據(jù)直到緩沖區(qū)數(shù)據(jù)讀完,所以需要設(shè)置文件描述符為非阻塞狀態(tài),讓read可以非阻塞讀取數(shù)據(jù),通過 read 的返回值判斷是否結(jié)束該死循環(huán)文章來源:http://www.zghlxwxcb.cn/news/detail-746393.html
int fcntl(int fd, int cmd, ...); int flag = fcntl(cfd, F_GETFL); flag = flag | O_NONBLOCK; fcntl(cfd, F_SETFL, flag); //設(shè)置文件描述符為非阻塞, read函數(shù)再讀取不會(huì)阻塞
最后因?yàn)檫@里已經(jīng)設(shè)置為非阻塞,可以根據(jù)read的返回值判斷是否已經(jīng)讀完緩沖區(qū)了,如果讀完了會(huì)有errno EAGAIN的錯(cuò)誤碼,根據(jù)該錯(cuò)誤碼跳出循環(huán)即可文章來源地址http://www.zghlxwxcb.cn/news/detail-746393.html
while(1) { int len = recv(curfd, buf, sizeof(buf), 0); if(len > 0) printf("打印接收的數(shù)據(jù)"); else if( len == 0) printf("斷開連接"); else { if(errno==EAGAIN) { printf("數(shù)據(jù)讀完了"); break; // 跳出循環(huán) } perror("接收錯(cuò)誤"); exit(0); } }
3. 代碼示例
3.1 TCP、epoll服務(wù)器
- 創(chuàng)建socket套接字
- 綁定ip和端口
- 設(shè)置監(jiān)聽
- 初始化一個(gè)epoll樹
- 將文件描述符加入epoll樹
- 委托內(nèi)核檢測文件描述符狀態(tài)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <sys/epoll.h> int main() { // 1. 創(chuàng)建套接字 int lfd = socket(AF_INET, SOCK_STREAM, 0); if(lfd == -1) { perror("socket error"); exit(1); } // 2.將 套接字 和 ip端口 綁定 struct sockaddr_in addr; addr.sin_family = AF_INET; // ipv4 addr.sin_addr.s_addr= INADDR_ANY; // 0地址(本地任意地址) addr.sin_port = htons(8989); // 端口轉(zhuǎn)為大端 int ret = bind(lfd, (struct sockaddr*)&addr, sizeof(addr)); if(ret == -1) { perror("bind error"); exit(2); } // 3.設(shè)置監(jiān)聽 ret = listen(lfd, 128); if(ret == -1) { perror("listen error"); exit(3); } // 4.初始化檢測的集合 int epfd = epoll_create(1); if(epfd == -1) { perror("epoll_create error"); exit(4); } // 5.將要檢測的節(jié)點(diǎn)添加到epoll樹中 struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = lfd; ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev); if(ret == -1) { perror("epoll_ctl"); exit(5); } // 6.委托內(nèi)核檢測epoll樹中的文件描述符狀態(tài) struct epoll_event evs[1024]; int size = sizeof(evs) / sizeof(evs[0]); while(1) { int num = epoll_wait(epfd, evs, size, -1); // 把文件描述符發(fā)生變化的儲(chǔ)存到 evs 數(shù)組中 printf("num = %d\n", num); // 遍歷evs數(shù)組 for(int i=0; i<num; i++) { int curfd = evs[i].data.fd; if(curfd == lfd) // lfd 套接字狀態(tài)改變說明有新鏈接請(qǐng)求 { int cfd = accept(lfd, NULL, NULL); ev.events = EPOLLIN; ev.data.fd = cfd; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev); // 把新的鏈接加入到epoll樹中 } else // 其他套接字狀態(tài)改變說明有新數(shù)據(jù)抵達(dá) { char buf[1024]; memset(buf, 0, sizeof(buf)); int len = recv(curfd, buf, sizeof(buf), 0); if(len == 0) { printf("客戶端斷開了鏈接...\n"); epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL); close(curfd); } else if(len>0) { printf("recv data: %s\n"); send(curfd, buf, len, 0); } else { perror("recv error"); exit(6); } } } } }
3.2 UDP
3.2.1 服務(wù)器
- UDP服務(wù)器需要?jiǎng)?chuàng)建套接字
- 綁定端口
- 接收數(shù)據(jù)
- 根據(jù)接收數(shù)據(jù)的客戶端發(fā)送數(shù)據(jù)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main() { // 1.創(chuàng)建通信套接字 int fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd==-1) { perror("socket"); exit(0); } // 2.接收數(shù)據(jù)需要綁定固定的端口 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); } // 通信 char ip[24]; char buf[1024]; struct sockaddr_in cliaddr; int clilen = sizeof(cliaddr); while(1) { // 3.接收數(shù)據(jù) int len = recvfrom(fd, buf, sizeof(buf), 0, (struct sockaddr*)&cliaddr, &clilen); // 把發(fā)送端數(shù)據(jù)保存在cliaddr中 if(len==-1) { break; } printf("client ip: %s, port: %d\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, sizeof(ip)), ntohs(cliaddr.sin_port)); // 打印發(fā)送端ip和port printf("client say: %s\n", buf); // 打印發(fā)送端發(fā)送的內(nèi)容 // 4.回復(fù)數(shù)據(jù) sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&cliaddr, clilen); } close(fd); return 0; }
3.2.2 客戶端
- UDP客戶端相對(duì)于服務(wù)器端減少了手動(dòng)綁定ip端口的步驟
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main() { // 1.創(chuàng)建通信套接字 int fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd==-1) { perror("socket"); exit(0); } // 服務(wù)器地址 struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(8989); inet_pton(AF_INET, "10.0.2.15", &serveraddr.sin_addr.s_addr); // 通信 char ip[24]; char buf[1024]; int num=0; while(1) { // 2.發(fā)送數(shù)據(jù) sprintf(buf, "Hello World!, %d\n", num++); sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&serveraddr, sizeof(serveraddr)); // 3.接收數(shù)據(jù) memset(buf, 0, sizeof(buf)); int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); // 把發(fā)送端數(shù)據(jù)保存在cliaddr中 if(len==-1) { break; } printf("client say: %s\n", buf); // 打印發(fā)送端發(fā)送的內(nèi)容 } close(fd); return 0; }
3.3 UDP廣播
3.3.1 服務(wù)器
- 服務(wù)器創(chuàng)建socket
-
設(shè)置廣播屬性
- 廣播開銷很小,只使用廣播地址就可以發(fā)送到多個(gè)接收端
- 但只能在局域網(wǎng)內(nèi)使用
- 發(fā)送端要設(shè)置廣播屬性,將消息發(fā)送到廣播地址和端口,接收端在對(duì)應(yīng)的端口等待?
- 向廣播ip端發(fā)送數(shù)據(jù)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> // 服務(wù)器就是廣播端, 不需要收數(shù)據(jù), 自動(dòng)綁定了以后發(fā)數(shù)據(jù)就行 int main() { // 1.創(chuàng)建socket int fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd==-1) { perror("socket"); exit(0); } // 2.設(shè)置廣播屬性 int opt = 1; // 1表示允許廣播, 0表示不允許廣播 setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt)); // 初始化數(shù)據(jù)接收端地址信息 struct sockaddr_in cliaddr; cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(8989); inet_pton(AF_INET, "10.0.2.255", &cliaddr.sin_addr.s_addr); // 3.廣播發(fā)送數(shù)據(jù) char buf[1024]; int num = 0; while(1) { sprintf(buf, "發(fā)送廣播數(shù)據(jù): %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; }
3.3.2 客戶端
- 客戶端創(chuàng)建socket
- 綁定固定的端口用來接收數(shù)據(jù)
- recvfrom接收數(shù)據(jù)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main() { // 1.創(chuàng)建通信套接字 int fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd==-1) { perror("socket"); exit(0); } // 綁定固定的端口 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); } // 通信 char ip[24]; char buf[1024]; while(1) { // 接收數(shù)據(jù) memset(buf, 0, sizeof(buf)); int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); // 把發(fā)送端數(shù)據(jù)保存在cliaddr中 if(len==-1) { break; } printf("boardcast say: %s\n", buf); // 打印發(fā)送端發(fā)送的內(nèi)容 } close(fd); return 0; }
3.4 UDP組播
- 組播只需要發(fā)送到特定地址,發(fā)送端開銷很小
- 組播需要組播地址,一種是Internet中使用,另一種是局域網(wǎng)使用,需要加入到多播組
- 相對(duì)于廣播,組播支持廣域網(wǎng)
3.4.1 服務(wù)器
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> int main() { // 1.創(chuàng)建socket int fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd==-1) { perror("socket"); exit(0); } // 設(shè)置組播屬性 struct in_addr addr; inet_pton(AF_INET, "239.0.0.10", &addr.s_addr); setsockopt(fd, IPPROTO_IP, IP_MULTICAST_IF, &addr, sizeof(addr)); // 初始化數(shù)據(jù)接收端地址信息 struct sockaddr_in cliaddr; cliaddr.sin_family = AF_INET; cliaddr.sin_port = htons(10000); inet_pton(AF_INET, "239.0.0.10", &cliaddr.sin_addr.s_addr); // 廣播發(fā)送數(shù)據(jù) char buf[1024]; int num = 0; while(1) { sprintf(buf, "組播數(shù)據(jù): %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; }
3.4.2 客戶端
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <arpa/inet.h> #include <net/if.h> int main() { // 1.創(chuàng)建通信套接字 int fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd==-1) { perror("socket"); exit(0); } // 綁定固定的端口 struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(10000); addr.sin_addr.s_addr = INADDR_ANY; int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr)); if(ret==-1) { perror("bind"); exit(0); } // 加入到多播組 struct ip_mreqn op; op.imr_address.s_addr = INADDR_ANY; // 本機(jī)地址 inet_pton(AF_INET, "239.0.0.10", &op.imr_multiaddr.s_addr); op.imr_ifindex = if_nametoindex("ens33"); // 網(wǎng)卡名轉(zhuǎn)到序號(hào) setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &op, sizeof(op)); // 通信 char ip[24]; char buf[1024]; while(1) { // 接收數(shù)據(jù) memset(buf, 0, sizeof(buf)); int len = recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL); // 把發(fā)送端數(shù)據(jù)保存在cliaddr中 if(len==-1) { break; } printf("boardcast say: %s\n", buf); // 打印發(fā)送端發(fā)送的內(nèi)容 } close(fd); return 0; }
3.5 本地套接字用于進(jìn)程間通信
3.5.1 服務(wù)器端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/un.h>
int main()
{
// 1.創(chuàng)建socket
int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
// 2.和本地套接字文件綁定
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, "./server.sock"); // 套接字文件存儲(chǔ)的目錄
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
printf("HELLO\n");
// 3.設(shè)置監(jiān)聽
listen(fd, 128);
// 4.等待接收連接
struct sockaddr_un cliaddr;
int clilen = sizeof(cliaddr);
int cfd = accept(fd, (struct sockaddr*)&cliaddr, &clilen);
printf("客戶端套接字文件路徑和名字:%s\n", cliaddr.sun_path);
// 5.通信
while(1)
{
char buf[1024];
memset(buf, 0, sizeof(buf));
int len = recv(cfd, buf, sizeof(buf), 0);
if(len==0)
{
printf("客戶端斷開連接\n");
break;
}
else if(len>0)
{
printf("client say: %s\n", buf);
send(cfd, buf, len, 0);
}
else
{
perror("recv");
break;
}
}
close(fd);
return 0;
}
3.5.2客戶端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/un.h>
int main()
{
// 1.創(chuàng)建socket
int fd = socket(AF_LOCAL, SOCK_STREAM, 0);
// 2.和本地套接字文件綁定
struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, "./client.sock"); // 套接字文件存儲(chǔ)的目錄
int ret = bind(fd, (struct sockaddr*)&addr, sizeof(addr));
// 3.鏈接服務(wù)器
struct sockaddr_un seraddr;
seraddr.sun_family = AF_LOCAL;
strcpy(seraddr.sun_path, "./server.sock"); // 套接字文件存儲(chǔ)的目錄
ret = connect(fd, (struct sockaddr*)&seraddr, sizeof(seraddr));
// 4.通信
int num = 0;
while(1)
{
// 發(fā)送數(shù)據(jù)
char buf[1024];
sprintf(buf, "本地套接字通信, %d\n", num++);
send(fd, buf, strlen(buf)+1, 0);
// 接收數(shù)據(jù)
memset(buf, 0, sizeof(buf));
recv(fd, buf, sizeof(buf), 0);
printf("server say: %s\n", buf);
}
close(fd);
return 0;
}
到了這里,關(guān)于Linux TCP/UDP socket 通信和IO多路復(fù)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!