【网络】UDP/TCP网络程序

news2025/2/8 22:12:31

目录

UDP网络程序

简单通信版本(UDP)

准备工作(接口学习、分析)

整体代码(Server.hpp/Server.cpp/Client.hpp/Client.cpp)

添加“婴儿版”业务逻辑

英译汉翻译

my_shell

聊天室

linux和windows通信

TCP网络程序

简单通信版本(TCP)

准备工作(接口学习、分析)

版本改进

多进程版本

多线程版本

线程池版本

日志

守护进程​编辑


UDP网络程序

简单通信版本(UDP)

准备工作(接口学习、分析)

●最终效果:客户端发送信息,服务端读取到客户端发送来的内容。

●接口学习和准备工作

1.socket:创建用于通信的端点。(创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)

 下述代码中要传的参数:

2.struct sockaddr_in:这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。

3.in_addr:用来表示一个IPv4的IP地址. 其实就是一个32位的整数。

4.bzero:将每个字节初始化为0。

例:
struct sockaddr_in local;
bzero(&local,sizeof(local));

5.bind:绑定端口号 (TCP/UDP, 服务器)。

例:
 int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));

6.recvfrom:从套接字接收消息。 

//读取发送过来的内容
 ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);

7.sendto:在套接字上发送消息。

例:
//发送数据和其他必要的东西
sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));

●代码分析

1.服务端和客户端逻辑框架

2.服务端创建socket

a.调用socket接口(创建 socket 文件描述符)。

b.结构体填充端口号、IP地址。

c.调用bind,绑定端口号 (TCP/UDP, 服务器)。服务端显示的绑定。

3.客户端创建socket

a.调用socket接口(创建 socket 文件描述符)。

b.客户端不用显示的绑定端口号,原因是客户端的端口号是多少并不重要,只要确保唯一性即可。对于客户端而言,操作系统帮我们完成了这项工作。

4.启动服务器

a.服务器一般而言都是死循环。

b.通过recvfrom从套接字接收消息。

5.启动客户端

a.通过sendto,在套接字上发送消息。

整体代码(Server.hpp/Server.cpp/Client.hpp/Client.cpp)

makefile

cc=g++

.PHONY:all
all:UdpServer UdpClient 

UdpServer:UdpServer.cpp
    $(cc) -o $@ $^ -std=c++11
UdpClient:UdpClient.cpp
    $(cc) -o $@ $^ -std=c++11

.PHONY:clean
clean:
    rm -f UdpClient UdpServer

服务端

udpServer.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;

//服务端IP的默认值
static const string defaultIp = "0.0.0.0";
static const int gnum = 1024;

//枚举一些错误码
enum {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR};

class UdpServer
{
public:
     //构造,完成对端口号,服务端Ip和_sockfd(类似文件描述符,作用是取代IP和端口号标识服务进程唯一性)
    UdpServer(const uint16_t& port,const string &ip = defaultIp)
        :_port(port)
        ,_ip(ip)
        ,_sockfd(-1)
    {}
    //创建socket
    void initServer()
    {
        //1.创建socket,网络套接字标识,数据报
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        //
        if(_sockfd == -1)
        {
            cerr<<"socket error:"<<errno<<" : "<<strerror(errno)<<endl;
            exit(SOCKET_ERR);
        }
        cout<<"socket success: "<<" : "<<_sockfd<<endl;
    
        //2.绑定端口号和ip
        struct sockaddr_in local;
        bzero(&local,sizeof(local));
        
        //填充结构体
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str());
        
        //绑定
        int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n == -1)
        {
            cout<<"bind error:"<<errno<<" : "<<strerror(errno)<<endl;
            exit(BIND_ERR);
        }
    }
    //启动服务器
    void start()
    {
         
        char buffer[gnum];
        while(true)
        {
            //读取数据

            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); //必填
            
            //读取发送过来的内容
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
   
            //读取成功
            if(s > 0)
            { 
                buffer[s] = 0;
                //客户端ip
                string clientip = inet_ntoa(peer.sin_addr);
                //客户端端口号
                uint16_t clientpoot = ntohs(peer.sin_port);
                //发送过来的内容
                string message = buffer;

                cout<<clientip<<"["<<clientpoot<<"]# "<<message<<endl;
            }
        }
    }

    ~UdpServer(){}
private:
    uint16_t _port;//端口号
    string _ip;//服务端ip
    int _sockfd;//套接字fd
};

udpServer.cpp

#include "UdpServer.hpp"
#include <memory>

using namespace std;

//使用手册,服务端要指明端口号
static void Usage(string proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}


int main(int argc,char* argv[])
{
    //argv表中的元素个数如果不是二
    //未按照规定使用,打印手册提示
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    
    //正确使用,获取端口号,将字符串转换成uint16_t类型
    uint16_t port = atoi(argv[1]);
    
    //创建服务端对象,用智能指针管理
    unique_ptr<UdpServer> user(new UdpServer(port));
    
    //创建套接字
    user->initServer();
    //启动服务器
    user->start();
    return 0;
}

客户端

udpClient.hpp

#pragma once

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

using namespace std;

class udpClient
{
public:
    //构造:初始化ip+端口号+_sockfd
    udpClient(const string &serverip,const uint16_t &_serverport)
        :_serverip(serverip)
        ,_serverport(_serverport)
        ,_sockfd(-1)
        ,_quit(false)
    {}
    //创建套接字
    void initClient()
    {
        //创建socket
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd == -1)
        {
            cerr<<"socker error: "<<errno<<" : "<<strerror(errno)<<endl;
            exit(2);
        }
        cout<<"socket success: "<<" : "<<_sockfd<<endl; 
        
        //不用显示的绑定端口号,操作系统帮我们完成这项工作,原因是服务端需要保证唯一性,要让大家都能看到
        //不能随意改变,而客户端不被关心,是剁多少都可以,显示写可能被重复使用,出现冲突。
    }
    //启动客户端
    void run()
    {
        struct sockaddr_in server;
        memset(&server,0,sizeof(server));
        //结构体天成
       
        server.sin_family=AF_INET;
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());
        server.sin_port = htons(_serverport);

        string message;
        while(!_quit)
        {
            cout<<"Please Enter# ";
            cin>>message;//要发送的内容
            
            //发送数据和其他必要的东西
            sendto(_sockfd,message.c_str(),message.size(),0,(struct sockaddr*)&server,sizeof(server));
        }
    }
    ~udpClient(){}
private:
    int _sockfd;//套接字fd
    string _serverip;//要发送给哪个主机
    uint16_t _serverport;//确定服务进程
    bool _quit;
};

udpClient.cpp

#include "UdpClient.hpp"
#include <memory>

using namespace std;

//客户端使用手册
static void Usage(string proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}


int main(int argc,char* argv[])
{
    //判断是否正确使用
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    
    //获取目的主机(服务端)的ip
    string serverip = argv[1];
    //获取目的主机目标程序端口号(服务进程端口号)
    uint16_t serverport = atoi(argv[2]);

    //创建客户端对象,并用智能指针管理
    unique_ptr<udpClient> ucli(new udpClient(serverip,serverport));
    
    //创建客户端套接字
    ucli->initClient();
    //启动客户端
    ucli->run();

    return 0;
}

