socket编程

目录

前言

端口号

什么是端口号

端口号 vs PID

端口号的划分

知名端口号

网络字节序

为什么网络字节序(多数情况下)是大端?

为什么主机字节序(多数情况下)是小端?

struct sockaddr

udp socket

socket()

bind()

recvfrom()

sendto()

close()

实现一个远程执行指令的服务

服务端

客户端

tcp socket

socket()

bind()

listen()

accept()

connect()

recv()/read()

send()/write()

close()

实现一个多进程/多线程/线程池版(含任务队列)服务器

任务模块

线程池模块

服务端

客户端


前言

数据从一台主机到另一台主机不是目的,真正的目的是让对端主机提供数据处理的服务。

而数据是人通过特定的客户端产生的,发送给特定的服务器。

客户端本身是一个进程,而服务器也是一个进程,所以网络通信的本质就是进程间通信。

端口号

什么是端口号

IP仅仅是解决了两台物理机器之间互相通信,我们还需要考虑如何保证双方对应的进程能接收和发送相应的数据。

端口号:标识一台机器上唯一的一个进程。

这样,通过IP + PORT的方式,就能标识互联网中唯一的一个进程。

端口号 vs PID

1.端口号是一台主机上一个进程的唯一标识符,但由于网络服务通常是需要暴露给用户的,而用户访问该服务需要 IP+PORT ,所以对应服务进程的端口号通常需要被固定下来,目的是方便用户使用服务。

2.PID,是系统分配给一个进程的唯一标识符,PID就是各进程的身份标识符,程序一运行,系统就会自动分配给进程一个独一无二的PID。进程终止后,PID被系统回收,可能会被继续分配给新运行的进程。目的是让操作系统更方便的管理进程。

3.一个进程可以关联多个端口号,但一个端口号不能关联多个进程。

4.一个进程的PID永远不会超过一个,因为在OS中管理该进程的内部数据结构中只有一个PID字段。

端口号的划分

0 - 1023:知名端口号,HTTP、FTP、SSH、等这些广为使用的应用层协议,它们的端口号都是固定的。

1024 - 65535:操作系统动态分配的端口号,客户端程序的端口号,就是由操作系统从这个范围分配的。

知名端口号

ssh服务器:使用22端口

ftp服务器:使用21端口

telnet服务器:使用23端口

http服务器:使用80端口

https服务器:使用443端口

网络字节序

为什么网络字节序(多数情况下)是大端?

由于历史原因,早年设备的缓存很小,先接受高字节能快速判断报文信息:包长度(需要准备多大缓存)、地址范围(IP地址是从前到后匹配的)。在性能不是很好的设备上,高字节序会更快一些。

为什么主机字节序(多数情况下)是小端?

小端的加法器比较好做,如果做一个8位*4的加法器,只需要一个8位加法器,然后依次从低到高循环加上所有字节就可以了,进位的电路非常简单,而如果是大端,则需要一次加载32位,不然的话进位的设计比较困难。


不过对于现在的设备来说,大小端的区别已经很小了。

struct sockaddr

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6、Unix Domain Socket等。然而,各种网络协议的地址格式并不相同。

不同的网络协议,其地址类型通常是不同的,这样,只要取得某种sockaddr结构体中的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。

socket API可以都用struct sockaddr* 类型表示,在使用的时候定义成sockaddr_in,而在传参的时候需要强转成sockaddr,可以接受各种类型的sockaddr结构体指针作为参数。

这样的好处是程序的通用性,不同的协议,可以使用同一套标准接口。

udp socket

socket()

// 1.创建套接字,打开网络文件
int sock = socket(AF_INET, SOCK_DGRAM, 0);

bind()

// 2.给该服务器绑定端口和ip(特殊处理)
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port); //小端转大端
local.sin_addr.s_addr = INADDR_ANY;
bind(sock, (struct sockaddr *)&local, sizeof(local))

recvfrom()

// 3.接收来自客户端的数据
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);

sendto()

// 4.向客户端发送数据
sendto(sock, sendto_client.c_str(), sendtoto_client.size(), 0, (struct sockaddr *)&peer, len);

close()

// 5.关闭打开的套接字文件
close(sock);

实现一个远程执行指令的服务

服务端

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

const uint16_t port = 6666;

