Unix/Linux系统网络编程

本文深入探讨了网络编程的核心概念,包括IP地址、端口、ISO/OSI七层协议模型、TCP/IP模型、协议与协议簇的区别,以及常见的网络协议如TCP、IP、UDP、HTTP、FTP等。详细解释了消息包的逐层封装过程,并介绍了Socket编程的基本步骤,包括本地通信与网络通信的服务器端与客户端实现。此外,文章还涵盖了基于TCP和UDP的网络编程流程,以及使用UDP构建时间服务器的例子。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

网络编程
网络常识//程序员需要知道关于网络的一些基本常识,最重要的是IP地址和端口;
网络编程: TCP 和 UDP;

    ISO/OSI七层协议模型
=================================
    7    应用层Application        ***    |
    6    表现层Presentation        *** |//Telnet/FTP/WWW等
    5    会话层Session            ***    |
--    --    --    --    --    --    --    --    --  --  --
    4    传输层Transport            ### //TCP/UDP
---    ---    ---    ---    ---    ---    ---    ---    --
    3    网络层Network            ### //IP和路由
----------------------------------
    2    数据链路层Data Link        ###    |
    1    物理层Physical            ###    |//网卡驱动
=================================
TCP/IP的模型
    上三层,下四层;
    5会话层/6表现层和7应用层统称为大应用层;
    4传输层和3网络层不变;
    1物理层和2数据链路层可以合也可以分开;

协议(Protocol)和协议簇(Protocol Family)
    协议就是数据传送的方式和准则;
    一系列相关的协议就组成协议簇;协议簇一般以最核心的协议命名;协议簇在某些资料上写成协议族;

常见协议
    TCP        Transmission Control Protocol传输控制协议,基于连接的服务;//传输层
    IP        Internet Protocol;Internet协议,信息传递机制;//网络层
    UDP        User Datagram Protocol用户数据报协议,无连接的服务;//传输层
    HTTP    超文本传输协议;
    FTP        文件传输协议(上传和下载);
    收发邮件协议
    ...

消息包的逐层递增
    Upper Layer Protocol    #Msg#
    TCP                        #Msg#@@
    IP                        #Msg#@@&&
    Enthernet              **#Msg#@@&&**

一些Socket编程的概念
    流stream
    连接connection
    阻塞block/非阻塞non-block
    同步synchronous/异步asynchronous
IP地址
    IP地址是计算机在网络中的唯一标识,可以定位网络中的计算机;
    IP地址的本质是一个整数,分IPV4和IPV6,IPV4是主流,占32位(4个字节),IPV6占128位;每个Internet包必须带有IP地址;
    IP地址有两种表示方式:
    1.底层就是一个32位的整数,用8位十六进制数表示;
    2.点分十进制;每个字节转成一个十进制数(255),中间用点分隔;
    计算机更喜欢8位十六进制,而人更喜欢十进制,但在底层都是存储32位二进制;这两种表示方式可以进行转换;比如:
    192.168.100.17和0xC0A86411是等价的;
0x:     C0  A8  64 11
    C语言也提供了转换函数;
    ipconfig可以查看windows的IP地址,ipconfig/all可以查看物理地址;
    ifconfig可以查看Unix/Linux的IP地址,ifconfig -a;
四级IP地址
    class A: 0 NetWork(7bit) Local_Address(24bit) //是下面的总和
    class B: 10 NetWork(14bit) Local_Address(16bit) //是下面的总和
    class C: 110 NetWork(21bit) Local_Address(8bit) //是下面的总和
    class D: 1110 Multicast_Address(28bit)

IP地址的工作原理
    IP地址需要和网卡的物理地址绑定才能定位计算机,每块网卡的地址是出厂时就设定好的,无重复,叫物理地址(MAC地址/*machine*/);
子网掩码(Subnet Mask)
    子网掩码用于区分两个IP是不是在同一个子网中:
    166.111.160.1与166.111.161.45
    子网掩码:255.255.254.0
    IP和子网掩码做位与运算,结果一样的是一个子网;
    166.111.160.1
    255.255.254.0 (位与)
    166.111.160.0

    166.111.161.45
    255.255.254.0 (位与)
    166.111.160.0
    因此166.111.160.1与166.111.161.45在同一个局域网中;
    
    IP地址只能找到计算机(家),但对应不了计算机中的进程(门),端口号负责对应计算机中的某个进程;

