Tcp与Udp区别
tcp需要握手后建立连接才可以开始服务
使用listen握手
[listen]声明:
int listen(int sockfd, int backlog);
[accept]声明:
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
[connect声明]
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
listen返回的fd是给accept使用的,不可用来I/O
我们应对accept 返回的fd进行I/O
Tcp的 accept 返回的fd更像是文件,可以用read读取,因为Tcp是面向字节流
Udp的recvfrom返回的fd不可read等操作,因为Udp不是面向字节流
和Udp一样,Tcp也是全双工通信的(可同时读写)
telnet [域名] 端口号
远程登陆
struct sockaddr
struct sockaddr 基类
struct sockaddr_in,inet,网络间通信
struct sockaddr_un,unix,本机通信
这三个struct前2字节都是16位地址类型(C语言实现多态)
与Tcp协议server交流时,client应先connect,connect成功后才可以write/read
到client与server段开链接时,server中的read会立即返回0,表示与client失去连接
fd & 多进程
子进程继承父进程的文件描述符表。两张,父子各一张(可以理解为子进程浅拷贝父进程fd表,表中fd指向与父进程相同)
父子进程共享的fd中,如果其中一个进程关闭fd,另一个进程中对应fd不受影响,因为fd指向文件具有引用计数,只有引用数为0时,fd指向文件才会真正关闭
将子进程与父进程脱离的方法
signal(SIGCHLD,SIG_IGN),会使系统自动回收子进程
在子进程中生成孙子进程,之后关闭子进程,孙子进程就变为孤儿进程,由系统自动回收。
和进程不同,父子线程共享一张fd表,所以子线程中不用也不能关闭fd。
recv /send 与 read/write
使用recv & send 代替read & write
后者可能导致数据不完整,所以建议前者
[recv]
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
[send]
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
tips:
fd是有用的有限的资源,fd周期随进程。如果不关闭fd,会造成fd泄漏
类静态函数成员
类的静态成员函数不能直接访问类的实例成员(非静态成员),但可以访问类的静态成员。
这是因为静态成员函数与类的实力无关(可能使用静态成员函数时,类还没有实例化)
非静态成员函数可以访问静态成员函数(因为后者实例化一定比前者早)
server类中调用pthread_thread_creat时其函数参数的对应函数(void*(void*))不能接收类内普通自定义参数(因为其只能传递server的this),
只能使server类的静态函数成员作为pthread_thread_creat的函数参数。
通过想类的静态函数成员传递包含有线程所需data+server的this指针,在静态成员函数中通过this调用server类的普通成员函数(同时传递data),实现传参。
popen & pclose
封装了pipe 与 fork / exec
#include <stdio.h>
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
板书笔记
无
TcpEchoShell_code
code/lesson35/1. EchoServer · whb-helloworld/112 - 码云 - 开源中国
TcpServer.cc
#include "TcpServer.hpp"
#include "CommandExec.hpp"
#include <functional>
#include <memory>
using task_t = function<std::string (std::string)>;
// using namespace
int main()
{
ENABLE_CONSOLE_LOG();
Command cmd;
task_t task = [&cmd](std::string cmdstr){
return cmd.Execute(cmdstr);
};
std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(task);
tsvr->InitServer();
tsvr->Start();
return 0;
}
TcpServer.hpp
#pragma once
#include <iostream>
#include <cstring>
#include <string>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>
#include "Log.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#define BACKLOG 8
// using namespace LogModule;
using namespace ThreadPoolModule;
static const uint16_t gport = 8080;
using handler_t = std::function<std::string(std::string)>;
class TcpServer
{
using task_t = std::function<void()>;
struct ThreadData
{
int sockfd;
TcpServer *self;
};
public:
TcpServer(handler_t handler, int port = gport):
_handler(handler),
_port(port),
_isrunning(false)
{
}
void InitServer()
{
// 先监听
_listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if(_listensockfd < 0)
{
LOG(LogLevel::FATAL)<<"socket error";
Die(SOCKET_ERR);
}
LOG(LogLevel::INFO)<<"socket create success, _listensocked is "<<_listensockfd;
// 后bind
struct sockaddr_in local;// 网络通信用sockaddr_in, 本地用sockaddr_un
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
int n = ::bind(_listensockfd, CONV(&local), sizeof(local));
if(n < 0)
{
LOG(LogLevel::FATAL)<<"bind error";
Die(BIND_ERR);
}
LOG(LogLevel::INFO) << "bind success, sockfd is: "<<_listensockfd;
// 设置为监听状态
n = listen(_listensockfd, BACKLOG);
if(n < 0)
{
LOG(LogLevel::FATAL)<<"listen error";
Die(LISTEN_ERR);
}
LOG(LogLevel::INFO)<<"listen success, socked is:"<<_listensockfd;
// 此处可使用::signal(SIGCHLD, SIG_IGN)来将父子进程解绑
}
void HandlerRequest(int sockfd)
{
LOG(LogLevel::INFO)<<"HandlerRequest, sockfd is:"<<sockfd;
char inbuffer[4096];
while(true)
{
ssize_t n = recv(sockfd, inbuffer, sizeof inbuffer, 0);
inbuffer[n] = 0;
LOG(LogLevel::INFO)<<"server recived:"<<inbuffer;
if(n > 0)
{
inbuffer[n] = 0;
std::string cmd_result = _handler(inbuffer);// 回调
::send(sockfd, cmd_result.c_str(), cmd_result.size(), 0);
LOG(LogLevel::INFO)<<"server sent:"<<cmd_result;
}
else if(n == 0)
{
LOG(LogLevel::INFO)<<"client quit"<<sockfd;
break;
}
else
{
break;
}
}
::close(sockfd);// 防止fd泄露
}
static void *ThreadEntry(void *args)// 设为静态函数就不用传递this
{// 当使用多线程(不是封装好的线程池), pthread_thread_create的函数只能接收一个参数
pthread_detach(pthread_self());
ThreadData *data = (ThreadData *)args;
data->self->HandlerRequest(data->sockfd);
return nullptr;
}
void Start()
{
_isrunning = true;
while(_isrunning)
{
struct sockaddr_in peer;
socklen_t peerlen = sizeof(peer);
LOG(LogLevel::DEBUG)<<"accepting...";
int sockfd = ::accept(_listensockfd, CONV(&peer), &peerlen);
if(sockfd < 0)
{
LOG(LogLevel::WARNING)<<"accept error:"<<strerror(errno);
continue;
}
// 连接成功
LOG(LogLevel::INFO)<<"accept success, sockfd is:"<<sockfd;
InetAddr addr(peer);
LOG(LogLevel::INFO)<<"client info:"<<addr.Addr();
// 使用线程池实现
ThreadPool<task_t>::getInstance()->Equeue([this, sockfd](){
this->HandlerRequest(sockfd);
});
}
}
~TcpServer()
{
}
private:
int _listensockfd;// 监听socket
uint16_t _port;
bool _isrunning;
// 处理上层任务入口
handler_t _handler;
};
TcpClient.cc
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
// #include "Common.hpp"
int main(int argc, char *argv[])
{
if(argc != 3)
{
std::cout<<"Usage:./client_tcp [server_ip] [server_port]"<<std::endl;
return 1;
}
std::string server_ip = argv[1];
int server_port = std::stoi(argv[2]);
int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
std::cout<<"create socket failed"<<std::endl;
return 2;
}
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());
// client 不需要显示进行 bind, tcp是面向连接的, connect底层自动bind
int n = ::connect(sockfd, (struct sockaddr*)(&server_addr), sizeof(server_addr));
if(n < 0)
{
std::cout<<"connet falied"<<std::endl;
return 3;
}
std::cout<<"connet 成功\n"<<std::endl;
std::string message;
while(true)
{
char inbuffer[1024];
std::cout<<"input message: ";
std::getline(std::cin, message);
n = ::write(sockfd, message.c_str(), message.size());
if(n > 0)
{
int m = read(sockfd, inbuffer, sizeof(inbuffer));
if(m > 0)
{
inbuffer[m] = 0;
std::cout<<inbuffer<<std::endl;
}
else break;
}
else break;
}
::close(sockfd);
return 0;
}
CommadExec.hpp
#pragma once
#include <iostream>
#include <string>
class Command
{
public:
std::string Execute(std::string cmdstr)
{
return std::string("命令执行完毕\n");
}
};
Common.hpp
#pragma once
#include <iostream>
#define Die(code) do{exit(code);}while(0)
#define CONV(v) (struct sockaddr *)(v)
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR
};
bool SplitString(std::string &in, std::string *key, std::string *val, std::string gap)
{
size_t pos = in.find(gap);
if (pos == std::string::npos) return false;
*key = in.substr(0, pos);
*val = in.substr(pos + gap.size());
if(key->empty() || val->empty()) return false;
return true;
}