学习记录——day30 网络编程 端口号port 套接字socket TCP实现网络通信

目录

[一、端口号 port](#一、端口号 port)

[二、套接字 socket](#二、套接字 socket)

1、原理

2、socket函数介绍

三、TCP实现网络通信

1、原理

2、TCP通信原理图

3、TCP相关函数

[1)bind 绑定](#1)bind 绑定)

[2)listen 监听](#2)listen 监听)

[3)accept 接收连接请求](#3)accept 接收连接请求)

[4)recv 接收](#4)recv 接收)

[5)send 发送](#5)send 发送)

[6)connect 连接请求](#6)connect 连接请求)

4、TCP服务器端代码实现

5、TCP客服端代码实现

四、UDP实现网络通信

1、UDP网络通信模型

2、UDP相关函数 recvfrom sendto

3、UDP服务器端代码实现

4、UDP客服端端代码实现

五、TCP和UDP基础通信模型注意事项


一、端口号 port

1、为了区分同一主机上的多个进程,使用端口号来进行处理

2、端口号是一个2字节的无符号整数存储,取值范围【0,65535】

3、网络通信中两个"地址",主机的地址------IP,进程的地址------端口号;

4、特殊的端口号:0-1023

由系统默认应用程序占用,编程不可使用

TCP 21端口:FTP文件传输服务

TCP 23端口:TELNET终端仿真服务

TCP 25端口:SMTP简单邮件传输服务

TCP 110端口:POP3邮局协议版本3

TCP 80端口:HTTP超文本传输服务

TCP 443端口:HTTPS加密超文本传输服务

UDP 53端口:DNS域名解析服务

UDP 69端口:TFTP文件传输服务

特殊的端口函数,存储在linux中的 /etc/services文件中

5、编程可使用的:1024-49151

编程可使用的端口号

6、临时端口号:49152-65535

客服端运行时动态选择的,编程时若未指定端口号,会分配临时端口号

二、套接字 socket

相关帮助指令 man 2 socket man 7 socket

1、原理

2、socket函数介绍

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int socket(int domain, int type, int protocol);
       功能:为通信创建一个端点,并返回该端点的文件描述符
       参数1:通信域
       Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   本地通信,同一主机之间进程通信     详情请看man 7 unix
       AF_INET             IPv4 提供的网络通信               详情请看man 7 ip
       AF_INET6            IPv6 提供的网络通信               详情请看man 7 ipv6
       参数2:指定通信语义,可以由多个宏值使用位或连接
       SOCK_STREAM:表示提供TCP协议的传输方式
       SOCK_DGRAM:表示提供UDP协议的传输方式
       SOCK_NONBLOCK:套接字设置非阻塞属性
       参数3:如果参数2中仅仅指定一个协议,那么参数3可以填0,如果指定多个,则参数3需要指定特定的协议
           TCP协议名称:IPPROTO_TCP
           UDP协议名称:IPPROTO_UDP
       返回值:成功返回创建的套接字文件描述符,失败返回     -1并置位错误码                 

三、TCP实现网络通信

1、原理

服务器端:

1)创建套接字1

2)给套接字1绑定服务器端端口号、ip地址

3)将套接字1的功能改为监听(套接字内部被改造,原本的收发缓冲区改为已连接、未连接队列),用于检测是否客服端连接(三次握手就发生在这一步)

4)阻塞等待连接,连接成功,创建套接字2,用于消息的收发

5)消息的发送与接收

6)关闭通信,可以由服务器端、客服端其中之一执行

客服端:

1)创建由于通信的套接字

2)绑定客服端端口号、ip地址

3)连接服务器端,连接成功进入未连接队列,马上从未连接队列向已连接队列转换,该过程非常迅速,但同时申请连接的数量过多(超过未连接队列大小)仍会阻塞

4)消息的发送与接收

5)关闭通信,可以由服务器端、客服端其中之一执行

2、TCP通信原理图

3、TCP相关函数

1)bind 绑定

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int bind(int sockfd, const struct sockaddr *addr,
 socklen_t addrlen);
       功能:位套接字分配名称
       参数1:通过socket函数创建出来的套接字文件描述符
       参数2:通用地址信息结构体,需要根据具体使用的地址族而定, struct sockaddr仅仅只是为了类型的强制转换,防止出现警告
           跨主机间通信:man 7 ip
            struct sockaddr_in {
               sa_family_t    sin_family; /* 表示通信域 */
               in_port_t      sin_port;   /* 端口号的网络字节序 */
               struct in_addr sin_addr;   /* ip地址 */
           };

           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* IP地址的网络字节序 */
           };
           同一主机间通信:man 7 uninx
          struct sockaddr_un {
               sa_family_t sun_family;               /* 表示通信域:AF_UNIX */
               char        sun_path[108];            /* 套接字文件的地址 */
           };

        参数3:参数2的大小
        返回值:成功返回0,失败返回-1并置位错误码
 注意关于bind的两个错误:
 1、 Cannot assign requested address:表示IP地址填写错误,检查IP是否有问题
 2、Address already in use:表示地址信息正在占用,可以调用函数快速重用,也可以等一会

