TCP是面向连接的,需要两端建立链接才能通信。
客户端主动连接,申请服务。
服务端被动连接,提供服务。
需要给用户一个建立连接的功能。所以就要创建两个套接字
设置套接字是listen状态,本质就是允许用户连接。
#include
#include
int listen(int sockfd, int backlog);
socket:创建套接字返回的文件描述符
backlog:底层队列的链接长度,长度是有限制的,不宜设置太大一般分为全链接队列和半链接队列
返回值:成功返回0,失败返回-1
接受请求,并返回一个新的套接字用于提供服务。
#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd是由socket函数返回的套接字文件描述符。
addr和addrlen用来返回已连接的客户端的协议地址。
如果我们对客户端的协议地址不感兴趣,可以把arrd和addrlen均置为空指针
返回值:失败返回-1,成功返回一个新的套接字文件描述符
用于向服务端发起连接
#include
#include
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
sockfd是由socket函数返回的套接字描述符。
addr和addrlen用是要连接服务端的协议地址。
返回值:失败返回-1,成功返回0
在通信时,仍然使用我们创建的套接字,也就是sockfd
TCP通信是通过文件流传递的,所以可以使用read/write
等系统文件流调用接口读取通信的数据。但也有两个专门用于读取网络通信流的接口函数。 可以根据对流的读取结果俩判断客户端是否退出。
#include
#include
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
sockfd:accept返回的文件描述符
buf:读数据缓冲区
len:期望读取的数据长度
flags:读数据是IO、不一定有数据让你读,如果读取条件不成立,就挂起等待(默认为0,阻塞等待)
返回值:>0读取到了,<0未读取到,=0表示对端关闭了
#include
#include
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
sockfd:accept返回的文件描述符
buf:发送的数据
len:发送的长度
flags:如果发送条件不成立,就挂起等待(默认为0,阻塞等待)
udp中的recvfrom和sendto是用于无连接通信,在有连接的tcp通信中,一般采用recv和send。
inet_ntoa()
函数将以网络字节顺序给出的 Internet 主机地址转换为 IPv4 点分十进制表示法的字符串。
char *inet_ntoa(struct in_addr in);
#include
#include
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string proc)
{
std::cout << "Usgae:" << proc << "port" << std::endl;
}
void ServiceIO(int new_sock)
{
while (true)
{
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
//int s = recv(new_sock, buffer, sizeof(buffer) - 1, 0);
int s = read(new_sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
std::cout << "client#" << buffer << std::endl;
// 写回去
const char* echo_client = "Hello Client";
int s = write(new_sock, echo_client, strlen(echo_client));
}
else if (s == 0)
{
std::cout << "client quit..." << std::endl;
break;
}
else
{
std::cerr << "read error" << std::endl;
break;
}
}
}
int main(int argc, char* argv[])
{
if (argc != 2)
{
Usage(argv[0]);
return 1;
}
// 1. 创建监听套接字
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
if (listen_sock < 0)
{
std::cerr <<"socket error: " << errno << std::endl;
return 2;
}
// 2.绑定IP和port
struct sockaddr_in local;
memset(&local, 0, sizeof local);
local.sin_family = AF_INET;
local.sin_addr.s_addr = INADDR_ANY;
local.sin_port = htons(atoi(argv[1]));
if (bind(listen_sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
std::cout << "bind error:" << errno << std::endl;
return 3;
}
// 3.设置监听状态
const int back_log = 5;
if (listen(listen_sock, back_log) < 0)
{
std::cout << "listen error: " << errno << std::endl;
return 4;
}
// 4.获取连接,提供服务
while (true)
{
struct sockaddr_in client;
memset(&client, 0, sizeof(client));
socklen_t len = sizeof(client);
int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
if (new_sock < 0)
{
// 连接失败就连接下一个
continue;
}
// 接受信息
std::string client_ip = inet_ntoa(client.sin_addr);
uint16_t client_port = ntohs(client.sin_port);
std::cout << "get a new link -> : [" << client_ip << ":" << client_port <<"]# " << new_sock << std::endl;
// 单进程处理通信
ServiceIO(new_sock);
// 处理完关闭文件,防止文件描述符泄漏
close(new_sock);
}
}
#include
#include
#include
#include
#include
#include
#include
#include
void Usage(std::string proc)
{
std::cout << "Usage: " << proc << "server_ip server_port" << std::endl;
}
int main(int argc, char* argv[])
{
if (argc != 3)
{
Usage(argv[0]);
return 1;
}
uint16_t ser_port = atoi(argv[2]);
std::string ser_ip = argv[1];
// 1.创建套接字文件
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "socket error" << errno << std::endl;
return 2;
}
struct sockaddr_in server;
// 置零函数,除memset外,还有个bzero: void bzero(void *s, size_t n);
server.sin_family = AF_INET;
// 点分十进制转成四字节整型,并且将主机序列转为网络序列
server.sin_addr.s_addr = inet_addr(ser_ip.c_str());
server.sin_port = htons(ser_port);
// 2. 发起连接
if (connect(sock, (struct sockaddr*)&server, sizeof(server)) < 0)
{
std::cout << "connect server failer !" << std::endl;
return 3;
}
std::cout << "connect success!" << std::endl;
while(true)
{
std::cout << "Please Enter# ";
char buffer[1024];
fgets(buffer, sizeof(buffer)-1, stdin);
write(sock, buffer, strlen(buffer));
buffer[0] = 0;
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if(s > 0)
{
buffer[s] = 0;
std::cout << "server echo# " << buffer << std::endl;
}
}
return 0;
}
当没有关闭文件描述符的时候,不断增加
关闭之后每次都从4开始
只需要修改调用服务函数的地方即可。
多进程也有两种实现方式
版本1
signal(SIGCHLD, SIG_IGN); // 显示设置忽略17号信号,当进程退出后,自动释放僵尸进程
// 多进程处理通信
pid_t pid = fork();
if (pid < 0)
{
continue;
}
else if (pid == 0)
{
// 由于子进程会拷贝父进程的文件描述符,但是子进程只用来处理服务
// 所以要关闭继承下来的监听文件描述符
close(listen_sock);
ServiceIO(new_sock);
close(new_sock);
exit(0);
}
else
{
// 父进程,不能等待子进程,否则就成了单执行流
close(new_sock);
}
版本2
// 多进程处理通信
pid_t pid = fork();
if (pid < 0)
{
continue;
}
else if (pid == 0)
{
// 由于子进程会拷贝父进程的文件描述符,但是子进程只用来处理服务
// 所以要关闭继承下来的监听文件描述符
close(listen_sock);
if (fork() > 0)
exit(0); // 退出,由孙子进程执行
ServiceIO(new_sock);
close(new_sock);
exit(0);
}
else
{
// 1. 父进程,不能等待子进程,否则就成了单执行流
// 2. 父进程等待子进程,由孙子进程执行服务
waitpid(pid, nullptr, 0); //阻塞等待,但很快
close(new_sock);
}
void* HandlerRequest(void* args)
{
// 分离线程
pthread_detach(pthread_self());
int sock = *(int*)args;
delete (int*)args;
ServiceIO(sock);
close(sock);
}
// 多线程版本
pthread_t tid;
int* pram = new int(new_sock);
pthread_create(&tid, nullptr, HandlerRequest, pram);
服用之前写的线程池,然后将服务函数封装在类里面,传入线程池处理即可
//1. 构建一个任务
Task t(new_sock);
//2. 将任务push到后端的线程池即可
ThreadPool<Task>::GetInstance()->PushTask(t); // 单例模式下的线程池
task.cc // 服务函数
#pragma once
#include
#include
#include
namespace ns_task
{
class Task
{
private:
int sock;
public:
Task() : sock(-1) {}
Task(int _sock) : sock(_sock)
{
}
int Run()
{
//提供服务,我们是一个死循环
// while (true)
// {
char buffer[1024];
memset(buffer, 0, sizeof(buffer));
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0; //将获取的内容当成字符串
std::cout << "client# " << buffer << std::endl;
//拉取逻辑
std::string echo_string = ">>>server<<<, ";
echo_string += buffer;
write(sock, echo_string.c_str(), echo_string.size());
}
else if (s == 0)
{
std::cout << "client quit ..." << std::endl;
// break;
}
else
{
std::cerr << "read error" << std::endl;
// break;
}
// }
close(sock);
}
~Task() {}
};
}