第四章-Linux网络编程

字节序

  • 字节序分为大端字节序和小端字节序,通信双方需要统一字节序才能正常通信。
  • 主机字节序-小端字节序
  • 网络字节序-大端字节序
    在这里插入图片描述

判断大端字节序和小端字节序

/*
    字节序:字节在内存中存储的顺序
        小端字节序:数据的高位字节存储在内存的高位地址,低位字节存储在内存的低位地址
        大端字节序:数据的高位字节存储在内存的低位地址,低位字节存储在内存的高位地址

*/
//通过代码检测当前主机的字节序
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/time.h>
#include <signal.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

int main(){
    union{
        short value;//2字节
        char bytes[sizeof(short)];//char[2]
    }test;
    test.value = 0x0102;
    if((test.bytes[0] == 1) && (test.bytes[1] == 2)){
        printf("大端字节序\n");
    }else if((test.bytes[0] == 2) && (test.bytes[1] == 1)){
        printf("小端字节序\n");
    }else{
        printf("未知");
    }

    return 0;
}

在这里插入图片描述

  • 字节序转换函数
/*

    网络通信时,需要将主机字节序转换成网络字节序(大端),
    另外一段获取到数据以后根据情况将网络字节序转换成主机字节序。

    // 转换端口
    uint16_t htons(uint16_t hostshort);		// 主机字节序 - 网络字节序
    uint16_t ntohs(uint16_t netshort);		// 主机字节序 - 网络字节序

    // 转IP
    uint32_t htonl(uint32_t hostlong);		// 主机字节序 - 网络字节序
    uint32_t ntohl(uint32_t netlong);		// 主机字节序 - 网络字节序

*/
#include <stdio.h>
#include<arpa/inet.h>
int main(){
    //htons  转换端口
    unsigned short a = 0x0102;
    printf("a : %x\n", a);
    unsigned short b = htons(a);
    printf("b : %x\n", b);

    //htonl  转换ip
    char buf[4] = {192, 168, 1, 100};
    int num = *(int *)buf;
    int sum = htonl(num);

    unsigned char *p = (char *)&sum;

    printf("%d %d %d %d\n",*p, *(p+1), *(p+2), *(p+3));


    //ntohl
    unsigned char buf1[4] = {1, 1, 168, 192};
    int num1 = *(int *)buf1;
    int sum1 = ntohl(num1);
    unsigned char * p1 = (unsigned char *)&sum1;
    printf("%d %d %d %d\n", *p1, *(p1+1), *(p1+2), *(p1+3));

    //ntohs 
    unsigned short c = 0x2211;
    printf("c : %x\n", c);
    unsigned short d = ntohs(c);
    printf("d : %x\n", d);


    return 0;
}

在这里插入图片描述

TCP通信

在这里插入图片描述
在这里插入图片描述

  • 服务端没有 listen,客户端发起连接建立,会发生什么?

  • 客户端为什么不使用bind?

    客户端也能用bind,但是不建议使用,服务器为什么要bind呢?因为服务器总是被动方,需要在一个众所周知的端口上等待连接请求,而且作为服务器它的端口号应该是固定的。服务器bind一个端口就表示会在这个端口提供一些特殊的服务。
    而客户端它是主动发起方,我们并不关心是客户端的哪个端口和服务器建立了连接。内核会自动为我们分配一个随机的不冲突的端口号,如果我们对客户端bind的话,反而有可能导致端口冲突。

实现TCP通信服务端

//实现TCP通信的服务器端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(){
    //创建socket(用于监听的套接字)
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socket");
        exit(-1);
    }

    //绑定
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    //inet_pton(AF_INET, "192.168.142.130", saddr.sin_addr.s_addr);
    saddr.sin_addr.s_addr = 0; //0.0.0.0 表示任意地址
    saddr.sin_port = htons(9999);
    int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }

    //监听
    ret = listen(lfd, 8);
    if(ret == -1){
        perror("listen");
        exit(-1);
    }

    //接收客户端连接
    struct sockaddr_in clientaddr;
    int len = sizeof(clientaddr);
    int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);
    if(cfd == -1){
        perror("accept");
        exit(-1);
    }

    //输出客户端的信息
    char clientip[16];
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientip, sizeof(clientip));
    unsigned short clientPort = ntohs(clientaddr.sin_port);
    printf("client ip is %s, port is %d\n", clientip, clientPort);


    //通信
    //获取客户端的数据
    char recvBuf[1024] = {0};
    while(1){
        int num = read(cfd, recvBuf, sizeof(recvBuf));
        if(num == 0){
            perror("read");
            exit(-1);
        }else if(num > 0){
            printf("recv client data : %s\n", recvBuf);
        }else if(num == 0){
            //表示客户端断开连接
            printf("client closed...");
            break;
        }

        //给客户端发送数据
        char *data = "hello,i am server";
        write(cfd, data, strlen(data));
    }
    
    //关闭文件描述符
    close(cfd);
    close(lfd);

    return 0;
}

