🌠 作者:@阿亮joy.
🎆专栏:《学会Linux》
🎇 座右铭:每个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根
目录
网络通信中的协议是指在网络中进行数据传输时遵循的一些规则和标准,用于确保不同设备之间的通信能够顺利进行。协议的本质是软件,它最终是需要通过计算机语言(编码)的方式来表现出来,协议如何编写取决于我们的应用场景。
通行双方在进行网络通信时:
序列化和反序列化是计算机中常用的概念,用于在不同系统或网络之间传输数据或存储数据时进行格式转换。
序列化是指将对象或数据结构转换成字节流的过程,以便于在网络或存储设备上进行传输或存储。在序列化的过程中,会将对象或数据结构的属性或元素逐个转换成二进制格式,并将这些二进制数据组成一个连续的字节流,以便于传输或存储。
反序列化是指将序列化后的字节流转换成对象或数据结构的过程,以便于在程序中进行操作。在反序列化的过程中,会将字节流逐个读取,并将其转换成相应的对象属性或数据结构元素,以便于程序对其进行操作。
在网络通信中,客户端向服务器发送请求时,需要将请求对象序列化成字节流进行传输;服务器收到请求后,需要将接收到的字节流反序列化成请求对象进行处理。
注:序列化和反序列化可以让上层业务和网络传输进行一定程度的解耦。