// udp_server

int main()
{
    // 1.创建套接字,打开网络文件
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket create errno: " << errno << std::endl;
        return 1;
    }

    // 2.给该服务器绑定端口和ip(特殊处理)
    struct sockaddr_in local;
    local.sin_family = AF_INET;
    local.sin_port = htons(port); //小端转大端
    // a.需要将认识别的点分十进制,字符串风格IP地址,转化成4字节整数IP
    // b.也要考虑大小端
    // in_addr inet_addr(const char* cp);能完成上面ab两个工作
    // 坑:云服务器,不允许用户直接bind公网IP,另外,正常编写的时候,我们也不会指明IP
    // local.sin_addr.s_addr = inet_addr("124.223.93.138");
    // INADDR_ANY:如果你bind的是确定的IP(主机),意味着只有发到该IP主机上面的数据
    // 才会交给你的网络进程,但是,一般服务器可能有多张网卡,配置多个IP,我们需要的不是
    // 某个IP上面的数据,我们需要的是,所有发到该主机,发送到该端口的数据!
    local.sin_addr.s_addr = INADDR_ANY;

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

    // 3.提供服务
    bool quit = false;
#define NUM 1024
    char buffer[NUM];
    while (!quit)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);

        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (cnt > 0)
        {
            buffer[cnt] = 0;//当作一个字符串命令
            FILE* fp = popen(buffer, "r");//popen是一个用来执行命令的函数

            std::string echo_to_client;
            char line[1024] = {0};
            while(fgets(line, sizeof(line), fp) != NULL)
            {
                echo_to_client += line;
            }

            pclose(fp);
            std::cout << "client# " << buffer << std::endl;
            
            //根据用户输入构建一个新的返回字符串
            sendto(sock, echo_to_client.c_str(), echo_to_client.size(), 0, (struct sockaddr *)&peer, len);
        }
        else
        {
            //TODO
        }
    }
    return 0;
}

客户端

#include <iostream>
#include <cerrno>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
​
void Usage(std::string proc)
{
    std::cout << "Usage: \n\t" << proc << " server_ip server_port" << std::endl;
}
​
//  ./udp_client server_ip server_port
​
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 0;
    }
​
    // 1.创建套接字,打开网络文件
    int sock = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket error: " << errno << std::endl;
        return 1;
    }
​
    //客户端需要显示的bind吗??
    // a. 首先,客户端必须也要有ip和port
    // b. 但是,客户端不需要显示的bind!一旦显示的bind,就必须明确client要和哪一个port关联
    // client指明的端口号,在client端一定会有吗??有可能被占用,被占用就导致client无法使用
    // server要的是port必须明确,而且不变,但client只要有就行!一般由OS自动bind()
    // 就是client正常发送数据的时候,OS会自动bind,采用的是随机端口的方式
​
    // b.数据给谁发?
    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]);
​
    // 2.使用服务
    while (1)
    {
        // a.数据从哪来?
        // std::string message;
        // std::cout << "请输入# ";
        // std::cin >> message;
        std::cout << "MyShell $ ";
        char line[1024] = {0};
        fgets(line, sizeof(line), stdin);
​
        sendto(sock, line, strlen(line), 0, (struct sockaddr *)&server, sizeof(server));
​
        struct sockaddr_in tmp;
        socklen_t len = sizeof(tmp);
        char buffer[1024];
        ssize_t cnt = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr*)&tmp, &len);
        if(cnt > 0)
        {
            buffer[cnt] = 0;
            std::cout << buffer << std::endl;
        }
        else
        {
            //TODO
        }
    }
​
    return 0;
}

tcp socket

socket()

// 1.创建套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);

bind()

// 2.bind
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
​
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[1]));
local.sin_addr.s_addr = INADDR_ANY;
​
bind(listen_sock, (struct sockaddr *)&local, sizeof(local)

listen()

// 3.监听
//需要允许用户连接
const int back_log = 5;
listen(listen_sock, back_log)//listen的第二个参数与全连接队列有关

accept()

// 4. 从监听套接字中获取新来的套接字
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock, (struct sockaddr *)&peer, &len);

connect()

