文章目录
- 一、预备知识
- 认识端口
- 认识TCP协议
- 认识UDP协议
- 网络字节序
- 二、socket编程接口
- 1.socket常见API
- 2.socket结构
- 总结
- 3.其他接口
- 1.IP地址《=》整数 inet_addr等等
- 2.读取报文 - recvfrom
- 三、简单的UDP网络程序 - 聊天室
- 1.源代码
- 展示
- 四、TCP
- 1.编写TCP服务器程序
- 1.创建套接字 - socket
- 2.填写服务信息 - struct sockaddr_in
- 3.本地信息写入内核 - bind
- 4.监听socket - listen
- 5.获取连接 - accept
- 6.开始服务
- 1.创建子进程执行服务
- 2.创建线程执行服务
- 3.完整代码
- 2.编写TPC客户端程序
- 1.创建套接字
- 2.发起链接请求
- 3.完整代码
- 五、守护进程
- 1.如何自己形成
- 2.系统接口生成守护进程 - daemon
- 3.命令行运行变成守护进程 (命令)
- 六、TCP理论(感性认识,铺垫下一章)
一、预备知识
认识端口
上一章节,已经认识IP地址,IP地址可以标识一台主机。
但在我们在网络通信的时候,只要让两台主机能够通信就可以了吗?
实际上,在进行通信的时候,不仅仅要考虑两台主机间互相交互数据。本质上讲,进行数据交互的时候是用户和用户在进行交互,用户的身份通常是用程序体现的。程序一定是在运行中——进程
主机间在通信的目的
本质是:在各自的主机上的两个进程在互相交换数据!
IP地址可以完成主机和主机的通信,而主机上各自的通信进程,才是发送和接受数据的一方
IP —— 确保主机的唯一性
端口号(port)—— 确保该主机上的进程的唯一性
IP+PORT = 标识互联网中唯一的一个进程!!把这个两个组合叫做secket。
网络通信的本质:进程间通信
认识TCP协议
此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一些细节问题
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
认识UDP协议
此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
网络字节序
网络字节序:大端
发送顺序:先发低地址再发高地址
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络
字节序和主机字节序的转换。
- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些 函数不做转换,将参数原封不动地返回。
二、socket编程接口
1.socket常见API
创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
- int socket(int domain, int type, int protocol);
参数:
domain:域,表明通信方式
a.本地通信填AF_UNIX
b.网络通信填AF_INET
type:套接字类型,决定了我们通信的时候对应的报文类型
a.流式(tcp)
b.用户数据报(udp)
protocol:协议类型
a.网络应用中:0
返回值:
文件描述符,没错就是open函数返回那个文件描述符。
绑定端口号 (TCP/UDP, 服务器)
- **int bind(int socket, const struct sockaddr *address,socklen_t address_len); **
开始监听socket (TCP, 服务器)
- int listen(int socket, int backlog);
接收请求 (TCP, 服务器)
- int accept(int socket, struct sockaddr* address,socklen_t* address_len);
建立连接 (TCP, 客户端)
- int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
2.socket结构
各种网络协议的地址格式并不相同,使用结构也不相同,可以先用struct sockaddr接受结构,再根据前16位地址类型判断是什么网络协议。
sockaddr 结构
sockaddr_in 结构
虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址
in_addr结构
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
总结
对于struct sockaddr_in 要填参数就是
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET;//协议家族
ip_ = ip_.empty()?INADDR_ANY:ip_;
local.sin_addr.s_addr = inet_addr(ip_.c_str());//ip 会用到inet_addr
local.sin_port =htons(port_);//端口 会用到htons
3.其他接口
1.IP地址《=》整数 inet_addr等等
自动转成网络字节序
2.读取报文 - recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
参数:
flags:0(阻塞式读取)
三、简单的UDP网络程序 - 聊天室
- 云服务禁止bind云服务器上的任何确定IP,只能使用INADDR_ANY
- 虚拟机随意,查看ip指令ifconfig
1.源代码
聊天室服务器代码:
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <unordered_set>
#include "Log.hpp"
using namespace std;
static void Usage(const std::string proc)
{
std::cout << "Usage:\n\t" << proc << " port [ip]" << std::endl;
}
// struct MyStructHash
// {
// size_t operator()(const sockaddr_in &obj) const
// {
// size_t port_hash = hash<uint16_t>{}(obj.sin_port);
// size_t ip_hash = hash<uint32_t>{}(obj.sin_addr.s_addr);
// return port_hash ^ (ip_hash << 1);
// }
// };
struct MyStructHash
{
std::size_t operator()(const struct sockaddr_in &addr) const
{
// Example hash calculation based on IP address and port
std::size_t hash = 17;
hash = hash * 31 + reinterpret_cast<const uint32_t *>(&addr.sin_addr)[0];
hash = hash * 31 + ntohs(addr.sin_port);
return hash;
}
};
struct MyStructEqual
{
bool operator()(const struct sockaddr_in &lhs, const struct sockaddr_in &rhs) const
{
return (lhs.sin_family == rhs.sin_family &&
lhs.sin_port == rhs.sin_port &&
memcmp(&lhs.sin_addr, &rhs.sin_addr, sizeof(struct in_addr)) == 0);
}
};
class UdpServer
{
public:
UdpServer(int port, std::string ip = "")
: port_((uint16_t)port),
ip_(ip),
sockfd_(-1)
{
}
~UdpServer() {}
void init()
{
// 1.创建socket套接字
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // 打开了一个文件
if (sockfd_ < 0)
{
logMessage(FATAL, "socket create error :%s:%d",
strerror(errno), sockfd_);
exit(1);
}
logMessage(DEBUG, "socket create success: %d", sockfd_);
// 2.绑定网络信息,指明ip+port
// 2.1先填充基本信息到struct sockaddr_in 在哪个头文件可以man inet_addr
struct sockaddr_in local;
// 初始化,全设置为0
bzero(&local, sizeof(local));
local.sin_family = AF_INET; // 填充协议家族
local.sin_port = htons(port_);
// 服务器都必须具有IP地址,"xx.yy.zz.aa",字符串风格点分十进制->4字节IP->uint32_t ip
// INADDR_ANY(0):程序员不关心会bind到哪里一个ip,任意地址bind,强烈推荐做法,所有服务器一般的做法
// inet_addr:指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行h->n
local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str());
if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) == -1)
{
logMessage(FATAL, "bind error: %s", strerror(errno));
exit(2);
}
logMessage(DEBUG, "socket bind success: %d", sockfd_);
// bind(sockfd,,sizeof(addr));
}
void broacast()
{
int cnt = 0;
for (const auto &e : clients_)
{
std::cout<<++cnt<<":"<<buffer<<endl;
sendto(sockfd_,buffer,sizeof(buffer)-1,0,(const struct sockaddr *)&e, (socklen_t)sizeof(e));
}
}
void start()
{
while (true)
{
struct sockaddr_in peer; // 输出型参数
socklen_t len = sizeof(peer); // 输入输出型参数
// logMessage(NOTICE, "Server 提供 servic 中...");
// sleep(1);
// UDP 无连接的
// 对方给你发消息,你想给对方回消息,要对方的ip和port
ssize_t s = recvfrom(sockfd_, buffer, sizeof(buffer) - 1,
0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
buffer[s] = 0;
logMessage(NOTICE,"client #%s",buffer);
clients_.insert(peer);
broacast();
}
else if (s == -1)
{
logMessage(WARNING, "recvfrom error:%s", strerror(errno));
continue;
}
// 除了对方的消息还有对方的ip和port
}
}
private:
char buffer[1024]; // 将来读取到的数据,都放在这里
uint16_t port_;
std::string ip_;
int sockfd_;
unordered_set<struct sockaddr_in, MyStructHash, MyStructEqual> clients_;
};
//.udpServer port [ip]
int main(int argc, char *argv[])
{
if (argc != 2 && argc != 3)
{
Usage(argv[0]);
exit(3);
}
uint16_t port = 0;
port = atoi(argv[1]);
std::string ip;
if (argc == 3)
{
ip = argv[2];
}
UdpServer svr(port, ip);
svr.init();
svr.start();
return 0;
}
聊天室输入栏代码:
#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cassert>
#include<stdlib.h>
static void Usage(std::string name)
{
std::cout << "Usage\n\t" << name << " server_ip server_port" << std::endl;
}
int main()
{
std::string server_ip = "114.55.229.240";
// std::string server_ip = "127.0.0.1";
uint16_t server_port = 8080;
std::string userName;
std::cout<<"请输入你昵称:";
std::cin>>userName;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
assert(sockfd > 0);
// client 需不需要bind不需要
// 所谓“不需要”,指的是:不需要用户之间bind端口号信息!因为OS会自动给你绑定,
// 非要做也可,但不推荐
// 为什么 ?clinet很多,不能给客户端port,port可能被别人使用
std::string buffer;
struct sockaddr_in peer;
bzero(&peer, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
peer.sin_port = htons(server_port);
while (true)
{
std::cout << userName<<": ";
std::cin >> buffer;
system("clear");
buffer=userName+": "+buffer;
ssize_t count = sendto(sockfd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&peer, (socklen_t)sizeof(peer));
if(count<0)
{
std::cout<<"sendto error"<<std::endl;
}
}
return 0;
}
聊天室显示栏代码:
#include <iostream>
#include <cstring>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cassert>
static void Usage(std::string name)
{
std::cout << "Usage\n\t" << name << " server_ip server_port" << std::endl;
}
char buffer[1024];
int main()
{
std::string server_ip = "114.55.229.240";
// std::string server_ip = "127.0.0.1";
uint16_t server_port = 8080;
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
assert(sockfd > 0);
struct sockaddr_in peer;
bzero(&peer, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
peer.sin_port = htons(server_port);
const char* str ="有人建立链接o~o";
ssize_t count = sendto(sockfd,str , strlen(str), 0, (struct sockaddr *)&peer, (socklen_t)sizeof(peer));
while (true)
{
ssize_t s = recvfrom(sockfd,buffer,sizeof(buffer)-1,0,nullptr,nullptr);
if(s>0)
{
buffer[s] =0;
std::cout<<buffer<<std::endl;
}
else
{
std::cerr<<"recvfrom error"<<std::endl;
}
}
return 0;
}
展示
四、TCP
1.编写TCP服务器程序
1.创建套接字 - socket
sock_ = socket(AF_INET, SOCK_STREAM, 0);
if (sock_ < 0)
{
logMessage(FATAL, "socket error: %s", strerror(errno));
exit(1);
}
2.填写服务信息 - struct sockaddr_in
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET; // 协议家族
local.sin_addr.s_addr = ip_.empty()?INADDR_ANY:inet_addr(ip_.c_str()); // ip 会用到inet_addr
local.sin_port = htons(port_); // 端口 会用到htons
3.本地信息写入内核 - bind
if (bind(sock_, (struct sockaddr *)&local, sizeof(local)) == -1)
{
logMessage(FATAL, "bind error: %s", strerror(errno));
exit(2);
}
4.监听socket - listen
为什么要监听?
tcp是面向连接的!
作用将套接字变成监听套接字,获取新的连接。
if(listen(listensock_,5)<0)
{
logMessage(FATAL,"listen : %s",strerror(errno));
exit(3);
}
logMessage(DEBUG, "listen success");
5.获取连接 - accept
void loop()
{
while(true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int servicSock = accept(listensock_,(struct sockaddr*)&peer,&len);
if(servicSock<0)
{
logMessage(WARNING,"accept error : %s",strerror(errno));
}
//获取客户端基本信息
int peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG,"accept success %s:%d, socket fd: %d",peerIp.c_str(),peerPort,servicSock);
//开始服务
}
}
6.开始服务
但开始服务,主线程不能执行,主线程还有获取链接的事情要做,所以就需要别的来做:
1.创建子进程执行服务
进程退出,如何回收资源?
方案一:
父进程将信号SIGCHLD设置为SIG_IGN,就不会产生僵尸进程
signal(SIGCHLD,SIG_IGN);
void loop()
{
signal(SIGCHLD,SIG_IGN);
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int servicSock = accept(listensock_, (struct sockaddr *)&peer, &len);
if (servicSock < 0)
{
logMessage(WARNING, "accept error : %s", strerror(errno));
}
// 获取客户端基本信息
int peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG, "accept success %s:%d, socket fd: %d", peerIp.c_str(), peerPort, servicSock);
// 提供服务
// 单进程版本,只能提供一个服务,
// transServic(servicSock,peerIp,peerPort);
// 多进程版本
pid_t id = fork();
assert(id != -1);
if (id == 0)
{
close(listensock_);
// 子进程
transServic(servicSock, peerIp, peerPort);
exit(0);
}
close(servicSock);//这一步是一定要。
}
}
方案二:
让孙子进行程服务,子进程立刻退出,孙子进程变成孤儿,由系统回收资源,父进程回收子进程资源就行,不会因为等待导致阻塞。
void loop()
{
// signal(SIGCHLD,SIG_IGN);
while (true)
{
std::cout<<"等待"<<std::endl;
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int servicSock = accept(listensock_, (struct sockaddr *)&peer, &len);
if (servicSock < 0)
{
logMessage(WARNING, "accept error : %s", strerror(errno));
}
// 获取客户端基本信息
int peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG, "accept success %s:%d, socket fd: %d", peerIp.c_str(), peerPort, servicSock);
// 提供服务
// 单进程版本,只能提供一个服务,
// transServic(servicSock,peerIp,peerPort);
// 多进程版本
pid_t id = fork();
assert(id != -1);
//对于子进程退出,如何回收资源
//方案一:设置信号SIGCLHD为默认
// if (id == 0)
// {
// close(listensock_);
// // 子进程
// transServic(servicSock, peerIp, peerPort);
// exit(0);
// }
//方案二:让孙子进程服务,子进程立刻退出,孙子进程变成孤儿,由系统回收资源
if(id==0)
{
close(listensock_);
if(fork()>0)exit(0);
transServic(servicSock, peerIp, peerPort);
exit(0);
}
close(servicSock);//这一步是一定要。
pid_t ret = waitpid(id,nullptr,0);
assert(ret>0);
(void)ret;
}
}
2.创建线程执行服务
void loop()
{
signal(SIGCHLD,SIG_IGN);
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int servicSock = accept(listensock_, (struct sockaddr *)&peer, &len);
if (servicSock < 0)
{
logMessage(WARNING, "accept error : %s", strerror(errno));
}
// 获取客户端基本信息
int peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG, "accept success %s:%d, socket fd: %d", peerIp.c_str(), peerPort, servicSock);
// 提供服务
// 单进程版本,只能提供一个服务,
// transServic(servicSock,peerIp,peerPort);
// // 多进程版本
// pid_t id = fork();
// assert(id != -1);
// //对于子进程退出,如何回收资源
// //方案一:设置信号SIGCLHD为默认
// if (id == 0)
// {
// close(listensock_);
// // 子进程
// transServic(servicSock, peerIp, peerPort);
// exit(0);
// }
// close(servicSock);//这一步是一定要。
// //方案二:让孙子进程服务,子进程立刻退出,孙子进程变成孤儿,由系统回收资源
// if(id==0)
// {
// close(listensock_);
// if(fork()>0)exit(0);
// transServic(servicSock, peerIp, peerPort);
// exit(0);
// }
// close(servicSock);//这一步是一定要。
// pid_t ret = waitpid(id,nullptr,0);
// assert(ret>0);
// (void)ret;
// 多线程版本
ThreadData* td = new ThreadData(peerPort,peerIp,servicSock,this);
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, (void *)td);
}
}
static void* threadRoutine(void* argc)
{
pthread_detach(pthread_self());
ThreadData* td = (ThreadData*)argc;
ServerTcp* ts = td->this_;
td->this_->transServic(td->sock_,td->clinetIp_,td->clientPort_);
close(td->sock_);
delete td;
}
class ThreadData
{
public:
uint16_t clientPort_;
std::string clinetIp_;
int sock_;
ServerTcp *this_;
ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts) : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
{}
};
3.完整代码
#include "util.hpp"
#include "Log.hpp"
class ServerTcp;
class ThreadData
{
public:
uint16_t clientPort_;
std::string clinetIp_;
int sock_;
ServerTcp *this_;
ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts) : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
{}
};
class ServerTcp
{
public:
ServerTcp(uint16_t port, const std::string ip = "") : port_(port), ip_(ip), listensock_(-1)
{
}
~ServerTcp()
{
}
void init()
{
listensock_ = socket(AF_INET, SOCK_STREAM, 0);
if (listensock_ < 0)
{
logMessage(FATAL, "socket error: %s", strerror(errno));
exit(1);
}
logMessage(DEBUG, "socket success");
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET; // 协议家族
local.sin_addr.s_addr = ip_.empty() ? INADDR_ANY : inet_addr(ip_.c_str()); // ip 会用到inet_addr
local.sin_port = htons(port_); // 端口 会用到htons
if (bind(listensock_, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind error: %s", strerror(errno));
exit(2);
}
logMessage(DEBUG, "bind success");
if (listen(listensock_, 5) < 0)
{
logMessage(FATAL, "listen : %s", strerror(errno));
exit(3);
}
logMessage(DEBUG, "listen success");
}
void loop()
{
signal(SIGCHLD,SIG_IGN);
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int servicSock = accept(listensock_, (struct sockaddr *)&peer, &len);
if (servicSock < 0)
{
logMessage(WARNING, "accept error : %s", strerror(errno));
}
// 获取客户端基本信息
int peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
logMessage(DEBUG, "accept success %s:%d, socket fd: %d", peerIp.c_str(), peerPort, servicSock);
// 提供服务
// 单进程版本,只能提供一个服务,
// transServic(servicSock,peerIp,peerPort);
// // 多进程版本
// pid_t id = fork();
// assert(id != -1);
// //对于子进程退出,如何回收资源
// //方案一:设置信号SIGCLHD为默认
// if (id == 0)
// {
// close(listensock_);
// // 子进程
// transServic(servicSock, peerIp, peerPort);
// exit(0);
// }
// close(servicSock);//这一步是一定要。
// //方案二:让孙子进程服务,子进程立刻退出,孙子进程变成孤儿,由系统回收资源
// if(id==0)
// {
// close(listensock_);
// if(fork()>0)exit(0);
// transServic(servicSock, peerIp, peerPort);
// exit(0);
// }
// close(servicSock);//这一步是一定要。
// pid_t ret = waitpid(id,nullptr,0);
// assert(ret>0);
// (void)ret;
// 多线程版本
ThreadData* td = new ThreadData(peerPort,peerIp,servicSock,this);
pthread_t tid;
pthread_create(&tid, nullptr, threadRoutine, (void *)td);
}
}
// 大小写转化服务
void transServic(int sock, const std::string clientIp, uint16_t clientPort)
{
assert(sock > 0);
assert(!clientIp.empty());
assert(clientPort >= 1024);
char inbuffer[BUFFER_SIZE];
while (true)
{
ssize_t s = read(sock, inbuffer, sizeof(inbuffer) - 1);
if (s > 0)
{
inbuffer[s] = '\0';
if (strcasecmp(inbuffer, "quit") == 0)
{
logMessage(DEBUG, "client quit--%s[%d]", clientIp.c_str(), clientPort);
break;
}
logMessage(DEBUG, "trans before:%s[%d]>>>%s", clientIp.c_str(), clientPort, inbuffer);
for (int i = 0; i < s; i++)
{
if (isalpha(inbuffer[i]) && islower(inbuffer[i]))
{
inbuffer[i] = toupper(inbuffer[i]);
}
}
logMessage(DEBUG, "trans after:%s[%d]>>>%s", clientIp.c_str(), clientPort, inbuffer);
write(sock, inbuffer, strlen(inbuffer));
}
else if (s == 0)
{
// pipe:读端一直在读,写端不写了,并且关闭写端,读端会如何?s == 0,代表对端关闭
// s == 0 :代表对端关闭,client退出
logMessage(DEBUG, "client quit--%s[%d]", clientIp.c_str(), clientPort);
break;
}
else
{
logMessage(DEBUG, "%s[%d] - read:%s", clientIp.c_str(), clientPort, strerror(errno));
exit(6);
}
}
// 只要走的这里client退出,服务到此结束
close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
logMessage(DEBUG, "server close %d done ", sock);
}
static void* threadRoutine(void* argc)
{
pthread_detach(pthread_self());
ThreadData* td = (ThreadData*)argc;
ServerTcp* ts = td->this_;
td->this_->transServic(td->sock_,td->clinetIp_,td->clientPort_);
close(td->sock_);
delete td;
}
private:
int listensock_;
uint16_t port_;
std::string ip_;
};
static void Usage(std::string proc)
{
std::cerr << "Usage:\n\t" << proc << " port [ip]" << std::endl;
std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1" << std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 3 && argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
std::string ip;
if (argc == 3)
{
ip = argv[2];
}
ServerTcp svr(port, ip);
svr.init();
svr.loop();
return 0;
}
2.编写TPC客户端程序
1.创建套接字
if (argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[2]);
std::string ip = argv[1];
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "sock error: " << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
2.发起链接请求
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
peer.sin_addr.s_addr = inet_addr(ip.c_str());
// 2.2发起请求
if (connect(sock, (const struct sockaddr *)&peer, sizeof(peer)) < 0)
{
std::cerr << "connect error : " << strerror(errno) << std::endl;
exit(CONNECT_ERR);
}
3.完整代码
#include "util.hpp"
#include "Log.hpp"
static void Usage(std::string proc)
{
std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8080" << std::endl;
}
volatile bool quit = false;
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[2]);
std::string ip = argv[1];
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "sock error: " << strerror(errno) << std::endl;
exit(SOCKET_ERR);
}
// 2.需要bind吗?需要,但不需要自己显示bind
// 3.需要listen吗?不需要
// 4.需要accpet吗?不需要
// 2需要发起链接请求-connect 向服务器发起请求
// 2.1先填充需要连接的远端主机的基本信息
struct sockaddr_in peer;
memset(&peer, 0, sizeof(peer));
peer.sin_family = AF_INET;
peer.sin_port = htons(port);
peer.sin_addr.s_addr = inet_addr(ip.c_str());
// 2.2发起请求
if (connect(sock, (const struct sockaddr *)&peer, sizeof(peer)) < 0)
{
std::cerr << "connect error : " << strerror(errno) << std::endl;
exit(CONNECT_ERR);
}
std::cout << "info :connect success:" << sock << std::endl;
std::string message;
while (!quit)
{
message.clear();
std::cout << "请输入你的消息>>>";
std::getline(std::cin, message);
if (strcasecmp(message.c_str(), "quit") == 0)
quit = true;
ssize_t s = write(sock, message.c_str(), message.size());
if (s > 0 )
{
message.resize(1024);
ssize_t s = read(sock, (char *)message.c_str(), 1024);
if (s > 0)
{
message[s] = 0;
}
else
{
break;
}
std::cout << " sever echo>>" << message << std::endl;
}
else if (s <= 0)
{
break;
}
}
close(sock);
return 0;
}
五、守护进程
一般以服务器的方式工作,对外提供服务的服务器,都是以守护进程(精灵进程)的方式在服务器中工作的,一旦启动之后,除非用户主动关闭,否则,一直会在运行。
当我们连接服务器,服务器就会给我构建一个会话,这个会话包含一个前台进程组,和0或多个后台进程组,而我们用的bash就是一个前台进程组。任何时候只能有一个前台进程组。
在命令行中启动一个进程就是在会话中启动一个进程组(可以一个),来完成某种任务
所有会话内的进程fork创建子进程,一般而言依旧属于当前会话!
一个进程自成进程组,自成新的会话就叫做守护进程or精灵进程
1.如何自己形成
注意:守护进程要编写,必须调用一个函数setsid():将调用进程设置成为独立的会话
进程组的组长,不能调用setsid()
我如何不成为组长?
你可以成为进程组内的第二个进程!常规做法,fork()子进程,子进程就不在是组长进程啦,它就可以成功调用setsid()
if(fork()>0)exit(0);
setsid()
管道:写端一直在写,读端关闭,写端会被终止,被信号终止SIGPIPE——为什么要做a原因
选做:
a.忽略SIGPIPE信号
b.更改进程的工作目录
c. 重定向/关闭0,1,2
1.关闭进程描述符(0,1,2)
2./dev/null文件 类似与所有Linux下的一个“文件黑洞&&垃圾桶”,凡是从/dev/null里面读写一概被丢弃
打开 /dev/null,并且进行对0,1,2,重定向!
注:守护进程名字格式: 名字+d
#pragma once
#include<signal.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
void deamonnize()
{
//1.忽略SIGPIPE
signal(SIGPIPE,SIG_IGN);
//2.更改进程工作目录
//chdir("路径")
//3.让自己不要成为进程组组长
if(fork()>0)exit(0);
//4.设置自己的一个独立的会话
setsid();
//5.重定向/关闭0,1,2
// //a.关闭
// close(0);
// close(1);
// close(2);
//b.打开/dev/null
int fd = open("dev/null",O_RDWR);
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
//STDIN_FILENO --0
//STDOUT_FILENO--1
//STDERR_FILENO--2
if(fd>2)close(fd);
}
2.系统接口生成守护进程 - daemon
3.命令行运行变成守护进程 (命令)
nohup ./程序 &
注:默认形成日志 nohup.out
六、TCP理论(感性认识,铺垫下一章)
1.tcp是面向连接的,client conect&&sercer accept
2.tcp在建立连接的时候,采用的是三次握手,在断开连接的时候,采用的是四次挥手
3.connect发起三次握手(client),close() client&& close() server->closet()执行4次挥手的2次
4.什么是3次握手?什么是4次挥手?
3次握手:
客户端->服务器:可以建立连接吗?
服务器->客户端:可以,什么时候开始?
客户端->服务器:就现在!
4次挥手:
客户端->服务器:断开连接可以吗?
服务器->客户端:可以
上面是客户端询问服务器
服务器->客户端:那我就和你断开连接可以吗?
客户端->服务器:好的!
上面是服务器询问客户端
在双方都表示同意断开连接时,才可以关闭相应fd(文件描述符)