端口
    在网络技术中,端口(Port)大致有两种意思:
    一是物理意义上的端口;比如,ADSL Modem/集线器/交换机/路由器用于连接其他网络设备的接口,如RJ-45端口/SC端口等等;
    二是逻辑意义上的端口;一般是指TCP/IP协议中的端口,端口号的范围从0到65535,比如用于浏览网页服务的80端口,用于FTP服务的21端口等等;
    端口就是计算机中某个进程的网络标识;只有"IP+端口"才能进行数据交互;
    端口也是一个整数,short 类型,范围0-65535;
    端口分类:
    0-1023//最好不要使用,系统使用其中的部分;
    1024-48xxx//正常使用的端口,有很少一部分会被某些安装的软件使用,比如,1521 Oracle数据库;
    48000以后的,动态端口,不稳定;
    
字节顺序(大端格式big endian/小端格式little endian)
    不同的机器存储整数时,存放方式不同;比如,某个整数的4个字节分别是A B C D,有的机器是按A B C D方式存储,而有的机器是D C B A;
    整数的存储有本机格式和网络格式之分;本机格式的字节顺序不确定,而网络格式的字节顺序是确定的;为保证端口号正确传输,需要把本机格式转成网络格式;
    int x = 0x12345678;
    再用 char 逐字节打印,就可以知道本机格式;

union的使用受系统大小端的影响
+----------------------+
|   大端格式           |
|   int i = 1;         |
| 0x1  0x0  0x0  0x0   |
|----------------------|
| 高地址      低地址   |
+----------------------+
+----------------------+
|   小端格式:          |
| 低位数据放在低地址   |
|   int i = 1;         |
| 0x0  0x0  0x0  0x1   |
|----------------------|
| 高地址      低地址   |
+----------------------+
union C {
    int i;
    char c;
};
union C c;
c.i = 1;
printf("%d\n", c.c); //??
// 如果是小端格式,1会存储在低地址,结果返回1
// 如果是大端格式,1会存储在高地址,结果返回0
/*
 * 大端格式小端格式验证
 */
#include <stdio.h>
int main(){
    int x = 0x12345678;
    char *p = (char *)&x;
    printf("%x, %x, %x, %x\n", p[0], p[1], p[2], p[3]);
    union C {
        int i;
        char c;
    } c;
    c.i = 1;
    printf("1 == 0x%08x\n", c.c);
    if (c.c == 1) {
        printf("little endian\n");
    } else {
        printf("big endian\n");
    }
    return 0;
}


ping命令可以查看网络是否畅通,ping后面跟IP地址或域名;

网络中的域名(网址)和IP的关系:
    上网应该是使用IP地址,因IP地址很难记忆,所以用域名(俗称网址)做助记,有专用的服务器负责把域名转成IP地址;

网络编程在C语言中叫socket编程;
    Unix/Linux系统是作为服务器操作系统存在至今的,因此Unix的网络功能是非常全面和强大的;
    网络编程编程其实有很成熟的套路,并且windows也能通用;
    网络编程又叫socket编程,socket的本意是插座,用于网络交互;
    socket编程分为本地通信和网络通信;本地通信由于其他IPC的存在,已经较少使用;重点就是网络通信;
    网络编程至少要写两个程序,服务器端和客户端;因此编程时需要考虑两端的程序;
