国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Linux TCP/UDP socket 通信和IO多路復(fù)用

這篇具有很好參考價(jià)值的文章主要介紹了Linux TCP/UDP socket 通信和IO多路復(fù)用。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

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)核檢測集合中文件描述符是否可寫
      • 通信的文件描述符
    • 異常集合:檢測文件描述符是否有異常
  • 返回時(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)

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ù)器

  1. 創(chuàng)建socket套接字
  2. 綁定ip和端口
  3. 設(shè)置監(jiān)聽
  4. 初始化一個(gè)epoll樹
  5. 將文件描述符加入epoll樹
  6. 委托內(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ù)器

  1. UDP服務(wù)器需要?jiǎng)?chuàng)建套接字
  2. 綁定端口
  3. 接收數(shù)據(jù)
  4. 根據(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ù)器

  1. 服務(wù)器創(chuàng)建socket
  2. 設(shè)置廣播屬性
    • 廣播開銷很小,只使用廣播地址就可以發(fā)送到多個(gè)接收端
    • 但只能在局域網(wǎng)內(nèi)使用
    • 發(fā)送端要設(shè)置廣播屬性,將消息發(fā)送到廣播地址和端口,接收端在對(duì)應(yīng)的端口等待?
  3. 向廣播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 客戶端

  1. 客戶端創(chuàng)建socket
  2. 綁定固定的端口用來接收數(shù)據(jù)
  3. 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組播

  1. 組播只需要發(fā)送到特定地址,發(fā)送端開銷很小
  2. 組播需要組播地址,一種是Internet中使用,另一種是局域網(wǎng)使用,需要加入到多播組
  3. 相對(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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 網(wǎng)絡(luò)編程 IO多路復(fù)用 [select版] (TCP網(wǎng)絡(luò)聊天室)

    網(wǎng)絡(luò)編程 IO多路復(fù)用 [select版] (TCP網(wǎng)絡(luò)聊天室)

    //head.h? ? ? ? ? ? ? ? ?頭文件 //TcpGrpSer.c? ? ? ? 服務(wù)器端 //TcpGrpUsr.c? ? ? ? 客戶端 select函數(shù)? 功能:阻塞函數(shù),讓內(nèi)核去監(jiān)測集合中的文件描述符是否準(zhǔn)備就緒,若準(zhǔn)備就緒則解除阻塞。 原型: head.h TcpGrpSer.c TcpGrpUsr.c ? ?

    2024年02月14日
    瀏覽(25)
  • 網(wǎng)絡(luò)編程 IO多路復(fù)用 [epoll版] (TCP網(wǎng)絡(luò)聊天室)

    網(wǎng)絡(luò)編程 IO多路復(fù)用 [epoll版] (TCP網(wǎng)絡(luò)聊天室)

    //head.h? ? ? ? ? ? 頭文件 //TcpGrpSer.c? ? ?服務(wù)器端 //TcpGrpUsr.c? ? ?客戶端 通過IO多路復(fù)用實(shí)現(xiàn)服務(wù)器在單進(jìn)程單線程下可以與多個(gè)客戶端交互 ?API epoll函數(shù) ?head.h TcpGrpSer.c TcpGrpUsr.c ?

    2024年02月11日
    瀏覽(25)
  • TCP服務(wù)器的演變過程:IO多路復(fù)用機(jī)制select實(shí)現(xiàn)TCP服務(wù)器

    TCP服務(wù)器的演變過程:IO多路復(fù)用機(jī)制select實(shí)現(xiàn)TCP服務(wù)器

    手把手教你從0開始編寫TCP服務(wù)器程序,體驗(yàn)開局一塊磚,大廈全靠壘。 為了避免篇幅過長使讀者感到乏味,對(duì)【TCP服務(wù)器的開發(fā)】進(jìn)行分階段實(shí)現(xiàn),一步步進(jìn)行優(yōu)化升級(jí)。 本節(jié),在上一章節(jié)的基礎(chǔ)上,將并發(fā)的實(shí)現(xiàn)改為IO多路復(fù)用機(jī)制,使用select管理每個(gè)新接入的客戶端連

    2024年02月03日
    瀏覽(17)
  • socket端口復(fù)用之TCP和UDP

    socket端口復(fù)用之TCP和UDP

    目錄 1.什么是端口復(fù)用? 2.多個(gè)socket可以綁定同一個(gè)端口嗎? 3.綁定同一端口的多個(gè)套接字如何接收數(shù)據(jù)? 4.SO_REUSEADDR和SO_REUSEPORT選項(xiàng)設(shè)置 5.SO_REUSEADDR和SO_REUSEPORT在實(shí)際中的運(yùn)用? 5.1 解決TCP套接字處于TIME_WAIT狀態(tài)占用端口號(hào)問題。 5.2 程序重啟后后,端口未釋放導(dǎo)致程序重啟

    2024年02月07日
    瀏覽(20)
  • Linux多路IO復(fù)用:epoll

    Linux多路IO復(fù)用:epoll

    ? ? ? ? epoll是為克服select、poll每次監(jiān)聽都需要在用戶、內(nèi)核空間反復(fù)拷貝,以及需要用戶程序自己遍歷發(fā)現(xiàn)有變化的文件描述符的缺點(diǎn)的多路IO復(fù)用技術(shù)。 epoll原理 創(chuàng)建內(nèi)核空間的紅黑樹; 將需要監(jiān)聽的文件描述符上樹; 內(nèi)核監(jiān)聽紅黑樹上文件描述符的變化; 返回有變化

    2024年02月04日
    瀏覽(22)
  • IO多路復(fù)用中select的TCP服務(wù)器模型和poll服務(wù)模型

    服務(wù)器端 客戶端 poll客戶端

    2024年02月12日
    瀏覽(32)
  • 【TCP服務(wù)器的演變過程】使用IO多路復(fù)用器epoll實(shí)現(xiàn)TCP服務(wù)器

    【TCP服務(wù)器的演變過程】使用IO多路復(fù)用器epoll實(shí)現(xiàn)TCP服務(wù)器

    手把手教你從0開始編寫TCP服務(wù)器程序,體驗(yàn)開局一塊磚,大廈全靠壘。 為了避免篇幅過長使讀者感到乏味,對(duì)【TCP服務(wù)器的開發(fā)】進(jìn)行分階段實(shí)現(xiàn),一步步進(jìn)行優(yōu)化升級(jí)。 本節(jié),在上一章節(jié)的基礎(chǔ)上,將IO多路復(fù)用機(jī)制select改為更高效的IO多路復(fù)用機(jī)制epoll,使用epoll管理每

    2024年01月17日
    瀏覽(17)
  • 使用IO多路復(fù)用select完成TCP循環(huán)服務(wù)器接收客戶端消息并打印

    使用IO多路復(fù)用select完成TCP循環(huán)服務(wù)器接收客戶端消息并打印

    服務(wù)器 ? ? ? 客戶端 ? ? 結(jié)果 ? ?

    2024年02月12日
    瀏覽(30)
  • linux并發(fā)服務(wù)器 —— IO多路復(fù)用(八)

    linux并發(fā)服務(wù)器 —— IO多路復(fù)用(八)

    半關(guān)閉只能實(shí)現(xiàn)數(shù)據(jù)單方向的傳輸;當(dāng)TCP 接中A向 B 發(fā)送 FIN 請(qǐng)求關(guān)閉,另一端 B 回應(yīng)ACK 之后 (A 端進(jìn)入 FIN_WAIT_2 狀態(tài)),并沒有立即發(fā)送 FIN 給 A,A 方處于半連接狀態(tài) (半開關(guān)),此時(shí) A 可以接收 B 發(fā)送的數(shù)據(jù),但是 A 已經(jīng)不能再向 B 發(fā)送數(shù)據(jù) close不會(huì)影響到其他進(jìn)程,shutdown會(huì)

    2024年02月09日
    瀏覽(22)
  • Linux多路IO復(fù)用技術(shù)——epoll詳解與一對(duì)多服務(wù)器實(shí)現(xiàn)

    Linux多路IO復(fù)用技術(shù)——epoll詳解與一對(duì)多服務(wù)器實(shí)現(xiàn)

    本文詳細(xì)介紹了Linux中epoll模型的優(yōu)化原理和使用方法,以及如何利用epoll模型實(shí)現(xiàn)簡易的一對(duì)多服務(wù)器。通過對(duì)epoll模型的優(yōu)化和相關(guān)接口的解釋,幫助讀者理解epoll模型的工作原理和優(yōu)缺點(diǎn),同時(shí)附帶代碼實(shí)現(xiàn)和圖解說明。

    2024年02月05日
    瀏覽(27)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包