<TCP Network Programming>——《Computer Network》

Table of contents

1. TCP network program

1.1 TCP socket API 

1.1.1 socket():

1.1.2 bind():​

1.1.3 listen(): ​

1.1.4 accept():

1.1.5 connect():

2. Encapsulate TCP socket

2.1 Implement a simple English-to-Chinese function

3. Simple TCP network program (multi-process version)

4. Simple TCP network program (multi-threaded version)

5. The thread pool version of the TCP server

5.1 TCP protocol communication process

5.1.1 Server initialization

5.1.2 The process of establishing a connection

5.1.3 Process of data transmission

5.1.4 Disconnection process

6. Comparison of TCP and UDP

7. TCP network programming (multi-version, multi-function)

version 1:

Version 2:

Version 3:

Postscript: ●Due to the limited level of the author, the article inevitably contains some errors. Readers are welcome to correct me. There are many slang words in this article. I sincerely hope for your advice!

                                                                           ——By Author: Xinxiao·Old Knowing


TCP network communication GIF:

 

1. TCP network program

1.1 TCP socket API 

The socket API used in the program is introduced below , and these functions are all in sys/socket.h .

1.1.1 socket():

  • socket() opens a network communication port , and if successful , returns a file descriptor just like open() ;
  • Applications can use read/write to send and receive data on the network just like reading and writing files ;
  • Returns -1 if the socket() call fails ;
  • For IPv4, the family parameter is specified as AF_INET;
  • For the TCP protocol , the type parameter is specified as SOCK_STREAM, indicating a stream-oriented transport protocol
  • The introduction of the protocol parameter is omitted , just specify it as 0

 1.1.2 bind():

  • The network address and port number monitored by the server program are usually fixed , and the client program can initiate a connection to the server after knowing the address and port number of the server program; the server needs to call bind to bind a fixed network address and port number ;
  • bind() returns 0 on success and -1 on failure .
  • The function of bind() is to bind the parameters sockfd and myaddr together , so that sockfd , a file descriptor for network communication, listens to the address and port number described by myaddr ;
  • As mentioned earlier , struct sockaddr * is a general pointer type , the myaddr parameter can actually accept the sockaddr structure of multiple protocols, and their lengths are different , so the third parameter addrlen is required to specify the length of the structure ;
The myaddr parameter is initialized like this in our program :
1. Clear the entire structure ;
2. Set the address type to AF_INET;
3. The network address is INADDR_ANY. This macro represents any local IP address , because the server may have multiple network cards , and each network card may also be bound to multiple IP addresses . This setting can monitor on all IP addresses until it matches a certain IP address. When a client establishes a connection, it is determined which IP address to use ;
4. The port number is SERV_PORT, which we define as 9999;

1.1.3 listen():

  • listen() declares that sockfd is in the listening state , and allows at most backlog clients to be in the connection waiting state . If more connection requests are received, it will be ignored. The setting here will not be too large ( usually 5). For details, students will learn After in-depth study ;
  • listen() returns 0 on success , -1 on failure

1.1.4 accept():

 

  • After the three-way handshake is completed , the server calls accept() to accept the connection ;
  • If there is no connection request from the client when the server calls accept() , it will block and wait until a client connects ;
  • addr is an outgoing parameter , the address and port number of the outgoing client when accept() returns ;
  • If you pass NULL to the addr parameter , it means you don't care about the client's address ;
  • The addrlen parameter is an incoming and outgoing parameter (value-result argument), which is provided by the caller , the length of the buffer addr to avoid buffer overflow problems, and the outgoing one is the actual length of the client address structure ( It is possible that the buffer provided by the caller is not full );
Our server program structure is like this :
Understanding the return value of accecpt : an example of restaurant solicitation

1.1.5 connect():

  • The client needs to call connect() to connect to the server ;
  • The parameter forms of connect and bind are the same , the difference is that the parameter of bind is its own address , while the parameter of connect is the address of the other party ;
  • connect() returns 0 successfully , returns -1 on error;

2. Encapsulate TCP socket

2.1 Implement a simple English-to-Chinese function

Interface demo: 

 

tcp_socket.hpp:
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <cassert>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
#define CHECK_RET(exp) \
    if (!(exp))        \
    {                  \
        return false;  \
    }