// 5.发起连接
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(argv[1]);//server ip
server.sin_port = htons(atoi(argv[2]));//server port
​
connect(sock, (struct sockaddr*)&server, sizeof(server)

recv()/read()

// 6.读数据
ssize_t s = read(sock, buffer, sizeof(buffer)-1);

send()/write()

// 7. 写数据
write(sock, buffer, sizeof(buffer)-1);

close()

// 8.关闭套接字文件
close(_sock);

实现一个多进程/多线程/线程池版(含任务队列)服务器

任务模块

#pragma once
​
#include <iostream>
#include <pthread.h>
​
namespace ljh_task
{
    class Task
    {
    private:
        int _sock;
​
    public:
        Task(int sock = -1)
            : _sock(sock)
        {
        }
​
        int Run()
        {
            // while (true)
            // {
            char buffer[1024];
            memset(&buffer, 0, sizeof(buffer));
            ssize_t s = read(_sock, buffer, sizeof(buffer) - 1);
            if (s > 0)
            {
                buffer[s] = 0;
                std::cout << "client# " << buffer << std::endl;
​
                std::string echo_string = ">>>server<<<, ";
                echo_string += buffer;
​
                write(_sock, echo_string.c_str(), echo_string.size());
            }
            else if (s == 0)
            {
                std::cout << "client quit ..." << std::endl;
                // break;
            }
            else
            {
                std::cerr << "read error: " << errno << std::endl;
                // break;
            }
            // }
​
            close(_sock);
        }
        ~Task()
        {}
    };
}

线程池模块

#pragma once
​
#include <iostream>
#include <string>
#include <queue>
#include <unistd.h>
#include <pthread.h>
​
namespace ljh_thread_pool
{
    const int g_num = 5;
    template<class T>
    class ThreadPool
    {
    private:
        int _num;
        std::queue<T> _task_queue;
​
        pthread_mutex_t _mtx;
        pthread_cond_t _cond;
​
        static ThreadPool<T>* ins;
    private:
        ThreadPool(int num = g_num)
            :_num(num)
        {
            pthread_mutex_init(&_mtx, nullptr);
            pthread_cond_init(&_cond, nullptr);
        }
        //拷贝构造
        ThreadPool(const ThreadPool<T>& tp) = delete;
        //赋值
        ThreadPool<T>& operator=(const ThreadPool<T>& tp) = delete;
    public:
        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mtx);
            pthread_cond_destroy(&_cond);
        }
    public:
        static ThreadPool<T>* GetInstance()
        {
            static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
            //双判定,减少锁的争用,提高获取单例的效率
            if(ins == nullptr)
            {
                pthread_mutex_lock(&mtx);
                if(ins == nullptr)
                {
                    ins = new ThreadPool<T>(); 
                    ins->InitThreadPool();
                    std::cout << "首次加载对象" << std::endl;
                }
                pthread_mutex_unlock(&mtx);
            }
​
            return ins;
        }
    private:
        void Lock()
        {
            pthread_mutex_lock(&_mtx);
        }
        void Unlock()
        {
            pthread_mutex_unlock(&_mtx);
        }
        bool IsEmpty()
        {
            return _task_queue.empty();
        }
        void Wait()
        {
            pthread_cond_wait(&_cond, &_mtx);
        }
        void Wakeup()
        {
            pthread_cond_signal(&_cond);
        }
    private:
        //在类中让线程执行的方法必须设为静态方法
        static void* Rountine(void* args)
        {
            pthread_detach(pthread_self());
            ThreadPool<T>* tp = (ThreadPool<T>*)args;
            while(true)
            {
                tp->Lock();
                while(tp->IsEmpty())
                {
                    tp->Wait();
                }
                T t;
                tp->PopTask(&t);
                tp->Unlock();
​
                t.Run();
            }
        }
        void InitThreadPool()
        {
            pthread_t tid;
            for(int i = 0; i < _num; ++i)
            {
                pthread_create(&tid, nullptr, Rountine, (void*)this);
            }
        }
    public:
        void PushTask(const T& in)
        {
            Lock();
            _task_queue.push(in);
            Unlock();
            Wakeup();
        }
    private:
        void PopTask(T* out)
        {
            *out = _task_queue.front();
            _task_queue.pop();
        }
    };
    template<class T>
    ThreadPool<T>* ThreadPool<T>::ins = nullptr;
}

