建立基于TCP的客户端和服务端

函数介绍:

1.socket()

作用:创建套接字

domain:

  • AF_INET:IPv4 Internet 协议族。
  • AF_INET6:IPv6 Internet 协议族。
  • AF_UNIX:Unix 域协议族,用于在同一台主机上的进程间通信。

type:

  • SOCK_STREAM:提供序列化的、可靠的、双向连接的字节流服务,通常用于TCP连接。
  • SOCK_DGRAM:提供无连接的、不可靠的数据报服务,通常用于UDP通信。

protocol:

这个参数指定了使用的特定协议。对于很多常见的套接字类,如 SOCK_STREAMSOCK_DGRAM,这个参数可以设置为0,系统会自动选择一个默认协议。

return value:

socket() 函数调用成功时,返回一个非负整数,即新创建的套接字的文件描述符(socket descriptor)。如果调用失败,函数返回-1,并且 errno 被设置为描述错误的值。

服务端:

2.bind()

**作用:**将套接字(fd)和ip和port绑定起来

sockfd:

  • 要绑定的文件描述符,也就是socket的返回值

addr:

指向 sockaddr 结构体的指针,该结构体包含了套接字的地址信息。

  • sin_family:协议家族
  • sin_port:要绑定的端口。
  • sin_addr.s_addr:要绑定的ip

介绍以下里面的函数:

h表示host,n表示network

htons 通常用于端口号,htonl 通常用于IP地址

作用:将IPv4地址的点分十进制字符串表示转换为网络字节序的二进制值

INADDR_ANY:用于指定一个特殊的IPv4地址0.0.0.0,表示"接受任何可用的网络接口",通常用在服务器监听上。

addrlen: 大小

return value:

成功返回0,失败返回-1并设置错误码。

3.listen()

**作用:**将套接字设置为监听状态可以监听链接请求

**sockfd:**socket()的返回值

**backlog:**一个大于0的整数(后续补充)

**return value:**成功返回0失败返回-1并设置错误码

4.accept()

**作用:**接受链接请求

**sockfd:**socket()的返回值

**addr和addrlen:**输出型参数,用于存储连接客户端的地址信息。

return value:

成功返回一个新的文件描述符用于与连接的客户端进行通信。之后的read和write传入的都是accept的返回值。

失败返回-1,设置错误码。

客户端:

2.connect()

**作用:**向服务端发起链接

**sockfd:**socket()的返回值

**addr:**要连接的服务端的地址信息

**addrlen:**addr的大小

return value:

成功返回0,失败返回-1并设置错误码。

代码:

tcpServer.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <cstdlib>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "log.hpp"
#include "threadpool.hpp"

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
        OPEN_ERR
    };
    static const uint16_t gport = 8080;
    static const int gbacklog = 5; // 底层链接队列的长度
    
    class TcpServer;
    class ThreadData
    {
    public:
        ThreadData(TcpServer *self, int sock) : _self(self), _sock(sock)
        {
        }

    public:
        TcpServer *_self;
        int _sock;
    };

    class TcpServer
    {
    public:
        TcpServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
        {
        }
        void initServer()
        {
            // 1.创建socket文件套接字对象
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success:%d", _listensock);

            // 2.bind绑定自己的网络信息
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY;
            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

            // 3.设置socket为监听状态,获取新的客户端链接
            if (listen(_listensock, gbacklog) < 0)
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }
        void start()
        {
            // 4.线程池初始化
            ThreadPool<Task>::getInstance()->run();
            logMessage(NORMAL, "Thread init success");
            // signal(SIGCHLD,SIG_IGN);//忽略这个信号
            for (;;)
            {
                // 4.server获取新链接
                // sock,和client进行通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success,get a new sock:%d", sock);
                serviceIO(sock);
                close(sock);//对一个已经使用完毕的sock,我们必须要关闭这个sock,要不然会导致文件描述符泄漏
            }
        }
        // static void *threadRoutine(void *args)
        // {
        //     pthread_detach(pthread_self());
        //     ThreadData *td = static_cast<ThreadData *>(args);
        //     td->_self->serviceIO(td->_sock);
        //     delete td;
        //     close(td->_sock);
        //     return nullptr;
        // }

        ~TcpServer() {}

    private:
        int _listensock;
        uint16_t _port;
    };
}
// 5.这里就是一个sock,未来通信我们就用这个sock,面向字节流的,后续全部都是文件操作!
//  serviceIO(sock);
//  close(sock);//对一个已经使用完毕的sock,我们必须要关闭这个sock,要不然会导致文件描述符泄漏