网络版计算器要实现的功能:我们需要客户端把数据和操作符发给服务器,然后由服务器进行计算,最后再把结果返回给客户端。为了实现这样的网络版计算器,我们就需要进行协议定制。
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define SEP "\r\n"
#define SEP_LEN strlen(SEP)
#define SIZE 1024
#define MYSELF
class Request
{
public:
Request() {}
Request(int x, int y, char op)
: _x(x)
, _y(y)
, _op(op)
{}
~Request() {}
std::string Serialize()
{
#ifdef MYSELF
std::string str = std::to_string(_x);
str += SPACE;
str += _op;
str += SPACE;
str += std::to_string(_y);
return str;
#else
#endif
}
bool Deserialize(std::string& str)
{
#ifdef MYSELF
size_t left = str.find(SPACE);
if(left == std::string::npos)
return false;
size_t right = str.rfind(SPACE);
if(right == std::string::npos)
return false;
if(left + SPACE_LEN >= str.size())
return false;
_x = atoi(str.substr(0, left).c_str());
_y = atoi(str.substr(right + SPACE_LEN).c_str());
_op = str[left + SPACE_LEN];
#else
#endif
}
public:
int _x;
int _y;
char _op;
};
class Response
{
public:
Response() {}
Response(int code, int ret, int x, int y, char op)
: _code(code)
, _ret(ret)
, _x(x)
, _y(y)
, _op(op)
{}
~Response() {}
std::string Serialize()
{
#ifdef MYSELF
std::string str = std::to_string(_code);
str += SPACE;
str += std::to_string(_ret);
return str;
#else
#endif
}
bool Deserialize(const std::string& str)
{
#ifdef MYSELF
size_t pos = str.find(SPACE);
if(pos == std::string::npos)
return false;
_code = atoi(str.substr(0, pos).c_str());
_ret = atoi(str.substr(pos + SPACE_LEN).c_str());
return true;
#else
#endif
}
public:
int _code;
int _ret;
int _x;
int _y;
char _op;
};
bool Recv(int sock, std::string* out)
{
char buffer[SIZE];
ssize_t s = recv(sock, buffer, sizeof(buffer) - 1, 0);
if(s > 0)
{
buffer[s] = '\0';
*out += buffer;
return true;
}
else if(s == 0)
{
std::cout << "Client Quit!" << std::endl;
return false;
}
else
{
std::cout << "Recv Error!" << std::endl;
return false;
}
}
void Send(int sock, const std::string& str)
{
int n = send(sock, str.c_str(), str.size(), 0);
if(n < 0)
std::cout << "Send Error!" << std::endl;
}
// 去除报头
std::string Decode(std::string& buffer)
{
size_t pos = buffer.find(SEP);
if(pos == std::string::npos)
return "";
int size = atoi(buffer.substr(0, pos).c_str());
int leftSize = buffer.size() - pos - 2 * SEP_LEN;
if(leftSize >= size)
{
// 至少有一个完整的报文
buffer.erase(0, pos + SEP_LEN);
std::string s = buffer.substr(0, size);
buffer.erase(0, size + SEP_LEN);
return s;
}
else // 没有完整的报文,不进行解析
return "";
}
// 添加报头
// 有效载荷长度\r\n有效载荷\r\n
std::string Encode(std::string& s)
{
std::string newPackage = std::to_string(s.size());
newPackage += SEP;
newPackage += s;
newPackage += SEP;
return newPackage;
}
功能说明:
\r\n有效载荷\r\n。对端收到报文后,需要先去除报头 Decode,然后才能进行反序列化。日志是计算机系统中的一种记录信息的机制,可以用来追踪系统运行的情况和出现问题时进行分析和调试。所以我们编写的网络版计算器也引入了之前写的日志组件。
#pragma once
#include <cstdio>
#include <cstdarg>
#include <string>
#include <iostream>
#include <ctime>
// 日志等级
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4
#define LOGFILE "./Calculate.log"
const char* levelMap[] =
{
"DEBUG",
"NORMAL",
"WARNING",
"ERROR",
"FATAL"
};
void logMessage(int level, const char* format, ...)
{
// 只有定义了DEBUG_SHOW,才会打印debug信息
// 利用命令行来定义即可,如-D DEBUG_SHOW
#ifndef DEBUG_SHOW
if(level == DEBUG) return;
#endif
char stdBuffer[1024]; // 标准部分
time_t timestamp = time(nullptr);
// struct tm *localtime = localtime(×tamp);
snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", levelMap[level], timestamp);
char logBuffer[1024]; // 自定义部分
va_list args; // va_list就是char*的别名
va_start(args, format); // va_start是宏函数,让args指向参数列表的第一个位置
// vprintf(format, args); // 以format形式向显示器上打印参数列表
vsnprintf(logBuffer, sizeof logBuffer, format, args);
va_end(args); // va_end将args弄成nullptr
FILE *fp = fopen(LOGFILE, "a");
// printf("%s%s\n", stdBuffer, logBuffer);
fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
fclose(fp);
}
#pragma once
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <string>
#include <cstring>
#include "Log.hpp"
class Sock
{
private:
const static int backlog = 20;
public:
Sock() {}
// 返回值是创建的套接字
int Socket()
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
logMessage(FATAL, "Create Socket Error! Errno:%d Strerror:%s", errno, strerror(errno));
exit(2);
}
logMessage(NORMAL, "Create Socket Success! Socket:%d", sock);
return sock;
}
// 绑定端口号
void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0")
{
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(port);
local.sin_addr.s_addr = inet_addr(ip.c_str());
if (bind(sock, (struct sockaddr *)&local, sizeof local) < 0)
{
logMessage(FATAL, "Bind Error! Errno:%d Strerror:%s", errno, strerror(errno));
exit(3);
}
}
// 将套接字设置为监听套接字
void Listen(int listenSock)
{
if (listen(listenSock, backlog) < 0)
{
logMessage(FATAL, "Listen Error! Errno:%d Strerror:%s", errno, strerror(errno));
exit(4);
}
logMessage(NORMAL, "Init Server Success!");
}
// 接收链接,返回值是为该连接服务的套接字
// ip和port是输出型参数,返回客户端的ip和port
int Accept(int listenSock, std::string *ip, uint16_t *port)
{
struct sockaddr_in src;
socklen_t len = sizeof(src);
int serviceSock = accept(listenSock, (struct sockaddr *)&src, &len);
if (serviceSock < 0)
{
logMessage(FATAL, "Accept Error! Errno:%d Strerror:%s", errno, strerror(errno));
return -1;
}
if (ip)
*ip = inet_ntoa(src.sin_addr);
if (port)
*port = ntohs(src.sin_port);
return serviceSock;
}
// 发起连接
bool Connet(int sock, const std::string &serverIP, const int16_t &serverPort)
{
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);
if (connect(sock, (struct sockaddr *)&server, sizeof server) == 0)
return true;
else
return false;
}
~Sock() {}
};
TcpServer.hpp
#pragma once
#include "Sock.hpp"
#include <functional>
#include <vector>
#include <unistd.h>
#include <pthread.h>
using func_t = std::function<void(int)>;
class TcpServer; // 类型声明
class ThreadData
{
public:
ThreadData(int sock, TcpServer* ptr)
: _sock(sock)
, _ptr(ptr)
{}
~ThreadData() {}
public:
int _sock;
TcpServer* _ptr;
};
class TcpServer
{
public:
TcpServer(const uint16_t& port, const std::string& ip = "0.0.0.0")
{
_listenSock = _sock.Socket();
_sock.Bind(_listenSock, port, ip);
_sock.Listen(_listenSock);
}
~TcpServer()
{
if(_listenSock >= 0) close(_listenSock);
}
void BindService(func_t func)
{
_func.push_back(func);
}
void Start()
{
while(true)
{
std::string clientIP;
uint16_t clientPort;
int sock = _sock.Accept(_listenSock, &clientIP, &clientPort);
if(sock == -1) continue; // 获取连接失败
logMessage(NORMAL, "Create A New Link! Socket:%d", sock);
pthread_t tid;
ThreadData* td = new ThreadData(sock, this);
// 创建线程完成用户的请求
pthread_create(&tid, nullptr, ThreadRoutine, (void*)td);
}
}
void ExcuteService(int sock)
{
for(auto& f : _func)
{
f(sock); // 执行服务端绑定的每一个服务
}
}
private:
static void* ThreadRoutine(void* args)
{
pthread_detach(pthread_self()); // 线程分离
ThreadData* td = static_cast<ThreadData*>(args);
td->_ptr->ExcuteService(td->_sock);
close(td->_sock); // 服务完成后关闭文件描述符
delete td;
return nullptr;
}
private:
Sock _sock;
int _listenSock;
std::vector<func_t> _func;
// std::unordered_map<std::string, func_t> _func;
};
CalServer.cc
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <memory>
#include <signal.h>
static void Usage(const std::string& proc)
{
std::cout << "\nUsage: " << proc << " Port" << std::endl;
}
static Response CalculatorHelper(const Request &req)
{
Response resp(0, 0, req._x, req._y, req._op);
switch (req._op)
{
case '+':
resp._ret = req._x + req._y;
break;
case '-':
resp._ret = req._x - req._y;
break;
case '*':
resp._ret = req._x * req._y;
break;
case '/':
if (req._y == 0)
resp._code = 1;
else
resp._ret = req._x / req._y;
break;
case '%':
if (req._y == 0)
resp._code = 2;
else
resp._ret = req._x % req._y;
break;
default:
resp._code = 3;
break;
}
return resp;
}
void Calculator(int sock)
{
std::string inbuffer;
while(true)
{
bool ret = Recv(sock, &inbuffer);
if(!ret) break;
// 读取成功
std::string package = Decode(inbuffer);
if(package.empty()) continue;
// 保证该报文是一个完整的报文
logMessage(NORMAL, "%s", package.c_str());
Request req;
// 反序列化:字节流 -> 结构化
req.Deserialize(package);
// 业务逻辑
Response resp = CalculatorHelper(req);
// 序列化
std::string respStr = resp.Serialize();
// 添加报头,形成一个完整的报文
respStr = Encode(respStr);
Send(sock, respStr); // 将处理结果返回给客户端
}
}
int main(int argc, char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(1);
}
signal(SIGPIPE, SIG_IGN);
std::unique_ptr<TcpServer> ptr(new TcpServer(atoi(argv[1])));
ptr->BindService(Calculator); // 绑定服务
ptr->Start(); // 开始服务
return 0;
}
为什么服务端通常需要忽略 SIGPIPE 和 SIGCHLD 信号呢?
#include "Protocol.hpp"
#include "Sock.hpp"
#include <ctime>
#include <unistd.h>
static void Usage(const std::string& proc)
{
std::cout << "\nUsage: " << proc << " ServerIP ServerPort" << std::endl;
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string ServerIP = argv[1];
uint16_t ServerPort = atoi(argv[2]);
Sock sock;
int sockfd = sock.Socket(); // 创建套接字
// 发起连接请求
if(!sock.Connet(sockfd, ServerIP, ServerPort))
{
std::cerr << "Connet Error!" << std::endl;
exit(2);
}
bool quit = false; // false表示不退出循环
std::string buffer;
srand((unsigned int)time(nullptr));
const char* op = "+-*/%";
while(!quit)
{
// 获取需求(此部分可以设置为手动输入)
Request req;
req._x = rand() % 100;
req._y = rand() % 100;
req._op = op[rand() % 5];
// 序列化
std::string str = req.Serialize();
std::string tmp = str;
// 添加报头
str = Encode(str);
// 向服务器发起请求
Send(sockfd, str);
// 接收服务器的应答
while(true)
{
bool ret = Recv(sockfd, &buffer);
// 服务器关闭连接或Recv异常
if(!ret)
{
quit = true;
break;
}
std::string package = Decode(buffer);
if(package.empty()) continue;
// 接收到一个完整的报文
Response resp;
resp.Deserialize(package);
std::string err;
switch(resp._code)
{
case 1:
err = "除0错误";
break;
case 2:
err = "模0错误";
break;
case 3:
err = "非法操作";
break;
default:
std::cout << "[Calculate Success] " << tmp << " = " << resp._ret << std::endl;
// std::cout << "[Calculate Success] " << resp._ret << std::endl;
// std::cout << "[Calculate Success] " << resp._x << " " << resp._op << " " << resp._y << " = " << resp._ret << std::endl;
break;
}
if(!err.empty()) std::cerr << err << std::endl;
sleep(1);
break;
}
}
close(sockfd);
return 0;
}