测试结果:多个客户端向服务端发送消息,服务端接收消息。

添加“婴儿版”业务逻辑

英译汉翻译

 ●思路分析:

a.业务逻辑要和网络通信的代码解耦,这样写的好处就是当有新增业务,或者需要改动的时候,只对业务部分代码进行处理。服务端增加一个成员变量,一个指向回调方法的函数指针。回调函数实现业务逻辑。

b.在下述代码的实现中,将单词和翻译按照“good:好”的形式添加到配置文件dict.txt中。查找到单词后向客户端返回翻译后的结果。反之返回"unknown"!

c.对2号信号进行捕捉,执行加载字典的动作。也就说,如果某个单词没有查询到,可以动态的将该单词填入字典中,通过发送信号重新加载字典。

d.对于字典的加载以及处理,和业务逻辑详见下述代码。其余部分和上述基础版通信基本相同。

e.“业务”逻辑

const string dictTxt = "./dict.txt";
unordered_map<string,string> dict;

static void Usage(string proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}

static bool cutString(const string &target,string* s1,string* s2,const string& sep)
{
    auto pos = target.find(sep);
    if(pos == string::npos)return false;
    *s1 = target.substr(0,pos);
    *s2 = target.substr(pos+sep.size());
    return true;
}

static void initDict()
{
    ifstream in(dictTxt,std::ios::binary);
    if(!in.is_open())
    {
        cerr<<"open file"<<dictTxt<<"error"<<endl;
        exit(1);
    }

    string line,key,value;
    while(getline(in,line))
    {
        if(cutString(line,&key,&value,":"))
        {
            dict.insert(make_pair(key,value));
        }
    }
    in.close();
    cout<<"load dict success"<<endl;
}

void handlerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    //对message进行业务处理,解耦
    string response_message;
    auto iter = dict.find(message);
    if(iter == dict.end()) response_message = "unknown";
    else response_message = iter->second;

    //返回
    struct sockaddr_in client;
    bzero(&client,sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    //cout<<response_message<<endl;
    sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
    
}

static void debugPrint()
{
    //cout<<"1111"<<endl;
    for(auto& dt : dict)
    {
        cout<<dt.first<<" # "<<dt.second<<endl;
    }
}

//捕捉方法
void reload(int signo)
{
    (void)signo;
    initDict();
}

●整体代码

makefiel

.PHONY:all
all:UdpServer UdpClient

UdpServer:UdpServer.cpp
    g++ -o $@ $^ -std=c++11
UdpClient:UdpClient.cpp
    g++ -o $@ $^ -std=c++11

.PHONY:clean
clean: 
    rm -rf UdpClient UdpServer

服务端

UdpServer.hpp

#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;

static const string defaultIp = "0.0.0.0";
static const int gnum = 1024;

enum {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR};

class UdpServer
{
public:
    UdpServer(const uint16_t& port,const string &ip = defaultIp)
        :_port(port)
        ,_ip(ip)
        ,_sockfd(-1)
    {}
    //创建socket
    void initServer()
    {
        //1.创建socket
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd == -1)
        {
            cerr<<"socket error:"<<errno<<" : "<<strerror(errno)<<endl;
            exit(SOCKET_ERR);
        }
        cout<<"socket success: "<<" : "<<_sockfd<<endl;

        //2.绑定端口号和ip
        struct sockaddr_in local;
        bzero(&local,sizeof(local));

        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str());

        int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n == -1)
        {
            cout<<"bind error:"<<errno<<" : "<<strerror(errno)<<endl;
            exit(BIND_ERR);
        }
    }
    //启动服务器
    void start()
    {
 
        char buffer[gnum];
        while(true)
        {
            //读取数据
            //struct sockaddr_in peer;
 
            //socklen_t len = sizeof(peer);

            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); //必填
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
            //ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);

            if(s > 0)
            { //cout<<"2222"<<endl;
                buffer[s] = 0;
                string clientip = inet_ntoa(peer.sin_addr);
                uint16_t clientpoot = ntohs(peer.sin_port);
                string message = buffer;

                cout<<clientip<<"["<<clientpoot<<"]# "<<message<<endl;
            }
            sleep(1);
        }
    }

    ~UdpServer(){}
private:
    uint16_t _port;
    string _ip;
    int _sockfd;
};

UdpServer.cpp

#include "UdpServer.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>

const string dictTxt = "./dict.txt";
unordered_map<string,string> dict;

static void Usage(string proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}

static bool cutString(const string &target,string* s1,string* s2,const string& sep)
{
    auto pos = target.find(sep);
    if(pos == string::npos)return false;
    *s1 = target.substr(0,pos);
    *s2 = target.substr(pos+sep.size());
    return true;
}

static void initDict()
{
    ifstream in(dictTxt,std::ios::binary);
    if(!in.is_open())
    {
        cerr<<"open file"<<dictTxt<<"error"<<endl;
        exit(1);
    }

    string line,key,value;
    while(getline(in,line))
    {
        if(cutString(line,&key,&value,":"))
        {
            dict.insert(make_pair(key,value));
        }
    }
    in.close();
    cout<<"load dict success"<<endl;
}

void handlerMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    //对message进行业务处理,解耦
    string response_message;
    auto iter = dict.find(message);
    if(iter == dict.end()) response_message = "unknown";
    else response_message = iter->second;

    //返回
    struct sockaddr_in client;
    bzero(&client,sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    //cout<<response_message<<endl;
    sendto(sockfd,response_message.c_str(),response_message.size(),0,(struct sockaddr*)&client,sizeof(client));
    
}

static void debugPrint()
{
    //cout<<"1111"<<endl;
    for(auto& dt : dict)
    {
        cout<<dt.first<<" # "<<dt.second<<endl;
    }
}

//捕捉方法
void reload(int signo)
{
    (void)signo;
    initDict();
}

//服务端
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[1]);

    signal(2,reload);
    initDict();
    //debugPrint();

    unique_ptr<UdpServer> user(new UdpServer(handlerMessage,port));
    
    user->initServer();
    user->start();
    return 0;
}

客户端

UdpClient.hpp

#include <iostream>
#include <cerrno>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIDD_ERR
};

class UdpClient
{
public:
    UdpClient(const string& ip,const uint16_t& port)
        :_serverip(ip)
        ,_serverport(port)
        ,_sockfd(-1)
    {}

    void initClient()
    {
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd == -1)
        {
            cout<<"SOCKET_ERR:"<<errno<<" : "<<strerror(errno)<<endl;
            exit(SOCKET_ERR);
        }
        cout<<"socket success "<<_sockfd<<endl;

    }

    void run()
    {
        struct sockaddr_in server;
        bzero(&server,sizeof(server));

        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        string message;
        char cmdline[1024];
        while(true)
        {
            cout<<"Plase Enter# ";
            cin>>message;


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

            char buffer[1024];
            struct sockaddr_in temp;
            socklen_t temp_len = sizeof(temp);

            size_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&temp_len);
            if(n > 0)buffer[n] = '\0';
            cout<<"翻译结果为# \n"<<buffer<<endl;

        }
    }

    ~UdpClient(){}
private:
    int _sockfd;//套接字fd
    string _serverip;//服务端ip
    uint16_t _serverport;//服务端端口号
};

 UdpClient.cpp