实现TCP通信客户端

//实现TCP通信的客户端 
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(){
    //创建套接字
    int fd = socket(AF_INET,SOCK_STREAM, 0);
    if(fd == -1){
        perror("socket");
        exit(-1);
    }
    //连接服务器
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.142.130", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }
    
    //通信
    char recvBuf[1024] = {0};
    while(1){
        //给服务器发送数据
        char *data = "hello,i am client";
        write(fd, data, strlen(data));
        sleep(1);
        //获取服务器端的数据    
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == 0){
            perror("read");
            exit(-1);
        }else if(len > 0){
            printf("recv server data : %s\n", recvBuf);
        }else if(len == 0){
            //表示服务器断开连接
            printf("server closed...");
            break;
        }
    }
    close(fd);
    return 0;
}

在这里插入图片描述
在这里插入图片描述

多进程实现TCP并发

在这里插入图片描述

  • 服务端实现
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>
void recyChild(int arg){
    while(1){
        int ret = waitpid(-1, NULL, WNOHANG);
        if(ret == -1){
            //所有的子进程都回收了
            break;
        }else if(ret == 0){
            //还有子进程活着
            break;
        }else if(ret > 0){
            //被回收了
            printf("子进程 %d 被回收了\n", ret);
        }
    }
}

int main(){
    //注册信号捕捉
    struct sigaction act;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    act.sa_handler = recyChild;
    sigaction(SIGCHLD, &act, NULL);

    //创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socket");
        exit(-1);
    }

    //绑定
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    int ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }

    //监听
    ret = listen(lfd, 128);
    if(ret == -1){
        perror("listen");
        exit(-1);
    }

    //不断循环等待客户端连接
    while(1){
        struct sockaddr_in cliaddr;
        int len = sizeof(cliaddr);
        int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
        if(cfd == -1){
            //当信号捕捉时,会执行回调函数,造成短暂的软中断,程序报错
            if(errno == EINTR){
                continue;
            }
            perror("accept");
            exit(-1);
        }
        //每一个连接进来,创建一个子进程跟客户端通信
        pid_t pid = fork();
        if(pid == 0){
            //子进程
            //获取客户端的信息
            char cliIP[16];
            inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIP, sizeof(cliIP));
            unsigned short cliPort = ntohs(cliaddr.sin_port);
            printf("client ip is : %s, port is %d\n", cliIP, cliPort);
            
            //接收客户端发来的数据
            char recvbuf[1024] = {0};
            while(1){
                int len = read(cfd, &recvbuf, sizeof(recvbuf));
                if(len == -1){
                    perror("read");
                    exit(-1);
                }else if(len > 0){
                    printf("recv client data : %s\n", recvbuf);
                }else if(len == 0){
                    printf("client closed\n");
                    break;
                } 
                write(cfd, recvbuf, strlen(recvbuf));
            }
            close(cfd);
            exit(0);//退出当前子进程
        }
        //通过信号释放子进程资源
        

    }
    close(lfd);
    return 0;
}
  • 客户端实现
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
int main(){
    //创建套接字
    int fd = socket(AF_INET,SOCK_STREAM, 0);
    if(fd == -1){
        perror("socket");
        exit(-1);
    }
    //连接服务器
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "192.168.142.130", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }
    
    //通信
    char recvBuf[1024] = {0};
    int i = 0;
    while(1){   
        sprintf(recvBuf, "data : %d\n", i++);
        //给服务器发送数据
        write(fd, recvBuf, strlen(recvBuf));
        sleep(1);
        //获取服务器端的数据    
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == 0){
            perror("read");
            exit(-1);
        }else if(len > 0){
            printf("recv server data : %s\n", recvBuf);
        }else if(len == 0){
            //表示服务器断开连接
            printf("server closed...");
            break;
        }
        
    }
    close(fd);
    return 0;
}

