首页 > 编程语言 >网络套接字编程(二)

网络套接字编程(二)

时间:2024-09-13 12:20:47浏览次数:12  
标签:int 编程 SOCK 网络 TCP server sockfd 接字

socket常见API

创建套接字:(TCP/UDP,客户端+服务器)

int socket(int domain, int type, int protocol);

绑定端口号:(TCP/UDP,服务器)

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

监听套接字:(TCP,服务器)

int listen(int sockfd, int backlog);

接收请求:(TCP,服务器)

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

建立连接:(TCP,客户端)

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

基于TCP协议实现网络通信

与学习UDP的接口相同,我们通过实现一个简单的TCP服务器与TCP客户端学习这些接口。首先我们编写服务器。

TCP服务器

我们同样使用一个类来实现服务器,TCP下的网络通信同样需要创建套接字,使用TCP创建套接字的与UDP下大致相同

//UDP
int socket_fd=socket(AF_INET,SOCK_DGRAM,0);

//TCP
int socket_fd=socker(AF_TNET,SOCK_STREAM,0);

与UDP协议不同,TCP创建套接字的第二个参数应该选择 SOCK_STREAM。

  • SOCK_DGRAM:套接字提供的数据传输方式为数据报。
  • SOCK_STREAM:套接字提供的数据传输方式为字节流。
  • 连接性:SOCK_DGRAM无连接,SOCK_STREAM面向连接。
  • 数据完整性:SOCK_STREAM保证数据的顺序和完整性,而SOCK_DGRAM不保证。
  • 速度:SOCK_DGRAM可能更快,因为它减少了协议开销,但牺牲了可靠性。
  • 用途:SOCK_DGRAM适用于小数据量和可以容忍一定数据丢失的场景,SOCK_STREAM适用于需要可靠传输大量数据的场景。

UDP是无连接的,TCP是有连接的。所以UDP采用的是数据报传输方式,TCP采用的是字节流传输方式。

创建套接字


class Server
{
public:
    Server(std::string ip,uint16_t port)
        :_sockfd(-1);
        :_ip(ip);
        :_port(port);
    {}

    bool init()
    {
        //创建套接字
        _sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(_sockfd<0)
        {
            std::cerr << "socket flase" << std::endl;
            return false;
        }
        
    }

    ~Server()
    {
        if(_sockfd>=0)
        {
            close(_sockfd);
        }
    }

private:
    int _sockfd;
    std::string _ip;
    uint16_t _port;
};

绑定ip与端口

与我们编写的UDP服务端相同我们需要绑定IP与端口。流程与UDP协议相同。

    bool init()
    {
        //创建套接字
        _sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(_sockfd<0)
        {
            std::cerr << "socket flase" << std::endl;
            return false;
        }
        //绑定端口
        struct sockaddr_in local;
        memset(&local,'\0',sizeof(local));
        //协议家族
        local.sin_family=AF_INET;
        //端口号
        local.sin_port=htons(_port);
        //ip
        local.sin_addr.s_addr = inet_addr(_ip.c_str());
        //绑定
        if(bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr_in)) < 0 )
        {
            //绑定失败
            std::cerr << "bind error" << std::endl;     
            return false;
        }
    }

设置为监听 

TCP是面向连接的,客户端与服务端需要建立连接后才能通信。所以TCP服务器需要时刻注意有无客户端的连接请求。我们需要将套接字设置为监听状态。

监听套接字:(TCP,服务器)

int listen(int sockfd, int backlog);

参数说明: 

  • sockfd 参数是要监听的套接字的文件描述符。
  • backlog 参数是系统应该允许的,处于未完成连接队列中的连接请求的最大数量 

如果有多个客户端同时发来连接请求,此时未被服务器处理的连接就会放入连接队列,backlog代表的就是这个全连接队列的最大长度,一般不要设置太大,设置为5或10即可。 

 返回值:

  • 监听成功返回0,监听失败返回-1,同时错误码会被设置。

在初始化中我们将套接字设置为监听套接字。

    bool init()
    {
        //创建套接字
        _sockfd = socket(AF_INET,SOCK_STREAM,0);
        if(_sockfd<0)
        {
            std::cerr << "socket flase" << std::endl;
            return false;
        }
        std::cerr << "socket succes" << std::endl;
        //绑定端口
        struct sockaddr_in local;
        memset(&local,'\0',sizeof(local));
        //协议家族
        local.sin_family=AF_INET;
        //端口号
        local.sin_port=htons(_port);
        //ip
        local.sin_addr.s_addr = inet_addr(_ip.c_str());

        //绑定
        if(bind(_sockfd,(struct sockaddr*)&local,sizeof(struct sockaddr_in)) < 0 )
        {
            //绑定失败
            std::cerr << "bind error" << std::endl;     
            return false;
        }
        std::cerr << "bind succes" << std::endl;     

        if(listen(_sockfd,5) < 0 )
        {
            //监听失败
            std::cerr << "listen error" << std::endl;     
            return false;
        }
        std::cerr << "listen succes" << std::endl;     
    }