socket(套接字)编程步骤
1.服务器端
    1.1创建一个socket,使用函数socket();
    int socket(int domain, int type, int protocol);
        参数:
        domain域,用来选择协议簇
            PF_UNIX PF_LOCAL PF_FILE //本地通信IPC
            PF_INET //网络通信(IPv4)
            PF_INET6 //网络通信(IPv6)
            注上面的PF都可以写成AF
        type用于选择通信类型
            SOCK_STREAM //数据流(TCP)
            SOCK_DGRAM  //数据报(UDP)
        protocol参数已经没有意义,因为协议已经被前两个参数决定,所以给0就可以了;
        函数返回socket描述符,出错返回-1;
    1.2准备通信地址(文件/IP和端口)
    有三个通信地址相关的结构体:
    struct sockaddr    //不存数据,只做参数类型;
    struct sockaddr_un    //存储本地通信的数据;
    本地通信使用的是一个文件做IPC媒介,因此存储了socket文件(.sock);
        #include <sys/un.h>
        struct sockaddr_un { //un是unix的缩写
            int sun_family; //协议簇//sun是sockaddr_un的缩写
            char sun_path[]; //文件名(带路径);
        };
    struct sockaddr_in //存储网络通信的数据
        #include <netinet/in.h>
        struct sockaddr_in {
            int sin_family; //协议簇
            short sin_port; //端口号
            struct in_addr sin_addr; //IP地址//其实in_addr只有一个成员
        };
    1.3绑定(socket描述符和通信地址)
    int bind(int sockfd, const struct sockaddr *addr,
                    socklen_t addrlen);
        bind(sockfd, (sockaddr*)(地址), sizeof(addr));
        第2个参数需要强制类型转换为(sockaddr*);
        绑定其实就是服务器对外开放了一个端口
    1.4通信(read()/write())
        使用读写文件描述符的方式读写socket描述符
    1.5关闭socket描述符
2.客户端
    步骤与服务器端一样;只需把第3步bind()绑定换成connect()连接即可;
    注connect()的参数和bind()完全一样;
    但bind()中提供的是服务器的IP和端口,connect()提供的是服务器端的IP和端口;自己的IP和端口封装在数据中发给服务器端,连接服务器时自己是不设防的;
    如果服务器在读,客户端应该写;如果服务器端在写,客户端应该读;
    //读写交互;
