TCP协议:传输控制协议
特点:面向连接的、可靠的、面向字节流传输的协议
客户端
1.创建套接字
2.绑定地址信息(不推荐主动绑定):操作系统会根据系统资源自动分配一个未使用的端口进行地址绑定
3.向服务端发起连接请求 :进行TCP三次握手与服务端建立连接
4.收发数据
5.关闭套接字
服务端
1.创建套接字
2.绑定地址信息
3.开始监听-将socket状态置为LISTEN状态
告诉系统可以开始处理客户端的连接请求了0。
当服务端收到一个新建连接请求时,会为这个客户端创建一个新的socket,用于通信
4.获取新建连接的描述符
5.收发数据--使用新建的连接与指定的客户端进行通信
6.关闭套接字
接口介绍
int socket(int domain, int type, int protocol);
type:SOCK_STREAM
protocol:IPPROTO_TCP
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int listen(int sockfd, int backlog);
sockfd:套接字描述符
backlog:服务端同一时间能够建立的连接数量
已完成连接队列的数量就是backlog+1
未完成连接队列:处于半连接的
int connect(int sockfd,struct sockaddr *addr,socklen_t len)
//获取新建连接:获取新建连接的描述符
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:监听套接字描述符
addr:新建连接的客户端的地址信息
len:输入输出线束,指定以及返回地址长度
返回值:新建连接socket的描述符--用于与指定客户端进行通信的句柄 失败返回-1;
ssize_t send(int sockfd,void *data,int len,int flag);
ssize_t recv(int sockfd,void *buf,int len,int flag);
返回值
1.返回实际获取到的长度
2.当连接断开时 返回0
3.出错返回-1表示send连接断开
int close(int fd);
公共头文件
#include
#include
#include
#include
#include
#include
using namespace std;
#define MAX_LISTEN 5
#define CHECK_RET(q) if((q)==false){return -1;}
class TcpSocket
{
public:
TcpSocket() : _sockfd(-1) {}
bool Socket()
{
_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (_sockfd < 0)
{
perror("socket error");
return false;
}
return true;
}
bool Bind(const string &ip, uint16_t port)
{
sockaddr_in addr;
Addr(&addr, ip, port);
int ret = bind(_sockfd, (sockaddr *)&addr, sizeof(sockaddr_in));
if (ret < 0)
{
perror("bind error");
return false;
}
return true;
}
bool Listen(int backlog = MAX_LISTEN)
{
int ret = listen(_sockfd, backlog);
if (ret < 0)
{
perror("listen error");
return false;
}
return true;
}
bool Connect(const string &ip, uint16_t port)
{
sockaddr_in addr;
Addr(&addr, ip, port);
int ret = connect(_sockfd, (sockaddr *)&addr, sizeof(sockaddr_in));
if (ret < 0)
{
perror("connect error");
return false;
}
return true;
}
bool Accept(TcpSocket *sock, string *ip = nullptr, uint16_t *port = nullptr)
{
sockaddr_in addr;
socklen_t len = sizeof(sockaddr_in);
int newfd = accept(_sockfd, (sockaddr *)&addr, &len);
if (newfd < 0)
{
perror("accept error");
return false;
}
if (ip != nullptr)
*ip = inet_ntoa(addr.sin_addr);
if (port != nullptr)
*port = ntohs(addr.sin_port);
sock->_sockfd = newfd;
return true;
}
bool Send(const string &data)
{
int total = 0;
while (total < data.size())
{
int ret = send(_sockfd, &data[0] + total, data.size() - total, 0);
if (ret < 0)
{
perror("send error");
return false;
}
total += ret;
}
return true;
}
bool Recv(string *buf)
{
char tmp[4096] = {0};
int ret = recv(_sockfd, tmp, 4096, 0);
if (ret < 0)
{
perror("recv error");
return false;
}
else if (ret == 0)
{
cout << "client shutdown" << endl;
return false;
}
buf->assign(tmp, ret);
return true;
}
bool Close()
{
if (_sockfd != -1)
close(_sockfd);
return true;
}
private:
void Addr(sockaddr_in *addr, const string &ip, uint16_t port)
{
addr->sin_port = htons(port);
addr->sin_family = AF_INET;
addr->sin_addr.s_addr = inet_addr(ip.c_str());
}
int _sockfd;
};
服务端
int main()
{
TcpSocket listen_sock;
listen_sock.Socket();
listen_sock.Bind("10.206.0.2", 9001);
listen_sock.Listen();
while (1)
{
TcpSocket io_sock;
string ip;
uint16_t port;
bool ret = listen_sock.Accept(&io_sock, &ip, &port);
if (ret == false)
{
usleep(1000);
continue;
}
cout << "client connect success!" << ip << ":" << port << endl;
string buf;
ret = io_sock.Recv(&buf);
if (!ret)
{
io_sock.Close();
continue;
}
cout << "client say:" << buf << endl;
buf.clear();
cout << "server say:";
cin >> buf;
ret = io_sock.Send(buf);
if (!ret)
{
io_sock.Close();
continue;
}
}
listen_sock.Close();
}
客户端
#include "tcpsocket.hpp"
int main()
{
TcpSocket client;
client.Socket();
CHECK_RET(client.Connect("10.206.0.2", 9001));
while (1)
{
string buf;
cout << "client say:";
cin >> buf;
CHECK_RET(client.Send(buf));
buf.clear();
CHECK_RET(client.Recv(&buf));
cout << "server say:" << buf << endl;
}
client.Close();
return 0;
}
从上面可以看到 客户端与服务端之间通信之后 ,客户端在发送数据服务器这边就不在显示客户端发送来的数据。原因:服务器代码中循环进行获取新连接和通信操作。当通信一次之后,代码流程会循环上去获取新的连接请求,此时服务端代码阻塞,导致客户端发送的数据没有显示。
处理方法:进行多执行流程,将通信流程分发给多个执行流,主流程只是获取连接请求,然后将通信任务分发给不同执行流,使得一个执行流对应一个客户端的通信。这里有两种方案:多进程与多线程
多进程版本中需要注意的问题
1.对于fork子进程后,子进程会复制父进程的所有信息,包括套接字描述符,由于父子进程数据独有,因此父进程创建完子进程后需要关闭当前获取的连接的套接字描述符,释放父进程的文件描述符资源。
2.当某一个客户端退出时,服务端中与其通信的进程也会退出,因此需要注意:由于主流程一直在循环获取新建连接请求分发任务,有可能某一个子进程退出时,此时父进程还在阻塞等待获取新的连接请求,没有去获取子进程退出码,让子进程退出,释放资源。因此需要注意僵尸进程问题。
由于主流程需要一直去不断获取新建连接请求,因此就不要使用同步接口wait或者waitpid了。直接使用异步操作的信号来进行处理。将SIGCHILD进行显示忽略
服务端
//v2.0 多进程版本
void work(TcpSocket &io_sock)
{
bool ret;
while (1)
{
string buf;
ret = io_sock.Recv(&buf);
if (!ret)
{
io_sock.Close();
return;
}
cout << "client say:" << buf << endl;
buf.clear();
cout << "server say:";
cin >> buf;
ret = io_sock.Send(buf);
if (!ret)
{
io_sock.Close();
return;
}
}
}
//多进程实现
int main()
{
TcpSocket listen_sock;
listen_sock.Socket();
listen_sock.Bind("10.206.0.2", 9000);
listen_sock.Listen();
//多进程中进程退出父进程不一定能处理过来 使用信号来进行异步操作
signal(SIGCHLD, SIG_IGN); //显式的忽略信号,让子进程退出后直接释放资源防止僵尸进程
string ip;
uint16_t port;
while (1)
{
TcpSocket io_sock;
bool ret = listen_sock.Accept(&io_sock, &ip, &port);
if (ret == false)
{
usleep(1000);
continue;
}
cout << "client connect success!" << ip << ":" << port << endl;
pid_t pid = fork();
if (pid == 0)
{
work(io_sock);
io_sock.Close();
exit(0);
}
//父进程要关闭io套接字 原因父子进程io套接字独有,底层只有一个io套接字
//但是有两个独有的文件描述符 父子进程文件描述符表独有
io_sock.Close(); //关闭父进程的文件描述符,释放资源
}
listen_sock.Close();
return 0;
}
//v3.0 多线程版本
void *work(void *arg)
{
TcpSocket *io_sock = (TcpSocket *)arg;
bool ret;
while (1)
{
string buf;
ret = io_sock->Recv(&buf);
if (!ret)
{
io_sock->Close();
delete io_sock;
return nullptr;
}
cout << "client say:" << buf << endl;
buf.clear();
cout << "server say:";
cin >> buf;
ret = io_sock->Send(buf);
if (!ret)
{
io_sock->Close();
delete io_sock;
return nullptr;
}
}
delete io_sock;
return nullptr;
}
int main()
{
TcpSocket listen_sock;
listen_sock.Socket();
listen_sock.Bind("10.206.0.2", 9000);
listen_sock.Listen();
string ip;
uint16_t port;
while (1)
{
TcpSocket *io_sock = new TcpSocket() ;
bool ret = listen_sock.Accept(io_sock, &ip, &port);
if (ret == false)
{
usleep(1000);
continue;
}
cout << "client connect success!" << ip << ":" << port << endl;
//pthread_t tid;
//tid = pthread_create(work,nullptr,nullptr,io_sock);
thread t1(work,io_sock);
t1.detach();
}
listen_sock.Close();
return 0;
}