#include "UdpClient.hpp"
#include <memory>
static void Usage(string proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"local_ip and local_port\n\n";
}

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

    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<UdpClient> ucli(new UdpClient(serverip,serverport));

    ucli->initClient();
    ucli->run();
    return 0;
}

my_shell

 ●思路分析:

a.对比英汉翻译业务,只需要在Server.cpp中添加业务。这也能体现代码解耦的好处。

b.对于my_shell,客户端向服务端发送命令(只处理一些简单命令),服务端接收命令后,将在服务器执行该命令后的结果返回给客户端。

c.popen(打开、关闭通往或离开进程的管道流)。popen = pipe + fork + exec*。

d.“业务”逻辑

//demo2
void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
    string response;
    FILE* fp = popen(cmd.c_str(),"r");
    if(fp == nullptr)response = cmd + "exe failed";
    char line[1024];
    while(fgets(line,sizeof(line),fp))
    {
        response += line;
    }
    pclose(fp);

     //返回
    struct sockaddr_in client;
    bzero(&client,sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));

}

●整体代码

服务端

#include "UdpServer.hpp"
#include <memory>
#include <fstream>
#include <unordered_map>
#include <signal.h>


static void Usage(string proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}


//demo2
void execCommand(int sockfd,string clientip,uint16_t clientport,string cmd)
{
    string response;
    FILE* fp = popen(cmd.c_str(),"r");
    if(fp == nullptr)response = cmd + "exe failed";
    char line[1024];
    while(fgets(line,sizeof(line),fp))
    {
        response += line;
    }
    pclose(fp);

     //返回
    struct sockaddr_in client;
    bzero(&client,sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));

}

//服务端
int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    uint16_t port = atoi(argv[1]);

    unique_ptr<UdpServer> user(new UdpServer(execCommand,port));
    user->initServer();
    user->start();
    return 0;
}

客户端

#include <iostream>
#include <cerrno>
#include <cstring>
#include <cerrno>
#include <cstdlib>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIDD_ERR
};

class UdpClient
{
public:
    UdpClient(const string& ip,const uint16_t& port)
        :_serverip(ip)
        ,_serverport(port)
        ,_sockfd(-1)
    {}

    void initClient()
    {
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd == -1)
        {
            cout<<"SOCKET_ERR:"<<errno<<" : "<<strerror(errno)<<endl;
            exit(SOCKET_ERR);
        }
        cout<<"socket success "<<_sockfd<<endl;

    }

    void run()
    {
        struct sockaddr_in server;
        bzero(&server,sizeof(server));

        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        string message;
        char cmdline[1024];
        while(true)
        {
            cout<<"[zxy@VM-2023_6_11]$ ";
            fgets(cmdline,sizeof(cmdline),stdin);
            message = cmdline;

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

            char buffer[1024];
            struct sockaddr_in temp;
            socklen_t temp_len = sizeof(temp);

            size_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&temp_len);
            if(n > 0)buffer[n] = '\0';

            cout<<"\n"<<buffer<<endl;
        }
    }

    ~UdpClient(){}
private:
    int _sockfd;//套接字fd
    string _serverip;//服务端ip
    uint16_t _serverport;//服务端端口号
};

聊天室

  ●思路分析:

1.增加onlineUsetr.hpp模块,主要包括用户的属性,如:用户ip、端口号(还可以添加昵称等)。用map存储用户标识和上线用户的结构体,提供添加和删除用户的成员方法,提供将消息发送给所有在线用户的成员方法们提供判断一个用户是否在线的成员方法。

2.服务端只增加聊天室业务,主要进行逻辑判断,如果用户未上线,打印提示信息。如果服务端发来的是上线或者下线请求,完成对应任务。如果是在线用户在正常发送消息,将该消息路由给所有的在线用户。

3.客户端创建了一个线程,主线程向服务端发送消息。创建的新线程接收服务端返回的信息。

4."业务“逻辑

OnlineUser onlineuser;
void routeMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    //上线
    if(message == "online")onlineuser.addUser(clientip,clientport);
    //下线
    if(message == "offline")onlineuser.delUser(clientip,clientport);

    //已在线,消息要路由给所有的在线用户 
    if(onlineuser.isOnline(clientip,clientport))
    {
        //消息的路由
        onlineuser.broadcastMessage(sockfd,clientip,clientport,message);
    }
    else//先上线
    {
        struct sockaddr_in client;
        bzero(&client,sizeof(client));

        client.sin_family = AF_INET;
        client.sin_port = htons(clientport);
        client.sin_addr.s_addr = inet_addr(clientip.c_str());

        string response = "你还没有上线,请先上线,运行:online";
        sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));

    }
}

●整体代码 

makefile

.PHONY:all
all:Server Client

Server:Server.cpp
    g++ -o $@ $^ -std=c++11
Client:Client.cpp
    g++ -o $@ $^ -lpthread -std=c++11

.PHONY:clean
clean:
    rm -rf Server Client

onlineUser.hpp

#pragma once

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

using namespace std;

class User
{
public:
    //构造
    User(const string& ip,const uint16_t& port)
        :_ip(ip)
        ,_port(port)
    {}
    ~User()
    {}
    //返回ip
    string ip(){return _ip;}
    //返回端口号
    uint16_t port(){return _port;}
private:
    string _ip;//用户ip
    uint16_t _port;//登录用户端口号
};
class OnlineUser
{
public:
    OnlineUser(){}
    //登录逻辑
    void addUser(const string &ip,const uint16_t &port)
    {
        string id = ip + "-" + to_string(port);
        users.insert(make_pair(id,User(ip,port)));
    }
    //删除用户
    void delUser(const string& ip,const uint16_t& port)
    {
        string id = ip + "-" + to_string(port);
        users.erase(id);
    }
    //判断用户是否上线
    bool isOnline(const string& ip,const uint16_t &port)
    {
        string id = ip + "-" + to_string(port);
        return users.find(id) == users.end() ? false : true;
    }
    //将消息路由给所有在线的用户
    void broadcastMessage(int sockfd,const string &ip,const uint16_t &port,const string& message)
    {
        for(auto& user : users)
        {
            struct sockaddr_in client;
            bzero(&client,sizeof(client));
            //填充结构体
            client.sin_family = AF_INET;
            client.sin_port = htons(user.second.port());
            client.sin_addr.s_addr = inet_addr(user.second.ip().c_str());

            string s = ip + "-" + to_string(port) + "#";
            s += message;
            //发送
            sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)&client,sizeof(client));
        }
    }
    ~OnlineUser(){}
private:
    unordered_map<string,User> users;
};

服务端

UdpServer.cpp

#include "Server.hpp"
#include "onlineUser.hpp"
#include <memory>

 static void Usage(string proc)
 {
     cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
 }

OnlineUser onlineuser;
void routeMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    //上线
    if(message == "online")onlineuser.addUser(clientip,clientport);
    //下线
    if(message == "offline")onlineuser.delUser(clientip,clientport);

    //已在线,消息要路由给所有的在线用户 
    if(onlineuser.isOnline(clientip,clientport))
    {
        //消息的路由
        onlineuser.broadcastMessage(sockfd,clientip,clientport,message);
    }
    else//先上线
    {
        struct sockaddr_in client;
        bzero(&client,sizeof(client));

        client.sin_family = AF_INET;
        client.sin_port = htons(clientport);
        client.sin_addr.s_addr = inet_addr(clientip.c_str());

        string response = "你还没有上线,请先上线,运行:online";
        sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));

    }
}