什么是前台进程?什么是后台进程?
在 Linux 系统中,一个进程可以在前台运行或者在后台运行。
前台进程是指用户当前正在与之交互的进程,通常在终端(Terminal)上显示进程的输出信息,同时接收用户输入的命令。当一个进程在前台运行时,它会阻塞终端(bash)的输入,直到该进程退出或者被暂停。前台进程可以使用 Ctrl+Z 将前台进程暂停,可以使用 Ctrl + C 将前台进程终止。
后台进程是指在后台运行的进程,不与终端交互,通常不会在终端上输出信息。后台进程可以继续运行,即使用户退出了终端。可以通过在命令行末尾添加“&”符号将进程放到后台运行。
任何一个 Xshell 登录,只允许一个前台进程和多个后台进程。
一个进程除了有 PID、PPID(父进程 ID),还有一个组 ID(PGID)。每个进程都属于一个进程组。进程组是一组具有相同进程组 ID(PGID)的进程的集合,同时被创建的多个进程可以成为一个进程组,第一个进程的进程 ID 成为进程组 ID。一个进程可以将它的子进程加入到同一个进程组中,从而使得这些进程可以共享同一个终端。

在 Linux 系统中,同一个父进程下的多个子进程称为兄弟进程。那么上面的 sleep 1000、sleep 2000 和 sleep 3000 就是兄弟进程,它们的父进程就是 bash。
什么是会话?
在 Linux 中,会话(session)是指从用户登录开始,到用户退出结束这段时间内的整个过程。通常情况下,一个会话包含多个进程,这些进程可以是由当前会话的 bash 启动的,也可以是由其他进程启动的。当用户退出登录时,会话会被终止。具体来说,这意味着所有与该会话相关的进程都将被终止,包括终端和 bash 进程。当用户使用 exit 命令或输入 Ctrl+D 组合键退出 bash 时,会话会被终止。在会话终止之前,系统会执行一些清理工作,例如向所有已连接的进程发送 SIGHUP 信号,以通知它们会话已经终止。注:bash 是自成一个进程组的!
终端和 bash 的关系

