Linux—Socket编程TCP

一 核心步骤

服务端步骤

  1. 创建套接字 → socket()
  2. 绑定 IP / 端口 → bind()
  3. 监听连接 → listen()
  4. 接收客户端连接 → accept()
  5. 读写数据 → read()/write()
  6. 关闭套接字 → close()

客户端步骤

  1. 创建套接字 → socket()
  2. 连接服务端 → connect()
  3. 读写数据 → read()/write()
  4. 关闭套接字 → close()

二 核心函数

1. 创建套接字:socket ()

2. 绑定地址端口:bind ()(仅服务端)

3. 监听连接:listen ()(仅服务端)

4. 接收连接:accept ()(仅服务端)

5. 发起连接:connect ()(仅客户端)

三 tcp编程实现服务器回显

仅展示主要接口,封装函数PthreadPool可看前面文章,主函数可自己调用

version1

tcpServer.hpp

cpp 复制代码
#include <iostream>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h>  // 提供 inet
#include <pthread.h>
#include <functional>
#include "PthreadPool.hpp"

#define MAX_NUM 1024
namespace Server
{
    static uint16_t deport = 8080; // 默认端口
    static int gbacklog = 5;
    enum
    {
        SOCkET_ERR = 0,
        BIND_ERR = 1,
        LISTEN_ERR = 2
    }; // 错误值
    
    

    //服务端io可放在类外
    void serverIO(int sock)
        {
            char buffer[MAX_NUM];
            while (1) // 一直读,类似管道
            {
                memset(&buffer,0,sizeof(buffer));
                ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
                if (n <= 0) // 读不到东西,代表客户端退出
                {
                    logMessage(NORMAL, "client quit! server too");
                    close(sock);
                    break;
                }
                buffer[n] = 0;
                cout << "rec message:" << buffer << endl;
                // 把接收到的消息转回客户端
                string outbuffer = buffer;
                outbuffer += "<-servre[echo]";
                write(sock,outbuffer.c_str(), outbuffer.size());
            }
        }
       
    class tcpServer
    {
    public:
        tcpServer(uint16_t port = deport) : _listensockfd(-1), _port(port)
        {
        }
        void init()
        {
            // 1.创建套接字
            _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensockfd < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCkET_ERR);
            }
            logMessage(NORMAL, "create socket success,listenfd:%d",_listensockfd);

            // 端口复用:解决程序重启时端口TIME_WAIT问题
            int opt = 1;
            setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

            // 2.bind绑定port与ip(ANY)
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = htonl(INADDR_ANY);
            local.sin_port = htons(_port);
            int n = bind(_listensockfd, (struct sockaddr *)&local,sizeof(local));
            if (n < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");
            // 3.设置监听状态
            if (listen(_listensockfd, gbacklog) < 0) // gbacklog后面讲
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
            // 4.在start中


            PthreadPool<calTask>::getInstance()->run();
        }
        void start()
        {
            // 4.获取新链接
            while (1)
            {
                struct sockaddr_in peer; // 用于获取客户端ip和port
                memset(&peer, 0, sizeof(peer));
                socklen_t len = sizeof(peer);
                int sock = accept(_listensockfd, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accpet error,again");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success,sock:%d",sock);

                // 5.获取到新的sock后,就用新sock进行未来通信(面向字节流,后续都是文件操作)
                //version_1(单进程阻塞一对一通信)
                serverIO(sock);
                close(sock);
               
            }
        }
        ~tcpServer() {}

    private:
        int _listensockfd;
        // 这里的sock不同于udp的sock,这个sock只负责监听
        uint16_t _port;
    };
}

tcpClient.hpp

cpp 复制代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h>  // 提供 inet