// version2,多进程版,多线程版,线程池版
// version1,多进程版
//  pid_t id = fork();
//  if(id == 0)//child
//  {
//      close(_listensock);
//      if(fork()>0) exit(0);//关闭child,创建了一个孙子。因为child退了,孙子进程成了孤儿进程,由操作系统回收
//      serviceIO(sock);
//      close(sock);//对一个已经使用完毕的sock,我们必须要关闭这个sock,要不然会导致文件描述符泄漏
//      exit(0);
//  }
// father
//  pid_t ret = waitpid(id,nullptr,0);
//  if(ret>0)
//  {
//      std::cout<<"wait success:"<<ret<<std::endl;
//  }

// version2,多进程版
//  pid_t id = fork();
//  if(id == 0)//child
//  {
//      close(_listensock);
//      serviceIO(sock);
//      close(sock);//对一个已经使用完毕的sock,我们必须要关闭这个sock,要不然会导致文件描述符泄漏
//      exit(0);
//  }
//  close(sock);

// version3:多线程版
//  pthread_t tid;
//  ThreadData* td = new ThreadData(this,sock);
//  pthread_create(&tid,nullptr,threadRoutine,td);

// version4:线程池
//ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));

tcpServer.cc

cpp 复制代码
#include"tcpServer.hpp"
#include<memory>
using namespace server;
using namespace std;
static void Usage(string proc)
{
    cout<<".\nUsage:\n\t"<<proc<<"local_port\n\n";
}
//tcp服务器,启动上和udp Server一模一样
//./tcpServer local_port
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    unique_ptr<TcpServer> tsvr(new TcpServer(port));
    tsvr->initServer();
    tsvr->start();
    return 0;
    
}

tcpClient.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include<string>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<cstring>
#define NUM 1024
class TcpClient
{
    public:
    TcpClient(const std::string &serverip,const uint16_t &serverport)
    :_sock(-1),_serverip(serverip),_serverport(serverport)
    {}
    void initClient()
    {
        //1.创建socket
        _sock = socket(AF_INET,SOCK_STREAM,0);
        if(_sock < 0)
        {
            std::cerr<<"socket create error"<<std::endl;
            exit(2);
        }
        //2.客户端不用显示的bind
        //3.要不要listen?没人连我啊
        //4.要不要accept?no
        //5.要什么呢?要发起链接!
    
    }
    void start()
    {
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());
        if(connect(_sock,(struct sockaddr*)&server,sizeof(server)) != 0)
        {
            std::cerr<<"socket connect error"<<std::endl;
        }else{
            std::string msg;
            while(1)
            {
                std::cout<<"Enter#";
                std::getline(std::cin,msg);
                write(_sock,msg.c_str(),msg.size());

                char buffer[NUM];
                int n = read(_sock,buffer,sizeof(buffer)-1);
                if(n>0)
                {
                    //目前把读到的数据当字符串
                    buffer[n] = 0;
                    std::cout<<"Server回显#"<<buffer<<std::endl;
                }else{
                    break;
                }
            }
        }
    }
    ~TcpClient()
    {
        if(_sock >= 0)close(_sock);//文件描述符的生命周期随进程,所以这里不写也行
    }
private:
    int _sock;
    std::string  _serverip;
    uint16_t _serverport;

};

tcpClient.cc

cpp 复制代码
#include"tcpClient.hpp"
#include<memory>
using namespace std;
static void Usage(string proc)
{
    cout<<".\nUsage:\n\t"<<proc<<"serverip serverport\n\n";
}
//./tcpClient serverip serverport
int main(int argc,char* argv[])
{
    if(argc!=3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);
    unique_ptr<TcpClient> tcli(new TcpClient(serverip,serverport));
    tcli->initClient();
    tcli->start();
    return 0;
}