服务端

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include "thread_pool.hpp"
#include "Task.hpp"

using namespace ljh_thread_pool;
using namespace ljh_task;

void Usage(std::string proc)
{
    std::cout << "Usage: " << proc << "port" << std::endl;
}

// void ServiceIO(int new_sock)
// {
//     //提供服务, 是一个死循环
//     while (true)
//     {
//         char buffer[1024];
//         memset(&buffer, 0, sizeof(buffer));
//         ssize_t s = read(new_sock, buffer, sizeof(buffer) - 1);
//         if (s > 0)
//         {
//             buffer[s] = 0;
//             std::cout << "client# " << buffer << std::endl;

//             std::string echo_string = ">>>server<<<, ";
//             echo_string += buffer;

//             write(new_sock, echo_string.c_str(), echo_string.size());
//         }
//         else if (s == 0)
//         {
//             std::cout << "client quit ..." << std::endl;
//             break;
//         }
//         else
//         {
//             std::cerr << "read error: " << errno << std::endl;
//             break;
//         }
//     }
// }

// void *HandlerRequest(void *args)
// {
//     pthread_detach(pthread_self());
//     int new_sock = *(int *)args;
//     delete (int *)args;

//     ServiceIO(new_sock);
//     close(new_sock);
// }

//./tcp_server 8081
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }

    // tcp_server
    // 1.创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        std::cerr << "socket error: " << errno << std::endl;
        return 2;
    }

    // 2.bind
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));

    local.sin_family = AF_INET;
    local.sin_port = htons(atoi(argv[1]));
    local.sin_addr.s_addr = INADDR_ANY;

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

    // 3.监听
    //需要允许用户连接
    const int back_log = 5;
    if (listen(listen_sock, back_log) < 0)
    {
        std::cerr << "listen error: " << errno << std::endl;
        return 4;
    }

    // signal(SIGCHLD, SIG_IGN);

    for (;;)
    {
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int new_sock = accept(listen_sock, (struct sockaddr *)&peer, &len);
        if (new_sock < 0)
        {
            continue;
        }

        uint16_t client_port = ntohs(peer.sin_port);
        std::string client_ip = inet_ntoa(peer.sin_addr);

        std::cout << "get a new link -> [" << client_ip << ": " << client_port << "] : " << new_sock << std::endl;

        // version 4 :加入线程池
        // version 2, 3存在的问题:a.创建的进程/线程无上限;b.客户链接来了,我们才给客户创建进程/线程
        // 1.构建一个任务
        Task t(new_sock);
        ThreadPool<Task>::GetInstance()->PushTask(t);

        // version 3版本(多线程)
        //  pthread_t id;
        //  int* pram = new int(new_sock);
        //  pthread_create(&id, nullptr, HandlerRequest, pram);

        // version 2版本(多进程)
        //  pid_t id = fork();
        //  if(id < 0)
        //  {
        //      continue;
        //  }
        //  else if(id == 0)
        //  {
        //      //child
        //      close(listen_sock);

        //     if(fork() > 0) exit(0);//不使用屏蔽信号的方式,但是要wait

        //     ServiceIO(new_sock);
        //     close(new_sock);
        //     exit(0);
        // }
        // else
        // {
        //     //father
        //     close(new_sock);
        //     waitpid(id, nullptr, 0);
        // }

        // version 1:单进程版,没人使用!
        // ServiceIO(new_sock);
    }

    return 0;
}

客户端

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

void Usage(std::string proc)
{
    std:: cout << "Usage: " << proc << " server_ip server_port" << std::endl;
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    std::string server_ip = argv[1];
    uint16_t server_port = htons(atoi(argv[2]));

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

    //2.发起连接
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = inet_addr(argv[1]);//server ip
    server.sin_port = htons(atoi(argv[2]));//server port

    if(connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
    {
        std::cerr << "connect error: " << errno << std::endl;
        return 3;
    }

    std::cout << "connect success!" << std::endl;

    //3.使用服务
    while(true)
    {
        std::cout << "Please Enter# ";
        char buffer[1024];
        fgets(buffer, sizeof(buffer)-1, stdin);

        write(sock, buffer, sizeof(buffer)-1);

        ssize_t s = read(sock, buffer, sizeof(buffer)-1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }

    return 0;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

waywt1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值