获取连接

在设置完监听状态后,我们需要接受客户端的连接请求。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

参数说明:

  • sockfd 参数是监听套接字的文件描述符。
  • addr 参数是一个指向 sockaddr 结构的指针,该结构用于接收连接客户端的地址信息。如果调用者不需要地址信息,可以将此参数设置为 NULL。
  • addrlen 参数是一个指向 socklen_t 类型的变量的指针,它在调用时指定 addr 缓冲区的长度,并在调用成功后更新为实际接收的地址结构长度。  

返回值:

  • 成功时,函数返回一个新的套接字文件描述符,用于与客户端进行通信。这个新的套接字会继承监听套接字的一些属性,如类型和协议。
  • 出错时,函数返回 -1 并设置全局变量以指示错误类型。
    void start()
    {
        while(1)
        {
            struct sockaddr_in sendfrom;
            memset(&sendfrom,'\0',sizeof(sendfrom));
            int client;
            socklen_t len=sizeof(struct sockaddr_in);
            client=accept(_sockfd,(struct sockaddr*)&sendfrom,&len);
            if(client<0)
            {
                std::cerr << "accept error" << std::endl;     
                continue;;
            }
            std::string client_ip = inet_ntoa(sendfrom.sin_addr);
			int client_port = ntohs(sendfrom.sin_port);
			std::cout<<"new link->"<<" ["<<client_ip<<"]:"<< client_port <<std::endl;
            server(client);
        }
    }

 服务端处理请求

accept返回的是一个描述符套接字·,我们在上一节讲到套接字描述符实际上就是文件描述符,同时文件的读写方式是字节流,而TCP协议的传输也是字节流,所以我们可以使用文件的读写函数接受和发送信息。

使用 read 函数从TCP套接字读取数据:

ssize_t read(int fd, void *buf, size_t count);

参数说明:

  • fd:特定的文件描述符,表示从该文件描述符中读取数据。
  • buf:数据的存储位置,表示将读取到的数据存储到该位置。
  • count:数据的个数,表示从该文件描述符中读取数据的字节数。

返回值说明:

  • 如果返回值大于0,则表示本次实际读取到的字节个数。
  • 如果返回值等于0,则表示对端已经把连接关闭了。
  • 如果返回值小于0,则表示读取时遇到了错误。

使用 write 函数向TCP套接字写入数据:

ssize_t write(int fd, const void *buf, size_t count);

参数说明:

  • fd:特定的文件描述符,表示将数据写入该文件描述符对应的套接字。
  • buf:需要写入的数据。
  • count:需要写入数据的字节个数。

返回值说明:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。
    bool server(int sock)
    {
        char buffer[1024];
        while (true)
        {
			ssize_t size = read(sock, buffer, sizeof(buffer)-1);
			if (size > 0)
            { //读取成功
				buffer[size] = '\0';
				std::cout << sock << " [client]:" << buffer << std::endl;

				write(sock, buffer, size);
			}
			else if (size == 0)
            { //对端关闭连接
				std::cout << " close!" << std::endl;
				break;
			}
			else
            { //读取失败
				std::cerr << sock << " read error!" << std::endl;
				break;
			}
		}
		close(sock); //归还文件描述符
	}

 我们在start中调用,在mian函数中实例化。

#include"tcp_server.hpp"

int main(int argc,char** argv)
{
    if(argc<2)
    {
        std::cerr << "port" << std::endl;
        return -1;
    }

    //端口号转化

    uint16_t port=atoi(argv[1]);
    std::string ip="127.0.0.1";
    Server server(ip,port);

    server.init();
    server.start();

    return 0;
}

客户端 

客户端也与UDP客户端相似,客户端不需要绑定与监听,首先我们创建套接字。

创建套接字 

 

class Client
{
public:
    Client(std::string server_ip , uint16_t server_port)
        :_sockfd(-1)
        ,_server_ip(server_ip)
        ,_server_port(server_port)
    {}

    bool init()
    {
        //创建套接字

        _sockfd=socket(AF_INET,SOCK_STREAM,0);
        if(_sockfd<0)
        {
            std::cerr << "socket error" <<std::endl;
            return false;
        }
        return true;
    }


    ~Client()
    {
        if(_sockfd>=0)
        {
            close(_sockfd);
        }
    }
private:
    uint16_t _server_port;
    std::string _server_ip;
    int _sockfd;
};

连接服务器 

发起连接请求的函数叫做connect,该函数的函数原型如下:

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数说明:

  • sockfd:特定的套接字,表示通过该套接字发起连接请求。
  • addr:对端网络相关的属性信息,包括协议家族、IP地址、端口号等。
  • addrlen:传入的addr结构体的长度。