class TcpSocket
{
public:
    TcpSocket()
        : fd_(-1)
    {
    }
    TcpSocket(int fd)
        : fd_(fd)
    {
    }
    bool Socket()
    {
        fd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (fd_ < 0)
        {
            perror("socket");
            return false;
        }
        printf("open fd = %d\n", fd_);
        return true;
    }
    bool Close() const
    {
        close(fd_);
        printf("close fd = %d\n", fd_);
        return true;
    }
    bool Bind(const std::string &ip, uint16_t port) const
    {
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(ip.c_str());
        addr.sin_port = htons(port);
        int ret = bind(fd_, (sockaddr *)&addr, sizeof(addr));
        if (ret < 0)
        {
            perror("bind");
            return false;
        }
        return true;
    }
    bool Listen(int num) const
    {
        int ret = listen(fd_, num);
        if (ret < 0)
        {
            perror("listen");
            return false;
        }
        return true;
    }
    bool Accept(TcpSocket *peer, std::string *ip = NULL, uint16_t *port = NULL) const
    {
        sockaddr_in peer_addr;
        socklen_t len = sizeof(peer_addr);
        int new_sock = accept(fd_, (sockaddr *)&peer_addr, &len);
        if (new_sock < 0)
        {
            perror("accept");
            return false;
        }
        printf("accept fd = %d\n", new_sock);
        peer->fd_ = new_sock;
        if (ip != NULL)
        {
            *ip = inet_ntoa(peer_addr.sin_addr);
        }
        if (port != NULL)
        {
            *port = ntohs(peer_addr.sin_port);
        }
        return true;
    }
    bool Recv(std::string *buf) const
    {
        buf->clear();
        char tmp[1024 * 10] = {0};
        // [注意!] 这里的读并不算很严谨, 因为一次 recv 并不能保证把所有的数据都全部读完
        // 参考 man 手册 MSG_WAITALL 节.
        ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);
        if (read_size < 0)
        {
            perror("recv");
            return false;
        }
        if (read_size == 0)
        {
            return false;
        }
        buf->assign(tmp, read_size);
        return true;
    }
    bool Send(const std::string &buf) const
    {
        ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);
        if (write_size < 0)
        {
            perror("send");
            return false;
        }
        return true;
    }
    bool Connect(const std::string &ip, uint16_t port) const
    {
        sockaddr_in addr;
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(ip.c_str());
        addr.sin_port = htons(port);
        int ret = connect(fd_, (sockaddr *)&addr, sizeof(addr));
        if (ret < 0)
        {
            perror("connect");
            return false;
        }
        return true;
    }
    int GetFd() const
    {
        return fd_;
    }

private:
    int fd_;
};

 TCP Generic Server

tcp_server.hpp:

 

#pragma once
#include <functional>
#include "tcp_socket.hpp"
typedef std::function<void(const std::string &req, std::string *resp)> Handler;
class TcpServer
{
public:
	TcpServer(const std::string &ip, uint16_t port) : ip_(ip), port_(port)
	{
	}
	bool Start(Handler handler)
	{
		// 1. 创建 socket;
		CHECK_RET(listen_sock_.Socket());
		// 2. 绑定端口号
		CHECK_RET(listen_sock_.Bind(ip_, port_));
		// 3. 进行监听
		CHECK_RET(listen_sock_.Listen(5));
		// 4. 进入事件循环
		for (;;)
		{
			// 5. 进行 accept
			TcpSocket new_sock;
			std::string ip;
			uint16_t port = 0;
			if (!listen_sock_.Accept(&new_sock, &ip, &port))
			{
				continue;
			}
			printf("[client %s:%d] connect!\n", ip.c_str(), port);
			// 6. 进行循环读写
			for (;;)
			{
				std::string req;
				// 7. 读取请求. 读取失败则结束循环
				bool ret = new_sock.Recv(&req);
				if (!ret)
				{
					printf("[client %s:%d] disconnect!\n", ip.c_str(), port);
					// [注意!] 需要关闭 socket
					new_sock.Close();
					break;
				}
				// 8. 计算响应
				std::string resp;
				handler(req, &resp);
				// 9. 写回响应
				new_sock.Send(resp);
				printf("[%s:%d] req: %s, resp: %s\n", ip.c_str(), port, req.c_str(), resp.c_str());
			}
		}
		return true;
	}

private:
	TcpSocket listen_sock_;
	std::string ip_;
	uint64_t port_;
};

English to Chinese server

EnToCh.cc:

 

#include <unordered_map>
#include "tcp_server.hpp"
std::unordered_map<std::string, std::string> g_dict;
void Translate(const std::string &req, std::string *resp)
{
	auto it = g_dict.find(req);
	if (it == g_dict.end())
	{
		*resp = "未找到";
		return;
	}
	*resp = it->second;
	return;
}
int main(int argc, char *argv[])
{
	if (argc != 3)
	{
		printf("Usage ./dict_server [ip] [port]\n");
		return 1;
	}
	// 1. 初始化词典
	g_dict.insert(std::make_pair("hello", "你好"));
	g_dict.insert(std::make_pair("world", "世界"));
	g_dict.insert(std::make_pair("linux", "开源的操作系统"));
	// 2. 启动服务器
	TcpServer server(argv[1], atoi(argv[2]));
	server.Start(Translate);
	return 0;
}
TCP Generic Client
tcp_client.hpp:
#pragma once
#include "tcp_socket.hpp"
class TcpClient
{
public:
	TcpClient(const std::string &ip, uint16_t port) : ip_(ip), port_(port)
	{
		// [注意!!] 需要先创建好 socket
		sock_.Socket();
	}
	~TcpClient()
	{
		sock_.Close();
	}
	bool Connect()
	{
		return sock_.Connect(ip_, port_);
	}
	bool Recv(std::string *buf)
	{
		return sock_.Recv(buf);
	}
	bool Send(const std::string &buf)
	{
		return sock_.Send(buf);
	}

private:
	TcpSocket sock_;
	std::string ip_;
	uint16_t port_;
};

English to Chinese client

dict_client.cc:
#include "tcp_client.hpp"
#include <iostream>
int main(int argc, char *argv[])
{
	if (argc != 3)
	{
		printf("Usage ./dict_client [ip] [port]\n");
		return 1;
	}
	TcpClient client(argv[1], atoi(argv[2]));
	bool ret = client.Connect();
	if (!ret)
	{
		return 1;
	}
	for (;;)
	{
		std::cout << "请输入要查询的单词:" << std::endl;
		std::string word;
		std::cin >> word;
		if (!std::cin)
		{
			break;
		}
		client.Send(word);
		std::string result;
		client.Recv(&result);
		std::cout << result << std::endl;
	}
	return 0;
}

 Since the client does not need a fixed port number, there is no need to call bind(), and the client's port number is automatically assigned by the kernel .