多线程实现TCP并发

  • 服务端实现
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>

struct sockInfo{
    int fd;//通信的文件描述符
    pthread_t tid;//线程号
    struct sockaddr_in addr;//客户端的信息
};

struct sockInfo sockinfos[128];

void *working(void * arg){
    //子线程和客户端通信
    //获取客户端的信息
    struct sockInfo * pinfo = (struct sockInfo *)arg;

    char cliIP[16];
    inet_ntop(AF_INET, &pinfo->addr.sin_addr.s_addr, cliIP, sizeof(cliIP));
    unsigned short cliPort = ntohs(pinfo->addr.sin_port);
    printf("client ip is : %s, port is %d\n", cliIP, cliPort);
    
    //接收客户端发来的数据
    char recvbuf[1024] = {0};
    while(1){
        int len = read(pinfo->fd, &recvbuf, sizeof(recvbuf));
        if(len == -1){
            perror("read");
            exit(-1);
        }else if(len > 0){
            printf("recv client data : %s\n", recvbuf);
        }else if(len == 0){
            printf("client closed\n");
            break;
        } 
        write(pinfo->fd, recvbuf, strlen(recvbuf));
    }
    close(pinfo->fd);
    return NULL;
}

int main(){
     //创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    if(lfd == -1){
        perror("socket");
        exit(-1);
    }

    //绑定
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    saddr.sin_addr.s_addr = INADDR_ANY; // 0.0.0.0
    int ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }

    //监听
    ret = listen(lfd, 128);
    if(ret == -1){
        perror("listen");
        exit(-1);
    }
    
    //初始化数据
    int max = sizeof(sockinfos)/sizeof(sockinfos[0]);
    for(int i=0; i<max; i++){
        bzero(&sockinfos[i],sizeof(sockinfos[i]));
        sockinfos[i].fd = -1;//初始化为-1表示可用
        sockinfos[i].tid = -1;
    }

    //循环等待客户端连接,一旦一个客户端连接进来,就创建一个子线程进行通信
    while(1){
        struct sockaddr_in cliaddr;
        int len = sizeof(cliaddr);
        //接收连接
        int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

        struct sockInfo *pinfo;
        for(int i=0; i<max; i++){
            //从数组中找到一个可以用的sockInfo元素
            if(sockinfos[i].fd == -1){
                pinfo = &sockinfos[i];
                break;
            }
            //如果达到数组最大值,等待一秒
            if(i == max - 1){
                sleep(1);
                i--;
            }
        }

        pinfo->fd = cfd;
        memcpy(&pinfo->addr, &cliaddr, len);
        
        pthread_t tid;
        //创建子线程
        pthread_create(&pinfo->tid, NULL, working, pinfo);
        //线程分离,不需要父线程回收资源
        pthread_detach(pinfo->tid);
    }
    close(lfd);
    return 0;
}

端口复用

I/O多路复用(I/O多路转换)

  • I/O多路复用使得程序能同时监听多个文件描述符,能够提高程序的性能,Linux下实现I/O多路复用的系统调用主要有select、poll和epoll

  • 多路复用的衍生:多进程/多线程->slect->poll->epoll

  • 多路复用将多个文件描述符存入到一个集合中,提供了中间站检测文件描述符中的读写缓冲区是否有数据;多进程/多线程进行TCP通信时,需要多次循环遍历读写缓冲区查看有无数据,造成CPU资源的浪费。

  • select()工作过程分析

    从用户态拷贝到内核态,只有A,B发送了数据,委托内核将A,B发送数据所对应的文件描述符置为1,没有发送数据的文件描述符置为0,其次返回给用户态中,开始读取数据。
    在这里插入图片描述

select.c