using namespace std;
namespace Client
{
    class tcpClient
    {
    public:
        tcpClient(string ip,uint16_t port):_sockfd(-1),_ip(ip),_port(port)
        {}
        void init()
        {
            //1.创建socket
            _sockfd = socket(AF_INET,SOCK_STREAM,0);
            if(_sockfd < 0)
            {
                cerr<<"create socket error"<<endl;
                exit(0);
            }
            //2.bind(客户端为系统隐式绑定)
        }
        void run()
        {
            //3.发起链接
            struct sockaddr_in server;
            memset(&server,0,sizeof(server));
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr(_ip.c_str());
            server.sin_port = htons(_port);
            socklen_t len = sizeof(server);
            if (connect(_sockfd, (struct sockaddr *)&server, len) != 0)
            {
                cerr << "connect socket error" << endl;
                exit(0);
            }
            else
            {
                while (1)
                {
                    string buffer;
                    cout << "Please Enter##";
                    getline(cin,buffer);
                    write(_sockfd,buffer.c_str(),buffer.size());

                    //接收server返回的数据
                    char buf[1024];
                    int n = read(_sockfd,buf,sizeof(buf)-1);
                    if (n > 0)
                    {
                        buf[n] = 0;
                        cout << "server回显##" << buf << endl;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
        ~tcpClient()
        {
            if(_sockfd >= 0) close(_sockfd);
        }
    private:
        int _sockfd;
        string _ip;
        uint16_t _port;
    };
}

log.hpp

cpp 复制代码
//服务端日志函数
#include <iostream>
#include <unistd.h>
#include <stdarg.h>
#include <time.h>

using namespace std;
#define NUM 1024
#define DEBUG 0
#define NORMAL 1
#define WARNNING 2
#define ERROR 3
#define FATAL 4

string retStr(int key)
{
    switch(key)
    {
        case 0 : return "DEBUG";
        break;
        case 1 :return "NORMAL";
        break;
        case 2 :return "WAENNING";
        break;
        case 3 :return "ERROR";
        break;
        case 4 :return "FATAL";
        break;
        default:  return "UNKNOW";
        break;
    }
}

void logMessage(int level,const char *format,...)
//void logMessage(int level,const string&message)
{
    //cout<<level<<":"<<message<<endl;

    //[日志等级][时间戳/时间][pid][messge]
    //[WARNING][2023-05-1118:09:08][123][创建socket失败]
    
    char logprefix[NUM];
    snprintf(logprefix,sizeof(logprefix),"[%s][%ld][%d]",retStr(level).c_str(),(long int)time(nullptr),getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg,format);
    vsnprintf(logcontent,sizeof(logcontent),format,arg);
    cout<<logprefix<<logcontent<<endl;
}

version2(多进程版)

仅修改start()函数即可

cpp 复制代码
#include <iostream>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h>  // 提供 inet
#include <pthread.h>
#include <functional>
#include "PthreadPool.hpp"

#define MAX_NUM 1024
namespace Server
{
    static uint16_t deport = 8080; // 默认端口
    static int gbacklog = 5;
    enum
    {
        SOCkET_ERR = 0,
        BIND_ERR = 1,
        LISTEN_ERR = 2
    }; // 错误值
    
    

    //服务端io可放在类外
    void serverIO(int sock)
        {
            char buffer[MAX_NUM];
            while (1) // 一直读,类似管道
            {
                memset(&buffer,0,sizeof(buffer));
                ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
                if (n <= 0) // 读不到东西,代表客户端退出
                {
                    logMessage(NORMAL, "client quit! server too");
                    close(sock);
                    break;
                }
                buffer[n] = 0;
                cout << "rec message:" << buffer << endl;
                // 把接收到的消息转回客户端
                string outbuffer = buffer;
                outbuffer += "<-servre[echo]";
                write(sock,outbuffer.c_str(), outbuffer.size());
            }
        }
    class tcpServer
    {
    public:
        tcpServer(uint16_t port = deport) : _listensockfd(-1), _port(port)
        {
        }
        void init()
        {
            // 1.创建套接字
            _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensockfd < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCkET_ERR);
            }
            logMessage(NORMAL, "create socket success,listenfd:%d",_listensockfd);

            // 端口复用:解决程序重启时端口TIME_WAIT问题
            int opt = 1;
            setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

            // 2.bind绑定port与ip(ANY)
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = htonl(INADDR_ANY);
            local.sin_port = htons(_port);
            int n = bind(_listensockfd, (struct sockaddr *)&local,sizeof(local));
            if (n < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");
            // 3.设置监听状态
            if (listen(_listensockfd, gbacklog) < 0) // gbacklog后面讲
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
            // 4.在start中


            PthreadPool<calTask>::getInstance()->run();
        }
        void start()
        {
            // 4.获取新链接
            while (1)
            {
                struct sockaddr_in peer; // 用于获取客户端ip和port
                memset(&peer, 0, sizeof(peer));
                socklen_t len = sizeof(peer);
                int sock = accept(_listensockfd, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accpet error,again");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success,sock:%d",sock);

                // 5.获取到新的sock后,就用新sock进行未来通信(面向字节流,后续都是文件操作)
               // version_2(多进程版)
                int k = fork();
                if (k < 0)
                {
                    logMessage(FATAL, "fork error");
                    exit(0);
                }
                if(k == 0)
                {
                    logMessage(NORMAL, "fork success");
                    serverIO(sock);
                }
                close(sock);
                // 非阻塞回收已退出的子进程(写在父进程中)
                pid_t ret_pid;
                // 循环回收所有已退出的子进程,避免僵尸进程堆积
                while ((ret_pid = waitpid(-1, NULL, WNOHANG)) > 0) {
                logMessage(NORMAL, "child process %d exited, recycled", ret_pid);
            }
        }
        ~tcpServer() {}

    private:
        int _listensockfd;
        // 这里的sock不同于udp的sock,这个sock只负责监听
        uint16_t _port;
    };
}

version3(多线程版)

cpp 复制代码
#include <iostream>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h>  // 提供 inet
#include <pthread.h>
#include <functional>
#include "PthreadPool.hpp"

#define MAX_NUM 1024
namespace Server
{
    static uint16_t deport = 8080; // 默认端口
    static int gbacklog = 5;
    enum
    {
        SOCkET_ERR = 0,
        BIND_ERR = 1,
        LISTEN_ERR = 2
    }; // 错误值
    
    

    //服务端io可放在类外
    void serverIO(int sock)
        {
            char buffer[MAX_NUM];
            while (1) // 一直读,类似管道
            {
                memset(&buffer,0,sizeof(buffer));
                ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
                if (n <= 0) // 读不到东西,代表客户端退出
                {
                    logMessage(NORMAL, "client quit! server too");
                    close(sock);
                    break;
                }
                buffer[n] = 0;
                cout << "rec message:" << buffer << endl;
                // 把接收到的消息转回客户端
                string outbuffer = buffer;
                outbuffer += "<-servre[echo]";
                write(sock,outbuffer.c_str(), outbuffer.size());
            }
        }
       void *threadIO(void *args)
        {
            // 设置线程分离:结束后自动释放资源,无需主线程join
            pthread_detach(pthread_self());
            int *p_sock = static_cast<int *>(args);
            int sock = *p_sock;
            delete p_sock;
            // 处理客户端通信
            serverIO(sock);
            close(sock);
            return nullptr;
        }
    class tcpServer
    {
    public:
        tcpServer(uint16_t port = deport) : _listensockfd(-1), _port(port)
        {
        }
        void init()
        {
            // 1.创建套接字
            _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensockfd < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCkET_ERR);
            }
            logMessage(NORMAL, "create socket success,listenfd:%d",_listensockfd);

            // 端口复用:解决程序重启时端口TIME_WAIT问题
            int opt = 1;
            setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

            // 2.bind绑定port与ip(ANY)
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = htonl(INADDR_ANY);
            local.sin_port = htons(_port);
            int n = bind(_listensockfd, (struct sockaddr *)&local,sizeof(local));
            if (n < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");
            // 3.设置监听状态
            if (listen(_listensockfd, gbacklog) < 0) // gbacklog后面讲
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
            // 4.在start中


            PthreadPool<calTask>::getInstance()->run();
        }
        void start()
        {
            // 4.获取新链接
            while (1)
            {
                struct sockaddr_in peer; // 用于获取客户端ip和port
                memset(&peer, 0, sizeof(peer));
                socklen_t len = sizeof(peer);
                int sock = accept(_listensockfd, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accpet error,again");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success,sock:%d",sock);

                // 5.获取到新的sock后,就用新sock进行未来通信(面向字节流,后续都是文件操作)

                // version_3(多线程版)
             
                int *p_sock = new int(sock);
                pthread_t t;
                pthread_create(&t,nullptr,threadIO,p_sock);
                //pthread_join(t,nullptr);//这里不能进行线程等待因为我们的目的并行,如果等待就会阻塞变成串行
                //close(sock);//这里因为是线程他们的资源描述符是共享的,所以应该在子线程内部去关闭描述符
            }
        }
        ~tcpServer() {}

    private:
        int _listensockfd;
        // 这里的sock不同于udp的sock,这个sock只负责监听
        uint16_t _port;
    };
}

version4(线程池版)

cpp 复制代码
#include <iostream>
#include "log.hpp"
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h> // 定义 sockaddr_in、in_addr、in_port_t 等
#include <arpa/inet.h>  // 提供 inet
#include <pthread.h>
#include <functional>
#include "PthreadPool.hpp"

#define MAX_NUM 1024
namespace Server
{
    static uint16_t deport = 8080; // 默认端口
    static int gbacklog = 5;
    enum
    {
        SOCkET_ERR = 0,
        BIND_ERR = 1,
        LISTEN_ERR = 2
    }; // 错误值
    
    

    //服务端io可放在类外
    void serverIO(int sock)
        {
            char buffer[MAX_NUM];
            while (1) // 一直读,类似管道
            {
                memset(&buffer,0,sizeof(buffer));
                ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
                if (n <= 0) // 读不到东西,代表客户端退出
                {
                    logMessage(NORMAL, "client quit! server too");
                    close(sock);
                    break;
                }
                buffer[n] = 0;
                cout << "rec message:" << buffer << endl;
                // 把接收到的消息转回客户端
                string outbuffer = buffer;
                outbuffer += "<-servre[echo]";
                write(sock,outbuffer.c_str(), outbuffer.size());
            }
        }
        void *threadIO(void *args)
        {
            // 设置线程分离:结束后自动释放资源,无需主线程join
            pthread_detach(pthread_self());
            int *p_sock = static_cast<int *>(args);
            int sock = *p_sock;
            delete p_sock;
            // 处理客户端通信
            serverIO(sock);
            close(sock);
            return nullptr;
        }
    class tcpServer
    {
    public:
        tcpServer(uint16_t port = deport) : _listensockfd(-1), _port(port)
        {
        }
        void init()
        {
            // 1.创建套接字
            _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensockfd < 0)
            {
                logMessage(FATAL, "create socket error");
                exit(SOCkET_ERR);
            }
            logMessage(NORMAL, "create socket success,listenfd:%d",_listensockfd);

            // 端口复用:解决程序重启时端口TIME_WAIT问题
            int opt = 1;
            setsockopt(_listensockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

            // 2.bind绑定port与ip(ANY)
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_addr.s_addr = htonl(INADDR_ANY);
            local.sin_port = htons(_port);
            int n = bind(_listensockfd, (struct sockaddr *)&local,sizeof(local));
            if (n < 0)
            {
                logMessage(FATAL, "bind socket error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");
            // 3.设置监听状态
            if (listen(_listensockfd, gbacklog) < 0) // gbacklog后面讲
            {
                logMessage(FATAL, "listen socket error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
            // 4.在start中


            PthreadPool<calTask>::getInstance()->run();
        }
        void start()
        {
            // 4.获取新链接
            while (1)
            {
                struct sockaddr_in peer; // 用于获取客户端ip和port
                memset(&peer, 0, sizeof(peer));
                socklen_t len = sizeof(peer);
                int sock = accept(_listensockfd, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accpet error,again");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success,sock:%d",sock);

                // 5.获取到新的sock后,就用新sock进行未来通信(面向字节流,后续都是文件操作)

                   //version_4(线程池版)
                calTask t(sock,serverIO);
                PthreadPool<calTask>::getInstance()->push(t);
            }
        }
        ~tcpServer() {}

    private:
        int _listensockfd;
        // 这里的sock不同于udp的sock,这个sock只负责监听
        uint16_t _port;
    };
}

Task.hpp

cpp 复制代码
#include <iostream>
#include <functional>

using namespace std;

class calTask
{
    typedef std::function<void(int)>func_t;
public:
    calTask()
    {}
    calTask(int x,func_t func):_x(x),_callback(func)
    {}
    string operator()()
    {
        _callback(_x);
    }
    ~calTask()
    {}
private:
    int _x;
    func_t _callback;
};
相关推荐
_OP_CHEN2 小时前
【Linux系统编程】(二十二)从磁盘物理结构到地址映射:Ext 系列文件系统硬件底层原理深度剖析
linux·操作系统·文件系统·c/c++·计算机硬件·ext文件系统·磁盘寻址
shehuiyuelaiyuehao2 小时前
图书管理系统
java·服务器·前端
一直跑2 小时前
通过所里的服务器连接到组里的服务器,然后可视化组里的文件和代码,并修改等操作(VScode/vscode/mobaxterm)
linux·运维·服务器
码农编程录3 小时前
【notes11】并发与竞争
linux
mobai73 小时前
Ubuntu环境上安装NTP服务
linux·运维·ubuntu
郝学胜-神的一滴3 小时前
Linux Socket编程核心:深入解析sockaddr数据结构族
linux·服务器·c语言·网络·数据结构·c++·架构
杨了个杨89828 小时前
nginx常见功能部署
运维·服务器·nginx
小天源10 小时前
linux漏洞一键扫描
linux·运维·服务器·漏洞扫描
m0_6962126811 小时前
个人微信api
运维·服务器