Note :
  • The client is not allowed to call bind(), but it is not necessary to call bind() to fix a port number . Otherwise, if multiple clients are started on the same machine, the port number will be occupied and the connection cannot be established correctly ;
  • The server does not have to call bind(), but if the server does not call bind(), the kernel will automatically assign a listening port to the server . The port number is different every time the server is started, and the client will encounter trouble when connecting to the server ;
Test for multiple connections
Start another client , try to connect to the server , and find that the second client cannot communicate with the server correctly .
The reason for the analysis is that after we have accepted a request , we have been trying to read in a while loop , and did not continue to call acceptcpt, resulting in the inability to accept new requests.
Our current TCP can only handle one connection , which is unscientific

3. Simple TCP network program ( multi-process version )

Support multiple connections by creating sub-processes for each request ;
tcp_process_server.hpp:
 
#pragma once
#include <functional>
#include <signal.h>
#include "tcp_socket.hpp"
typedef std::function<void(const std::string &req, std::string *resp)> Handler;
// 多进程版本的 Tcp 服务器
class TcpProcessServer
{
public:
	TcpProcessServer(const std::string &ip, uint16_t port) : ip_(ip), port_(port)
	{
		// 需要处理子进程
		signal(SIGCHLD, SIG_IGN);
	}
	void ProcessConnect(const TcpSocket &new_sock, const std::string &ip, uint16_t port,
						Handler handler)
	{
		int ret = fork();
		if (ret > 0)
		{
			// father
			// 父进程不需要做额外的操作, 直接返回即可.
			// 思考, 这里能否使用 wait 进行进程等待?
			// 如果使用 wait , 会导致父进程不能快速再次调用到 accept, 仍然没法处理多个请求
			// [注意!!] 父进程需要关闭 new_sock
			new_sock.Close();
			return;
		}
		else if (ret == 0)
		{
			// child
			// 处理具体的连接过程. 每个连接一个子进程
			for (;;)
			{
				std::string req;
				bool ret = new_sock.Recv(&req);
				if (!ret)
				{
					// 当前的请求处理完了, 可以退出子进程了. 注意, socket 的关闭在析构函数中就完成了
					printf("[client %s:%d] disconnected!\n", ip.c_str(), port);
					exit(0);
				}
				std::string resp;
				handler(req, &resp);
				new_sock.Send(resp);
				printf("[client %s:%d] req: %s, resp: %s\n", ip.c_str(), port, req.c_str(), resp.c_str());
			}
		}
		else
		{
			perror("fork");
		}
	}
	bool Start(Handler handler)
	{
		// 1. 创建 socket;
		CHECK_RET(listen_sock_.Socket());
		// 2. 绑定端口号
		CHECK_RET(listen_sock_.Bind(ip_, port_));
		// 3. 进行监听
		CHECK_RET(listen_sock_.Listen(5));
		// 4. 进入事件循环
		for (;;)
		{
			// 5. 进行 accept
			TcpSocket new_sock;
			std::string ip;
			uint16_t port = 0;
			if (!listen_sock_.Accept(&new_sock, &ip, &port))
			{
				continue;
			}
			printf("[client %s:%d] connect!\n", ip.c_str(), port);
			ProcessConnect(new_sock, ip, port, handler);
		}
		return true;
	}

private:
	TcpSocket listen_sock_;
	std::string ip_;
	uint64_t port_;
};

 dict_server.cc slightly modified

Change the TcpServer class to the TcpProcessServer class

4. Simple TCP network program ( multi-threaded version )

Through each request , create a thread to support multiple connections ;
tcp_thread_server.hpp:
#pragma once
#include <functional>
#include <pthread.h>
#include "tcp_socket.hpp"
typedef std::function<void(const std::string &, std::string *)> Handler;
struct ThreadArg
{
	TcpSocket new_sock;
	std::string ip;
	uint16_t port;
	Handler handler;
};
class TcpThreadServer
{
public:
	TcpThreadServer(const std::string &ip, uint16_t port) : ip_(ip), port_(port)
	{
	}
	bool Start(Handler handler)
	{
		// 1. 创建 socket;
		CHECK_RET(listen_sock_.Socket());
		// 2. 绑定端口号
		CHECK_RET(listen_sock_.Bind(ip_, port_));
		// 3. 进行监听
		CHECK_RET(listen_sock_.Listen(5));
		// 4. 进入循环
		for (;;)
		{
			// 5. 进行 accept
			ThreadArg *arg = new ThreadArg();
			arg->handler = handler;
			bool ret = listen_sock_.Accept(&arg->new_sock, &arg->ip, &arg->port);
			if (!ret)
			{
				continue;
			}
			printf("[client %s:%d] connect\n", arg->ip.c_str(), arg->port);
			// 6. 创建新的线程完成具体操作
			pthread_t tid;
			pthread_create(&tid, NULL, ThreadEntry, arg);
			pthread_detach(tid);
		}
		return true;
	}
	// 这里的成员函数为啥非得是 static?
	static void *ThreadEntry(void *arg)
	{
		// C++ 的四种类型转换都是什么?
		ThreadArg *p = reinterpret_cast<ThreadArg *>(arg);
		ProcessConnect(p);
		// 一定要记得释放内存!!! 也要记得关闭文件描述符
		p->new_sock.Close();
		delete p;
		return NULL;
	}
	// 处理单次连接. 这个函数也得是 static
	static void ProcessConnect(ThreadArg *arg)
	{
		// 1. 循环进行读写
		for (;;)
		{
			std::string req;
			// 2. 读取请求
			bool ret = arg->new_sock.Recv(&req);
			if (!ret)
			{
				printf("[client %s:%d] disconnected!\n", arg->ip.c_str(), arg->port);
				break;
			}
			std::string resp;
			// 3. 根据请求计算响应
			arg->handler(req, &resp);
			// 4. 发送响应
			arg->new_sock.Send(resp);
			printf("[client %s:%d] req: %s, resp: %s\n", arg->ip.c_str(), arg->port, req.c_str(), resp.c_str());
		}
	}

private:
	TcpSocket listen_sock_;
	std::string ip_;
	uint16_t port_;
};

  Makefile:

 

