(。・∀・)ノ゙嗨!你好这里是ky233的主页:这里是ky233的主页,欢迎光临~
https://blog.csdn.net/ky233?type=blog
点个关注不迷路⌯'▾'⌯
目录
不做过多介绍,前文有
SOCK_STREM,TCP用这个
- #include
/* See NOTES */ - #include
-
- int listen(int sockfd, int backlog);
将TCP套接字设置成监听状态
- #include
/* See NOTES */ - #include
-
- int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
获取链接
- #include
/* See NOTES */ - #include
-
- int connect(int sockfd, const struct sockaddr *addr,
- socklen_t addrlen);
根据创建好的套接字,让客户端具有连接服务器的能力,会自动绑客户端的ip和端口
- #include
- #include
-
- ssize_t send(int sockfd, const void *buf, size_t len, int flags);
基于TCP向目标返回消息
参数四:一般为0
5.recv
- #include
- #include
-
- ssize_t recv(int sockfd, void *buf, size_t len, int flags);
基于TCP来读消息
参数四:一般为0
- #pragma once
- #include "tcp_server.hpp"
- #include
-
-
-
- static void usage(std::string proc)
- {
- std::cout<<"\nUsage:"<<"proc\n"<
- }
-
-
- int main(int argc,char* argv[])
- {
- if (argc!=2)
- {
- usage(argv[0]);
- exit(1);
- }
-
- //存储端口号
- uint16_t port=atoi(argv[1]);
- std::unique_ptr
svr(new TcpServer(port)) ; - svr->initServer();
- svr->start();
- return 0;
- }
2.tcp_server.hpp
- #pragma once
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "log.hpp"
-
- static void service(int sock, const std::string &client_ip, const uint16_t &client_port)
- {
- // echo server
- char buffer[1024];
- while (1)
- {
- // 先读取
- // read和write可以直接被使用,套接字也就是文件描述符
- ssize_t s = read(sock, &buffer, sizeof buffer);
- if (s > 0)
- {
- buffer[s] = 0; // 直接将发送过来的数据当作字符串打印
- std::cout << client_ip << ":" << client_port << "# " << buffer << std::endl;
- }
- else if (s = 0) // 对端关闭连接
- {
- logMessage(NORMAL, "%s:%d shutdown, me too!", client_ip.c_str(), client_port);
- break;
- }
- else // 读取失败
- {
- logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
- break;
- }
- write(sock, &buffer, sizeof buffer);
- }
- }
- class TcpServer
- {
-
- const static int g_backlog = 20; // 不能太大也不能太小
- public:
- TcpServer(uint16_t port, std::string ip = "")
- : port_(port), ip_(ip), listensock_(-1)
- {
- }
-
- void initServer()
- {
- // 1.创建套接字
- listensock_ = socket(AF_INET, SOCK_STREAM, 0);
- if (listensock_ < 0)
- {
- logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
- exit(2);
- }
- logMessage(NORMAL, "create socket success, listensock_: %d", listensock_);
-
- // 2.bind,将服务器和端口信息进行绑定
- struct sockaddr_in local;
- memset(&local, 0, sizeof(local));
- local.sin_family = AF_INET;
- local.sin_port = htons(port_);
- local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
- if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)))
- {
- logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
- exit(3);
- }
-
- // 因为TCP是面向连接的,所以正是通信的时候需要先建立连接
- // 3.开始监听
- if (listen(listensock_, g_backlog) < 0)
- {
- logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
- exit(4);
- }
- logMessage(NORMAL, "init server success");
- }
-
- void start()
- {
- signal(SIGCHLD,SIG_IGN);//子进程退出会像父进程发送SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
- while (1)
- {
- // 4.获取连接,服务器要获取客户端的连接,这样来方便返回
- struct sockaddr_in src;
- socklen_t len = sizeof(src);
- // listensock_和listensock_有什么区别呢?
- // 其中listensock_通过这个套接字只是为了把底层的连接获取上来
- // 而listensock_才是真正的进行连接的
- int listensock = accept(listensock_, (struct sockaddr *)&src, &len);
- if (listensock < 0)
- {
- logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
- continue;
- }
- // 获取成功了
- uint16_t client_port = ntohs(src.sin_port);
- std::string client_ip = inet_ntoa(src.sin_addr);
- logMessage(NORMAL, "link success, listensock_: %d | %s : %d |\n",
- listensock, client_ip.c_str(), client_port);
- // 开始进行通信
- // 单进程循环版,只能够一次处理一个客户端,处理完成了才能处理下一个,很明显是不可以的
- //这是因为单进程的,下面函数是个死循环,所以不退出就没办法获取新的连接
- //service(listensock_, client_ip, client_port);
- //多进程版本
- //创建子进程,那么子进程可以打开父进程曾经打开的fd吗?
- //是可以的,父进程创建子进程时候,子进程的文件描述符表,会继承自于父进程,所以会看到同一个文件
- pid_t id= fork();
- assert(id!=-1);
- if(id==0)//子进程
- {
- //子进程会不会继承父进程打开的文件与文件描述符fd呢?这是肯定的
- //子进程是来提供服务的,需不需要监听socket呢?
- service(listensock_, client_ip, client_port);
-
- close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
- exit(0);//子进程退出会造成僵尸进程
- }
- close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
- //父进程
- //waitpid是阻塞式等待,也会占用资源无意义,所以这里用signal
-
-
-
- }
- }
-
- ~TcpServer()
- {
- }
-
- private:
- uint16_t port_;
- std::string ip_;
- int listensock_;
- };
3.tcp_client
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- void usage(std::string proc)
- {
- std::cout << "\nUsage: " << proc << " serverIp serverPort\n"
- << std::endl;
- }
-
- int main(int argc, char *argv[])
- {
- if (argc != 3)
- {
- usage(argv[0]);
- exit(1);
- }
-
- // 获取ip和port
- std::string ip = argv[1];
- uint16_t port = atoi(argv[2]);
-
- // 创建套接字
- int sock = socket(AF_INET, SOCK_STREAM, 0);
- if (sock < 0)
- {
- std::cerr << "socket error" << std::endl;
- exit(2);
- }
-
- // client是不需要显示bind的,因为如果bind了就是一个非常具体的端口号了,多个客户端可能会出现冲突,可能会导致启动失败,所以不需要显示的bind
- // 让OS自动选择
- // 但是客户端必须拥有连接的能力
- struct sockaddr_in server;
- memset(&server, 0, sizeof(server));
- server.sin_family = AF_INET;
- server.sin_port = ntohs(port);
- server.sin_addr.s_addr = inet_addr(ip.c_str());
-
- if (connect(sock, (struct sockaddr *)&server, sizeof(server)))
- {
- std::cerr << "connect error" << std::endl;
- exit(3); // TODO
- }
- std::cout << "connect success" << std::endl;
-
- //连接成功直接通信
- while(1)
- {
- std::string line;
- std::cout<< "请输入# ";
- std::getline(std::cin,line);
-
- //发送数据
- send(sock,line.c_str(),line.size(),0);
- char buffer[1024];
- ssize_t s=recv(sock,buffer,sizeof(buffer)-1,0);
- if(s>0)
- {
- buffer[s]=0;
- std::cout << "server 回显# " << buffer << std::endl;
- }
- else if(s==0)
- {
- break;
- }
- else
- {
- break;
- }
- }
-
- return 0;
- }
4.结果
可以看到我们的server是由两个的,一个是子进程一个是父进程

