赞
踩
目录
直接代码开整
准备阶段 -- 目前和UDP是一样的
- #include "tcpServer.hpp"
- #include <memory>
-
- using namespace std;
- using namespace server;
-
- static void Usage(string proc)
- {
- cout << "Usage:\n\t" << proc << " local_port\n\n"; // 命令提示符
- }
-
- // tcp服务器,启动和 udp server 一模一样
- // ./tcpserver lock_port
- int main(int argc, char *argv[])
- {
- if (argc != 2)
- {
- Usage(argv[0]);
- exit(USAGE_ERR);
- }
- uint16_t port = atoi(argv[1]);
-
- unique_ptr<TcpServer> tsvr(new TcpServer());
- tsvr->initServer();
- tsvr->start();
-
- return 0;
- }

这个日志在后序完善TCP之后再进行修改,现在只实现简单的打印功能
这里有个细节,我们会发现当我们接受数据的时候是不需要主机转网路序列的,因为关于IO类的接口,内部都帮我们实现了这一功能,这里不帮我们做是因为我们传入的是一个结构体,系统做不到
底层链接的长度+1,先不管他,在后序讲原理再讲述
- void initServer()
- {
- // 1. 创建socket文件套接字对象 -- 流式套接字
- _sock = socket(AF_INET, SOCK_STREAM, 0); // 第三个参数默认 0
- if (_sock < 0)
- {
- logMessage(FATAL, "create socket error");
- exit(SOCKET_ERR);
- }
- logMessage(NORMAL, "create socket success");
-
- // 2.bind绑定自己的网路信息 -- 注意包含头文件
- struct sockaddr_in local;
- memset(&local, 0, sizeof(local));
- local.sin_family = AF_INET;
- local.sin_port = htons(_port); // 这里有个细节,我们会发现当我们接受数据的时候是不需要主机转网路序列的,因为关于IO类的接口,内部都帮我们实现了这一功能,这里不帮我们做是因为我们传入的是一个结构体,系统做不到
- local.sin_addr.s_addr = INADDR_ANY; // 接受任意ip地址
- if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
- {
- logMessage(FATAL, "bind socket error");
- exit(BIND_ERR);
- }
- logMessage(NORMAL, "bind socket success");
-
- // 3. 设置socket 为监听状态 -- TCP与UDP不同,它先要建立链接之后,TCP是面向链接的,后面还会有“握手”过程
- if (listen(_sock, gbacklog) < 0) // 第二个参数backlog后面再填这个坑
- {
- logMessage(FATAL, "listen socket error");
- exit(LISTEN_ERR);
- }
- logMessage(FATAL, "listen socket success");
- }

注意这里是起始版本,在认识下面的一个接口的时候,需要整改
一个比喻:就像一家饭店的门口招呼人的张三,当张三从外边招呼人进来的时候,就向饭店里面喊人,让李四去服务客人,但是张三不会进来,又返回去在门口拉客
因为随着客户端的不断增多,TCP服务器上可能存在多个套接字,就像饭店里面会有多个客人有多个服务员
至此我们需要把之前的_sock 修改为 _listensock
至此我们获取的sock就是一个文件操作符,可以使用文件操作类的函数进行处理,例如read之类的
从一个文件描述符中读取
- void serviceIO(int sock)
- {
- // 先用最简单的,读取再写回去
- char buffer[1024];
- while (true)
- {
- ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
- if (n > 0)
- {
- // 截至目前,我们把读到的数据当作字符串
- buffer[n] = 0;
- std::cout << "recv message: " << buffer << std::endl;
-
- std::string outbuffer = buffer;
- outbuffer += "server[echo]";
-
- write(sock, outbuffer.c_str(), outbuffer.size()); // 在多路转接的时候再详谈write的返回值
- }
- else if(n == 0)
- {
- // 代表client退出 -- 把它想象成一个建立好的管道,客户端不写了,并且把它的文件描述符关了,读端就会像管道一样读到 0 TCP同理
- logMessage(NORMAL, "client quit, me too!");
- }
- }
- }

调用逻辑
- #include "tcpClient.hpp"
- #include <memory>
-
- using namespace std;
-
- static void Usage(string proc)
- {
- cout << "Usage:\n\t" << proc << " serverip serverport\n\n"; // 命令提示符
- }
-
- // ./tcpclient serverip serverport 调用逻辑
- int main(int argc, char *argv[])
- {
- if(argc != 3)
- {
- Usage(argv[0]);
- exit(1);
- }
- string serverip = argv[1];
- uint16_t serverport = atoi(argv[2]);
-
- unique_ptr<TcpClient> tcli(new TcpClient(serverip, serverport));
- tcli->initClient();
- tcli->start();
-
- return 0;
- }