int main(int argc,char* argv[])
{
    //只需要一个端口号
    if(argc != 2)
    {
        //错误的使用,打印手册
        Usage(argv[0]);
        exit(USAGE_ERR);
    }

    //将端口号进行转换
    uint16_t pport = atoi(argv[1]);

    //创建服务端对象,并用智能指针管理
    unique_ptr<Server> user(new Server(routeMessage,pport));

    //创建套接字
    user->initServer();
    //启动服务器
    user->start();

    return 0;    
}

客户端

UdpClient.hpp

#pragma once

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

using namespace std;

enum{USAGE_ERR = 1,SOCK_ERR,BIND_ERR};

class Client
{
public:
    Client(const string& serverip,const uint16_t& serverport)
        :_serverip(serverip)
        ,_serverport(serverport)
        ,_sockfd(-1)
    {}

    //创建套接字
    void initClinet()
    {
        //创建socket
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd == -1)
        {
            cerr<<"socket errno"<<errno<<" : "<<strerror(errno)<<endl;
            exit(SOCK_ERR);
        }

        cout<<"socket success: "<<" : "<<_sockfd<<endl;

        //不用显示的绑定,操作系统完成这项工作。因为对于客户端而言,端口号是
        //多少不被关心
    }

    static void* readMessage(void* args)
    {
        int sockfd = *(static_cast<int*>(args));
        pthread_detach(pthread_self());

        while(true)
        {
            char buffer[1024];
            struct sockaddr_in temp;
            socklen_t temp_len = sizeof(temp);

            size_t n = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&temp_len);
            if(n >= 0)
            {
                buffer[n] = 0;
            } 
            cout<<buffer<<endl;
        }
        return nullptr;
    }

    //客户端发送请求
    void run()
    {
        pthread_create(&_reader,nullptr,readMessage,(void*)&_sockfd);

        struct sockaddr_in server;
        bzero(&server,sizeof(&server));

        //填充结构体
        server.sin_family=AF_INET;
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());
        server.sin_port = htons(_serverport);

        string message;
        char cmdline[1024];
        while(true)
        {
            //cout<<"Plase Say# "<<endl;
            //cin>>message;
            fprintf(stderr,"Enter# ");
            fflush(stderr);
            fgets(cmdline,sizeof(cmdline),stdin);
            cmdline[strlen(cmdline)-1] = 0;
            message = cmdline;

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

        }
    }

    ~Client(){}
private:
    int _sockfd;
    uint16_t _serverport;
    string _serverip;

    pthread_t _reader;
};

服务端和客户端1: 

客户端2(本机)

客户端3(另一台机器)

linux和windows通信

linux环境代码

server.hpp

#pragma once

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <functional>
#include <arpa/inet.h>
#include <netinet/in.h>
using namespace std;

static const string defaultIp = "0.0.0.0";
static const int gnum = 1024;

enum {USAGE_ERR = 1,SOCKET_ERR,BIND_ERR};

typedef function<void(int,string,uint16_t,string)> func_t;

class UdpServer
{
public:
    UdpServer(const func_t& func,const uint16_t& port,const string &ip = defaultIp)
        :_port(port)
        ,_ip(ip)
        ,_sockfd(-1)
        ,_callback(func)
    {}
    //创建socket
    void initServer()
    {
        //1.创建socket
        _sockfd = socket(AF_INET,SOCK_DGRAM,0);
        if(_sockfd == -1)
        {
            cerr<<"socket error:"<<errno<<" : "<<strerror(errno)<<endl;
            exit(SOCKET_ERR);
        }
        cout<<"socket success: "<<" : "<<_sockfd<<endl;

        //2.绑定端口号和ip
        struct sockaddr_in local;
        bzero(&local,sizeof(local));

        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str());

        int n = bind(_sockfd,(struct sockaddr*)&local,sizeof(local));
        if(n == -1)
        {
            cout<<"bind error:"<<errno<<" : "<<strerror(errno)<<endl;
            exit(BIND_ERR);
        }
    }
    //启动服务器
    void start()
    {
 
        char buffer[gnum];

        while(true)
        {
            //读取数据
            //struct sockaddr_in peer;
 
            //socklen_t len = sizeof(peer);

            struct sockaddr_in peer;
            socklen_t len = sizeof(peer); //必填
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr*)&peer, &len);
            //ssize_t s = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);

            if(s > 0)
            { 
                buffer[s] = 0;
                string clientip = inet_ntoa(peer.sin_addr);
                uint16_t clientport = ntohs(peer.sin_port);
                string message = buffer;

                cout<<clientip<<"["<<clientport<<"]# "<<message<<endl;
                _callback(_sockfd,clientip,clientport,message);
            }
            sleep(1);
        }
    }

    ~UdpServer(){}
private:
    uint16_t _port;
    string _ip;
    int _sockfd;
    func_t _callback;
};

server.cpp

#include <iostream>
#include "Server.hpp"
#include <memory>


using namespace std;
static void Usage(string proc)
{
    cout<<"\nUsage:\n\t"<<proc<<"local_port\n\n";
}

void routeMessage(int sockfd,string clientip,uint16_t clientport,string message)
{
    string response = message;
    response += "[server echo]";
    //返回
    struct sockaddr_in client;
    bzero(&client,sizeof(client));

    client.sin_family = AF_INET;
    client.sin_port = htons(clientport);
    client.sin_addr.s_addr = inet_addr(clientip.c_str());

        
    sendto(sockfd,response.c_str(),response.size(),0,(struct sockaddr*)&client,sizeof(client));
}

int main(int argc,char* argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    unique_ptr<UdpServer> user(new UdpServer(routeMessage,port));

    user->initServer();
    user->start();
    return 0;
}

windows环境代码(client)

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
#include <cstring>
#include <string>
#include <WinSock2.h>
#include <Ws2tcpip.h>

#pragma comment(lib,"ws2_32.lib")

using namespace std;

uint16_t serverport = 8080;
string serverip = "43.138.30.78";

int main()
{
	WSAData wsd;

	//启动Winsock
	if (WSAStartup(MAKEWORD(2, 2), &wsd) != 0)
	{
		cout << "WSAStartup Error = " << WSAGetLastError() << endl;
	}
	else
	{
		cout << "WSAStartup Success" << endl;
	}

	SOCKET csock = socket(AF_INET, SOCK_DGRAM, 0);
	if (csock == SOCKET_ERROR)
	{
		cout << "Sock Error = " << WSAGetLastError() << endl;
		return 1;
	}
	else
	{
		cout << "socket Success" << endl;
	}

	struct sockaddr_in server;
	memset(&server, 0, sizeof(server));
	server.sin_family = AF_INET;
	server.sin_port = htons(serverport);

	inet_pton(AF_INET, serverip.c_str(), &server.sin_addr.s_addr);//vs2013版本以上使用新的函数转换IP地址

#define NUM 1024
	char inbuffer[NUM];
	string line;
	while (true)
	{
		cout << "Please Enter# ";
		getline(cin, line);
		int n = sendto(csock, line.c_str(), line.size(), 0, (struct sockaddr*)&server, sizeof(server));
		if (n < 0)
		{
			cerr << "sendto error" << endl;
			break;
		}

		struct sockaddr_in peer;
		int peerlen = sizeof(peer);

		inbuffer[0] = 0;
		n = recvfrom(csock, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&peer, &peerlen);
		if (n > 0)
		{
			inbuffer[n] = 0;
			cout << "client# " << inbuffer << endl;
		}
		else break;
	}
	closesocket(csock);
	WSACleanup();
	return 0;
}