/*
 * 本地通信服务器端
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
int main() {
    //1 用socket()获取描述符
    int sockfd = socket(PF_UNIX, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2 准备通信地址
    struct sockaddr_un addr;
    addr.sun_family = PF_UNIX;    //本地通信
    strcpy(addr.sun_path, "a.sock");    //会新建a.sock文件
    //3 用bind()绑定
    int res = bind(sockfd,
               (struct sockaddr *)&addr,    //强制类型转换一下
               sizeof(addr));
    // 判断返回值,如果返回-1表示失败;
    if (res == -1) {
        perror("bind"), exit(-1);
    }
    //4 通信
    char buf[100] = { };
    int i = 0;
    for (i = 0; i <= 9; i++) {
        int len = read(sockfd, buf, sizeof(buf));
        printf("读到%d字节,内容:%s\n", len, buf);
        sleep(1);
    }
    //5 关闭描述符
    close(sockfd);
    printf("读取结束\n");
    return 0;
}

/*
 * 本地通信客户端
 */
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <string.h>
int main() {
    //1 用socket()获取描述符
    int sockfd = socket(PF_UNIX, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2 准备通信地址
    struct sockaddr_un addr;
    addr.sun_family = PF_UNIX;    //本地通信
    strcpy(addr.sun_path, "a.sock");    //会新建a.sock文件
    //3 用connect()连接
    int res = connect(sockfd,
              (struct sockaddr *)&addr,    //强制类型转换一下
              sizeof(addr));
    // 判断是否出错
    if (res == -1) {
        perror("connect"), exit(-1);
    }
    //4 读写
    int i = 0;
    for (i = 0; i <= 9; i++) {
        int len = write(sockfd, "hello", 5);
        printf("写入%d字节,%d\n", len, i);
        sleep(1);
    }
    //5 关闭描述符
    close(sockfd);
    printf("发送完毕\n");
    return 0;
}


注使用网络编程时,网络通信地址中的IP和端口都需要使用转换函数
    需要包含sys/socket.h和arpa/inet.h与netinet/in.h
    in_addr_t inet_addr(const char *cp);
        /* 点分十进制的IP转换为整数IP;  */
    uint16_t htons(uint16_t hostshort);
        /* 端口,本机格式转换为网络格式host to network hostshort; */
    还有一个整数转为点分十进制的函数
    char *inet_ntoa(struct in_addr in);
/*
 * 本地网络编程服务端
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
    //1获取socket描述符
    int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);    //host to network hostshort
    //addr.sin_addr = "172,17.5.3"; //类型不兼容,不可以
    //addr.sin_addr.s_addr = inet_addr("172.17.5.3");    //取结构中的成员转换函数
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //IP地址为本服务器IP
    //3绑定
    int res = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    if (res == -1) { perror("bind"), exit(-1);}
    //4通信
    printf("通信已建立\n");
    char buf[100] = { };
    int res1 = read(sockfd, buf, sizeof(buf));
    if (res1 == -1) {
        perror("read"), exit(-1);
    }
    printf("已经读取%d字节,内容:%s\n", res1, buf);
    //5关闭socket描述符
    close(sockfd);
    return 0;
}

/*
 * 本地网络编程客户端
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
    //1获取socket描述符
    int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);    //host to network hostshort
    //addr.sin_addr = "";//不可以
    //addr.sin_addr.s_addr = inet_addr("172.17.5.3");    //取结构中的成员转换函数
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //IP地址为连接目标
    //3连接
    int res = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    //if(res == -1){perror("bind"),exit(-1);}
    //4通信
    printf("请输入一个字符串\n");
    char buf[100] = { };
    scanf("%s", buf);
    write(sockfd, buf, sizeof(buf));
    printf("已经写入\n");
    //5关闭socket描述符
    close(sockfd);
    return 0;
}


    以上写的是典型的1对1的模式,商业开发更多使用的是基于TCP和基于UDP的开发模式;

基于TCP协议的网络编程
    TCP是有连接协议,所有操作基于客户端和服务器端且保持连接,会重发一切的错误数据;
TCP协议一对多编程步骤
1.服务器端//在以前基础上添加两步;
    1.socket()获取一个socket描述符;
    2.准备通信地址;
    3.bind()绑定;
    4.listen()监听;//可以设置当多个客户端同时访问时,只能操作一个,其他的放入队列中;listen()用于设置队列的最大长度;
    5.等待客户端连接accept();
    int accept(int sockfd, sockaddr* addr, socklen_t* addrlen);
        参数:
        sockfd 就是第一步的返回值;
        addr是用于接收连接的客户端通信地址;
        addrlen是传入传出参数;
            先传入通信地址的大小,再传出获取到的客户端通信地址的大小;
        返回新的描述符,用于和客户端的读写通信;
    6.读写;对于第5步返回的描述符;
    7.关闭两个socket描述符;//socket()和accept()各自返回的描述符;
2.客户端不需要做任何更改;
/*
 * TCP网络编程服务端
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
int main() {
    printf("服务器 pid = %d\n", getpid());
    //1 获取socket描述符
    int sockfd = socket(PF_INET, SOCK_STREAM, 0); //用SOCK_STREAM//TCP
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2 准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);
    /* addr.sin_addr.s_addr = inet_addr("172.17.5.3"); */
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //解决地址被占用的问题address already in use
    int reuseaddr = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
            sizeof(reuseaddr));
    //3 bind()
    int res = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    if (res == -1) {
        perror("bind"), exit(-1);
    }
    printf("bind ok\n"); //bind()成功是一个里程碑,意味着服务器对外开放
    //4 listen()
    listen(sockfd, 100); //最多可以监听100+1个;
    //5 accept()
    struct sockaddr_in from; //用来存放连接到的客户端地址
    socklen_t len = sizeof(from);
    int fd = accept(sockfd, (struct sockaddr *)&from, &len);
    //会等待客户端的连接;//accept()是阻塞函数
    //如果客户端没有连接会一直等待;
    if (fd == -1) {
        perror("accept"), exit(-1);
    }
    printf("%s连接上了\n", inet_ntoa(from.sin_addr));
    //6 读写
    char buf[100] = { }, buf1[100] = { };
    res = read(fd, buf, sizeof(buf));
    if (res == -1) {
        return -1;
    }
    printf("接收到内容client>%s\n", buf);
    strcpy(buf1, "welcome");
    write(fd, buf1, strlen(buf1)); //回发数据;
    //7 关闭两个描述符
    close(sockfd);
    close(fd);
    //close(sockfd);
    return 0;
}