什么是守护进程?
守护进程(Daemon)是一种在后台运行的进程,通常在启动系统时自动启动,一直运行直到系统关闭。守护进程通常不会与用户交互,也不会直接响应用户请求,而是通过监听网络端口或定期执行任务等方式,提供某种服务或功能。守护进程常常用于网络服务、系统监控、定时任务等方面。
在 Linux 系统中,守护进程通常通过 fork 函数创建子进程的方式启动,并且需要调用 setsid 函数创建新会话(Session)和进程组(Process Group),以便于与终端(Terminal)分离,避免受到用户登录或注销的影响。此外,守护进程还需要关闭不需要的文件描述符(File Descriptor)、改变工作目录(Working Directory)等操作,以提高系统的安全性和稳定性。
注:setsid 要调用成功,必须保证当前进程不是进程组的组长。守护进程是自成一个会话的!
#pragma once
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
void MyDaemon()
{
// 1.忽略信号:SIGPIPE,SIGCHLD
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
// 2.不要让自己成为进程组组长
if(fork() > 0) exit(0);
// 3.调用setsid
setsid();
// 4.标准输入、输出、错误的重定向
int devnull = open("/dev/null", O_RDONLY | O_RDONLY);
if(devnull > 0)
{
dup2(0, devnull);
dup2(1, devnull);
dup2(2, devnull);
close(devnull);
}
}

