【Linux 网络编程1】使用UDP/TCP编写套接字,多进程/多线程版本的TCP编写的套接字,将套接字封装

news2025/7/31 14:23:32

目录

1.学习网络编程前的一些基础知识

2.UDP(user datagram protocol)协议的特点

3.使用有UPD编写套接字

4.使用TCP编写套接字

4.2.TCP客服端 

4.3.TCP服务器端

4.4.单进程版本(没有人会使用)

4.5.多进程版本

4.6.多线程版本

5.把套接字封装


1.学习网络编程前的一些基础知识

1.1.IP地址

  • IP地址是在IP协议中, 用来标识网络中不同主机的地址(一个IP地址标识一台主机);
  • 通常使用 "点分十进制" 的字符串表示IP地址, 例如127.0.0.1 ; 用点分割的每一个数字表示一个字节, 范围是 0 - 255,就是一个字节的大小,IP地址刚好是4字节, 32位的整数;

1.2.端口号(port) 

  • 端口号是一个2字节16位的整数
  • 端口号用来标识一个进程,那么IP地址+端口号就可以标识某一台主机的某一个进程

1.3.TCP/IP四层模型 

 

1.4.网络字节序 

  • 网络字节序就是大端字节序(低位放在高地址上),使用相同的字节序便于网络间通信;

有系统提供的接口 

2.UDP(user datagram protocol)协议的特点

UDP协议是一个传输层协议

  • 无连接:没有连接,客户端发给服务器端,服务器端要先保存客户端的信息,服务器端再使用这个信息发给对应的客户端(简单地说就是需要指明发送给谁
  • 不可靠传输:只是传递数据,成功与否都不会反馈
  • 面向数据报:不能向面向字节流的TCP一样使用read和write来读写

3.使用有UPD编写套接字

3.1.服务器端

3.1.1.创建套接字

    int sock=socket(AF_INET,SOCK_DGRAM,0);//创建套接字
    if(sock<0)
    {
        std::cerr<<"socket fail: "<<errno<<std::endl;
        return 1;
    } 

3.1.2.bind服务器 

网络字节序就是大端字节序(低位放在高地址上),使用相同的字节序便于网络间通信;

  1. 1.需要将人识别的点分十进制,字符串风格IP地址,转化为4字节整数IP.2.考虑大小端的问题

服务器不用bind一个固定的IP的原因

  1. 1.云服务器,不允许bind公网IP,另外,在一般编写也不会指明IP.2.一般主机有多个IP,如果只是bind一个IP,发给其他IP的数据就不会交给主机处理
  2. INADDR_ANY只要是发给这个主机的数据都会被处理

    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(port);
    //1.需要将人识别的点分十进制,字符串风格IP地址,转化为4字节整数IP.2.考虑大小端的问题
    //1.云服务器,不允许bind公网IP,另外,在一般编写也不会指明IP.2.一般主机有多个IP,如果只是bind一个IP,发给其他IP的数据就不会交给主机处理;
    //INADDR_ANY只要是发给这个主机的数据都会被处理
    local.sin_addr.s_addr=INADDR_ANY;
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)//bind主机
    {
        std::cerr << "bind error : " << errno << std::endl;
        return 2;
    }

3.1.3.传递接受数据

  • UDP是无连接的,需指明发给谁,也需要保存发送端的信息

    //业务逻辑
    char message[1024];
    bool quit=false;
    while(!quit){
        //保存发送端信息
        struct sockaddr_in peer; 
        socklen_t len=sizeof(peer);
        //接受数据
        ssize_t s=recvfrom(sock,message,sizeof(message)-1,0,(struct sockaddr*)&peer,&len);
        if(s>0){
            message[s]=0;
            std::cout<<"client# "<<message<<std::endl;
        }else{
            std::cerr<<"recvfrom"<<errno<<std::endl;
            return 2;
        }
        //给对端发送一个你好吗
        std::string tm="你好吗?";
        std::cout<<"server to client: "<<tm<<std::endl;
        sendto(sock,tm.c_str(),tm.size(),0,(struct sockaddr*)&peer,len);
    }

 3.2.客户端

  • 客户端不需要显示bind,当传输第一个数据是会自动随机bind一个port(没有被使用的port)
