目录
echo_service
server
单进程单线程
多进程
多线程
线程池
client
echo_service_code
echo_service
还是跟之前UDP一样,我们先通过实际的代码来实现一些小功能,简单的来使用TCP协议进行简单的通信,话不多说,我们先实现一个echo_serve,就是客户端向服务器发消息,服务器把客户端发的消息返回给客户端
server
首先,我们还是先写服务器,先创建socket套接字,只不过与UDP的选项不同,第二个选项我们选SOCK_STREAM,因为TCP是面向字节流的
其次,还是要bind,bind之前要填充服务器相关信息
由于TCP是面向连接的,所以通信前要先建立连接,因为服务器是被连接的一端,所以要将服务器设置为监听状态
这里第二个参数我们暂且先用上,后面会有讲解
至此,我们初始化的工作就做完了
下面就是要开始我们的服务,我们上面说要先建立连接,所以不能直接接收数据,要先获取连接,我们用下面的接口
可以看到,它的返回值是一个文件描述符,可是socket函数的返回值不也是一个文件描述符吗?它们之间有什么区别呢?我么可以认为socket函数的返回值是一个饭店中站在门口拉客的,它拉到了客人就交给饭店内某个人去一对一服务,这个就是accept的返回值
做完了上面的一系列工作就可以进行通信了,由于TCP是面向字节流的,而文件也是面向字节流的,所以可以用read和write进行通信
网络中read的返回值有不同的含义,大于0就是读到的字节的个数,小于0是读取出错,等于0是客户端退出并且关闭连接,我们就可以用返回值去判断客户端的状态
上面就是大体的过程,下面我们要具体的来考虑一下服务器与客户端的交互过程应该如何写:
我们分为单进程单线程、多进程、多线程、线程池
单进程单线程
首先这个方案是绝对不可以的,这样只能处理一个请求,在和一个客户端交互的时候就不可能去accept新连接
多进程
单进程单线程肯定是不行的,我们必须多创建几个执行流来保证聊天和获取连接都要有执行流去执行
此时我们就可以创建子进程让子进程去和客户端进行交互,父进程重新去获取连接,这样就是可以的,但是父进程是要回收子进程的,父进程也不可能去一直等待,那应该如何做呢?我们之前学过一个信号SIG_CHLD,可以把这个信号的处理动作改为SIG_IGN
这样子进程在退出后会自动清理掉,不会产生僵尸,也不会通知父进程。
还有一种方法就是子进程马上创建孙进程,让孙进程去执行任务,之后子进程立马退出,父进程就可以直接wait到子进程,并且孙进程成为了孤儿进程,会由系统领养,我们就不用关心孙进程的回收问题了
并且要注意,因为与客户端的echo服务已经交给了子进程,那么父进程就没有必要保留这个文件描述符了,直接关掉即可,防止文件描述符泄露(只要是不用,但是占着资源的都叫泄露)
子进程是继承父进程的文件描述符表的,继承后各自独立,互不影响
多线程
我们可以使用原生的线程创建的接口来实现多线程的创建,虽然主线程也是要等待新线程的,但是我们可以将新线程设置为分离状态,这样主线程就不用等待了
线程池
当然除了上面的方式,我们自然可以引入线程池,但是其实线程池适合那些短的任务,就是很快做完又可以领新任务,像这种echo的长服务不适合线程池来做,但是我们还是写一下用一下
thread_t 就是线程池中要放的任务的类型
client
客户端跟UDP一样,先创建socket套接字,然后填充服务器相关信息,之后关键的一步是向服务器发起连接,因为服务器正等着人连接呢
收发消息不止可以用read和write,还可以用recv和send,用法也是很简单的
其实客户端就是如此简单
echo_service_code
//TcpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include<functional>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<sys/wait.h>
#include <unistd.h>
#include<pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"
#include"Threadpool.hpp"
enum ERRor
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR,
};
class TcpServer;//声明一下
struct threaddata
{
threaddata(int fd,InetAddr&addr,TcpServer*tp):_fd(fd),_addr(addr),_self(tp)
{}
int _fd;
InetAddr _addr;
TcpServer* _self;
};
class TcpServer
{
public:
TcpServer(uint16_t port) : _port(port), _listensock(-1)
{
}
void InitServer()
{
_listensock = socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0)
{
LOG(FATAL, "socket err");
exit(SOCKET_ERR);
}
LOG(INFO, "socket success");
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(_port);
server.sin_addr.s_addr = INADDR_ANY;
int n = bind(_listensock, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
LOG(FATAL, "bind err");
exit(BIND_ERR);
}
LOG(INFO, "bind success");
n = listen(_listensock, 16);
if (n < 0)
{
LOG(FATAL, "listen err");
exit(LISTEN_ERR);
}
LOG(INFO, "listen success");
}
void service(int fd, InetAddr addr)
{
LOG(INFO, "server get a new link,ip is %s,port is %d", addr.Ip().c_str(), addr.Port());
while (true)
{
char buffer[1024];
int n = read(fd, buffer, sizeof(buffer) - 1);
if (n > 0)
{
buffer[n] = 0;
LOG(INFO, "[%s:%d]#%s", addr.Ip().c_str(), addr.Port(), buffer);
std::string message = "server echo#";
message += buffer;
write(fd, message.c_str(), message.size());
}
else if (n < 0) // 读取出错
{
LOG(ERROR, "read error");
break;
}
else // n==0 client退出&&关闭连接
{
LOG(INFO, "[%s,%d] quit", addr.Ip().c_str(), addr.Port());
break;
}
}
}
static void*newthreaddo(void*arg)
{
pthread_detach(pthread_self());
threaddata*ptd=static_cast<threaddata*>(arg);
ptd->_self->service(ptd->_fd,ptd->_addr);
delete ptd;
return nullptr;
}
void StartServer()
{
while (true)
{
struct sockaddr_in client;
socklen_t len = sizeof(client);
int fd = accept(_listensock, (struct sockaddr *)&client, &len);
if (fd < 0)
{
LOG(WARNING, "accept err");
continue;
}
InetAddr addr(client);
// service(fd, addr);单进程单线程
// close(fd);
// int f = fork();//多进程
// if (f == 0)
// {
// // son
// if (fork() == 0)
// {// grandson
// close(_listensock);
// service(fd, addr);
// close(fd);
// }
// exit(0);//son创建完孙子后立马退出,孙子有由系统领养
// }
// // father
// close(fd);
// waitpid(f,nullptr,0);
// pthread_t t;//多线程
// threaddata* ptd=new threaddata(fd,addr,this);
// pthread_create(&t,nullptr,newthreaddo,ptd);
thread_t t=std::bind(&TcpServer::service,this,fd,addr);//线程池
ThreadPool<thread_t>::Getinstance()->Enqueue(t);
}
}
~TcpServer()
{
}
private:
uint16_t _port;
int _listensock;
};
//TcpClient.cc
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>
using std::cout;
using std::endl;
void Usage(const char *arg)
{
cout << "USAGE:" << endl;
cout << "# please enter: " << arg << "serverip serverport" << endl;
}
// ./client 127.0.0.1 8888
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string ip(argv[1]);
uint16_t port = std::stoi(argv[2]);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0)
{
cout << "socket err" << endl;
exit(1);
}
cout << "socket success" << endl;
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(port);
server.sin_addr.s_addr = INADDR_ANY;
int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
if (n < 0)
{
cout << "connect err" << endl;
exit(1);
}
cout << "connect success" << endl;
while (true)
{
cout << "please enter#";
std::string what;
std::getline(std::cin, what);
send(sockfd, what.c_str(), what.size(), 0);
char buffer[1024];
int num = recv(sockfd, buffer, sizeof(buffer), 0);
if (num > 0)
{
buffer[num] = 0;
cout << buffer << endl;
}
else if (num < 0)
{
cout << "recv error" << endl;
continue;
}
}
return 0;
}