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

目录

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;
            }
        }
    };
}

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值