守护进程通常不应该向显示器输出信息,因为守护进程运行在后台,没有终端(Terminal)或标准输入输出(stdin/stdout/stderr)设备,也没有交互界面。如果守护进程尝试向显示器输出信息,可能会导致进程暂停或终止。
/dev/null 文件的介绍
/dev/null 是 Linux 操作系统中的一种特殊文件,它通常用于丢弃不需要的输出或输入数据。在 Linux 操作系统中,一切皆文件,/dev/null 也被看做是一个文件,但是它并不会存储数据,而是会将一切写入它的操作视为成功,并不做任何操作。
守护进程和孤儿进程的区别
守护进程是孤儿进程的一种,它们的父进程都是 1 号进程,最主要的区别就是守护进程自成一个会话,而孤儿进程是属于某个会话的。
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于数据的序列化和跨语言数据交换。JSON 采用键值对的方式来组织数据,数据格式为键值对之间用逗号分隔,键和值之间用冒号分隔,整个数据由一对大括号包含。
例如,以下是一个 JSON 格式的数据:
{
"name": "Alice",
"age": 20,
"gender": "female",
"interests": ["reading", "music", "travel"]
}
这个数据包含了一个人的姓名、年龄、性别和兴趣爱好。其中,键名是字符串类型,键值可以是字符串、数字、布尔值、数组或对象。

JSON的优点是:
JSON 库的安装
sudo yum install -y jsoncpp-devel

JSON 库的简单使用
#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
int a = 10;
int b = 20;
char op = '+';
Json::Value root;
root["aa"] = a;
root["bb"] = b;
root["op"] = op;
Json::Value sub;
sub["other"] = 200;
sub["other1"] = "hello";
root["sub"] = sub;
Json::StyledWriter writer;
// Json::FastWriter writer;
std::string s = writer.write(root);
std::cout << s << std::endl;
return 0;
}