- void initClient()
- {
- // 1. 创建socket
- _sock = socket(AF_INET, SOCK_STREAM, 0);
- if (_sock < 0)
- {
- // 客户端也可以有日志,不过这里就不再实现了,直接打印错误
- std::cout << "socket create error" << std::endl;
- exit(2);
- }
-
- // 2. tcp的客户端要不要bind? 要的! 但是不需要显示bind,这里的client port要让OS自定!
- // 3. 要不要listen? -- 不需要!客户端不需要建立链接
- // 4. 要不要accept? -- 不要!
- // 5. 要什么? 要发起链接!
- }

- void start()
- {
- struct sockaddr_in server;
- memset(&server, 0, sizeof(server));
- server.sin_family = AF_INET;
- server.sin_port = htons(_serverport);
- server.sin_addr.s_addr = inet_addr(_serverip.c_str());
-
- if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
- {
- std::cerr << "socket connect error" << std::endl;
- }
- else
- {
- std::string msg;
- while (true)
- {
- std::cout << "Enter# ";
- std::getline(std::cin, msg);
- write(_sock, msg.c_str(), msg.size());
-
- char buffer[NUM];
- int n = read(_sock, buffer, sizeof(buffer) - 1);
- if (n > 0)
- {
- // 目前我们把读到的数据当成字符串, 截至目前
- buffer[n] = 0;
- std::cout << "Server回显# " << buffer << std::endl;
- }
- else
- {
- break;
- }
- }
- }
- }

但是至此,我们所写的不过是一个单进程版的,所以会出现下面的情况,后续需要进一步修改成为多进程形式的
- #pragma once
-
- #include <iostream>
- #include <string>
-
- // 定义五种不同的信息
- #define DEBUG 0
- #define NORMAL 1
- #define WARNING 2
- #define ERROR 3 //一种不影响服务器的错误
- #define FATAL 4 //致命错误
-
- void logMessage(int level, const std::string message)
- {
- // 格式如下
- // [日志等级] [时间戳/时间] [pid] [message]
- // [FATAL0] [2023-06-11 16:46:07] [123] [创建套接字失败]
-
- // 暂定
- std::cout << message << std::endl;
- }

- cc=g++
- .PHONY:all
- all:tcpserver tcpclient
-
- tcpclient:tcpClient.cc
- $(cc) -o $@ $^ -std=c++11
-
- tcpserver:tcpServer.cc
- $(cc) -o $@ $^ -std=c++11
-
- .PHONY:clean
- clean:
- rm -f tcpserver tcpclient
- #include "tcpClient.hpp"
- #include <memory>
-
- using namespace std;
-
- static void Usage(string proc)
- {
- cout << "Usage:\n\t" << proc << " serverip serverport\n\n"; // 命令提示符
- }
-
- // ./tcpclient serverip serverport 调用逻辑
- int main(int argc, char *argv[])
- {
- if(argc != 3)
- {
- Usage(argv[0]);
- exit(1);
- }
- string serverip = argv[1];
- uint16_t serverport = atoi(argv[2]);
-
- unique_ptr<TcpClient> tcli(new TcpClient(serverip, serverport));
- tcli->initClient();
- tcli->start();
-
- return 0;
- }

- #pragma once
-
- #include <iostream>
- #include <string>
- #include <cstring>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <unistd.h>
-
- #define NUM 1024
-
- class TcpClient
- {
- public:
- TcpClient(const std::string &serverip, const uint16_t &port)
- : _sock(1), _serverip(serverip), _serverport(port)
- {
- }
- void initClient()
- {
- // 1. 创建socket
- _sock = socket(AF_INET, SOCK_STREAM, 0);
- if (_sock < 0)
- {
- // 客户端也可以有日志,不过这里就不再实现了,直接打印错误
- std::cout << "socket create error" << std::endl;
- exit(2);
- }
-
- // 2. tcp的客户端要不要bind? 要的! 但是不需要显示bind,这里的client port要让OS自定!
- // 3. 要不要listen? -- 不需要!客户端不需要建立链接
- // 4. 要不要accept? -- 不要!
- // 5. 要什么? 要发起链接!
- }
-
- void start()
- {
- struct sockaddr_in server;
- memset(&server, 0, sizeof(server));
- server.sin_family = AF_INET;
- server.sin_port = htons(_serverport);
- server.sin_addr.s_addr = inet_addr(_serverip.c_str());
-
- if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
- {
- std::cerr << "socket connect error" << std::endl;
- }
- else
- {
- std::string msg;
- while (true)
- {
- std::cout << "Enter# ";
- std::getline(std::cin, msg);
- write(_sock, msg.c_str(), msg.size());
-
- char buffer[NUM];
- int n = read(_sock, buffer, sizeof(buffer) - 1);
- if (n > 0)
- {
- // 目前我们把读到的数据当成字符串, 截至目前
- buffer[n] = 0;
- std::cout << "Server回显# " << buffer << std::endl;
- }
- else
- {
- break;
- }
- }
- }
- }
- ~TcpClient()
- {
- if(_sock >= 0) close(_sock); //不写也行,因为文件描述符的生命周期随进程,所以进程退了,自然也就会自动回收了
- }
-
- private:
- int _sock;
- std::string _serverip;
- uint16_t _serverport;
- };