返回值说明:

  • 连接或绑定成功返回0,连接失败返回-1,同时错误码会被设置。
    void start()
    {
        char buffer[1024];
        struct sockaddr_in server;
        memset(&server, '\0', sizeof(server));
        //协议家族
        server.sin_family = AF_INET;
        //端口号
        server.sin_port=htons(_server_port);
        //IP
        server.sin_addr.s_addr = inet_addr(_server_ip.c_str());
        socklen_t len=sizeof(server);
        //连接
        if(connect(_sockfd,(struct sockaddr*)&server,len) <0)
        {
            std::cout << "connect false" <<std::endl;
            return;
        }
        
        while (1)
        {
            std::cout << "Please enter# ";
            std::cin.getline(buffer, sizeof(buffer));
            std::string s=buffer;
            write(_sockfd,s.c_str(),s.size());   
        }
    }

我们在main中实例化并调用。

#include"tcp_client.hpp"

int main(int argc,char** argv)
{
    if(argc < 3)
    {
        std::cout << "name  ip  port" << std::endl;
        return -1;
    }
    uint16_t port=atoi(argv[2]);
    std::string ip=argv[1];
    Client client(ip,port);

    client.init();
    client.start();

    return 0;
}

我们简单测试一下。 

863ee401e5214f4e85c692f132ee017a.png

 可以看到他们成功完成了通讯。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

标签:int,编程,SOCK,网络,TCP,server,sockfd,接字
From: https://blog.csdn.net/2301_80926085/article/details/140986191

相关文章

  • 计算机网络
    怎么学计算机网络(考研):计算机网络的概念:网络的功能:网络的组成:网络的分类:脑图时刻1:标准化工作及相关组织:速率相关的性能指标:1.速率注意区分:速率与存储量的单位(一个是Kb,一个是KB)带宽:吞吐量:带宽与吞吐量的区别:带宽是理想的,不能达到的,吞吐量是实际的。1.1.4......
  • MetaMask手动添加网络(连接Ganache)
    下图中的ganache是我自己之前添加的网络,下面说明具体操作步骤:首先,登录进去之后点击页面下方的添加网络;然后,点击手动添加网络;最后,按照Ganache的地址配置网络,保存即可 网络名称:随便取,自己认识即可新的RPCURL:Ganache中RPCSERVER部分显示,如下图所示链ID:1337货币符号:ETH区......
  • OpenAI使用AI编程给出了数数问题的解决方案 —— 如何解决ChatGPT不会数数的问题
    总所周知的一个问题,那就是ChatGPT不会数数,不过今天突然发现OpenAI给出了一个神奇的解决方法,那就是AI编程。问题案例如下:Thetextprovidedwillbeanalyzedtocalculatethewordcount.text="""Therehasbeenrapidlygrowinginterestinmeta-learningasamet......
  • 【花雕学编程】Arduino动手做(230)---ESP32 CAM 长时延时摄影:在拍摄之间使设备休眠并记
    37款传感器与执行器的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里准备逐一动手尝试系列实验,不管成功(程序走通)与否,都会记录下来——小小的......
  • JDBC的编程
    1.数据库编程的必备条件 编程语言:C,C++.JAVA, python等 数据库:mysql,oracle,sqlserver等 数据库驱动包:不同的数据库,对应不同的编程语言提供了不同的数据库驱动包,如:MySQL提  供了Java的驱动包mysql-connector-java,需要基于Java操作MySQL即需要该驱动包。同样......
  • 网络运维故障处理
    本篇纯是之前的工作经验做一个分享,大家看个热闹就好。1.突然的断网,在上家上班的时候,有一天突然下午厂区内开始出现大面积网络卡顿,teams,outlook不好使等情况,且网盘也上不去,所以开始排查问题,由于是大规模且没有楼层规律,所以判断问题不在网线,交换机等,开始排查光纤,遂去机房,查看入......
  • 详细教您怎么用IP地址查询防范网络威胁
    网络安全表里如一有待考察。IP地址查询获得到的相关信息包含以下几个方面:最基础的地理位置—确定IP地址所属的地区、城市、甚至是街道,那么威胁溯源的追踪就容易很多了,如果发现可疑网络活动,IP地址查询快速确定来源地。这里不排除攻击来自高风险地区,要高度警惕。网络服务提供......
  • 【含文档】基于Springboot+Vue的大学生计算机基础网络教学系统管理(含源码数据库)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能这个系......
  • 小工具:windows测试自己的网络类型
    介绍NatTypeTester是一款NAT路由类型检测工具,测试NAT类型的小工具,方便与各位玩家判断自己网络是否适用于使用STUN内网穿透。NAT类型一般分成下列4种:网络类型1.FullConeNAT(彻底圆锥型)2.RestrictedConeNAT(详细地址限定圆锥型)3.PortRestrictedConeNAT......
  • 一文搞定高并发编程:CompletableFuture的supplyAsync与runAsync
    CompletableFuture是Java8中引入的一个类,用于简化异步编程和并发操作。它提供了一种方便的方式来处理异步任务的结果,以及将多个异步任务组合在一起执行。CompletableFuture支持链式操作,使得异步编程更加直观和灵活。在引入CompletableFuture之前,Java已经有了Future接口来......