/*
 * TCP网络编程客户端
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
    //1获取socket描述符
    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);    //host to network hostshort
    //addr.sin_addr = "";//不可以
    //addr.sin_addr.s_addr = inet_addr("172.17.5.3");    //取结构中的成员转换函数
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //IP地址为连接目标
    //3连接
    int res = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    //if(res == -1){perror("bind"),exit(-1);}
    //4通信
    printf("请输入一个字符串\n");
    char buf[100] = { };
    scanf("%s", buf);
    write(sockfd, buf, sizeof(buf));
    printf("已经写入\n");
    read(sockfd, buf, sizeof(buf));
    printf("server:%s\n", buf);
    //5关闭socket描述符
    close(sockfd);
    return 0;
}

   
练习
    改良服务器端的代码,改成可以为多个客户端服务的版本
    加无限循环,退出时用信号2;
    因为listen()负责监听,所以把listen()之后改为无限循环;

练习
    改良程序,客户端要求能多次输入(scanf),并把客户端的每次输入发给服务器端,服务器端做转发(就是把客户端的输入发回给客户端);客户端输入bye退出;
    客户端加循环,服务器也随之加;
    客户端退出交互循环,服务器也退出交互循环;
/*
 * TCP网络编程服务端
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
int sockfd;
void fa(int signo) {
    printf("捕获到信号%d\n", signo);
    if (signo == 2) {
        printf("服务器即将关闭\n");
        close(sockfd); //回收资源时释放sockfd;
        sleep(1);
        exit(0);
    }
}
int main() {
    signal(SIGINT, fa);
    printf("按Ctrl+C退出服务器\n");
    printf("服务器已启动 pid = %d\n", getpid());
    //1 获取socket描述符
    sockfd = socket(PF_INET, SOCK_STREAM, 0);    //用SOCK_STREAM//TCP
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2 准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);
    addr.sin_addr.s_addr = inet_addr("172.17.5.3");
    //解决地址被占用的问题address already in use
    int reuseaddr = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &reuseaddr,
           sizeof(reuseaddr));
    //3 bind()
    int res = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    if (res == -1) {
        perror("bind"), exit(-1);
    }
    printf("bind ok\n");
    //4 listen()
    listen(sockfd, 100);
    while (1) {
        //5 accept()
        struct sockaddr_in from;
        socklen_t len = sizeof(from);
        int fd = accept(sockfd, (struct sockaddr *)&from, &len);
        //会等待客户端的连接;//accept()是阻塞函数
        if (fd == -1) {
            perror("accept"), exit(-1);
        }
        pid_t pid = fork();    //支持多进程
        if (pid == 0) {
            printf("%s已连接\n", inet_ntoa(from.sin_addr));
            //6 读写
            char buf[100] = { }, buf1[100] = {
            };
            while (1) {
                res = read(fd, buf, sizeof(buf));
                if (res == -1) {
                    break;
                }
                printf("接收到内容:\nclient>%s\n", buf);
                if (!strcmp(buf, "bye")) {
                    break;
                }
                strcpy(buf1, buf);
                write(fd, buf1, strlen(buf1));
                //write(fd,buf1,strlen(buf1)+1);//可以不用清空
                memset(buf, 0, strlen(buf));    //清零
                memset(buf1, 0, strlen(buf1));    //清零
            }
            //7 关闭两个描述符
            close(fd);    //关闭子进程fd
        }
        close(fd);    //关闭父进程fd
        printf("服务器 pid = %d\n", getpid());
    }
    //close(sockfd);
    return 0;
}

/*
 * TCP网络编程客户端
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
    //1获取socket描述符
    int sockfd = socket(PF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);    //host to network hostshort
    //addr.sin_addr = "";//不可以
    addr.sin_addr.s_addr = inet_addr("172.17.5.3");    //取结构中的成员转换函数
    //IP地址为连接目标
    //3连接
    int res = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    //if(res == -1){perror("bind"),exit(-1);}
    //4通信
    char buf[100] = { };
    char ret[100] = { };
    while (1) {
        printf("请输入聊天信息:>");
        printf("如退出请输入bye,否则按enter键:\n>");
        scanf("%s", buf);
        scanf("%*[^\n]");
        scanf("%*c");
        write(sockfd, buf, sizeof(buf));
        if (!strcmp(buf, "bye")) {
            break;
        }
        printf("已发送%s\n", buf);
        read(sockfd, ret, sizeof(ret));
        printf("服务器返回内容:\n>>%s\n", ret);
        memset(buf, 0, strlen(buf));    //清0
        memset(ret, 0, strlen(ret));
    };
    //5关闭socket描述符
    close(sockfd);
    printf("客户端已退出\n");
    return 0;
}



UDP的编程
    UDP协议是无连接协议,不保持连接;因此UDP不保证数据的完整和正确,因为不会重发错误数据;
    TCP占用资源大,但能保证数据的正确完整;
    UDP占用资源小,但不保证数据的正确完整;
    UDP虽然是无连接协议,但是也可以建立UDP连接;
    QQ是基于UDP的,大部分网络程序也是基于UDP的;

    SOCK_STREAM    //数据流(用于TCP);卖自来水
    SOCK_DGRAM    //数据报(用于UDP);卖瓶装矿泉水

    UDP不需要connect(),它使用不同的数据发送函数和接收函数;
    sendto()//发送,相当于connect()和write()的结合
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                    const struct sockaddr* dest_addr, socklen_t addrlen);
        成功返回发送数据的大小,失败返回-1;
    sendto(sockfd, buf1, sizeof(buf1), 0,
            (struct sockaddr*)&from, sizeof(from));
    recvfrom()//接收
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
        成功返回接收到的数据大小,失败返回-1;
    socklen_t len = sizeof(from);
    recvfrom(sockfd, buf, sizeof(buf), 0,
                (struct sockaddr*)&from, &len);
    //注意最后一个参数与sendto的最后一个参数类型不同
    无连接的UDP发送数据时无法使用write(),而接收数据时如不需要保留发送方通信地址可以使用read(),如果向直到发送方是谁需要使用recvfrom();
/*
 * UDP网络编程服务端
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
    //1获取socket描述符
    int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);    //host to network hostshort
    //addr.sin_addr.s_addr = inet_addr("172.17.5.3");    //取结构中的成员转换函数
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //IP地址为本服务器IP
    //3绑定
    int res = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    //if(res == -1){perror("bind"),exit(-1);}
    //4通信
    printf("通信已建立\n");
    char buf[100] = { }, buf1[100] = { };
    //int res1 = read(sockfd, buf, sizeof(buf));
    //如果还要得到客户端的地址,需要用recvfrom();
    struct sockaddr_in from;
    socklen_t len = sizeof(from);
    int res1 = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &len);    //接收     
    strcpy(buf1, buf);
    if (res1 == -1) {
        perror("read"), exit(-1);
    }
    printf("已经读取%d字节,\n内容:%s\n", res1, buf);
    sendto(sockfd, buf1, sizeof(buf1), 0, (struct sockaddr *)&from, sizeof(from));    //发送
    printf("已返回%s\n", buf1);
    //5关闭socket描述符
    close(sockfd);
    return 0;
}

/*
 * UDP网络编程客户端
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
    //1获取socket描述符
    int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);    //host to network hostshort
    //addr.sin_addr.s_addr = inet_addr("172.17.5.3");    //取结构中的成员转换函数
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    //IP地址为连接目标
    //3连接//无连接的UDP不需要connect()
    //int res = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
    //if(res == -1){perror("bind"),exit(-1);}
    //4通信
    printf("请输入一个字符串\n");
    char buf[100] = { }, buf1[100] = { };
    scanf("%s", buf);
    //write(sockfd,buf,sizeof(buf));//write()发送不了无连接的UDP
    sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&addr,
           sizeof(addr));
    //sendto()相当于connect()与write()的结合;
    printf("已经写入\n");
    read(sockfd, buf1, sizeof(buf1));
    printf("服务器返回:%s\n", buf1);
    //5关闭socket描述符
    close(sockfd);
    return 0;
}


使用UDP做时间服务器
/*
 * UDP做时间服务器
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <time.h>
int sockfd;
void fa(int signo) {
    printf("\nserver down...\n");
    sleep(1);
    printf("...\n");
    close(sockfd);
    exit(0);
}
int main() {
    printf("请按Ctrl+C退出系统\n");
    signal(SIGINT, fa);
    printf("本服务器已启动pid = %d\n", getpid());
    //1获取socket描述符
    sockfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);    //host to network hostshort
    //addr.sin_addr.s_addr = inet_addr("172.17.5.3");    //取结构中的成员转换函数
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    printf("本服务器IP为%s\n", inet_ntoa(addr.sin_addr));
    //IP地址为本服务器IP
    //3绑定
    int res = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
    //if (res == -1) { perror("bind"), exit(-1); }
    //4通信
    printf("通信已建立...\n");
    char buf[100] = { }, buf1[100] = { };
    //int res1 = read(sockfd, buf, sizeof(buf));
    struct sockaddr_in from;
    socklen_t len = sizeof(from);
    while (1) {
        int res1 = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&from, &len);    //接收     
        if (res1 == -1) {
            perror("read"), exit(-1);
        }
        strcpy(buf1, buf);
        printf("%s已连接\n", inet_ntoa(from.sin_addr));
        printf("已经读取%d字节:%s\n", res1, buf);
        /* 取系统时间 */
        time_t cur_time = time(0);
        struct tm *cur = localtime(&cur_time);
        //strcpy(buf1, ctime(&cur_time));//自动转换格式的时间
        sprintf(buf1, "系统时间是%4d-%02d-%02d %02d:%02d:%02d",
            cur->tm_year + 1900, cur->tm_mon + 1, cur->tm_mday,
            cur->tm_hour, cur->tm_min, cur->tm_sec);
        //变长的buf要清0,定长的buf不用清0;
        sendto(sockfd, buf1, strlen(buf1), 0, (struct sockaddr *)&from, sizeof(from));    //发送
        printf("已返回%s\n", buf1);
    }
    //5关闭socket描述符
    //close(sockfd);
    return 0;
}