.PHONY:all
all:dictclient entoch

dictclient: dict_client.cc
	g++ -o $@ $^ -std=c++11
entoch:EnToCh.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f dictclient entoch

5. The thread pool version of the TCP server

According to the thread pool learned before , modify the server to the thread pool version by yourself .

5.1 TCP protocol communication process

love example
The following figure is the general flow of the client / server program based on the TCP protocol :

5.1.1 Server initialization

  • Call socket to create a file descriptor ;
  • Call bind to bind the current file descriptor and ip/port together ; if this port is already occupied by other processes , bind will fail;
  • Call listen to declare the current file descriptor as a server file descriptor , ready for the following accept ;
  • Call acceptcpt, and block , waiting for the client to connect ;

5.1.2 The process of establishing a connection

  • Call socket to create a file descriptor ;
  • Call connect to initiate a connection request to the server ;
  • connect will issue a SYN segment and block waiting for the server to reply ; ( first time )
  • The server receives the SYN from the client , and will respond with a SYN-ACK segment indicating " agreed to establish a connection "; ( second time )
  • After receiving the SYN-ACK , the client will return from connect() and reply with an ACK segment at the same time ; ( the third time )
This process of establishing a connection is usually called a three-way handshake ;

5.1.3 Process of data transmission

  • After the connection is established , the TCP protocol provides full-duplex communication services ; the so-called full-duplex means that in the same connection , at the same time , both communicating parties can write data at the same time; the relative concept is called half-duplex . At the same time , only one party can write data ;
  • The server calls read() immediately after returning from accept() . Reading the socket is like reading a pipeline . If no data arrives, it will block and wait ;
  • At this time, the client calls write() to send a request to the server , and the server returns from read() after receiving it , and processes the client's request . During this period, the client calls read() to block and wait for the server's response ;
  • The server calls write() to send the processing result back to the client , and calls read() again to block and wait for the next request ;
  • After the client receives it , it returns from read() , sends the next request , and so on ;

5.1.4 Disconnection process

  • If the client has no more requests , call close() to close the connection , and the client will send a FIN segment to the server ( for the first time );
  • At this time, after the server receives FIN , it will respond with an ACK, and read will return 0 ( the second time );
  • After read returns , the server knows that the client has closed the connection , and also calls close to close the connection . At this time, the server will send a FIN to the client ; ( the third time )
  • The client receives the FIN, and then returns an ACK to the server ; ( the fourth time )
This disconnection process , commonly known as the four wave
When learning the socket API , pay attention to how the application program and the TCP protocol layer interact :
  • What action does the TCP protocol layer complete when the application calls a socket function , for example, calling connect() will send a SYN segment
  • How does the application know the state change of the TCP protocol layer , for example, returning from a blocked socket function indicates that the TCP protocol has received some segments , and for example, if read() returns 0 , it indicates that the FIN segment has been received

6. Comparison of TCP and UDP

  • Reliable transport vs unreliable transport
  • Connection vs Connectionless
  • byte stream vs datagram

7. TCP network programming (multi-version, multi-function)

version 1:

Run the demo:

serverTcp.cc:

#include "util.hpp"

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), sock_(-1)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1. 创建socket
        sock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (sock_ < 0)
        {
            logMessage(FATAL, "socket: %s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket: %s,  sock_: %d", strerror(errno), sock_);

        // 2. bind绑定
        // 2.1 填充服务器信息
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;      //进行网络通信
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
        // 2.2 本地socket信息,写入sock_对应的内核区域
        if (bind(sock_, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s,  sock_: %d", strerror(errno), sock_);

        // 3. 监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(sock_, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s,  sock_: %d", strerror(errno), sock_);
        // 允许别人来连接你了
    }
    void loop()
    {
        while (true)
        {
            // 4. 获取连接, accept 的返回值是一个新的socket fd ??

            // logMessage(DEBUG, "server 提供 service start ...");
            // sleep(1);
        }
    }

private:
    // sock
    int sock_;
    // port
    uint16_t port_;
    // ip
    std::string ip_;
};

int main()
{
    ServerTcp svr(8080);
    svr.init();
    svr.loop();
    return 0;
}

clientTcp.cc:

#include "util.hpp"

int main()
{
    return 0;
}

log.hpp:

#pragma once

#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};

// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{
    assert(level >= DEBUG);
    assert(level <= FATAL);

    char *name = getenv("USER");

    char logInfo[1024];
    va_list ap; // ap -> char*
    va_start(ap, format);

    vsnprintf(logInfo, sizeof(logInfo)-1, format, ap);

    va_end(ap); // ap = NULL


    FILE *out = (level == FATAL) ? stderr:stdout;

    fprintf(out, "%s | %u | %s | %s\n", \
        log_level[level], \
        (unsigned int)time(nullptr),\
        name == nullptr ? "unknow":name,\
        logInfo);

    // char *s = format;
    // while(s){
    //     case '%':
    //         if(*(s+1) == 'd')  int x = va_arg(ap, int);
    //     break;
    // }
}

 util.hpp:

#pragma once

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

#define SOCKET_ERR 1
#define BIND_ERR   2
#define LISTEN_ERR 3

makefile:

.PHONY:all
all:clientTcp serverTcp

clientTcp: clientTcp.cc
	g++ -o $@ $^ -std=c++11
serverTcp:serverTcp.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f serverTcp clientTcp

Version 2:

Execute the demo:

serverTcp.cc:

#include "util.hpp"
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>

class ServerTcp; // 申明一下ServerTcp

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;
public:
    ThreadData(uint16_t port, std::string ip, int sock,  ServerTcp *ts)
        : clientPort_(port), clinetIp_(ip), sock_(sock),this_(ts)
    {}
};

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string &ip = "") : port_(port), ip_(ip), listenSock_(-1)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1. 创建socket
        listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)
        {
            logMessage(FATAL, "socket: %s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_);

        // 2. bind绑定
        // 2.1 填充服务器信息
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
        // 2.2 本地socket信息,写入sock_对应的内核区域
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);

        // 3. 监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(listenSock_, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);
        // 允许别人来连接你了
    }
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self()); //设置线程分离
        ThreadData *td = static_cast<ThreadData*>(args);
        td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
        delete td;
        return nullptr;
    }
    void loop()
    { 
        // signal(SIGCHLD, SIG_IGN); // only Linux

        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4. 获取连接, accept 的返回值是一个新的socket fd ??
            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                // 获取链接失败
                logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1 获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
            // 5 提供服务, echo -> 小写 -> 大写
            // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
            //transService(serviceSock, peerIp, peerPort);
            
            // 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
            //     close(listenSock_); //建议
            //     //子进程
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0); // 进入僵尸
            // }
            // // 父进程
            // close(serviceSock); //这一步是一定要做的!

            // 5.1 v1.1 版本 -- 多进程版本  -- 也是可以的
            // 爷爷进程
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     // 爸爸进程
            //     close(listenSock_);//建议
            //     // 又进行了一次fork,让 爸爸进程
            //     if(fork() > 0) exit(0);
            //     // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0);
            // }
            // // 父进程
            // close(serviceSock); //这一步是一定要做的!
            // // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
            // pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式
            // assert(ret > 0);
            // (void)ret;

            // 5.2 v2 版本 -- 多线程
            // 这里不需要进行关闭文件描述符吗??不需要啦
            // 多线程是会共享文件描述符表的!
            ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, (void*)td);

            // waitpid(); 默认是阻塞等待!WNOHANG
            // 方案1

            // logMessage(DEBUG, "server 提供 service start ...");
            // sleep(1);
        }
    }
    // 大小写转化服务
    // TCP && UDP: 支持全双工
    void transService(int sock, const std::string &clientIp, uint16_t clientPort)
    {
        assert(sock >= 0);
        assert(!clientIp.empty());
        assert(clientPort >= 1024);

        char inbuffer[BUFFER_SIZE];
        while (true)
        {
            ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); //我们认为我们读到的都是字符串
            if (s > 0)
            {
                // read success
                inbuffer[s] = '\0';
                if(strcasecmp(inbuffer, "quit") == 0)
                {
                    logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                    break;
                }
                logMessage(DEBUG, "trans before: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
                // 可以进行大小写转化了
                for(int i = 0; i < s; i++)
                {
                    if(isalpha(inbuffer[i]) && islower(inbuffer[i])) 
                        inbuffer[i] = toupper(inbuffer[i]);
                }
                logMessage(DEBUG, "trans after: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);

                write(sock, inbuffer, strlen(inbuffer));
            }
            else if (s == 0)
            {
                // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
                // s == 0: 代表对方关闭,client 退出
                logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                break;
            }
            else
            {
                logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
                break;
            }
        }

        // 只要走到这里,一定是client退出了,服务到此结束
        close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
        logMessage(DEBUG, "server close %d done", sock);
    }

private:
    // sock
    int listenSock_;
    // port
    uint16_t port_;
    // ip
    std::string ip_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;
    std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n" << std::endl;

}

// ./ServerTcp local_port local_ip
int main(int argc, char *argv[])
{
    if(argc != 2 && argc != 3 )
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if(argc == 3) ip = argv[2];

    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}

clientTcp.cc:

#include "util.hpp"
// 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!!
// 3. 需要listen吗?不需要的!
// 4. 需要accept吗?不需要的!

volatile bool quit = false;

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
              << std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    // 1. 创建socket SOCK_STREAM
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket: " << strerror(errno) << std::endl;
        exit(SOCKET_ERR);
    }

    // 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽
    // 2.1 先填充需要连接的远端主机的基本信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverPort);
    inet_aton(serverIp.c_str(), &server.sin_addr);
    // 2.2 发起请求,connect 会自动帮我们进行bind!
    if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0)
    {
        std::cerr << "connect: " << strerror(errno) << std::endl;
        exit(CONN_ERR);
    }
    std::cout << "info : connect success: " << sock << std::endl;

    std::string message;
    while (!quit)
    {
        message.clear();
        std::cout << "请输入你的消息>>> ";
        std::getline(std::cin, message);
        if (strcasecmp(message.c_str(), "quit") == 0)
            quit = true;

        ssize_t s = write(sock, message.c_str(), message.size());
        if (s > 0)
        {
            message.resize(1024);
            ssize_t s = read(sock, (char *)(message.c_str()), 1024);
            if (s > 0)
                message[s] = 0;
            std::cout << "Server Echo>>> " << message << std::endl;
        }
        else if (s <= 0)
        {
            break;
        }
    }
    close(sock);
    return 0;
}

