文章目录
- TCP通信所需要的套接字
- socket()
- bind()
- listen()
- accept
- connect()
- 封装TCP socket
TCP通信所需要的套接字
socket()
socket()函数主要作用是返回一个描述符,他的作用就是打开一个网络通讯端口,返回的这个描述符其实就可以理解为一个文件描述符,tcp在通讯的时候是会开辟一个缓存空间的,我们发送和读取消息可以理解为在这个缓存空间中进行的。因此这里我们可以知道我们可以直接用write和read函数进行消息的写入和读取,如果socket()调用出错则返回-1;
对于IPv4, family参数指定为AF_INET;
对于TCP协议,type参数指定为SOCK_STREAM, 表示面向流的传输协议
protocol参数的介绍从略,指定为0即可。
代码如下以ipv4协议为例
int socketfd=socket(AF_INET,SOCK_STREAM,0);
bind()
bind()函数的作用是为了让socket返回的描述符与端口号进行绑定在bind函数之前我们还需要做一个工作就是将我们的信息存入一个结构体sockaddr_in中,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度
我们的程序中对myaddr参数是这样初始化的:
sockaddr_in local;
bzero(&local, sizeof(local));//将整个结构体清0
local.sin_port = htons(port_);//porty_是我们的端口号这里是将这个端口号转化为网络序列一般我们的端口号都设置为8000以上
local.sin_family = AF_INET;//设置地址类型为AF_INET
inet_aton(ip_.c_str(), &(local.sin_addr));//网络地址为INADDR_ANY, 这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址, 这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址;
listen()
liasten函数生命socketfd正处于监听状态并且最多可以允许backlog个客户端处于连接等待状态如果收到更多连接请求就会忽略
listen成功返回0 失败则是-1
accept
accept函数是为了接受来自客户端的连接请求的函数它是在三次握手之后发挥作用,这里也会有一些问题比如说accept发挥作用的时候此时并没有客户发送连接请求该怎么办呢?这里会陷入阻塞等待的状态也就是一直等到有连接请求为止。
connect()
客户端需要调用connect()连接服务器;
connect和bind的参数形式一致, 区别在于bind的参数是自己的地址, 而connect的参数是对方的地址;
connect()成功返回0,出错返回-1
封装TCP socket
tcp_socket.hpp
#pragma once
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <string>
#include <cassert>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
#define CHECK_RET(exp) if (!(exp)) {\
return false;\
}
class TcpSocket {
public:
TcpSocket() : fd_(-1) { }
TcpSocket(int fd) : fd_(fd) { }
bool Socket() {
fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (fd_ < 0) {
perror("socket");
return false;
}
printf("open fd = %d\n", fd_);
return true;
}
bool Close() const {
close(fd_);
printf("close fd = %d\n", fd_);
return true;
}
bool Bind(const std::string& ip, uint16_t port) const {
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
int ret = bind(fd_, (sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("bind");
return false;
}
return true;
}
bool Listen(int num) const {
int ret = listen(fd_, num);
if (ret < 0) {
perror("listen");
return false;
}
return true;
}
bool Accept(TcpSocket* peer, std::string* ip = NULL, uint16_t* port = NULL) const {
sockaddr_in peer_addr;
socklen_t len = sizeof(peer_addr);
int new_sock = accept(fd_, (sockaddr*)&peer_addr, &len);
if (new_sock < 0) {
perror("accept");
return false;
}
printf("accept fd = %d\n", new_sock);
peer->fd_ = new_sock;
if (ip != NULL) {
*ip = inet_ntoa(peer_addr.sin_addr);
}
if (port != NULL) {
*port = ntohs(peer_addr.sin_port);
}
return true;
}
bool Recv(std::string* buf) const {
buf->clear();
char tmp[1024 * 10] = {0};
// [注意!] 这里的读并不算很严谨, 因为一次 recv 并不能保证把所有的数据都全部读完
// 参考 man 手册 MSG_WAITALL 节.
ssize_t read_size = recv(fd_, tmp, sizeof(tmp), 0);
if (read_size < 0) {
perror("recv");
return false;
}
if (read_size == 0) {
return false;
}
buf->assign(tmp, read_size);
return true;
}
bool Send(const std::string& buf) const {
ssize_t write_size = send(fd_, buf.data(), buf.size(), 0);
if (write_size < 0) {
perror("send");
return false;
}
return true;
}
bool Connect(const std::string& ip, uint16_t port) const {
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
int ret = connect(fd_, (sockaddr*)&addr, sizeof(addr));
if (ret < 0) {
perror("connect");
return false;
}
return true;
}
int GetFd() const {
return fd_;
}
private:
int fd_;
};
tcp_server.hpp
#pragma once
#include <functional>
#include "tcp_socket.hpp"
typedef std::function<void (const std::string& req, std::string* resp)> Handler;
class TcpServer {
public:
TcpServer(const std::string& ip, uint16_t port) : ip_(ip), port_(port) {
}
bool Start(Handler handler) {
// 1. 创建 socket;
CHECK_RET(listen_sock_.Socket());
// 2. 绑定端口号
CHECK_RET(listen_sock_.Bind(ip_, port_));
// 3. 进行监听
CHECK_RET(listen_sock_.Listen(5));
// 4. 进入事件循环
for (;;) {
// 5. 进行 accept
TcpSocket new_sock;
std::string ip;
uint16_t port = 0;
if (!listen_sock_.Accept(&new_sock, &ip, &port)) {
continue;
}
printf("[client %s:%d] connect!\n", ip.c_str(), port);
// 6. 进行循环读写
for (;;) {
std::string req;
// 7. 读取请求. 读取失败则结束循环
bool ret = new_sock.Recv(&req);
if (!ret) {
printf("[client %s:%d] disconnect!\n", ip.c_str(), port);
// [注意!] 需要关闭 socket
new_sock.Close();
break;
}
// 8. 计算响应
std::string resp;
handler(req, &resp);
// 9. 写回响应
new_sock.Send(resp);
printf("[%s:%d] req: %s, resp: %s\n", ip.c_str(), port,
req.c_str(), resp.c_str());
}
}
return true;
}
private:
TcpSocket listen_sock_;
std::string ip_;
uint64_t port_;
};
我们开开心心的在一起