/*
 * UDP时间服务客户端
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
    //1获取socket描述符
    int sockfd = socket(PF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        perror("socket"), exit(-1);
    }
    //2准备通信地址
    struct sockaddr_in addr;
    addr.sin_family = PF_INET;
    addr.sin_port = htons(2222);    //host to network hostshort
    //addr.sin_addr = "";//不可以
    char ip_addr[20] = { };
    printf("请输入一个IP地址:\n>>");
    scanf("%s", ip_addr);
    //addr.sin_addr.s_addr = inet_addr("172.17.5.3");
    addr.sin_addr.s_addr = inet_addr(ip_addr);    //取结构中的成员转换函数
    //IP地址为连接目标
    //3连接//无连接的UDP不需要connect()
    //int res = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
    //if(res == -1){perror("bind"),exit(-1);}
    //4通信
    printf("请输入一个字符串\n");
    char buf[100] = { }, buf1[100] = {
    };
    scanf("%s", buf);
    //write(sockfd,buf,sizeof(buf));//发送不了无连接的UDP
    sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&addr,
           sizeof(addr));
    //sendto()相当于connect()与write()的结合;
    printf("已经写入\n");
    read(sockfd, buf1, sizeof(buf1));
    printf("服务器返回:>>>>>>%s\n", buf1);
    //5关闭socket描述符
    close(sockfd);
    return 0;
}


从sockaddr中取得Ip地址和端口号
    在socket编程中,服务器端accept()等待一个客户端的连接,当连接成功后,accept拷贝客户端的地址信息到sin_addr里面,我们如何从sin_addr取得此客户端的Ip地址和端口号呢?
  实际上,当sockaddr_in.sin_family = AF_INET时,sockaddr = sockaddr_in;
  据此,我们可以做一下转换,就可以利用inet_ntoa()来得到ip地址和端口号:
  int new_fd = accept(sock, &clientAddr, &sin_size);
  if (new_fd < 0) {
        printf("accept error\n");
  } else {
        //将sockaddr强制转换为sockaddr_in
      sockaddr_in sin;
      memncpy(&sin, &clientAddr, sizoef(sin));
      //取得ip和端口号
      sprintf(info.ip, inet_ntoa(sin.sin_addr));
      info.port = sin.sin_port;
      info.sock = new_fd;
  }



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值