2)listen 监听

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);
       功能:将套接字设置成被动监听状态,已接受客户端的连接请求
       参数1:套接字文件描述符
       参数2:容纳连接的队列的最大长度,一般填128
       返回值:成功返回0,失败返回-1并置为错误码

3)accept 接收连接请求

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
       功能:用于阻塞接收客户端连接请求
       参数1:服务器套接字文件描述符
       参数2:用于接收对端地址信息结构体的指针
       参数3:接收对端地址信息的长度
       返回值:成功返回一个新的用于通信的套接字文件描述符,失败返回-1并置位错误码

4)recv 接收

       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t recv(int sockfd, void *buf, size_t len, int flags);
       功能:从套接字中读取数据到buf中
       参数1:用于通信的套接字文件描述符
       参数2:接收数据后的容器地址
       参数3:接收的数据的大小
       参数4:是否阻塞接收
              0:表示阻塞接收消息
              MSG_DONTWAIT:表示非阻塞接收数据
        返回值:
            >0:表示成功读取的字符个数
            =0:表示通信对端已经下线
            =-1:表示出错,置位错误码                    

5)send 发送

       #include <sys/types.h>
       #include <sys/socket.h>

       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       功能:向通信套接字文件描述符中写入数据
       参数1:通信的套接字文件描述符
       参数2:要发送数据的起始地址
       参数3:要发送数据的大小
       参数4:是否阻塞接收
              0:表示阻塞接收消息
              MSG_DONTWAIT:表示非阻塞接收数据
       返回值:成功返回发送字符的个数,失败返回-1并置位错误码       

6)connect 连接请求

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

       int connect(int sockfd, const struct sockaddr *addr,
 socklen_t addrlen);
       功能:将套接字文件描述符连接到addr指向的地址空间中
       参数1:客户端套接字文件描述符
       参数2:对端地址信息结构体
       参数3:参数2的大小
       返回值:成功返回0,失败返回-1并置位错误码

4、TCP服务器端代码实现

#include <myhead.h>
#define SER_PORT 6666
#define SER_IP "192.168.232.129"
int main(int argc, char const *argv[])
{
    // 1、创建套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    // 参数1:ipv4的网络通信
    // 参数2:TCP通信方式
    // 参数3:默认使用一个协议

    if (sfd == -1)
    {
        perror("socket error");
        return -1;
    }

    printf("socket success, sfd = %d\n", sfd); // 3

    // 2、为套接字绑定ip地址和端口号
    // 2.1 填充地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;                // 通信域
    sin.sin_port = htons(SER_PORT);          // 端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址

    // 2.2 绑定
    if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");

    // 3、将套接字设置为被动监听状态,用于接收
    if (listen(sfd, 128) == -1)
    {
        perror("listen error");
        return -1;
    }
    printf("listen success\n");

    // 4、阻塞等待客户端的连接请求
    // 4.1 定义n变量用于e接收客服端的信息
    struct sockaddr_in cin;
    socklen_t addrlen = sizeof(cin);

    // 4.2 接收连接
    int newsfd = accept(sfd, (struct sockaddr *)&cin, &addrlen);
    if (newsfd == -1)
    {
        perror("accept error");
        return -1;
    }
    printf("[%s:%d]:accept on\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));

    //5、数据收发
    char buf[128] = "";

    while (1)
    {
        // 从客户端套接字中接收数据
        int res = recv(newsfd, buf, sizeof(buf),0);
        if (res == -1)
        {
            perror("read error");
            return -1;
        }
        else if (res == 0)
        {
            printf("客户端已下线\n");
            close(newsfd); // 关闭客户端套接字
            break;
        }

        // 接收数据
        printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);

        // 对接收到的数据进行处理
        strcat(buf, ":D");

        // 将消息返回到客户端
        if (send(newsfd, buf, strlen(buf),0) == -1)
        {
            perror("发送失败\n");
            return -1;
        }
        printf("发送成功\n");
        bzero(buf,sizeof(buf));//清空容器
    }
    return 0;
}

5、TCP客服端代码实现