#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include<sys/select.h>
int main(){
    //创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    //绑定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    //监听
    listen(lfd, 8);

    //创建一个fd_set集合,存放的是需要检测的文件描述符
    fd_set rdset, tmp;
    FD_ZERO(&rdset);
    FD_SET(lfd, &rdset);
    int maxfd = lfd;

    while(1){
        tmp = rdset;
        //调用select系统函数,让内核帮检测那些文件描述符有数据
        int ret = select(maxfd + 1, &tmp, NULL, NULL, NULL);
        if(ret == -1){
            perror("select");
            exit(-1);
        }else if(ret == 0){
            continue;
        }else if(ret > 0){
            //说明检测到文件描述符对应的缓冲区的数据发生了改变
            if(FD_ISSET(lfd, &tmp)){
                //表示有新的客户端连接进来了
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                //将新的文件描述符添加到集合中
                FD_SET(cfd, &rdset);
                //更新最大的文件描述符
                maxfd = maxfd > cfd ? maxfd : cfd;
            }
            
            for(int i = lfd + 1; i <= maxfd; i++){
                if(FD_ISSET(i, &tmp)){
                    //说明文件描述符发来了数据
                    char buf[1024] = {0};
                    int len = read(i, buf, sizeof(buf));
                    if(len == -1){
                        perror("read");
                        exit(-1);
                    }else if(len == 0){
                        printf("client closed...\n");
                        close(i);
                        FD_CLR(i, &rdset);
                    }else if(len > 0){
                        printf("read buf = %s\n", buf);
                        write(i, buf, strlen(buf) + 1);
                    }
                }
            }
        }
    }
    close(lfd);
    return 0;
}

在这里插入图片描述

  • select的缺点
    • select默认的文件描述符是1024比特位,共有128字节,代表1024个文件描述符。
    • fds集合不能重用需要多创建一个临时集合供内核进行修改,不会对原始的集合做出改变,避免影响下次调用。
      在这里插入图片描述
  • poll
  • 不同于select的是定义了结构体存放文件描述符和检测的事件,由内核返回发生的事件,不会修改原始的事件,此外,没有大小的限制。但是也具有select的缺点,只是稍微地改进了一下。
  • poll的缺点:同select一样,每次的集合都需要从用户态拷贝到内核态,由内核态检测再返回到用户态中,中间过程消耗时间。
poll.c

#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <poll.h>
int main(){
    //创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    //绑定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    //监听
    listen(lfd, 8);

    //初始化检测的文件描述符数组
    struct pollfd fds[1024];
    for(int i = 0; i < 1024; i++){
        fds[i].fd = -1;
        fds[i].events = POLLIN;//检测读事件
    }
    //将监听的文件描述符添加到数组中
    fds[0].fd = lfd;

    int nfds = 0;

    while(1){
        
        //调用poll系统函数,让内核帮检测那些文件描述符有数据
        int ret = poll(fds, nfds + 1, -1);
        if(ret == -1){
            perror("poll");
            exit(-1);
        }else if(ret == 0){
            continue;
        }else if(ret > 0){
            //说明检测到文件描述符对应的缓冲区的数据发生了改变
            if(fds[0].revents & POLLIN){
                //表示有新的客户端连接进来了
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                //将新的文件描述符添加到集合中
                for(int i = 1; i < 1024; i++){
                    if(fds[i].fd == -1){
                        fds[i].fd = cfd;
                        fds[i].events = POLLIN;
                        break;
                    }
                }

                //更新最大的文件描述符的索引
                nfds = nfds > cfd ? nfds : cfd;
            }
            
            for(int i = 1; i <= nfds; i++){
                if(fds[i].revents & POLLIN){
                    //说明文件描述符发来了数据
                    char buf[1024] = {0};
                    int len = read(fds[i].fd, buf, sizeof(buf));
                    if(len == -1){
                        perror("read");
                        exit(-1);
                    }else if(len == 0){
                        printf("client closed...\n");
                        close(fds[i].fd);
                        fds[i].fd = -1;
                    }else if(len > 0){
                        printf("read buf = %s\n", buf);
                        write(fds[i].fd, buf, strlen(buf) + 1);
                    }
                }
            }
        }
    }
    close(lfd);
    return 0;
}

在这里插入图片描述

  • epoll
  • 不用拷贝集合到内核中,在内核态中创建数据结构eventpoll,提高了遍历的效率。
  • 调用epoll_wait函数检测rbr数据结构中的文件描述符是否有数据传过来,如果有几个文件描述符发生改变就将其拷贝到rdlist,(只拷贝一次并且只拷贝有数据的文件描述符),然后返回到用户态中。
    在这里插入图片描述