效果展示:

TCP网络程序

简单通信版本(TCP)

准备工作(接口学习、分析)

●最终效果:客户端发送消息,服务端读取到客户端发来的消息,在将收到消息回显给客户端。

 ●接口学习和准备工作

1.socket:创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)

 

a.socket()打开一个网络通讯端口,如果打开成功的话就像open一样返回一个文件描述符。打开失败的话返回-1。

b.应用程序可以像读文件一样用read/write在网络上收发数据。

c.对于IPv4,family参数指定为AF_INET。

d.对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。

 e.第三个参数指定为0即可。

2.bind:绑定端口号 (TCP/UDP, 服务器)。

 a.服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用bind绑定一个固定的网络地址和端口号;

b. bind()成功返回0,失败返回-1。

c. bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号;

d. struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。

e.结构体初始化,例如:

 3.listen:开始监听socket (TCP, 服务器)。

a. listen()声明sockfd处于监听状态, 并且最多允许有backlog个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略;

b.listen()成功返回0,失败返回-1;

4.accept:接收请求 (TCP, 服务器)。

a. 三次握手完成后, 服务器调用accept()接受连接。

b. 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。

c.  addr是一个传出参数,accept()返回时传出客户端的地址和端口号。

d. 如果给addr 参数传NULL,表示不关心客户端的地址。

e. addrlen参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区addr的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。

5.connect:建立连接 (TCP, 客户端)。

a.客户端需要调用connect()连接服务器;

b. connect和bind的参数形式一致, bind的参数是自己的地址, 而connect的参数是对方的地址;

c. connect()成功返回0,出错返回-1;

●整体代码

makefile

.PHONY:all
all:tcpClient tcpServer

tcpClient:tcpClient.cpp
    g++ -o $@ $^ -std=c++11
tcpServer:tcpServer.cpp
    g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
    rm -rf tcpServer tcpClient

 log.hpp

#pragma once

#include <iostream>
using namespace std;

#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

void logMessage(int level,const char* format)
{
    cout<<level<<": "<<format<<endl;
}

server.hpp

#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "log.hpp"
using namespace std;

enum
{
    USAGE_ERR = 1,
    SOCK_ERR,
    BIND_ERR,
    LISTEN_ERR
};

static const uint16_t pport = 8080;
static const int gbacklog = 5;
class tcpServer
{
public:
    tcpServer(const uint16_t& port = pport)
        :_port(port)
        ,_listensock(-1)
    {}
    void initServer()
    {
        //创建套接字
        _listensock = socket(AF_INET,SOCK_STREAM,0);
        if(_listensock < 0)
        {
            logMessage(FATAL,"creat socker error");
            exit(SOCK_ERR);
        }
        logMessage(NORMAL,"creat socker error");

        //填充结构体绑定网络信息
        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;

        int n = bind(_listensock,(struct sockaddr*)&local,sizeof(local));
        if(n < 0)
        {
            logMessage(FATAL,"bind socket error");
            exit(BIND_ERR);
        }
        logMessage(NORMAL,"bind socket success");

        //设置监听状态
        if(listen(_listensock,gbacklog) < 0)
        {
            logMessage(FATAL,"listen socket error");
            exit(LISTEN_ERR);
        }
        logMessage(NORMAL,"listen socket success");
    }

    void start()
    {
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            int sock = accept(_listensock,(struct sockaddr *)&peer,&len);
            if(sock < 0)
            {
                logMessage(ERROR,"accept error,next");
                continue;
            }

            serviceIO(sock);
            close(sock);
        }
    }

    void serviceIO(int sock)
    {
        char buffer[1024];
        while(true)
        {
            ssize_t n = read(sock,buffer,sizeof(buffer)-1);
            if(n > 0)
            {
                buffer[n] = 0;
                cout<<"recv message: "<<buffer<<endl;

                string outbuffer = buffer;
                outbuffer += "server[echo]";

                write(sock,outbuffer.c_str(),outbuffer.size());
            }
            else if(n == 0)
            {
                logMessage(NORMAL,"client quit,me too!");
                break;
            }
        }
    }

    ~tcpServer(){}
private:
    int _listensock;//揽客张三,监听链接到来获取新链接
    uint16_t _port;
};

server.cpp

#include "tcpServer.hpp"
#include <stdlib.h>
#include <memory>
using namespace std;

static void Usage(string prve)
{
    cout<<"\n\tUsage: loca_prot\n\n";
}

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

client.hpp

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

#define NUM 1024

using namespace std;

class tcpClient
{
public:
    tcpClient(const string& serverip,const uint16_t &serverport)
        :_sock(-1)
        ,_serverip(serverip)
        ,_serverport(serverport)
    {}
    void initClient()
    {
        //创建socket
        _sock = socket(AF_INET,SOCK_STREAM,0);
        if(_sock < 0)
        {
            cerr<<"socket creat error"<<endl;
            exit(2);
        }
    }
    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)
        {
            cerr<<"socket connect error"<<endl;
        }
        else
        {
            string msg;
            while(true)
            {
                cout<<"Enter# ";
                getline(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;
                    cout<<"Server回显# "<<buffer<<endl;
                }
                else
                {
                    break;
                }
            }
        }
    }
    ~tcpClient()
    {
        if(_sock >= 0)
        {
            close(_sock);
        }
    }
private:
    int _sock;
    string _serverip;
    uint16_t _serverport;
};

client.cpp

#include "tcpClient.hpp"
#include <memory>

using namespace std;
static void Usage(string prve)
{
    cout<<"\n\tUsage: loca_ip and loca_port\n\n";
}

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

 问题:客户端的访问是串行的。

版本改进

多进程版本

普通版本的基础上,在服务端做简单的改动。

整体代码 

server.cpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include "log.hpp"
using namespace std;

enum
{
    USAGE_ERR = 1,
    SOCK_ERR,
    BIND_ERR,
    LISTEN_ERR,
    ACCEPT_ERR
};