#include<iostream>
#include<cerrno>
#include<string>
#include<cstdlib>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

//client serverIP serverPort
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        std::cout<<"请按格式输入: client serverIP serverPort"<<std::endl;
        return 2;
    }
    int sock=socket(AF_INET,SOCK_DGRAM,0);//创建套接字
    if(sock<0)
    {
        std::cout<<"socket create errno: "<<errno<<std::endl;
        return 1;
    }
    //客户端不用显示bind,OS会自动bind;
    //服务器端会有规划,让port是没有被占用的,让别人来访问这个port;
    //client正常发送数据的时候,OS会自动给你bind,采用随机端口的方式

        struct sockaddr_in server;
        server.sin_family=AF_INET;
        server.sin_port=htons(atoi(argv[2]));
        server.sin_addr.s_addr=inet_addr(argv[1]);
    while(1)
    {
        std::string message;
        std::cout<<"请输入#";
        std::cin>>message;

        sendto(sock,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));

        struct sockaddr_in tmp;
        socklen_t tlen=sizeof(tmp);
        char buffer[1024];
        ssize_t s=recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&tmp,&tlen);
        if(s>0){
            std::cout<<"server say#: "<<buffer<<std::endl;
            buffer[s]=0;   
        }else{
            std::cerr<<"recvfrom"<<errno<<std::endl;
            return 2;
        }
    }
    return 0;
}

4.使用TCP编写套接字

4.1.TCP的特点

TCP是一个传输层协议,和UDP的特点相反

  • 有连接
  • 可靠传输
  • 面向字节流

4.2.TCP客服端 

先写的客户端,因为服务器端会写多个版本

#include<iostream>
#include<stdio.h>
#include<cerrno>
#include<unistd.h>
#include<string>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>

void Usage()
{
    std::cout<<"usage:./client server_IP server_port"<<std::endl;
}
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage();
        return 1;
    }
    //建立套接字
    int sock=socket(AF_INET,SOCK_STREAM,0);
    if(sock<0){
        std::cerr<<"socket"<<errno<<std::endl;
        return 2;
    }
    //自动bind
    //连接服务器
    struct sockaddr_in local;
    local.sin_addr.s_addr=inet_addr(argv[1]);
    local.sin_port=htons(atoi(argv[2]));
    local.sin_family=AF_INET;
    connect(sock,(struct sockaddr*)&local,sizeof(local));


    //业务逻辑
    while(1)
    {
        char buffer[1024]={0};
        std::cout<<"请输入";
        fgets(buffer,sizeof(buffer),stdin);
        write(sock,buffer,sizeof(buffer));

        char mes[1024]={0};
        read(sock,mes,sizeof(mes));
        std::cout<<mes<<std::endl;
    }
    return 0;
}

4.3.TCP服务器端

4.3.1.创建套接字和bind服务器

  • socket的第二个参数是SOCK_STREAM,UPD是SOCK_DGRAM(datagram),TCP是SOCK_STREAM(stream,就是字节流)
//建立套接字
    int listen_sock=socket(AF_INET,SOCK_STREAM,0);
    if(listen_sock<0)
    {
        std::cerr<<"socket"<<errno<<std::endl;
        return 1;
    }
    //bind服务器
    struct sockaddr_in local;
    local.sin_family=AF_INET;
    local.sin_port=htons(PORT);
    local.sin_addr.s_addr=INADDR_ANY;//INADDR_ANY会bind执行代码的主机

    if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        std::cerr<<"bind"<<errno<<std::endl;
        return 2;
    }