#include <stdio.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/time.h>
#include <sys/epoll.h>
int main(){
    //创建socket
    int lfd = socket(PF_INET, SOCK_STREAM, 0);
    struct sockaddr_in saddr;
    saddr.sin_port = htons(9999);
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;

    //绑定
    bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));

    //监听
    listen(lfd, 8);

    //调用epoll_create()创建一个epoll实例
    int epfd = epoll_create(100);

    //将监听的文件描述符相关的检测信息添加到epoll实例中
    struct epoll_event epev;
    epev.events = EPOLLIN;
    epev.data.fd = lfd;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev);
    
    struct epoll_event epevs[1024];

    while(1){

        int ret = epoll_wait(epfd, epevs, 1024, -1);
        if(ret == -1){
            perror("epoll_wait");
            exit(-1);
        }     
        printf("ret = %d\n", ret);

        for(int i = 0; i < ret; i++){

            int curfd = epevs[i].data.fd;

            if(curfd == lfd){
                //监听的文件描述符有数据到达,有客户端连接
                struct sockaddr_in cliaddr;
                int len = sizeof(cliaddr);
                int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);

                epev.events = EPOLLIN | EPOLLOUT;
                epev.data.fd = cfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev);
            }else{
                if(epevs[i].events & EPOLLOUT){
                    continue;
                }
                //有数据到达,需要通信
                char buf[1024] = {0};
                int len = read(curfd, buf, sizeof(buf));
                if(len == -1){
                    perror("read");
                    exit(-1);
                }else if(len == 0){
                    printf("client closed...\n");
                    epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL);
                    close(curfd);
                }else if(len > 0){
                    printf("read buf = %s\n", buf);
                    write(curfd, buf, strlen(buf) + 1);
                }
            }
        }

    }
    close(lfd);
    close(epfd);


    return 0;
}
  • epoll的两种工作模式

  • LT模式
    在这里插入图片描述

  • 将服务端的读取的缓冲区减小,在客户端输入的字节数大于缓冲区,说明服务端需要多次读取。这就是水平触发,每次调用都会epoll_wait()都会检测通知。
    在这里插入图片描述

  • 服务端
    在这里插入图片描述

  • 客户端
    在这里插入图片描述

  • ET模式
    在这里插入图片描述

  • EPOLLET(边沿触发)
    在这里插入图片描述

  • 设置边沿触发
    在这里插入图片描述

  • 服务端

  • 每次读取缓冲区大小的数据,阻塞在epoll_wait()
    在这里插入图片描述

  • 客户端
    在这里插入图片描述

  • ET模式设置循环读取数据

  • 将read()通过fcntl()设置为非阻塞,在while循环中,注意的是当缓冲区的数据读取完之后,继续读取会出现EAGAIN的错误。
    在这里插入图片描述

  • 服务端
    在这里插入图片描述

  • 客户端
    在这里插入图片描述

UDP通信

在这里插入图片描述

  • 服务端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
int main(){
    //创建一个通信的socket
    int fd = socket(PF_INET, SOCK_DGRAM, 0);

    if(fd == -1){
        perror("socket");
        exit(-1);
    }
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    addr.sin_addr.s_addr = INADDR_ANY;

    //绑定
    int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
    if(ret == -1){
        perror("bind");
        exit(-1);
    }

    //通信
    while(1){
        char recvbuf[1024];
        char ipbuf[16];
        struct sockaddr_in cliaddr;
        int len = sizeof(cliaddr);

        //接收数据
        int num = recvfrom(fd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&cliaddr, &len);
        if(num == -1){
            perror("recvform");
            exit(-1);
        }

        printf("client IP : %s, Port : %d\n",
            inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
            ntohs(cliaddr.sin_port));
        
        printf("client say : %s\n", recvbuf);

        //发送数据
        sendto(fd, recvbuf, strlen(recvbuf) + 1, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
    }
    close(fd);

}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
int main(){
    //创建一个通信的socket
    int fd = socket(PF_INET, SOCK_DGRAM, 0);

    if(fd == -1){
        perror("socket");
        exit(-1);
    }
    
    //服务器的地址信息
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(9999);
    inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);

    int num = 0;
    //通信
    while(1){
        char sendBuf[128];
        sprintf(sendBuf, "hello, i am client %d\n", num++);

         //发送数据
        sendto(fd, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr *)&saddr, sizeof(saddr));

        //接收数据
        int num = recvfrom(fd, sendBuf, sizeof(sendBuf), 0, NULL, NULL);
        if(num == -1){
            perror("recvform");
            exit(-1);
        }
        
        printf("client say : %s\n", sendBuf);
        sleep(1);
    }
    close(fd);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值