static const uint16_t pport = 8080;
static const int gbacklog = 5; 
class TcpServer
{
public:
    TcpServer(const uint16_t &port = pport)
        :_listensock(-1)
        ,_port(pport)
    {}
    void initServer()
    {
        //创建套接字
        _listensock = socket(AF_INET,SOCK_STREAM,0);
        if(_listensock < 0)
        {
            logMessage(FATAL,"creat socker error");
            exit(SOCK_ERR);
        }
        logMessage(NORMAL,"creat socker success");
        //填充结构体,绑定网络信息
        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;

        int n = bind(_listensock,(struct sockaddr*)&local,sizeof(local)); 
        if(n < 0)
        {
            logMessage(FATAL,"bind socket error");
            exit(BIND_ERR);
        }
        logMessage(NORMAL,"bind socket success");
        //开始监听
        if(listen(_listensock,gbacklog) <0)
        {
            logMessage(FATAL,"listen socket error");
            exit(LISTEN_ERR);            
        }
        logMessage(NORMAL,"listen socket success");
    }
    void start()
    {
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
            if(sock < 0)
            {
                logMessage(ACCEPT_ERR,"accept error,next");
                continue;
            }

            pid_t id = fork();
            if(id == 0)
            {
                //子进程
                close(_listensock);
                if(fork()>0) exit(0);

                serviceIO(sock);
                close(sock);
                exit(0);
            }
            
            close(sock);
            
            pid_t ret = waitpid(id,nullptr,0);
            if(ret>0)
            {
                cout<<"waitsuccess: "<<ret<<endl;
            }
        }
    }
    void serviceIO(int sock)
    {
        char buffer[1024];
        while(true)
        {
            ssize_t n = read(sock,buffer,sizeof(buffer)-1);
            if(n>0)
            {
                buffer[n] = 0;
                cout<<"recv message: "<<buffer<<endl;

                string outbuffer = buffer;
                outbuffer += "server[echo]";

                write(sock,outbuffer.c_str(),outbuffer.size());
            }
            else if(n == 0)
            {
                logMessage(NORMAL,"client quit,me too!");
                break;
            }
        }
    }
    ~TcpServer(){}
private:
    int _listensock;//揽客张三,负责建立链接
    uint16_t _port;//服务端绑定端口号
};

多线程版本

普通版本的基础上,在服务端做简单的改动。

TreeadDate

创建多线程

线程方法

 server.cpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>
#include "log.hpp"
using namespace std;

enum
{
    USAGE_ERR = 1,
    SOCK_ERR,
    BIND_ERR,
    LISTEN_ERR,
    ACCEPT_ERR
};

static const uint16_t pport = 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 = pport)
        :_listensock(-1)
        ,_port(pport)
    {}
    void initServer()
    {
        //创建套接字
        _listensock = socket(AF_INET,SOCK_STREAM,0);
        if(_listensock < 0)
        {
            logMessage(FATAL,"creat socker error");
            exit(SOCK_ERR);
        }
        logMessage(NORMAL,"creat socker success");
        //填充结构体,绑定网络信息
        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;

        int n = bind(_listensock,(struct sockaddr*)&local,sizeof(local)); 
        if(n < 0)
        {
            logMessage(FATAL,"bind socket error");
            exit(BIND_ERR);
        }
        logMessage(NORMAL,"bind socket success");
        //开始监听
        if(listen(_listensock,gbacklog) <0)
        {
            logMessage(FATAL,"listen socket error");
            exit(LISTEN_ERR);            
        }
        logMessage(NORMAL,"listen socket success");
    }
    void start()
    {
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
            if(sock < 0)
            {
                logMessage(ACCEPT_ERR,"accept error,next");
                continue;
            }
            logMessage(NORMAL,"accept success");
            cout<<"sock: "<<sock<<endl;

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

        }
    }

    static void* threadRoutine(void* args)
    {
        //线程分离
        pthread_detach(pthread_self());

        ThreadData* td = static_cast<ThreadData*>(args);
        td->_self->serviceIO(td->_sock);
        
        close(td->_sock);
        delete td;
        return nullptr;
    }

    void serviceIO(int sock)
    {
        char buffer[1024];
        while(true)
        {
            ssize_t n = read(sock,buffer,sizeof(buffer)-1);
            if(n>0)
            {
                buffer[n] = 0;
                cout<<"recv message: "<<buffer<<endl;

                string outbuffer = buffer;
                outbuffer += "server[echo]";

                write(sock,outbuffer.c_str(),outbuffer.size());
            }
            else if(n == 0)
            {
                logMessage(NORMAL,"client quit,me too!");
                break;
            }
        }
    }
    ~TcpServer(){}
private:
    int _listensock;//揽客张三,负责建立链接
    uint16_t _port;//服务端绑定端口号
};

线程池版本

●除了服务端和客户端外,还增加了线程池,“简陋”任务,RAII风格锁的模块。

Server.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>
#include <pthread.h>
#include "SingletonTP.hpp"
#include "log.hpp"
#include "Task.hpp"
using namespace std;

enum
{
    USAGE_ERR = 1,
    SOCK_ERR,
    BIND_ERR,
    LISTEN_ERR,
    ACCEPT_ERR
};

static const uint16_t pport = 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 = pport)
        :_listensock(-1)
        ,_port(pport)
    {}
    void initServer()
    {
        //创建套接字
        _listensock = socket(AF_INET,SOCK_STREAM,0);
        if(_listensock < 0)
        {
            logMessage(FATAL,"creat socker error");
            exit(SOCK_ERR);
        }
        logMessage(NORMAL,"creat socker success");
        //填充结构体,绑定网络信息
        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;

        int n = bind(_listensock,(struct sockaddr*)&local,sizeof(local)); 
        if(n < 0)
        {
            logMessage(FATAL,"bind socket error");
            exit(BIND_ERR);
        }
        logMessage(NORMAL,"bind socket success");
        //开始监听
        if(listen(_listensock,gbacklog) <0)
        {
            logMessage(FATAL,"listen socket error");
            exit(LISTEN_ERR);            
        }
        logMessage(NORMAL,"listen socket success");
    }
    void start()
    {
        //线程池版本,线程初始化
        ThreadPool<CalTask>::getInstance()->run();
        logMessage(NORMAL,"Thread init success");
     
        while(true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            int sock = accept(_listensock,(struct sockaddr*)&peer,&len);
            if(sock < 0)
            {
                logMessage(ACCEPT_ERR,"accept error,next");
                continue;
            }
            logMessage(NORMAL,"accept success");
            cout<<"sock: "<<sock<<endl;

            //线程池版本
            ThreadPool<CalTask>::getInstance()->push(CalTask(sock,serviceIO));
        }
    }

    ~TcpServer(){}
private:
    int _listensock;//揽客张三,负责建立链接
    uint16_t _port;//服务端绑定端口号
};

线程池(单例模式)

#pragma once

#include <cstring>
#include <vector>
#include <string>
#include <mutex>
#include <queue>
#include "Thread.hpp"
#include "LockGuard.hpp"

const int gnum = 10;

using namespace zxy;

template<class T>
class ThreadPool;

template<class T>
class ThreadDate
{
public:
    ThreadDate(ThreadPool<T>* tp,const std::string& n)
        :threadpool(tp),name(n)   
    {}
public:
    ThreadPool<T>* threadpool;//指向线程池对象的指针
    std::string name;//线程名
};

template<class T>
class ThreadPool
{
private:
     //构造,将一批线程放入线程池中
    //初始化锁和条件变量
    ThreadPool(const int& num = gnum)
        :_num(num)
    {
        //初始化条件变量和互斥锁
        pthread_mutex_init(&_mutex,nullptr);
        pthread_cond_init(&_cond,nullptr);
        //创建一批线程
        for(int i = 0;i<_num;++i)
        {
            _threads.push_back(new Thread());
        }
    }
    void operator=(const ThreadPool &) = delete;
    ThreadPool(const ThreadPool &) = delete;