4.3.2.listen设为聆听状态

    //listen状态
    listen(listen_sock,5);

 4.3.3.accept接受客户端的连接,并返回一个文件描述符

  • 因为TCP套接字是有连接的,连接成功后也是使用返回的套接字和对端进行网络通信

struct sockaddr_in tmp;
socklen_t tlen=sizeof(tmp);
//建立连接
int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);

4.4.单进程版本(没有人会使用)

  • 一次只能让一个客户端访问
  • 写端关闭读端读到文件结尾,再读返回0
int main()
{
    //...创建套接字、bind、listen都省略了,每次都一样冗余
    while(1)
    {
        struct sockaddr_in tmp;
        socklen_t tlen=sizeof(tmp);
        //建立连接
        int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);
        if(fd<0)
        {
            std::cerr<<"accept "<<errno<<std::endl;
            return 3;
        }
        std::cout<<"get a new link "<<std::endl;
        
        //1.单进程version
        while(1)
        {
            char buffer[1024]={0};
            ssize_t s=read(fd,buffer,sizeof(buffer));
            if(s>0){
                buffer[s]=0;
                std::cout<<"client to server:"<<buffer<<std::endl;
                
                std::string message;
                message+="server to client:你好!";
                //给连接的客户端发一个你好
                write(fd,message.c_str(),message.length());
            }
            else if(s==0){//写端关闭读端读到文件结尾,再读返回0
                std::cout<<"client quit!"<<std::endl;
                break;
            }
            else{
                std::cerr<<"read "<<errno<<std::endl;
                break;
            }
        }
    }
}

4.5.多进程版本

4.5.1.父进程是一个循环,他要一直接收新的客服端不能等待子进程,解决方法

  1. 父进程等待子进程,子进程创建后再创建孙子进程执行后序代码,子进程秒退等待时间可以忽略不计;下面代码

pid_t pid = fork();
        if(pid<0){
            continue;
        }
        else if(pid==0){//子进程
            close(listen_sock);
            if(fork()>0)
                exit(0);//子进程创建就直接退出,创建的孙子进程执行后序代码,孙子进程变成孤儿进程被1号进程领养
            serviceIO(fd);
            close(fd);
            exit(0);
        }
        else{//父进程
            close(fd);//子进程的PCB以父进程的PCB做模板初始化,不是共享的
            //waitpid(&pid,NULL,0);//父进程等待子进程,子进程创建后再创建孙子进程执行后序代码,子进程秒退等待时间可以忽略不计
        }

        2.signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信号,子进程将自动释放; 

2.关闭不用的文件描述符,父进程关闭accept返回的文件描述符,子进程关闭socket返回的文件描述符

void serviceIO(const int& fd)
{
    //1.单进程version,做成一个函数
    while(1)
    {
        char buffer[1024]={0};
        ssize_t s=read(fd,buffer,sizeof(buffer));
        if(s>0){
            buffer[s]=0;
            std::cout<<"client to server:"<<buffer<<std::endl;
            std::string message;
            message+="server to client:你好!";
            //给连接的客户端发一个你好
            write(fd,message.c_str(),message.length());
        }
        else if(s==0){//写端关闭读端读到文件结尾,再读返回0
            std::cout<<"client quit!"<<std::endl;
            break;
        }
        else{
            std::cerr<<"read "<<errno<<std::endl;
            break;
        }
    }
}
       
int main()
{
    //...创建套接字、bind、listen都省略了,每次都一样冗余
    signal(SIGCHLD,SIG_IGN);//忽略SIGCHLD信号,子进程将自动释放
    while(1)
    {
        struct sockaddr_in tmp;
        socklen_t tlen=sizeof(tmp);
        //建立连接
        int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);
        std::cout<<"get a new link "<<std::endl;
        if(fd<0)
        {
            std::cerr<<"accept "<<errno<<std::endl;
            return 3;
        }
        //2.多进程version
        pid_t pid = fork();
        if(pid<0){
            continue;
        }
        else if(pid==0){//子进程
            close(listen_sock);
            serviceIO(fd);
            close(fd);
            exit(0);
        }
        else{//父进程
            close(fd);//子进程的PCB以父进程的PCB做模板初始化,不是共享的
        }
         
    }
}