- #include "tcpServer.hpp"
- #include <memory>
-
- using namespace server;
- using namespace std;
-
- static void Usage(string proc)
- {
- cout << "Usage:\n\t" << proc << " local_port\n\n"; // 命令提示符
- }
-
- // tcp服务器,启动上和udp server一模一样
- // ./tcpserver local_port
- int main(int argc, char *argv[])
- {
- if (argc != 2)
- {
- Usage(argv[0]);
- exit(USAGE_ERR);
- }
- uint16_t port = atoi(argv[1]);
-
- unique_ptr<TcpServer> tsvr(new TcpServer(port));
- tsvr->initServer();
- tsvr->start();
-
- return 0;
- }

- #pragma once
-
- #include <iostream>
- #include <string>
- #include <cstring>
- #include <cstdlib>
- #include <sys/types.h>
- #include <sys/socket.h>
- #include <sys/socket.h>
- #include <netinet/in.h>
- #include <arpa/inet.h>
- #include <unistd.h>
- #include "log.hpp"
-
- namespace server
- {
- enum
- {
- USAGE_ERR = 1,
- SOCKET_ERR,
- BIND_ERR,
- LISTEN_ERR
-
- };
-
- static const uint16_t gport = 8080;
- static const int gbacklog = 5; // 10、20、50都可以,但是不要太大比如5千,5万
-
- class TcpServer
- {
- public:
- TcpServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
- {
- }
- void initServer()
- {
- // 1. 创建socket文件套接字对象 -- 流式套接字
- _listensock = socket(AF_INET, SOCK_STREAM, 0); // 第三个参数默认 0
- if (_listensock < 0)
- {
- logMessage(FATAL, "create socket error");
- exit(SOCKET_ERR);
- }
- logMessage(NORMAL, "create socket success");
-
- // 2.bind绑定自己的网路信息 -- 注意包含头文件
- struct sockaddr_in local;
- memset(&local, 0, sizeof(local));
- local.sin_family = AF_INET;
- local.sin_port = htons(_port); // 这里有个细节,我们会发现当我们接受数据的时候是不需要主机转网路序列的,因为关于IO类的接口,内部都帮我们实现了这一功能,这里不帮我们做是因为我们传入的是一个结构体,系统做不到
- local.sin_addr.s_addr = INADDR_ANY; // 接受任意ip地址
- if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
- {
- logMessage(FATAL, "bind socket error");
- exit(BIND_ERR);
- }
- logMessage(NORMAL, "bind socket success");
-
- // 3. 设置socket 为监听状态 -- TCP与UDP不同,它先要建立链接之后,TCP是面向链接的,后面还会有“握手”过程
- if (listen(_listensock, gbacklog) < 0) // 第二个参数backlog后面再填这个坑
- {
- logMessage(FATAL, "listen socket error");
- exit(LISTEN_ERR);
- }
- logMessage(NORMAL, "listen socket success");
- }
-
- void start()
- {
- for (;;) // 一个死循环
- {
- // 4. server 获取新链接
- // sock 和client 进行通信的fd
- struct sockaddr_in peer;
- socklen_t len = sizeof(peer);
- int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
- if (sock < 0)
- {
- logMessage(ERROR, "accept error, next"); // 这个不影响服务器的运行,用ERROR,就像张三不会因为没有把人招呼进来就不干了
- continue;
- }
- logMessage(NORMAL, "accept a new link success");
- std::cout << "sock: " << sock << std::endl;
-
- // 5. 这里就是一个sock, 未来通信我们就用这个sock, 面向字节流的,后续全部都是文件操作!
- // 我们就可以直接使用read之类的面向字节流的操作都行
- // version 1
- serviceIO(sock);
- close(sock); // 走到这里就说明客户端已经关闭
- // 对一个已经使用完毕的sock,我们要关闭这个sock,要不然会导致,文件描述符会越来越少,因为文件描述符本质就是一个数组下标
- // 只要是数组下标就会有尽头,提供服务的上限 就等于文件描述符的上限
- // 对一个已经使用完毕的sock,我们要关闭这个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;
- std::cout << "recv message: " << buffer << std::endl;
-
- std::string outbuffer = buffer;
- outbuffer += "server[echo]";
-
- write(sock, outbuffer.c_str(), outbuffer.size()); // 在多路转接的时候再详谈write的返回值
- }
- else if (n == 0)
- {
- // 代表client退出 -- 把它想象成一个建立好的管道,客户端不写了,并且把它的文件描述符关了,读端就会像管道一样读到 0 TCP同理
- logMessage(NORMAL, "client quit, me too!");
- break;
- }
- }
- }
-
- ~TcpServer() {}
-
- private:
- int _listensock; // 修改二:改为listensock 不是用来进行数据通信的,它是用来监听链接到来,获取新链接的!
- uint16_t _port;
- };
-
- } // namespace server

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。