#include <myhead.h>
#define SER_PORT 6666            // 与服务器保持一致
#define SER_IP "192.168.232.129" // 服务器ip地址
#define CLI_PORT 8888            // 客服端端口号
#define CLI_IP "192.168.232.129" // 客服端ip地址
int main(int argc, char const *argv[])
{
    //1、 创建用于通信的套接字文件描述符
    int cfd = socket(AF_INET, SOCK_STREAM, 0);
    if (cfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("cfd = %d\n", cfd);
    //2、 绑定IP地址和端口号
    struct sockaddr_in cin;
    cin.sin_family = AF_INET;                // 通信域
    cin.sin_port = htons(CLI_PORT);          // 端口号
    cin.sin_addr.s_addr = inet_addr(CLI_IP); // ip地址

    //2.2、 绑定
    if (bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");
    //3、 连接服务器
    //3.1、 填充服务器地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET; //通信域
    sin.sin_port = htons(SER_PORT);
    sin.sin_addr.s_addr = inet_addr(SER_IP);

    //3.2、连接服务器
    if(connect(cfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
    {
        perror("connect error");
        return -1;
    }
    printf("连接服务器成功\n");

    //4、数据收发
    char buf[128] = "";
    while (1)
    {
        printf("输入:");
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1] = 0;

        //将数据发送到服务器
        send(cfd,buf,strlen(buf),0);
        printf("发送结束\n");

        //接收服务器发送的数据
        bzero(buf,sizeof(buf));//清空容器
        recv(cfd,buf,sizeof(buf),0);
        printf("收到服务器信息:%s\n",buf);
    }
    
    //5、关闭套接字
    close(cfd);
    return 0;
}

四、UDP实现网络通信

1、UDP网络通信模型

2、UDP相关函数 recvfrom sendto

       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
       //功能:从套接字文件描述符中读取数据,并将对端地址信息结构体接收
       参数1:套接字文件描述符
       参数2:要接收数据的起始地址
       参数3:要接收的数据大小
       参数4:是否阻塞,0表示阻塞,MSG_NOWAIT表示非阻塞
       参数5:接收对端地址信息结构体
       参数6:参数5的大小
       返回值:成功返回读取的字节的大小,失败返回-1并置位错误码
       
        ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
      //功能:向套接字文件描述符中读取数据,写给指定的对端接收
       参数1:套接字文件描述符
       参数2:要发送数据的起始地址
       参数3:要发送的数据大小
       参数4:是否阻塞,0表示阻塞,MSG_NOWAIT表示非阻塞
       参数5:接收对端地址信息结构体
       参数6:参数5的大小
       返回值:成功返回发送的字节的大小,失败返回-1并置位错误码

3、UDP服务器端代码实现

#include <myhead.h>
#define SER_PORT 9999            // 服务器端口号
#define SER_IP "192.168.232.129" // 服务器ip地址
int main(int argc, char const *argv[])
{
    // 1、创建用于通信的套接字i文件描述符
    int sfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sfd == -1)
    {
        perror("scoket error");
        return -1;
    }
    printf("sfd = %d\n", sfd); // 3
    // 2、绑定ip地址和端口号
    //  2.1 填充地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family = AF_INET;                // 通信域
    sin.sin_port = htons(SER_PORT);          // 端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP); // ip地址

    // 2.2、 绑定
    if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");
    // 3、数据收发
    char buf[128] = "";

    struct sockaddr_in cin;          // 接收对端地址信息
    socklen_t addrlen = sizeof(cin); // 接收地址长度

    while (1)
    {
        // 清空容器
        bzero(buf, sizeof(buf));

        // 从套接字中读取数据
        recvfrom(sfd, buf, sizeof(buf), 0,(struct sockaddr*)&cin,&addrlen);
        printf("收到信息:%s\n", buf);

        // 处理收到的信息
        strcat(buf, ":(");

        if (sendto(sfd, buf, sizeof(buf), 0,(struct sockaddr*)&cin,sizeof(cin)) == -1)
        {
            perror("send error");
            return -1;
        }
        printf("发送成功\n");
    }

    // 4、关闭文件描述符
    close(sfd);
    return 0;
}

4、UDP客服端端代码实现

#include <myhead.h>
#define SER_PORT 9999            // 与服务器保持一致
#define SER_IP "192.168.232.129" // 服务器ip地址
#define CLI_PORT 5555            // 客服端端口号
#define CLI_IP "192.168.232.129" // 客服端ip地址
int main(int argc, char const *argv[])
{
    // 1、 创建用于通信的套接字文件描述符
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (cfd == -1)
    {
        perror("socket error");
        return -1;
    }
    printf("cfd = %d\n", cfd);
    // 2、 绑定IP地址和端口号
    struct sockaddr_in cin;
    cin.sin_family = AF_INET;                // 通信域
    cin.sin_port = htons(CLI_PORT);          // 端口号
    cin.sin_addr.s_addr = inet_addr(CLI_IP); // ip地址

    // 2.2、 绑定
    if (bind(cfd, (struct sockaddr *)&cin, sizeof(cin)) == -1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");

    // 3、数据收发
    char buf[128] = "";

    // 3.1 填充服务器地址信息结构体
    struct sockaddr_in sin;                  // 接收对端地址信息
    sin.sin_family = AF_INET;                // 服务器的通信域
    sin.sin_port = htons(SER_PORT);          // 服务器的端口号
    sin.sin_addr.s_addr = inet_addr(SER_IP); // 服务器的ip地址

    while (1)
    {
        printf("输入:");
        fgets(buf, sizeof(buf), stdin);
        buf[strlen(buf) - 1] = 0;

        sendto(cfd, buf, strlen(buf), 0, (struct sockaddr *)&sin, sizeof(sin));

        printf("发送成功\n");

        bzero(buf, sizeof(buf));
        recvfrom(cfd, buf, sizeof(buf), 0, NULL, NULL);
        printf("收到服务器信息:%s\n", buf);
    }

    // 4、关闭套接字
    close(cfd);
    return 0;
}

五、TCP和UDP基础通信模型注意事项

1、无论时TCP还是UDP通信中,服务器必须绑定ip地址和端口号,以便于让客服端找到该服务器。对于客服端而言,ip地址和端口号可以不绑定,若不绑定,端口号由系统动态分配(49152-65535)

2、对于TCP通信而言,可以使用recv和send进行通信,也可以使用read、write进行通信,还可以使用sendto和recvfrom进行通信

3、对于UDP通信而言,如果当前端只是用于接收数据,不发送数据,可以使用recvfrom、recv、read进行接收;如果当前端接收数据后还要发送数据给对端,则需要使用recvfrom进行接收数据,以便接收对端地址信息结构体

4、UDP通信中,服务器端可以使用connect函数与指定的客服端建立一个唯一的通道,在解除这种连接前,其他客服端与服务器端间不能通信。可通过将与服务器端建立连接的那个客服端的地址消息结构体中的sin.family设置未 AF_UNSPEC, 后再次使用connect函数断开连接

UDP中通信使用connect连接的好处:

1)提高信息传输效率、完整度

例如:A和B同时向服务器发送消息,但是A发送的消息较大,需要较长的时间,发送过程中可能会出现时间片用完,服务器转而接收B的消息的情况,这会导致消息混乱。这时就可以先单独跟A建立连接,等所有数据传输结束后,再跟B通信

2)传输性能高

一般的UDP通信:获取对端地址信息 -->将信息加载到内核 -->数据收发--->获取对端地址信息 -->将信息加载到内核 --->数据收发 --->获取对端地址信息 -->将信息加载到内核 -->数据收发 -->......

会经历多次用户空间到内核空间的转换,该过程对于cpu而言是一个漫长的过程

UDP建立连接后:获取对端地址信息 ->将信息加载到内核 ->数据收发 ->数据收发 >数据收发 >数据收发完成>进行其他对端的信息处理.....

会有效的减少用户空间到内核空间的转换次数

相关推荐
数据的世界0111 分钟前
.NET开发人员学习书籍推荐
学习·.net
CircleMouse17 分钟前
Centos7, 使用yum工具,出现 Could not resolve host: mirrorlist.centos.org
linux·运维·服务器·centos
是Dream呀23 分钟前
Python从0到100(七十八):神经网络--从0开始搭建全连接网络和CNN网络
网络·python·神经网络
四口鲸鱼爱吃盐25 分钟前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
木子Linux1 小时前
【Linux打怪升级记 | 问题01】安装Linux系统忘记设置时区怎么办?3个方法教你回到东八区
linux·运维·服务器·centos·云计算
kaixin_learn_qt_ing1 小时前
了解RPC
网络·网络协议·rpc
不惑_1 小时前
小白入门 · 腾讯云轻量服务器部署 Hadoop 3.3.6
服务器·hadoop·腾讯云
阿甘知识库2 小时前
宝塔面板跨服务器数据同步教程:双机备份零停机
android·运维·服务器·备份·同步·宝塔面板·建站
安全小王子2 小时前
Kali操作系统简单介绍
网络·web安全
OopspoO3 小时前
qcow2镜像大小压缩
学习·性能优化