0

    socket套接字详解(TCP与UDP)

    2023.05.04 | admin | 229次围观

    学习LInux缺少网络连接需要的windows套接字,网络编程套接字是基础,也是新手学习的难点,通过本篇文章,读者可以通过图解、作者的代码实现思路全面理解IP地址、端口号、TCP、UDP概念、socket API用法、模拟客户端/服务器通信等。

    文章重点:

    前面介绍过,本地的进程间通信(IPC)有很多种方式,常见的总结以下几点:

     1、管道(包括无名管道和命名管道);
     2、消息队列;
     3、信号量;
     4、共享存储。
     5、……( Socket和Streams支持不同主机上的两个进程IPC)。

    认识网络层通信过程:

    初识IP:

    (IP就是:Internet协议IP)

    在通信时,IP有源IP和目的IP之分,

    对比寄快递:网络通信相当于收发快递,IP就是收件/发件人地址,仅仅知道地址还不行,还要知道派送人是谁?这就对比于网络中的端口号概念,端口号标识了一个进程,告诉操作系统,当前这个数据交给哪一个程序进行解析。

    端口号:

    端口号(port)是传输层协议的内容。

    端口号 & 进程:

    进程有唯一的pid标识,端口号也能标识进程;

    一个进程可以绑定多个端口号,一个端口号不能被多个进程绑定。

    传输层协议(TCP/IP)的数据段中有两个端口号,分别叫做源端口号和目的端口号,就是在描述“数据是谁的?发给谁?”

    TCP:

    (TCP)传输控制协议,面向连接。是一种提供可靠数据传输的通用协议。

    UDP:

    (UDP)用户数据报协议,是一个面向无连接的协议。采用该协议不需要两个应用程序先建立连接。UDP协议不提供差错恢复,不能提供数据重传,因此该协议传输数据安全性差。

    网络字节序:

    其实很容易理解这个问题缺少网络连接需要的windows套接字,就是C语言中比较讲究的大小端问题。

    socket API:

    //创建socket文件描述符  (TCP/UDP,客户端+服务器)
    int socket(int domain, int type, int protocol);

    参数1(domain): 选择创建的套接字所用的协议族;

    AF_INET : IPv4协议;

    AF_INET6: IPv6协议;

    AF_LOCAL: Unix域协议;

    AF_ROUTE:路由套接口;

    AF_KEY :密钥套接口。

    参数2(type):指定套接口类型,所选类型有:

    SOCK_STREAM:字节流套接字;

    SOCK_DGRAM : 数据报套接字;

    SOCK_RAW : 原始套接口。

    procotol: 使用的特定协议,一般使用默认协议(NULL)。

    //绑定端口号  (TCP/IP,服务器)
    int bind(int socket, const struct sockaddr *address, socklen_t address_len);

    参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

    socket套接字详解(TCP与UDP)

    参数2(address):指向特定协议的地址指针。

    参数3(address_len):上面地址结构的长度。

    返回值:没有错误,bind()返回0,否则SOCKET_ERROR。

    //开始监听socket  (TCP,服务器)
    int listen(int socket, int backlog);

    参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

    参数2(backlog):所监听的端口队列大小。

    //接受请求  (TCP,服务器)
    int accept(int socket, struct sockaddr* address, socklen_t* address_len);

    参数1(socket) : 是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

    参数2(address):指向特定协议的地址指针。

    参数3(addrlen):上面地址结构的长度。

    返回值:没有错误,bind()返回0,否则SOCKET_ERROR。

    //建立连接  (TCP,客户端)
    int connect(int sockfd, const struct struct sockaddr *addr, aocklen_t addrlen);

    //关闭套接字
    int close(int fd);

    参数(fd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。

    socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4,IPv6,……

    简单的TCP网络程序:

    服务器代码:

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    #define SERVER_PORT  5050               //端口号
    #define SERVER_IP    "192.168.3.254"    //服务器ip
    #define QUEUE_SIZE   5                  //所监听端口队列大小
    int main(int argc, char *argv[])
    {
        //创建一个套接字,并检测是否创建成功
        int sockSer;                        
        sockSer = socket(AF_INET, SOCK_STREAM, 0);
        if(sockSer == -1){
            perror("socket");
        }
        //设置端口可以重用,可以多个客户端连接同一个端口,并检测是否设置成功
        int yes = 1;
        if(setsockopt(sockSer, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1){
            perror("setsockopt");
        }
        struct sockaddr_in addrSer,addrCli;        //创建一个记录地址信息的结构体
        addrSer.sin_family = AF_INET;              //所使用AF_INET协议族
        addrSer.sin_port = htons(SERVER_PORT);     //设置地址结构体中的端口号
        addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置其中的服务器ip
        //将套接字地址与所创建的套接字号联系起来。并检测是否绑定成功
        socklen_t addrlen = sizeof(struct sockaddr);
        int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
        if(res == -1)
            perror("bind");
        listen(sockSer, QUEUE_SIZE);       //监听端口队列是否由连接请求,如果有就将该端口设置位可连接状态,等待服务器接收连接
        printf("Server Wait Client Accept......\n");
        //如果监听到有连接请求接受连接请求。并检测是否连接成功,成功返回0,否则返回-1
        int sockConn = accept(sockSer, (struct sockaddr*)&addrCli, &addrlen);
        if(sockConn == -1)
            perror("accept");
        else
        {
            printf("Server Accept Client OK.\n");
            printf("Client IP:> %s\n", inet_ntoa(addrCli.sin_addr));
            printf("Client Port:> %d\n",ntohs(addrCli.sin_port));
        }
        char sendbuf[256];         //申请一个发送缓存区
        char recvbuf[256];         //申请一个接收缓存区
        while(1)
        {
            printf("Ser:>");
            scanf("%s",sendbuf);
            if(strncmp(sendbuf,"quit",4) == 0)    //如果所要发送的数据为"quit",则直接退出。
                break;
            send(sockConn, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
            recv(sockConn, recvbuf, 256, 0);    //接收客户端发送的数据
            printf("Cli:> %s\n",recvbuf);
        }
        close(sockSer);         //关闭套接字
        return 0;
    }

    客户端代码:

    #include
    #include
    #include
    #include
    #include
    #include
    #include
    using namespace std;
    #define SERVER_PORT  5050
    #define SERVER_IP    "192.168.3.254"
    int main(int argc, char *argv[])
    {
        //创建客户端套接字号,并检测是否创建成功
        int sockCli;
        sockCli = socket(AF_INET, SOCK_STREAM, 0);
        if(sockCli == -1)
            perror("socket");
        //创建一个地址信息结构体,并对其内容进行设置
        struct sockaddr_in addrSer;     
        addrSer.sin_family = AF_INET;         //使用AF_INET协议族
        addrSer.sin_port = htons(SERVER_PORT);  //设置端口号
        addrSer.sin_addr.s_addr = inet_addr(SERVER_IP);   //设置服务器ip
        bind(sockCli,(struct sockaddr*)&addrCli, sizeof(struct sockaddr));    //将套接字地址与所创建的套接字号联系起来
        //创建一个与服务器的连接,并检测连接是否成功
        socklen_t addrlen = sizeof(struct sockaddr);
        int res = connect(sockCli,(struct sockaddr*)&addrSer, addrlen);
        if(res == -1)
            perror("connect");
        else
            printf("Client Connect Server OK.\n");
        char sendbuf[256];     //申请一个发送数据缓存区
        char recvbuf[256];     //申请一个接收数据缓存区
        while(1)
        {
            recv(sockCli, recvbuf, 256, 0);    //接收来自服务器的数据
            printf("Ser:> %s\n",recvbuf);
            printf("Cli:>");
            scanf("%s",sendbuf);
            if(strncmp(sendbuf,"quit", 4) == 0)    //如果客户端发送的数据为"quit",则退出。
                break;
            send(sockCli, sendbuf, strlen(sendbuf)+1, 0);   //发送数据
        }
        close(sockCli);       //关闭套接字
        return 0;
    }

    简单的UDP网络程序:

    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

    参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)

    参数2(buf):指向存有发送数据的缓冲区的指针

    参数3(len):缓冲区长度。

    **参数4(flags):**flags的值或为0,或为其他

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

    参数1(sockfd):是由socket()调用返回的并且未作连接的套接字描述符(套接字号)

    参数2(buf):指向存有接收数据的缓冲区的指针

    参数3(len):缓冲区长度

    **参数4(flags):**flags的值或为0,或为其他

    服务器端代码:

    #include
    #include
    #include
    #include
    #include
    #include
    int main()
    {
        //创建一个套接字,并检测是否创建成功
        int sockSer = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockSer == -1)
            perror("socket");
        struct sockaddr_in addrSer;  //创建一个记录地址信息的结构体 
        addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
        addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
        addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip
        //将套接字地址与所创建的套接字号联系起来,并检测是否绑定成功
        socklen_t addrlen = sizeof(struct sockaddr);
        int res = bind(sockSer,(struct sockaddr*)&addrSer, addrlen);
        if(res == -1)
            perror("bind");
        char sendbuf[256];    //申请一个发送数据缓存区
        char recvbuf[256];    //申请一个接收数据缓存区
        struct sockaddr_in addrCli;
        while(1)
        {
            recvfrom(sockSer,recvbuf,256,0,(struct  sockaddr*)&addrCli, &addrlen);     //从指定地址接收客户端数据
            printf("Cli:>%s\n",recvbuf);
            printf("Ser:>");    
            scanf("%s",sendbuf);
            sendto(sockSer,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr*)&addrCli, addrlen);    //向客户端发送数据
        }
        return 0;
    }

    客户端代码:

    #include
    #include
    #include
    #include
    #include
    #include
    int main()
    {
        //创建一个套接字,并检测是否创建成功
        int sockCli = socket(AF_INET, SOCK_DGRAM, 0);
        if(sockCli == -1){
            perror("socket");
        }
        addrSer.sin_family = AF_INET;    //使用AF_INET协议族 
        addrSer.sin_port = htons(5050);     //设置地址结构体中的端口号
        addrSer.sin_addr.s_addr = inet_addr("192.168.3.169");  //设置通信ip
        socklen_t addrlen = sizeof(struct sockaddr);
        char sendbuf[256];    //申请一个发送数据缓存区
        char recvbuf[256];    //申请一个接收数据缓存区
        while(1){
            //向客户端发送数据
            printf("Cli:>");
            scanf("%s",sendbuf);
            sendto(sockCli, sendbuf, strlen(sendbuf)+1, 0, (struct sockaddr*)&addrSer, addrlen);   
            接收来自客户端的数据
            recvfrom(sockCli, recvbuf, BUFFER_SIZE, 0, (struct sockaddr*)&addrSer, &addrlen);
            printf("Ser:>%s\n", recvbuf);
        }
        return 0;
    }


    【推荐课程:TCP/IP视频教程】

    以上就是socket套接字详解(TCP与UDP)的详细内容,更多请关注php中文网其它相关文章!

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论