log.hpp:

#pragma once

#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};

// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{
    assert(level >= DEBUG);
    assert(level <= FATAL);

    char *name = getenv("USER");

    char logInfo[1024];
    va_list ap; // ap -> char*
    va_start(ap, format);

    vsnprintf(logInfo, sizeof(logInfo)-1, format, ap);

    va_end(ap); // ap = NULL


    FILE *out = (level == FATAL) ? stderr:stdout;

    fprintf(out, "%s | %u | %s | %s\n", \
        log_level[level], \
        (unsigned int)time(nullptr),\
        name == nullptr ? "unknow":name,\
        logInfo);

    // char *s = format;
    // while(s){
    //     case '%':
    //         if(*(s+1) == 'd')  int x = va_arg(ap, int);
    //     break;
    // }
}

util.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

#define SOCKET_ERR 1
#define BIND_ERR   2
#define LISTEN_ERR 3
#define USAGE_ERR  4
#define CONN_ERR   5

#define BUFFER_SIZE 1024

makefile:

.PHONY:all
all:clientTcp serverTcp

clientTcp: clientTcp.cc
	g++ -o $@ $^ -std=c++11
serverTcp:serverTcp.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f serverTcp clientTcp

 Version 3:

 Generally, it works as a server, and the servers that provide external services work in the server as a daemon process (elf process). Once started, unless the user actively closes it. The daemon needs to be written by itself.

A function setsid() must be called to set the calling process as an independent session, but the leader of the process group (usually the first process) cannot call setsid(). Because once it is called, it becomes a new other session , then the original process group members have no "leadership".

How not to be a team leader? Can be used as a second process in a process group! Conventional practice, fork() the child process, the child process is no longer the group leader process, and can successfully call setsid().

 

 serverTcp.cc:

#include "util.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
#include "daemonize.hpp"

#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>

class ServerTcp; // 申明一下ServerTcp

// 大小写转化服务
// TCP && UDP: 支持全双工
void transService(int sock, const std::string &clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);

    char inbuffer[BUFFER_SIZE];
    while (true)
    {
        ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1); //我们认为我们读到的都是字符串
        if (s > 0)
        {
            // read success
            inbuffer[s] = '\0';
            if (strcasecmp(inbuffer, "quit") == 0)
            {
                logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
                break;
            }
            logMessage(DEBUG, "trans before: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);
            // 可以进行大小写转化了
            for (int i = 0; i < s; i++)
            {
                if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
                    inbuffer[i] = toupper(inbuffer[i]);
            }
            logMessage(DEBUG, "trans after: %s[%d]>>> %s", clientIp.c_str(), clientPort, inbuffer);

            write(sock, inbuffer, strlen(inbuffer));
        }
        else if (s == 0)
        {
            // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
            // s == 0: 代表对方关闭,client 退出
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }

    // 只要走到这里,一定是client退出了,服务到此结束
    close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
    logMessage(DEBUG, "server close %d done", sock);
}

void execCommand(int sock, const std::string &clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);

    char command[BUFFER_SIZE];
    while (true)
    {
        ssize_t s = read(sock, command, sizeof(command) - 1); //我们认为我们读到的都是字符串
        if (s > 0)
        {
            command[s] = '\0';
            logMessage(DEBUG, "[%s:%d] exec [%s]", clientIp.c_str(), clientPort, command);
            // 考虑安全
            std::string safe = command;
            if((std::string::npos != safe.find("rm")) || (std::string::npos != safe.find("unlink")))
            {
                break;
            }
            // 我们是以r方式打开的文件,没有写入
            // 所以我们无法通过dup的方式得到对应的结果
            FILE *fp = popen(command, "r");
            if(fp == nullptr)
            {
                logMessage(WARINING, "exec %s failed, beacuse: %s", command, strerror(errno));
                break;
            }
            char line[1024];
            while(fgets(line, sizeof(line)-1, fp) != nullptr)
            {
                write(sock, line, strlen(line));
            }
            // dup2(fd, 1);
            // dup2(sock, fp->_fileno);
            // fflush(fp);
            pclose(fp);
            logMessage(DEBUG, "[%s:%d] exec [%s] ... done", clientIp.c_str(), clientPort, command);
        }
        else if (s == 0)
        {
            // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
            // s == 0: 代表对方关闭,client 退出
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }

    // 只要走到这里,一定是client退出了,服务到此结束
    close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
    logMessage(DEBUG, "server close %d done", sock);
}

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;