4.6.多线程版本

  • 线程是共享PCB的,所以在线程内使用完毕关闭文件描述符即可;
  • 不等待线程可以使用线程分离的方法
void serviceIO(const int& fd)
{
    //1.单进程version
    while(1)
    {
        char buffer[1024]={0};
        ssize_t s=read(fd,buffer,sizeof(buffer));
        if(s>0){
            buffer[s]=0;
            std::cout<<"client to server:"<<buffer<<std::endl;
            std::string message;
            message+="server to client:你好!";
            //给连接的客户端发一个你好
            write(fd,message.c_str(),message.length());
        }
        else if(s==0){//写端关闭读端读到文件结尾,再读返回0
            std::cout<<"client quit!"<<std::endl;
            break;
        }
        else{
            std::cerr<<"read "<<errno<<std::endl;
            break;
        }
    }
}
void* HandlerRequest(void* agrs)
{
    pthread_detach(pthread_self());
    int fd=*((int*)agrs);
    serviceIO(fd);
    close(fd);//记得关闭文件描述符
}
int main()
{
   
    while(1)
    {
        struct sockaddr_in tmp;
        socklen_t tlen=sizeof(tmp);
        //建立连接
        int fd=accept(listen_sock,(struct sockaddr*)&tmp,&tlen);
        std::cout<<"get a new link "<<std::endl;
        if(fd<0)
        {
            std::cerr<<"accept "<<errno<<std::endl;
            return 3;
        }

        pthread_t tid;
        pthread_create(&tid,nullptr,HandlerRequest,(void*)&fd);
    }
    return 0;
}

5.把套接字封装

  • 使用静态是因为不用创建对象就可以使用sock::Socket()等函数;
#pragma once
#include<iostream>
#include<cstdlib>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>