三、多进程版本
上面的其实已经是多进程版本的了,但是我们使用的signal信号来实现子进程退出的,下面我们用另一种方式来实现
为什么我们创建了子进程之后还要再创建一下呢?这是因为我们让子进程退出之间再次fork一下,会留下一个孙子进程,孙子进程变成了孤儿进程,会被OS领养,会被OS自己退出,所以父进程等待是不会阻塞的!
tcp_server.hpp
- #pragma once
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "log.hpp"
-
- static void service(int sock, const std::string &client_ip, const uint16_t &client_port)
- {
- // echo server
- char buffer[1024];
- while (1)
- {
- // 先读取
- // read和write可以直接被使用,套接字也就是文件描述符
- ssize_t s = read(sock, &buffer, sizeof buffer);
- if (s > 0)
- {
- buffer[s] = 0; // 直接将发送过来的数据当作字符串打印
- std::cout << client_ip << ":" << client_port << "# " << buffer << std::endl;
- }
- else if (s = 0) // 对端关闭连接
- {
- logMessage(NORMAL, "%s:%d shutdown, me too!", client_ip.c_str(), client_port);
- break;
- }
- else // 读取失败
- {
- logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
- break;
- }
- write(sock, &buffer, sizeof buffer);
- }
- }
- class TcpServer
- {
-
- const static int g_backlog = 20; // 不能太大也不能太小
- public:
- TcpServer(uint16_t port, std::string ip = "")
- : port_(port), ip_(ip), listensock_(-1)
- {
- }
-
- void initServer()
- {
- // 1.创建套接字
- listensock_ = socket(AF_INET, SOCK_STREAM, 0);
- if (listensock_ < 0)
- {
- logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
- exit(2);
- }
- logMessage(NORMAL, "create socket success, listensock_: %d", listensock_);
-
- // 2.bind,将服务器和端口信息进行绑定
- struct sockaddr_in local;
- memset(&local, 0, sizeof(local));
- local.sin_family = AF_INET;
- local.sin_port = htons(port_);
- local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
- if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)))
- {
- logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
- exit(3);
- }
-
- // 因为TCP是面向连接的,所以正是通信的时候需要先建立连接
- // 3.开始监听
- if (listen(listensock_, g_backlog) < 0)
- {
- logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
- exit(4);
- }
- logMessage(NORMAL, "init server success");
- }
-
- void start()
- {
- // signal(SIGCHLD,SIG_IGN);//子进程退出会像父进程发送SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
- while (1)
- {
- // 4.获取连接,服务器要获取客户端的连接,这样来方便返回
- struct sockaddr_in src;
- socklen_t len = sizeof(src);
- // listensock_和servicesock有什么区别呢?
- // 其中listensock_通过这个套接字只是为了把底层的连接获取上来
- // 而servicesock才是真正的进行连接的
- int servicesock = accept(listensock_, (struct sockaddr *)&src, &len);
- if (servicesock < 0)
- {
- logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
- continue;
- }
- // 获取成功了
- uint16_t client_port = ntohs(src.sin_port);
- std::string client_ip = inet_ntoa(src.sin_addr);
- logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",
- servicesock, client_ip.c_str(), client_port);
- // 开始进行通信
- // 单进程循环版,只能够一次处理一个客户端,处理完成了才能处理下一个,很明显是不可以的
- // 这是因为单进程的,下面函数是个死循环,所以不退出就没办法获取新的连接
- // service(listensock_, client_ip, client_port);
-
- // 多进程版本
- // 创建子进程,那么子进程可以打开父进程曾经打开的fd吗?
- // 是可以的,父进程创建子进程时候,子进程的文件描述符表,会继承自于父进程,所以会看到同一个文件
- // pid_t id= fork();
- // assert(id!=-1);
- // if(id==0)//子进程
- // {
- // //子进程会不会继承父进程打开的文件与文件描述符fd呢?这是肯定的
- // //子进程是来提供服务的,需不需要监听socket呢?
- // service(listensock_, client_ip, client_port);
-
- // close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
- // exit(0);//子进程退出会造成僵尸进程
- // }
- // close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
- // //父进程
- // //waitpid是阻塞式等待,也会占用资源无意义,所以这里用signal
-
- // 2.2多进程版本,不用signal
- pid_t id = fork();
- if (id == 0)
- {
- if (fork() > 0)//这是因为我们让子进程退出之间再次fork一下,会留下一个孙子进程,孙子进程变成了孤儿进程
- //会被OS领养,会被OS自己退出,所以父进程等待是不会阻塞的!
- {
- close(listensock_);
- exit(0);
- service(servicesock, client_ip, client_port);
- exit(0);
- }
- }
- // 父进程
- waitpid(id, nullptr, 0);
- close(servicesock);
- }
- }
-
- ~TcpServer()
- {
- }
-
- private:
- uint16_t port_;
- std::string ip_;
- int listensock_;
- };
四、多线程版本
tcp_server.hpp
- #pragma once
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "log.hpp"
-
- static void service(int sock, const std::string &client_ip, const uint16_t &client_port)
- {
- // echo server
- char buffer[1024];
- while (1)
- {
- // 先读取
- // read和write可以直接被使用,套接字也就是文件描述符
- ssize_t s = read(sock, &buffer, sizeof buffer);
- if (s > 0)
- {
- buffer[s] = 0; // 直接将发送过来的数据当作字符串打印
- std::cout << client_ip << ":" << client_port << "# " << buffer << std::endl;
- }
- else if (s = 0) // 对端关闭连接
- {
- logMessage(NORMAL, "%s:%d shutdown, me too!", client_ip.c_str(), client_port);
- break;
- }
- else // 读取失败
- {
- logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
- break;
- }
- write(sock, &buffer, sizeof buffer);
- }
- }
-
- class ThreadData
- {
- public:
- int sock_;
- std::string ip_;
- uint16_t port_;
- };
-
- class TcpServer
- {
-
- const static int g_backlog = 20; // 不能太大也不能太小
- static void* threadRoutine(void* args)//这里我们要让新线程去干事情,肯定需要套接字,但是上面已经写了所以我们直接调用这个函数就可以了
- {
- pthread_detach(pthread_self());//为了避免内存泄漏要进行线程分离,让子线程执行完毕自动销毁
- ThreadData *td=static_cast
(args); - service(td->sock_, td->ip_,td->port_);
- delete td;
-
- return nullptr;
- }
- public:
- TcpServer(uint16_t port, std::string ip = "")
- : port_(port), ip_(ip), listensock_(-1)
- {
- }
-
- void initServer()
- {
- // 1.创建套接字
- listensock_ = socket(AF_INET, SOCK_STREAM, 0);
- if (listensock_ < 0)
- {
- logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
- exit(2);
- }
- logMessage(NORMAL, "create socket success, listensock_: %d", listensock_);
-
- // 2.bind,将服务器和端口信息进行绑定
- struct sockaddr_in local;
- memset(&local, 0, sizeof(local));
- local.sin_family = AF_INET;
- local.sin_port = htons(port_);
- local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
- if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)))
- {
- logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
- exit(3);
- }
-
- // 因为TCP是面向连接的,所以正是通信的时候需要先建立连接
- // 3.开始监听
- if (listen(listensock_, g_backlog) < 0)
- {
- logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
- exit(4);
- }
- logMessage(NORMAL, "init server success");
- }
-
- void start()
- {
- // signal(SIGCHLD,SIG_IGN);//子进程退出会像父进程发送SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
- while (1)
- {
- // 4.获取连接,服务器要获取客户端的连接,这样来方便返回
- struct sockaddr_in src;
- socklen_t len = sizeof(src);
- // listensock_和servicesock有什么区别呢?
- // 其中listensock_通过这个套接字只是为了把底层的连接获取上来
- // 而servicesock才是真正的进行连接的
- int servicesock = accept(listensock_, (struct sockaddr *)&src, &len);
- if (servicesock < 0)
- {
- logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
- continue;
- }
- // 获取成功了
- uint16_t client_port = ntohs(src.sin_port);
- std::string client_ip = inet_ntoa(src.sin_addr);
- logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",
- servicesock, client_ip.c_str(), client_port);
-
- //3.0多线程版本
- ThreadData *td = new ThreadData();
- td->sock_ = servicesock;
- td->ip_= client_ip;
- td->port_ = client_port;
- pthread_t tid;
- //在多线程这里是不用关闭特定的文件描述符的,因为主线程与副线程是共享文件描述符的
- pthread_create(&tid,nullptr,threadRoutine,td);
- //close(servicesock)//走到最后主线程结束还是要关闭的
-
-
- // 1.0开始进行通信
- // 单进程循环版,只能够一次处理一个客户端,处理完成了才能处理下一个,很明显是不可以的
- // 这是因为单进程的,下面函数是个死循环,所以不退出就没办法获取新的连接
- // service(listensock_, client_ip, client_port);
-
- // 2.0多进程版本
- // 创建子进程,那么子进程可以打开父进程曾经打开的fd吗?
- // 是可以的,父进程创建子进程时候,子进程的文件描述符表,会继承自于父进程,所以会看到同一个文件
- // pid_t id= fork();
- // assert(id!=-1);
- // if(id==0)//子进程
- // {
- // //子进程会不会继承父进程打开的文件与文件描述符fd呢?这是肯定的
- // //子进程是来提供服务的,需不需要监听socket呢?
- // service(listensock_, client_ip, client_port);
-
- // close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
- // exit(0);//子进程退出会造成僵尸进程
- // }
- // close(listensock);//这里让父子关闭不需要的文件描述符,不然文件描述符会越来越少
- // //父进程
- // //waitpid是阻塞式等待,也会占用资源无意义,所以这里用signal
-
- // 2.1多进程版本,不用signal
- // pid_t id = fork();
- // if (id == 0)
- // {
- // if (fork() > 0)//这是因为我们让子进程退出之间再次fork一下,会留下一个孙子进程,孙子进程变成了孤儿进程
- // //会被OS领养,会被OS自己退出,所以父进程等待是不会阻塞的!
- // {
- // close(listensock_);
- // exit(0);
- // service(servicesock, client_ip, client_port);
- // exit(0);
- // }
- // }
- // // 父进程
- // waitpid(id, nullptr, 0);
- // close(servicesock);
- }
- }
-
- ~TcpServer()
- {
- }
-
- private:
- uint16_t port_;
- std::string ip_;
- int listensock_;
- };
五、线程池版本
代码自取

六、变成小写转换大写的方法