    //回调方法
    static void* handlerTask(void* args)
    {
        ThreadDate<T>* td = static_cast<ThreadDate<T>*>(args);
        while(true)
        {
            T t;
            {
                LockGuard lockguard(td->threadpool->mutex());
                while(td->threadpool->IsQueueEmpty())
                {
                    //没有任务
                    td->threadpool->threadWait();
                }
                //走到这一定有任务
                t = td->threadpool->pop();
            }//临界资源域
            //std::cout<<td->name<<"获取一个任务"<<t.ToString()<<"start..."  ;
            t();
        }
        delete td;
        return nullptr;
    }
public:
    //添加任务
    void push(const T& in)
    {
        LockGuard lockguard(&_mutex);
        //向任务队列中添加数据
        _task_queue.push(in);
        //走到这里,任务队列中一定有数据
        pthread_cond_signal(&_cond);
    }
    //获取任务
    T pop()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }
    //启动线程
    void run()
    {
        for(auto& thd : _threads)
        {
            ThreadDate<T>* td = new ThreadDate<T>(this,thd->TreadName());

            thd->Start(handlerTask,td);
            std::cout<<thd->TreadName()<<"start..."<<std::endl;
        }
    }
    //析构
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);

        for(const auto &t : _threads)
        {
            delete t;
        }
    }

    static ThreadPool<T>* getInstance()
    {
        if(tp == nullptr)
        {
            _singleton_lock.lock();
            if(tp == nullptr)
            {
                tp = new ThreadPool<T>();
            }
            _singleton_lock.unlock();
        }
        return tp;
    }

public:
    void lockQueue(){pthread_mutex_lock(&_mutex);}
    void unlockQueue(){pthread_mutex_unlock(&_mutex);}
    bool IsQueueEmpty(){return _task_queue.empty();}
    void threadWait(){pthread_cond_wait(&_cond,&_mutex);}
    pthread_mutex_t* mutex(){return &_mutex;}
private:
    int _num;
    std::vector<Thread*> _threads;//线程池
    std::queue<T> _task_queue;//任务队列
    pthread_mutex_t _mutex;//互斥量
    pthread_cond_t _cond;//条件变量

    static ThreadPool<T>* tp;
    static std::mutex _singleton_lock;
};

template <class T>
ThreadPool<T>* ThreadPool<T>::tp = nullptr;

template <class T>
std::mutex ThreadPool<T>::_singleton_lock;

通信任务

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <map>
#include <functional>

    void serviceIO(int sock)
    {
        char buffer[1024];
        while(true)
        {
            ssize_t n = read(sock,buffer,sizeof(buffer)-1);
            if(n>0)
            {
                buffer[n] = 0;
                cout<<"recv message: "<<buffer<<endl;

                string outbuffer = buffer;
                outbuffer += "server[echo]";

                write(sock,outbuffer.c_str(),outbuffer.size());
            }
            else if(n == 0)
            {
                logMessage(NORMAL,"client quit,me too!");
                break;
            }
        }
    }

class CalTask
{
    //指向任务的函数指针
    typedef std::function<void(int)> func_t;
public:
    CalTask(){}
    //构造
    CalTask(int sock,func_t func)
        :_sock(sock)
        ,_func(func)
    {
    }
    //处理任务后的结果
    void operator()()
    {
        _func(_sock);
    }
private:
    int _sock;
    func_t _func;
};

RAII风格的锁

#pragma once

#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex(pthread_mutex_t* lock_p = nullptr)
    :_lock_p(lock_p)
    {}
    void lock()
    {
        if(_lock_p)
        {
            pthread_mutex_lock(_lock_p);
        }
    }
    void unlock()
    {
        if(_lock_p)
        {
            pthread_mutex_unlock(_lock_p);
            
        }
    }
    ~Mutex(){}    
private:
    pthread_mutex_t* _lock_p;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* mutex):_mutex(mutex)
    {
        _mutex.lock();//在构造函数中进行加锁
    }
    ~LockGuard()
    {
        _mutex.unlock();//在析构函数中进行解锁
    }
private:
    Mutex _mutex;
};

日志

#pragma once

#include <iostream>
#include <stdarg.h>
#include <string>
#include <ctime>
#include <unistd.h>
using namespace std;

#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"

#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";
        default: return nullptr;
    }
}