namespace ns_socket{
    class sock{
    public:
        static int Socket()
        {
            int sock=socket(AF_INET,SOCK_STREAM,0);
            if(sock<0)
            {
                std::cerr<<"socket"<<std::endl;
                exit(1);
            }
            return sock;
        }
        static void Bind(int sock,char* port)
        {
            struct sockaddr_in local;
            local.sin_family=AF_INET;
            local.sin_addr.s_addr=INADDR_ANY;
            local.sin_port=htons(atoi(port));
            if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
            {
                std::cerr<<"bind"<<std::endl;
                exit(2);
            }
        }
        static void Listen(int sock)
        {
            if(listen(sock,5)<0)
            {
                std::cerr<<"listen"<<std::endl;
                exit(3);
            }
        }
        static int Accept(int sock) 
        {
            struct sockaddr_in tmp;
            socklen_t tlen=sizeof(tmp);
            int new_sock=accept(sock,(struct sockaddr*)&tmp,&tlen);
            if(new_sock<0)
            {
                std::cerr<<"accept"<<std::endl;
                exit(4);
            }
            return new_sock;  
        }
        static void Connect(int sock,char* server_ip,char* server_port)
        {
            struct sockaddr_in local;
            local.sin_family=AF_INET;
            local.sin_addr.s_addr=inet_addr(server_ip);
            local.sin_port=htons(atoi(server_port));
            if(connect(sock,(struct sockaddr*)&local,sizeof(local))<0)
            {
                std::cerr<<"connect"<<std::endl;
                exit(5);
            }
            else
            {
                std::cout<<"connet success"<<std::endl;
            }
        }
    };
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/394488.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

扬帆优配|2600亿新能源巨头狂飙!外资唱多中国:再涨15%

全国停摆的危机&#xff0c;正在迫临法国。 大停工正在将法国推向风险境地。法国政府估计&#xff0c;当地时间3月7日&#xff0c;将迸发全国大型停工游行。法国总工会宣告&#xff0c;到时将让全法国停摆。法国担任交通业务的部长级代表克莱蒙博讷正告称&#xff0c;7日将成为…

JavaScript 混淆技术

根据JShaman&#xff08;JShaman是专业的JavaScript代码混淆加密网站&#xff09;提供的消息&#xff0c;JavaScript混淆技术大体有以下几种&#xff1a; 变量混淆 将带有JS代码的变量名、方法名、常量名随机变为无意义的类乱码字符串&#xff0c;降低代码可读性&#xff0c;如…

原神 Android 教程 —安卓版

准备材料 一台能读写 /system 分区的 Android 手机(或:一台安装了 Magisk 的 Android 手机) 有人搞出来免root端了,此条件不再必须私服客户端

数据同步工具Sqoop

大数据Hadoop之——数据同步工具SqoopSqoop基本原理及常用方法 1 概述 Apache Sqoop&#xff08;SQL-to-Hadoop&#xff09;项目旨在协助RDBMS&#xff08;Relational Database Management System&#xff1a;关系型数据库管理系统&#xff09;与Hadoop之间进行高效的大数据交…

HStream Console、HStreamDB 0.14 发布

近两个月&#xff0c;HStreamDB 相继发布了 0.13 和 0.14 版本&#xff0c;包含多项已知问题修复。同时&#xff0c;我们也发布了全新的 HStream Console 组件&#xff0c;为 HStreamDB 带来了简洁友好的图形化管理界面&#xff0c;将帮助用户更轻松地使用和管理 HStreamDB. H…

LinqConnect兼容性并支持Visual Studio 2022版本

LinqConnect兼容性并支持Visual Studio 2022版本 现在支持Microsoft Visual Studio 2022版本17.5预览版。 添加了Microsoft.NET 7兼容性。 共享代码-共享相同的代码&#xff0c;以便在不同的平台上处理数据。LinqConnect是一种数据库连接解决方案&#xff0c;适用于不同的基于.…

Ubuntu下安装Docker

大家好&#xff0c;我是中国码农摘星人。 欢迎分享/收藏/赞/在看&#xff01; 文章目录1 安装Docker1.1 使用官方安装脚本自动安装 &#xff08;仅适用于公网环境&#xff09;1.2 手动安装帮助1.2.1 Ubuntu 14.04 16.04 (使用apt-get进行安装)2 安装校验3 镜像加速器3.1 安装&a…

在 k8S 中搭建 SonarQube 7.4.9 版本(使用 PostgreSQL 数据库)

本文搭建的 SonarQube 版本是 7.4.9-community&#xff0c;由于在官方文档中声明 7.9 版本之后就不再支持使用 MySQL 数据库。所以此次搭建使用的数据库是 PostgreSQL 11.4 版本。 一、部署 PostgreSQL 服务 1. 创建命名空间 将 PostgreSQL 和 SonarQube 放在同一个命名空间…

Docker(二)

5.容器数据卷 1.什么是容器数据卷 docker理念回顾 将应用和环境打包成一个镜像&#xff01; 数据&#xff1f;如果数据都在容器中&#xff0c;那么我们容器删除&#xff0c;数据就会丢失&#xff01;需求&#xff1a;数据可以持久化 MySQL&#xff0c;容器删了&#xff0c…

都工作3年了,怎么能不懂双亲委派呢?(带你手把手断点源码)

&#x1f497;推荐阅读文章&#x1f497; &#x1f338;JavaSE系列&#x1f338;&#x1f449;1️⃣《JavaSE系列教程》&#x1f33a;MySQL系列&#x1f33a;&#x1f449;2️⃣《MySQL系列教程》&#x1f340;JavaWeb系列&#x1f340;&#x1f449;3️⃣《JavaWeb系列教程》…

c盘怎么清理到最干净?有什么好的清理方法

c盘怎么清理到最干净?有什么好的清理方法&#xff1f;清理C盘空间是电脑维护的重要步骤之一。C盘是Windows操作系统的核心部分&#xff0c;保存了许多重要的系统文件&#xff0c;因此空间不足会影响计算机的性能和稳定性。下面是一些清理C盘空间的方法 一.清理临时文件 在使用…

【Java学习笔记】10.条件语句 - if...else及switch case 语句

前言 本章介绍Java的条件语句 - if…else和switch case 语句。 Java 条件语句 - if…else Java 中的条件语句允许程序根据条件的不同执行不同的代码块。 一个 if 语句包含一个布尔表达式和一条或多条语句。 语法 if 语句的语法如下&#xff1a; if(布尔表达式) {//如果布…

实验二:动态规划

1.双11的红包雨 问题描述 双11到了&#xff0c;据说这2天会下红包雨&#xff0c;每个红包有不同的价值&#xff0c;小k好开心&#xff0c;但有个规则&#xff0c;就只能接掉落在他身旁的10米范围内的红包&#xff08;0-10这11个位置&#xff09;。小k想尽可能的多抢红包&…

评价提高分子对接性能的组合策略

评价提高分子对接性能的组合策略 相关背景&#xff1a; 分子对接可能是应用于基于结构的药物设计和虚拟筛选活动中最快速、成本最低的计算技术&#xff0c;它可以从巨大的化合物文库中找到潜在的活性分子&#xff0c;用于先导发现。 在分子对接中&#xff0c;配体(通常是小分…

LIME: Low-light Image Enhancement viaIllumination Map Estimation

Abstract当人们在低光条件下拍摄图像时&#xff0c;图像通常会受到低能见度的影响。除了降低图像的视觉美感外&#xff0c;这种不良的质量还可能显著降低许多主要为高质量输入而设计的计算机视觉和多媒体算法的性能。在本文中&#xff0c;我们提出了一种简单而有效的微光图像增…

2023年最新的站内SEO指南:如何通过关键词优化提高网站排名

SEO或搜索引擎优化是指通过改善网站的内部和外部元素&#xff0c;以获得更好的自然搜索引擎排名和更多的网站流量。 链接建设和外链是SEO的重要组成部分&#xff0c;因为它们可以提高网站的权威性和可信度&#xff0c;从而使其在搜索引擎中排名更高。 在此指南中&#xff0c;…

MySQL三范式

1、查询语句写的烂2、索引失效&#xff08;数据变更&#xff09;3、关联查询太多join&#xff08;设计缺陷或不得已的需求&#xff09;4、服务器调优及各个参数设置&#xff08;缓冲、线程数等&#xff09; 通常SQL调优过程&#xff1a; 观察&#xff0c;至少跑1天&#xff0…

CF756div3 vp

又被薄纱了&#xff0c;rk就不放了&#xff0c;好丢人QwQDashboard - Codeforces Round 756 (Div. 3) - CodeforcesA. Make Even小分类讨论题意&#xff1a;给定一个数&#xff0c;每次操作可以选取其前缀然后翻转其前缀&#xff0c;问你最少操作几次可以把该数变为偶数思路&am…

基于深度学习的轴承寿命预测实践,开发CNN、融合LSTM/GRU/ATTENTION

关于轴承相关的项目之前做的大都是故障识别诊断类型的&#xff0c;少有涉及回归预测的&#xff0c;周末的时候宅家发现一个轴承寿命加速实验的数据集就想着拿来做一下寿命预测。首先看下数据集如下&#xff1a;直接百度即可搜到&#xff0c;这里就不再赘述了。Learning_set为训…

什么是蜕变测试?

文章目录1.传统测试2.蜕变测试2.1.蜕变测试的理解2.2.蜕变测试的步骤2.2.1.生成蜕变关系2.2.2.生成蜕变用例2.2.3.执行蜕变用例2.2.4.校验蜕变关系参考文献1.传统测试 在没有蜕变测试的时代&#xff0c;传统软件测试的原理是&#xff1a;给定输入&#xff0c;观察被测软件的输…