Task.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <functional>
#include <unistd.h>
#include <string.h>
#include"log.hpp"
void serviceIO(int sock)
{
    char buffer[1024];
    while (true)
    {
        ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            // 目前我们把读到的数据当成字符串,截止目前
            // 为什么UDP并不符合文件特性,文件也是字节流的,UDP是数据报的,所以要用特殊函数来读
            buffer[n] = 0;
            std::cout << "recv messages:" << buffer << std::endl;
            write(sock, buffer, strlen(buffer)); // 多路转接
        }
        else if (n == 0)
        {
            // 代表client退出
            logMessage(NORMAL, "client quit,me too!");
            break;
        }
    }
    close(sock);
}

class Task
{
    using func_t = std::function<void(int)>;

public:
    Task() {}
    Task(int sock, func_t func)
        : _sock(sock), _callback(func)
    {
    }
    void operator()()
    {
       _callback(_sock);
    }
   private:
    int _sock;
    func_t _callback;
};

log.hpp

cpp 复制代码
#pragma once
#include<iostream>
#include<string>
#include<cstdarg>
#include<ctime>
#include<unistd.h>
//cat /var/log/messages系统日志
//日志等级
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4//致命错误

const char* to_levelstr(int level)
{
    switch(level)
    {
        case DEBUG:
        return "DEBUG";
        case NORMAL:
        return "NORMAL";
        case WARNING:
        return "WARNING";
        case ERROR:
        return "ERROR";
        case FATAL:
        return "FATAL";
    }
}
// void logMessage(int level,const char* messages)
// {
//     std::cout<<messages<<std::endl;
// }
// void logMessage(DEBUG,"hello %f ,%d,%c",3.14,10,'C');
void logMessage(int level,const char* format,...)
{
    //[日志等级][时间戳/时间][pid][message]
    //暂定
    // va_list start;
    // va_start(start);
    // while(*p)
    // {
    //     switch(*p)
    //     {
    //         case '%':
    //             p++;
    //             if(*p == 'f') arg = va_arg(start,float);
    //         ...
    //     }
        
    // }
    // va_end(start);
    #define NUM 1024
    char logprefix[NUM];
    snprintf(logprefix,sizeof(logprefix),"[%s][%ld][pid:%d]",to_levelstr(level),(long int)time(nullptr),getpid());
    char logcontent[NUM];
    va_list arg;
    va_start(arg,format);
    vsnprintf(logcontent,sizeof(logcontent),format,arg);
    std::cout<<logprefix<<logcontent<<std::endl;
}

makefile

cpp 复制代码
cc=g++
.PHONY:all
all:tcpClient tcpServer

tcpClient:tcpClient.cc
	$(cc) -o $@ $^ -std=c++11 -lpthread
tcpServer:tcpServer.cc
	$(cc) -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f tcpClient tcpServer
相关推荐
vvw&10 分钟前
如何部署和配置项目管理工具 Plane - 开源 Jira 替代方案
linux·运维·服务器·ubuntu·开源·jira·plane
明 庭38 分钟前
在 Ubuntu 下通过 Docker 部署 WebDAV 服务器
服务器·ubuntu·docker
itachi-uchiha1 小时前
XX服务器上的npm不知道咋突然坏了
运维·服务器·npm
小丑西瓜6661 小时前
网络基础概念
linux·网络·tcp/ip·c/c++
一只小鱼儿吖1 小时前
数据采集:各地区动态IP数据质量差异分析
网络·网络协议·tcp/ip
开心工作室_kaic1 小时前
springboot430校园食堂订餐系统boot(论文+源码)_kaic
运维·服务器·数据库·vue.js·旅游
XY.散人2 小时前
初识Linux · 编写生产消费模型(1)
linux·运维·服务器
南桥几晴秋2 小时前
【Linux网络编程】传输协议UDP
linux·服务器·网络·c++·udp·传输层
qq_353199252 小时前
TCP客户端模拟链接websocket服务端
websocket·tcp/ip·c#
夜空晚星灿烂2 小时前
C#网络编程--TCP/IP协议与Socket的区别以及关系
网络·tcp/ip·c#