void logMessage(int lever,const char* format,...)
{
    //cout<<lever<<" : "<<format<<endl;
#define NUM 1024
    char logprefix[NUM];
    snprintf(logprefix,sizeof(logprefix),"[%s][%ld][pid:%d]",
            to_levelstr(lever),(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;
    FILE* log = fopen(LOG_NORMAL,"a");
    FILE* err = fopen(LOG_ERR,"a");

    if(log != nullptr && err != nullptr)
    {
        FILE* curr = nullptr;
        if(lever == DEBUG || lever == NORMAL || lever == WARNING)curr = log;
        if(lever == ERROR || lever == FATAL) curr = err;
        if(curr)fprintf(curr,"%s%s\n",logprefix,logcontent);

        fclose(log);
        fclose(err);
    }
}

 

守护进程

 1.守护进程(Daemon)是在操作系统后台运行的一种特殊进程,通常用于执行一些系统级别的任务,如网络服务、定时任务、日志记录等。它们通常在系统启动时启动,并一直运行,直到系统关闭或手动停止。

2.守护进程通常不与用户交互,也不会在屏幕上显示任何信息。它们在后台默默地运行,不会占用用户的资源或干扰用户的工作。守护进程通常以root权限运行,因为它们需要访问系统资源和执行特权操作。

3.守护进程的好处是可以在系统空闲时执行任务,避免了用户手动执行任务的繁琐和错误。同时,它们可以在系统崩溃或重启后自动恢复,确保系统的稳定性和可靠性。

 

daemon.hpp

#pragma once
#include <unistd.h>
#include <signal.h>
#include <cstdlib>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEV "/dev/null"

void daemomSelf(const char* currPath = nullptr)
{
    //让调用进程忽略掉异常的信号
    signal(SIGPIPE,SIG_IGN);
    //让自己不是组长
    if(fork() > 0)
    {
        exit(0);
    }
    //子进程,守护进程当组长
    pid_t n = setsid();
    assert(n != 1);
    //守护进程脱离终端
    int fd = open(DEV,O_RDWR);
    if(fd > 0)
    {
        dup2(fd,0);
        dup2(fd,1);
        dup2(fd,2);

        close(fd);
    }
    else
    {
        close(0);
        close(1);
        close(2);
    }
    //可选:执行路径发生改变
    if(currPath)chdir(currPath);
}

 服务器关掉,依然能回显消息。

 查看日志

 

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

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

相关文章

AB32VG1:SDK_AB53XX_V061(3)IO口复用功能的补充资料

文章目录 1.IO口功能复用表格2.功能映射寄存器 FUNCTION03.功能映射寄存器 FUNCTION14.功能映射寄存器 FUNCTION2 AB5301A的官方数据手册很不完善&#xff0c;没有开放出来。我通过阅读源码补充了一些关于IO口功能复用寄存器的资料。 官方寄存器文档&#xff1a;《 AB32VG1_Re…

chatgpt赋能python:Python怎么截屏Windows

Python怎么截屏Windows Python是一种高级编程语言&#xff0c;具有快速开发、易于学习、可移植性强等优点&#xff0c;因此在实现Windows屏幕截图方面也是一种非常强大的工具。 什么是Windows屏幕截图&#xff1f; Windows屏幕截图是将当前屏幕或窗口的图像保存成文件或剪贴…

Android BlueToothBLE入门(二)——设备的连接和通讯(附Demo源码地址)

学更好的别人&#xff0c; 做更好的自己。 ——《微卡智享》 本文长度为7870字&#xff0c;预计阅读12分钟 前言 接《Android BlueToothBLE入门&#xff08;一&#xff09;——低功耗蓝牙介绍》上篇&#xff0c;这篇文章主要就是来做Demo实现Android两台设备的数据通讯。 实现效…

chatgpt赋能python:Python如何快速处理数据

Python如何快速处理数据 在当今数据爆炸的时代&#xff0c;数据处理已经成为一项非常重要的任务。因此&#xff0c;如何快速、高效地处理数据就成为了每个数据科学家、数据工程师以及数据分析师的必备技能之一。而Python正是其中的佼佼者。 为什么选择Python进行数据处理 Py…

Spring事物失效的八大场景

1.方法内的自调用&#xff1a;spring事物是基于aop的&#xff0c;只要使用代理对象调用某个方法时&#xff0c;spring事物才能生效&#xff0c;而在一个方法内使用this.xxx()时。this并不是代理对象&#xff0c;所以会失效&#xff08;实际上是transaction注解失效&#xff09;…

用程序控制对文本的复制和粘贴pyperclip模块

【小白从小学Python、C、Java】 【等级考试500强双证书考研】 【Python-数据分析】 用程序控制对文本的复制和粘贴 pyperclip模块 选择题 关于下列代码说法错误的是&#xff1f; import pyperclip print(【执行】pyperclip.copy("Python 太强大了&#xff01;")) p…

读发布!设计与部署稳定的分布式系统(第2版)笔记01_生产环境的生存法则

1. 系统“应该”做什么 1.1. 添加所需特性 2. 系统“不应该”做什么 2.1. 崩溃 2.2. 停止响应 2.3. 丢失数据 2.4. 侵犯隐私 2.5. 损失金钱 2.6. 摧毁公司 2.7. “杀死”客户 3. QA部门的测试 3.1. 团队的大部分工作是想方设法地通过测试 3.2. 做了敏捷、务实和自动…

【设计模式与范式:行为型】57 | 观察者模式(下):如何实现一个异步非阻塞的EventBus框架?

上一节课中&#xff0c;我们学习了观察者模式的原理、实现、应用场景&#xff0c;重点介绍了不同应用场景下&#xff0c;几种不同的实现方式&#xff0c;包括&#xff1a;同步阻塞、异步非阻塞、进程内、进程间的实现方式。 同步阻塞是最经典的实现方式&#xff0c;主要是为了…

GreenPlum分布式集群部署实战

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

GC演变过程、三色标记法、大白话讲解G1

文章目录 GC演变过程并发垃圾回收需要解决的问题怎么确定一个垃圾?并发收集存在的问题 三色标记法CMS垃圾收集器G1垃圾收集器介绍,主要特点优点使用注意点 GC演变过程 在Java中,垃圾收集一直是一个非常重要的组成部分, 到目前为止,垃圾收集器已经有十种了, 在不停的优化. 那为…

GoogleTest之Actions的用法

目录 返回值Actions的组合验证复杂参数mock副作用改变mock对象的行为设置返回类型的默认值使用自定义函数作为Actions 通用示例 namespace mock_action { class Foo { public:virtual ~Foo() {}virtual int& GetBar() 0; // 1virtual int GetPointerValue() 0; //…

Linux CentOS7虚拟机配置静态IP并允许上网的配置方法

文章目录 前言一、开启本地电脑VMnet8二、Linux配置静态IP1. NAT模式设置2. 开启虚拟机登录root用户3. 执行命令设置静态IP4. 重启网卡① 重启网卡 (正常)② 重启网卡 (异常)③ 解决方式&#xff1a;禁用NetworkManager 5. 查看ip6. 本地电脑cmd窗口ping虚拟机7. 虚拟机ping本地…

Golang每日一练(leetDay0095) 第一个错误的版本、完全平方数

目录 278. 第一个错误的版本 First Bad Version &#x1f31f; 279. 完全平方数 Perfect Squares &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Rust每日一练 专栏 Golang每日一练 专栏 Python每日一练 专栏 C/C每日一练 专栏 Java每日…

springboot的数据访问和数据视图

当使用 Spring Boot 进行数据访问时&#xff0c;我们可以选择使用 MyBatis 或 JPA&#xff08;Java Persistence API&#xff09;来实现增删改查操作。下面我将分别给出使用这两种方式整合数据访问的详细步骤和示例&#xff0c;同时结合 Thymeleaf 实现数据展现。 方式一: 使用…

AI实战营:语义分割与MMSegmentation

目录 OpenMMLab图像分割算法库MMSegmentation 深度学习下的语义分割模型 全卷积网络Fully Convolutional Network 201 ​编辑 上下文信息与PSPNet模型 空洞卷积与DeepLab模型 语义分割算法总结 语义分割 前沿算法 SegFormer K-Net MaskFormer Mask2Former Seg…

PySide2 or PyQt5???该如何抉择???

1. 区别 Qt库里面有非常强大的图形界面开发库&#xff0c;但是Qt库是C语言开发的&#xff0c;PySide2、PyQt5可以让我们通过Python语言使用Qt。 但是 PySide2、PyQt5 这两者有什么区别呢&#xff1f; 可以形象地这样说&#xff1a; PySide2 是Qt的 亲儿子 &#xff0c; PyQt5 …

面向对象程序设计|静态友元

题目一&#xff1a;复数运算 题目描述&#xff1a; 复数类的声明如下 要求如下&#xff1a; 1. 实现复数类和友元函数addCom和outCom&#xff1b; 2. 参考addCom函数为复数类增加一个友元函数minusCom&#xff0c;用于实现两个复数的减法&#xff1b; 3. 在main函数中&…

待办事项JS:DHTMLX To Do List 1.2 cRACK

DHTMLX To Do List用于有效任务管理的DHTMLX JavaScript 待办事项列表 使用 JavaScript/HTML 5 中的待办事项列表来管理您的任务并确定其优先级。将组件连接到 DHTMLX 甘特图&#xff0c;并允许用户以简单直观的方式快速组织他们的业务流程。 DHTMLX JavaScript 待办事项列表的…

chatgpt赋能python:Python建模块最佳实践

Python建模块最佳实践 Python是一种灵活、易于使用的编程语言&#xff0c;因其强大的模块化支持和丰富的第三方模块而备受推崇。本文将介绍Python建模块的最佳实践&#xff0c;以便帮助开发人员创建可重用、可维护和易于测试的Python模块。 基本概念 在Python中&#xff0c;…

创业很长时间以后…

创业过很长时间以后…综合能力是有滴 创业和打工后的思维习惯 为了效率&#xff0c;一般情况是这样滴 趣讲大白话&#xff1a;区别还是有滴 【趣讲信息科技195期】 **************************** 创业还是很难滴 每年成立很多新公司 有很多公司关门 公司平均生存时间&#xff1…