public:
    ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts)
        : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
    {
    }
};

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string &ip = "")
        : port_(port),
          ip_(ip),
          listenSock_(-1),
          tp_(nullptr)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1. 创建socket
        listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)
        {
            logMessage(FATAL, "socket: %s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_);

        // 2. bind绑定
        // 2.1 填充服务器信息
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
        // 2.2 本地socket信息,写入sock_对应的内核区域
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);

        // 3. 监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(listenSock_, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);
        // 运行别人来连接你了

        // 4. 加载线程池
        tp_ = ThreadPool<Task>::getInstance();
    }
    // static void *threadRoutine(void *args)
    // {
    //     pthread_detach(pthread_self()); //设置线程分离
    //     ThreadData *td = static_cast<ThreadData *>(args);
    //     td->this_->transService(td->sock_, td->clinetIp_, td->clientPort_);
    //     delete td;
    //     return nullptr;
    // }
    void loop()
    {
        // signal(SIGCHLD, SIG_IGN); // only Linux
        tp_->start();
        logMessage(DEBUG, "thread pool start success, thread num: %d", tp_->threadNum());
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4. 获取连接, accept 的返回值是一个新的socket fd ??
            // 4.1 listenSock_: 监听 && 获取新的链接-> sock
            // 4.2 serviceSock: 给用户提供新的socket服务
            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                // 获取链接失败
                logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1 获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
            // 5 提供服务, echo -> 小写 -> 大写
            // 5.0 v0 版本 -- 单进程 -- 一旦进入transService,主执行流,就无法进行向后执行,只能提供完毕服务之后才能进行accept
            // transService(serviceSock, peerIp, peerPort);

            // 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
            //     close(listenSock_); //建议
            //     //子进程
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0); // 进入僵尸
            // }
            // // 父进程
            // close(serviceSock); //这一步是一定要做的!

            // 5.1 v1.1 版本 -- 多进程版本  -- 也是可以的
            // 爷爷进程
            // pid_t id = fork();
            // if(id == 0)
            // {
            //     // 爸爸进程
            //     close(listenSock_);//建议
            //     // 又进行了一次fork,让 爸爸进程
            //     if(fork() > 0) exit(0);
            //     // 孙子进程 -- 就没有爸爸 -- 孤儿进程 -- 被系统领养 -- 回收问题就交给了系统来回收
            //     transService(serviceSock, peerIp, peerPort);
            //     exit(0);
            // }
            // // 父进程
            // close(serviceSock); //这一步是一定要做的!
            // // 爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
            // pid_t ret = waitpid(id, nullptr, 0); //就用阻塞式
            // assert(ret > 0);
            // (void)ret;

            // 5.2 v2 版本 -- 多线程
            // 这里不需要进行关闭文件描述符吗??不需要啦
            // 多线程是会共享文件描述符表的!
            // ThreadData *td = new ThreadData(peerPort, peerIp, serviceSock, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, threadRoutine, (void*)td);

            // 5.3 v3 版本 --- 线程池版本
            // 5.3.1 构建任务
            // 5.3 v3.1
            // Task t(serviceSock, peerIp, peerPort, std::bind(&ServerTcp::transService, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));
            // tp_->push(t);

            // 5.3 v3.2
            // Task t(serviceSock, peerIp, peerPort, transService);
            // tp_->push(t);
            // 5.3 v3.3
            Task t(serviceSock, peerIp, peerPort, execCommand);
            tp_->push(t);

            // waitpid(); 默认是阻塞等待!WNOHANG
            // 方案1

            // logMessage(DEBUG, "server 提供 service start ...");
            // sleep(1);
        }
    }

private:
    // sock
    int listenSock_;
    // port
    uint16_t port_;
    // ip
    std::string ip_;
    // 引入线程池
    ThreadPool<Task> *tp_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;
    std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n"
              << std::endl;
}

// ./ServerTcp local_port local_ip
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
        ip = argv[2];
    
    daemonize(); // 我们的进程就会成为守护进程

    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}

 clientTcp.cc:

#include "util.hpp"
// 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!!
// 3. 需要listen吗?不需要的!
// 4. 需要accept吗?不需要的!

volatile bool quit = false;

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
              << std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    // 1. 创建socket SOCK_STREAM
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket: " << strerror(errno) << std::endl;
        exit(SOCKET_ERR);
    }

    // 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽
    // 2.1 先填充需要连接的远端主机的基本信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverPort);
    inet_aton(serverIp.c_str(), &server.sin_addr);
    // 2.2 发起请求,connect 会自动帮我们进行bind!
    if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0)
    {
        std::cerr << "connect: " << strerror(errno) << std::endl;
        exit(CONN_ERR);
    }
    std::cout << "info : connect success: " << sock << std::endl;

    std::string message;
    while (!quit)
    {
        message.clear();
        std::cout << "请输入你的消息>>> ";
        std::getline(std::cin, message); // 结尾不会有\n
        if (strcasecmp(message.c_str(), "quit") == 0)
            quit = true;

        ssize_t s = write(sock, message.c_str(), message.size());
        if (s > 0)
        {
            message.resize(1024);
            ssize_t s = read(sock, (char *)(message.c_str()), 1024);
            if (s > 0)
                message[s] = 0;
            std::cout << "Server Echo>>> " << message << std::endl;
        }
        else if (s <= 0)
        {
            break;
        }
    }
    close(sock);
    return 0;
}

 log.hpp:

#pragma once

#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

const char *log_level[] = {"DEBUG", "NOTICE", "WARINING", "FATAL"};

#define LOGFILE "serverTcp.log"

// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{
    assert(level >= DEBUG);
    assert(level <= FATAL);

    char *name = getenv("USER");

    char logInfo[1024];
    va_list ap; // ap -> char*
    va_start(ap, format);

    vsnprintf(logInfo, sizeof(logInfo) - 1, format, ap);

    va_end(ap); // ap = NULL

    // 每次打开太麻烦
    umask(0);
    int fd = open(LOGFILE, O_WRONLY | O_CREAT | O_APPEND, 0666);
    assert(fd >= 0);

    FILE *out = (level == FATAL) ? stderr : stdout;

    dup2(fd, 1);
    dup2(fd, 2);

    fprintf(out, "%s | %u | %s | %s\n",
            log_level[level],
            (unsigned int)time(nullptr),
            name == nullptr ? "unknow" : name,
            logInfo);

    fflush(out); // 将C缓冲区中的数据刷新到OS
    fsync(fd);   // 将OS中的数据尽快刷盘 

    close(fd);
    // char *s = format;
    // while(s){
    //     case '%':
    //         if(*(s+1) == 'd')  int x = va_arg(ap, int);
    //     break;
    // }
}

 ThreadPool.hpp:

#pragma once

#include <iostream>
#include <cassert>
#include <queue>
#include <memory>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#include <sys/prctl.h>
#include "Lock.hpp"

using namespace std;

int gThreadNum = 15;

template <class T>
class ThreadPool
{
private:
    ThreadPool(int threadNum = gThreadNum) : threadNum_(threadNum), isStart_(false)
    {
        assert(threadNum_ > 0);
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T>&) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        static Mutex mutex;
        if (nullptr == instance) //仅仅是过滤重复的判断
        {
            LockGuard lockguard(&mutex); //进入代码块,加锁。退出代码块,自动解锁
            if (nullptr == instance)
            {
                instance = new ThreadPool<T>();
            }
        }

        return instance;
    }
    //类内成员, 成员函数,都有默认参数this
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        // prctl(PR_SET_NAME, "follower"); // 更改线程名称
        while (1)
        {
            tp->lockQueue();
            while (!tp->haveTask())
            {
                tp->waitForTask();
            }
            //这个任务就被拿到了线程的上下文中
            T t = tp->pop();
            tp->unlockQueue();
            t(); // 让指定的先处理这个任务
        }
    }
    void start()
    {
        assert(!isStart_);
        for (int i = 0; i < threadNum_; i++)
        {
            pthread_t temp;
            pthread_create(&temp, nullptr, threadRoutine, this);
        }
        isStart_ = true;
    }
    void push(const T &in)
    {
        lockQueue();
        taskQueue_.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    int threadNum()
    {
        return threadNum_;
    }

private:
    void lockQueue() { pthread_mutex_lock(&mutex_); }
    void unlockQueue() { pthread_mutex_unlock(&mutex_); }
    bool haveTask() { return !taskQueue_.empty(); }
    void waitForTask() { pthread_cond_wait(&cond_, &mutex_); }
    void choiceThreadForHandler() { pthread_cond_signal(&cond_); }
    T pop()
    {
        T temp = taskQueue_.front();
        taskQueue_.pop();
        return temp;
    }

private:
    bool isStart_;
    int threadNum_;
    queue<T> taskQueue_;
    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *instance;
    // const static int a = 100;
};

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

 Lock.hpp:

#pragma once

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

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&lock_, nullptr);
    }
    void lock()
    {
        pthread_mutex_lock(&lock_);
    }
    void unlock()
    {
        pthread_mutex_unlock(&lock_);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&lock_);
    }

private:
    pthread_mutex_t lock_;
};

class LockGuard
{
public:
    LockGuard(Mutex *mutex) : mutex_(mutex)
    {
        mutex_->lock();
        std::cout << "加锁成功..." << std::endl;
    }

    ~LockGuard()
    {
        mutex_->unlock();
        std::cout << "解锁成功...." << std::endl;
    }

private:
    Mutex *mutex_;
};

 Task.hpp:

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include "log.hpp"

class Task
{
public:
    //等价于
    // typedef std::function<void (int, std::string, uint16_t)> callback_t;
    using callback_t = std::function<void (int, std::string, uint16_t)>;
private:
    int sock_; // 给用户提供IO服务的sock
    uint16_t port_;  // client port
    std::string ip_; // client ip
    callback_t func_;  // 回调方法
public:
    Task():sock_(-1), port_(-1)
    {}
    Task(int sock, std::string ip, uint16_t port, callback_t func)
    : sock_(sock), ip_(ip), port_(port), func_(func)
    {}
    void operator () ()
    {
        logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 开始啦...",\
            pthread_self(), ip_.c_str(), port_);

        func_(sock_, ip_, port_);

        logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 结束啦...",\
            pthread_self(), ip_.c_str(), port_);
    }
    ~Task()
    {}
};

 util.hpp:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

#define SOCKET_ERR 1
#define BIND_ERR   2
#define LISTEN_ERR 3
#define USAGE_ERR  4
#define CONN_ERR   5

#define BUFFER_SIZE 1024

daemonize.hpp:

#pragma once

#include <cstdio>
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void daemonize()
{
    int fd = 0;
    // 1. 忽略SIGPIPE
    signal(SIGPIPE, SIG_IGN);
    // 2. 更改进程的工作目录
    // chdir();
    // 3. 让自己不要成为进程组组长
    if (fork() > 0)
        exit(1);
    // 4. 设置自己是一个独立的会话
    setsid();
    // 5. 重定向0,1,2
    //"/dev/null"——>Linux"文件黑洞/垃圾桶"
    if ((fd = open("/dev/null", O_RDWR)) != -1) // fd == 3
    {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);
        // 6. 关闭掉不需要的fd
        if(fd > STDERR_FILENO) close(fd);
    }
}

 makefile:

.PHONY:all
all:clientTcp serverTcpd

clientTcp: clientTcp.cc
	g++ -o $@ $^ -std=c++11
serverTcpd:serverTcp.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f serverTcpd clientTcp

Postscript:
●Due to the limited level of the author, there are inevitable errors in the article. Readers are welcome to correct the article, and the article is full of slang, and I sincerely hope to give advice!

                                                                           ——By Author: Xinxiao·Old Knowing

 

Guess you like

Origin blog.csdn.net/m0_57859086/article/details/128608118