注意:编译时需要加上 -ljosncpp,否则无法找到库。
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <jsoncpp/json/json.h>
class Request
{
public:
Request() {}
Request(int x, int y, char op)
: _x(x)
, _y(y)
, _op(op)
{}
~Request() {}
std::string Serialize()
{
#ifndef MYSELF
std::string str = std::to_string(_x);
str += SPACE;
str += _op;
str += SPACE;
str += std::to_string(_y);
return str;
#else
Json::Value root;
root["x"] = _x;
root["op"] = _op;
root["y"] = _y;
Json::FastWriter writer;
return writer.write(root);
#endif
}
bool Deserialize(std::string& str)
{
#ifndef MYSELF
size_t left = str.find(SPACE);
if(left == std::string::npos)
return false;
size_t right = str.rfind(SPACE);
if(right == std::string::npos)
return false;
if(left + SPACE_LEN >= str.size())
return false;
_x = atoi(str.substr(0, left).c_str());
_y = atoi(str.substr(right + SPACE_LEN).c_str());
_op = str[left + SPACE_LEN];
#else
Json::Value root;
Json::Reader reader;
reader.parse(str, root);
_x = root["x"].asInt();
_op = root["op"].asInt();
_y = root["y"].asInt();
return true;
#endif
}
public:
int _x;
int _y;
char _op;
};
class Response
{
public:
Response() {}
Response(int code, int ret, int x, int y, char op)
: _code(code)
, _ret(ret)
, _x(x)
, _y(y)
, _op(op)
{}
~Response() {}
std::string Serialize()
{
#ifndef MYSELF
std::string str = std::to_string(_code);
str += SPACE;
str += std::to_string(_ret);
return str;
#else
Json::Value root;
root["code"] = _code;
root["ret"] = _ret;
root["x"] = _x;
root["op"] = _op;
root["y"] = _y;
Json::FastWriter writer;
return writer.write(root);
#endif
}
bool Deserialize(const std::string& str)
{
#ifndef MYSELF
size_t pos = str.find(SPACE);
if(pos == std::string::npos)
return false;
_code = atoi(str.substr(0, pos).c_str());
_ret = atoi(str.substr(pos + SPACE_LEN).c_str());
return true;
#else
Json::Value root;
Json::Reader reader;
reader.parse(str, root);
_code = root["code"].asInt();
_ret = root["ret"].asInt();
_x = root["x"].asInt();
_op = root["op"].asInt();
_y = root["y"].asInt();
return true;
#endif
}
public:
int _code;
int _ret;
int _x;
int _y;
char _op;
};
本篇博客主要讲解了协议的概念、序列化和反序列化、守护进程以及网络版计算器的编写等。那么以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!💖💝❣️
尝试通过RVM将RubyGems升级到版本1.8.10并出现此错误:$rvmrubygemslatestRemovingoldRubygemsfiles...Installingrubygems-1.8.10forruby-1.9.2-p180...ERROR:Errorrunning'GEM_PATH="/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/ruby-1.9.2-p180@global:/Users/foo/.rvm/gems/ruby-1.9.2-p180:/Users/foo/.rvm/gems/rub
我正在使用puppet为ruby程序提供一组常量。我需要提供一组主机名,我的程序将对其进行迭代。在我之前使用的bash脚本中,我只是将它作为一个puppet变量hosts=>"host1,host2"我将其提供给bash脚本作为HOSTS=显然这对ruby不太适用——我需要它的格式hosts=["host1","host2"]自从phosts和putsmy_array.inspect提供输出["host1","host2"]我希望使用其中之一。不幸的是,我终其一生都无法弄清楚如何让它发挥作用。我尝试了以下各项:我发现某处他们指出我需要在函数调用前放置“function_”……这
我正在编写一个gem,我必须在其中fork两个启动两个webrick服务器的进程。我想通过基类的类方法启动这个服务器,因为应该只有这两个服务器在运行,而不是多个。在运行时,我想调用这两个服务器上的一些方法来更改变量。我的问题是,我无法通过基类的类方法访问fork的实例变量。此外,我不能在我的基类中使用线程,因为在幕后我正在使用另一个不是线程安全的库。所以我必须将每个服务器派生到它自己的进程。我用类变量试过了,比如@@server。但是当我试图通过基类访问这个变量时,它是nil。我读到在Ruby中不可能在分支之间共享类变量,对吗?那么,还有其他解决办法吗?我考虑过使用单例,但我不确定这是
这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,
我的最终目标是安装当前版本的RubyonRails。我在OSXMountainLion上运行。到目前为止,这是我的过程:已安装的RVM$\curl-Lhttps://get.rvm.io|bash-sstable检查已知(我假设已批准)安装$rvmlistknown我看到当前的稳定版本可用[ruby-]2.0.0[-p247]输入命令安装$rvminstall2.0.0-p247注意:我也试过这些安装命令$rvminstallruby-2.0.0-p247$rvminstallruby=2.0.0-p247我很快就无处可去了。结果:$rvminstall2.0.0-p247Search
我在理解Enumerator.new方法的工作原理时遇到了一些困难。假设文档中的示例:fib=Enumerator.newdo|y|a=b=1loopdoy[1,1,2,3,5,8,13,21,34,55]循环中断条件在哪里,它如何知道循环应该迭代多少次(因为它没有任何明确的中断条件并且看起来像无限循环)? 最佳答案 Enumerator使用Fibers在内部。您的示例等效于:require'fiber'fiber=Fiber.newdoa=b=1loopdoFiber.yieldaa,b=b,a+bendend10.times.m
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
从MB升级到新的MBP后,Apple的迁移助手没有移动我的gem。我这次是通过macports安装rubygems,希望在下次升级时避免这种情况。有什么我应该注意的陷阱吗? 最佳答案 如果你想把你的gems安装在你的主目录中(在传输过程中应该复制过来,作为一个附带的好处,会让你以你自己的身份运行geminstall,而不是root),将gemhome:键设置为您在~/.gemrc中的主目录中的路径. 关于通过MacPorts的RubyGems是个好主意吗?,我们在StackOverf
我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b
当我执行>rvminstall1.9.2时一切顺利。然后我做>rvmuse1.9.2也很顺利。但是当涉及到ruby-v时..sam@sjones:~$rvminstall1.9.2/home/sam/.rvm/rubies/ruby-1.9.2-p136,thismaytakeawhiledependingonyourcpu(s)...ruby-1.9.2-p136-#fetchingruby-1.9.2-p136-#downloadingruby-1.9.2-p136,thismaytakeawhiledependingonyourconnection...%Total%Rece