TCP协议:面向链接,面向字节流,可靠通信
创建tcp_server
1.创建套接字
域:依旧选择AF_INET
连接方式:
选择SOCK_STREAM 可靠的
2.bind
3.监听装置
client要通信,要先建立连接,client主动建立连接,所以服务端要一直等待连接
4.获取连接
成功返回新的sockfd,失败返回-1
我们在使用UDP时,我们的sockfd一直都是同一个,但是在TCP这里我们收到了一个新的sockfd,那我们客户端服务端通信的时候是用哪一个socket呢?
一个故事:有一家饭店,有一个叫张三的员工在外面拉客,拉到客后就进入餐馆叫服务员A给顾客A提供服务,所以顾客A的所有要求都由服务员A提供。
所以我们在通信时用的是返回的新的socket,我们创建的socket理论上叫Listensocket
如果失败了,返回-1,我们得让循环退出吗?
不,我们的continue让socket持续监听
提供服务
调用read write进行读写
read如果读到了0,表示读到了文件结尾,表明(对端关闭了连接)
#pragma once
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "Comm.hpp"
using namespace std;
const int defaultblacklog = 5;
class TcpServer
{
public:
TcpServer(const uint16_t &port) : _port(port)
{
}
void Server(int sockfd)
{
while (true)
{
char buff[1024];
int n = read(sockfd, buff, sizeof(buff) - 1);
if (n > 0)
{
//读取成功
buff[n]=0;
lg.LogMessage(Info,"read success");
cout<<"#Client say:"<<buff<<endl;
// string sendbuff;
// cout<<"#Please Enter"<<endl;
// getline(cin,sendbuff);
// int m=write(sockfd,sendbuff.c_str(),sizeof(sendbuff));
// if(m<0)
// {
// lg.LogMessage(Fatal,"write failed errno:%d :%s",errno,strerror(errno));
// }
// lg.LogMessage(Info,"Write success.......");
}
else if(n==0)
{
break;
lg.LogMessage(Info,"Client quit.......");
}
else
{
lg.LogMessage(Fatal,"read failed errno:%d :%s",errno,strerror(errno));
}
}
}
void Init()
{
// 创建套接字
_listensocket = socket(AF_INET, SOCK_STREAM, 0);
if (_listensocket < 0)
{
lg.LogMessage(Fatal, "create socket failed errno %d:%s\n", errno, strerror(errno));
}
lg.LogMessage(Debug, "create socket success _socket:%d\n", _listensocket);
// bind
struct sockaddr_in addr;
bzero(&addr,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = _port;
addr.sin_addr.s_addr = INADDR_ANY;
int n = bind(_listensocket, CONV(&addr), sizeof(addr));
if (n < 0)
{
lg.LogMessage(Fatal, "create socket failed errno %d:%s\n", errno, strerror(errno));
}
lg.LogMessage(Debug, "bind socket success _socket:%d\n", _listensocket);
// 监听装置
int m = listen(_listensocket, defaultblacklog);
if (m < 0)
{
lg.LogMessage(Fatal, "listen failed errno %d:%s\n", errno, strerror(errno));
}
lg.LogMessage(Debug, "listen success _socket:%d\n", _listensocket);
}
void Start()
{
// 获取连接
while (true)
{
struct sockaddr_in peer;
socklen_t peerlen = sizeof(peer);
int wrsockfd = accept(_listensocket, CONV(&peer), &peerlen);
if (wrsockfd > 0)
{
// 获取连接成功 提供服务
Server(wrsockfd);
}
else
{
// 获取连接失败 但是一直获取
continue;
}
// 提供服务
close(wrsockfd);
}
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensocket;
};
netstat -nltp 查看服务器
客户端
1.创建套接字 (同sever端)
2.建立连接 连接后自动进行bind
inet_pton:更安全
#include <iostream>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "Comm.hpp"
using namespace std;
void Usage(std::string proc)
{
std::cout << "Usage : \n\t" << proc << "server_ip server_port\n"
<< std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
cerr << "Usage error" << endl;
exit(Usage_Err);
}
// 创建套接字
int _socket = socket(AF_INET, SOCK_STREAM, 0);
if (_socket < 0)
{
lg.LogMessage(Fatal, "create socket failed errno %d:%s\n", errno, strerror(errno));
}
lg.LogMessage(Debug, "create socket success _socket:%d\n", _socket);
// 建立连接
string serverip=argv[1];
uint16_t serverport=stoi(argv[2]);
struct sockaddr_in serveraddr;
bzero(&serveraddr,sizeof(serveraddr));
serveraddr.sin_family=AF_INET;
serveraddr.sin_port=serverport;
if(inet_pton(AF_INET,(serverip.c_str()),&serveraddr.sin_addr));
int n=connect(_socket,CONV(&serveraddr),sizeof(serveraddr));
if(n<0)
{
lg.LogMessage(Fatal, "connect failed errno %d:%s\n", errno, strerror(errno));
}
lg.LogMessage(Debug, "connect success _socket:%d\n", _socket);
// 输入信息
while(true)
{
string buff;
cout<<"Please Enter:"<<endl;
getline(cin,buff);
int n=write(_socket,buff.c_str(),sizeof(buff));
}
}
main,cc
#include "TcpServer.hpp"
#include "Comm.hpp"
#include "Daemon.hpp"
void Usage(std::string proc)
{
std::cout << "Usage : \n\t" << proc << "local_port\n" << std::endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
cerr<<"Usage error"<<endl;
exit(Usage_Err);
}
uint16_t port=stoi(argv[1]);
TcpServer *tcps=new TcpServer(port);
tcps->Init();
tcps->Start();
Daemon(true,false);
lg.Enable(ClassFile);
}
udp面向数据报和tcp面向字节流有什么区别?
数据报:数据和数据是有边界的 sendto一次对应着recv一次
字节流:write 一次十次一百次,read可能一次就读完,也可能几十次,但是read端与write端无关
我们编写IO代码,尤其是网络IO时,我们的read和write是有BUG的
我们的服务器得以后台进程的方式运行,真正的服务,是以后台进程以守护进程(精灵进程)的方式运行
守护进程
进程组ID 会话ID
同时启动的进程可以是一个进程组的,进程组id通常是其中的某一个进程的pid
每次我们登入linux->OS默认提供1.bash2.提供一个终端->给用户提供命令解释的服务->叫做一个会话组
在一个命令行启动的所有进程,最终都是这个会话内的一个进程组。
会话内的进程组,任何时候,一个会话内部存在很多个进程组,但是默认法人和时刻只允许一个进程组在前台(前台进程组)。
前台进程:可以接受IO的是前台进程、
jobs查看
fg task_number 前台
ctrl+Z bg task_number 后台 或者进程+&
守护进程自己是一个独立的会话,他不属于任何的bash会话。
谁启动setsid,谁就是会话的所属进程。
调用setsid的进程不能是组长,启动多个进程时,第一个启动进程就是组长,启动一个进程时,这个进程就是组长
所以我们一般要创建子进程,让父进程直接退出,所以守护进程一般都是孤儿进程
创建守护进程
1.忽略可能引起异常退出的信号
例如SIG_CHLD SIG_PIPE
2.让父进程退出
3.让自己成为一个新的会话setsid
4.将进程的CWD更改为/根目录 提高效率
chdir
5.独立的会话组,也就是说没有bash进程了,所以可以将所有文件dup(nullfd,1/0/2)
/dev/null
tips:1.命名守护进程我们都以d结尾
2.kill -9
ls /proc/pid -l
#include <iostream>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
using namespace std;
const char *rootdic = "/";
const char *nulldic = "/dev/null";
void Daemon(bool isroot, bool isclose)
{
// 忽略可能引起异常的型号
signal(SIGCHLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
// 守护进程不能是组长,所以得创建孤儿进程
pid_t id = fork();
if (id != 0)
exit(0);
// 创建一个新的会话
setsid();
// 根目录
if (isroot)
{
chdir(rootdic);
}
if (isclose)
{
close(0);
close(1);
close(2);
}
else
{
int nullfd = open(nulldic, O_WRONLY);
if (nullfd > 0)
{
dup2(nullfd, 0);
dup2(nullfd, 1);
dup2(nullfd, 2);
close(nullfd);
}
}
}
系统有没有将进程守护化的方法?
daemon
将服务器守护化,但企业一般都自己会实现进程守护化