tcp是面向连接的通信方式,一旦连接断开就无法进行通信
问题:如何在代码中知道连接断开,体现在哪
recv返回值是0,不仅代表没有接收导数据,更重要表现连接断开
- 如果代码中没有recv函数,也会知道连接断开
客户端发数据,关闭服务端
客户端第一次发送数据,会显示发送成功,第二次发送新数据,客户端才会退出
数据不是直接发送出去的,而是在发送缓冲区中,由OS在合适的时候发送,多次没有发送成功,才能检测到发送不成功- 直接退出的原因:连接断开,send发送数据的时候,程序直接异常退出
但是程序不能因为连接断开而退出程序
通过修改信号防止程序退出
netstat -anptu
查看当前程序所有网络连接状态
a查看所有信息
n不以读完名称显示端口或地址,比如22端口被识别成ssh
t过滤只显示tcp套接字信息
u过滤只显示udp套接字信息
问题:有时候网络程序关闭后,无法立即启动,会bind绑定地址报错,绑定失败,地址已经被使用
如果服务端主动关闭,无法重新启动;因为主动关闭连接,该连接不会被立即释放(对应的地址和端口依然被占用),而是要等待一段时间
如果客户端主动关闭,
#include<iostream>
#include<stdio.h>
#include<string>
#include<unistd.h>
#include<arpa/inet.h>//字节序转换接口文件
#include<netinet/in.h>//地址结构类型以及协议类型宏文件
#include<sys/socket.h>//套接字接口文件
using namespace std;
class TcpSocket
{
private:
int sockfd_;
public:
TcpSocket():sockfd_(-1)
{}
~TcpSocket()
{
Close();
//直接屏蔽掉close函数,每个客户端只能通信一次
//服务器的newsockfd不断开辟新对象,获取一个新连接收发数据完毕后,
//就又获取新连接生成新套接字,上次的fd还在服务器中
}
//创建套接字
bool Socket()
{
sockfd_=socket(AF_INET,SOCK_STREAM,0);
if(sockfd_<0)
{
cout<<"创建套接字失败"<<endl;
return false;
}
return true;
}
//服务端绑定IP和端口
//经过封装后,只需要传IP和端口(将本进程与本主机的IP和端口绑定)
//bind函数只是服务器调用,固定服务器的IP和端口号
bool Bind(const string& ip,uint16_t port)//port是16位整形
{
struct sockaddr_in addr;//定义结构体
addr.sin_family=AF_INET;
addr.sin_port=htons(port);//转换端口字节序
addr.sin_addr.s_addr=inet_addr(ip.c_str());
socklen_t len=sizeof(struct sockaddr_in);//地址长度
int ret=bind(sockfd_,(struct sockaddr*)&addr,len);//将文件描述符和IP端口绑定
if(ret<0)
{
cout<<"bind error"<<endl;
return false;
}
return true;
}
//服务端监听
//监听是将套接字状态设置成LISTEN状态,只有处于这个状态的套接字才能处理客户端的连接请求
bool Listen(int backlog)
{
int ret=listen(sockfd_,backlog);
if(ret<0)
{
cout<<"listen error"<<endl;
return false;
}
return true;
}
bool Connect(const string& srvip,uint16_t srvport)
{
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_addr.s_addr=inet_addr(srvip.c_str());
addr.sin_port=htons(srvport);
socklen_t len=sizeof(struct sockaddr_in);
int ret=connect(sockfd_,(struct sockaddr*)&addr,len);
if(ret<0)
{
cout<<"connect连接失败"<<endl;
return false;
}
return true;
}
//服务端获取新连接
//tcp服务器,涉及到对多个socket进行操作(有多少客服端连接上,就有多少个socket)
//每一个socket都需要accept,recv,send
//这三个操作都是阻塞操作,accept如果没有新连接就阻塞,recv没有接收到消息就阻塞
bool Accept(TcpSocket* newsocket ,string* cli_ip,uint16_t* cli_port)//用来接收客户端的IP和端口
{
struct sockaddr_in addr;
socklen_t addrlen=sizeof(struct sockaddr_in);
int newsockfd=accept(sockfd_,(struct sockaddr*)&addr,&addrlen);//newfd是进行实际通信的
if(newsockfd<0)
{
cout<<"newfd error"<<endl;
return false;
}
newsocket->sockfd_=newsockfd;
*cli_ip=inet_ntoa(addr.sin_addr);//拿到客户端的IP和端口
*cli_port=ntohs(addr.sin_port);
return true;
}
//发送接口封装后,只需输入要发送的内容就行
bool Send(const string& body)
{
ssize_t ret=send(sockfd_,body.c_str(),body.size(),0);
if(ret<0)
{
cout<<"发送错误"<<endl;
return false;
}
return true;
}
bool Recv(string* body)//recv返回值的三种含义 =0表示连接断开
// <0表示连接出错
// >0表示接收到数据
{
char tmp[1024];
ssize_t ret=recv(sockfd_,tmp,1023,0);//0表示阻塞接收,缓冲区没数据就阻塞
body->assign(tmp,ret);//从tmp中截取ret大小的数据放入body中
}
bool Close()
{
if(sockfd_!=-1)
{
close(sockfd_);
sockfd_=-1;
}
return true;
}
};
TCP客户端
#include"tcp.hpp"
int main(int argc,char* argv[])
{
if(argc!=3)
{
cout<<"输入有误"<<endl;
return -1;
}
TcpSocket sockfd;
string ip=argv[1];
uint16_t port=std::stoi(argv[2]);
sockfd.Socket();
sockfd.Connect(ip,port);
while(1)
{
string buf;
cout<<"客户端发送的数据:";
fflush(stdout);
cin>>buf;
sockfd.Send(buf);
buf.clear();
sockfd.Recv(&buf);
cout<<"客户端接收到的数据:"<<buf<<endl;
}
return 0;
}
TCP服务端
#include"tcp.hpp"
#include<signal.h>
void creatwork(TcpSocket sock);
int main(int argc,char* argv[])
{
if(argc!=3)
{
cout<<"输入有误"<<endl;
return -1;
}
string ip=argv[1];
uint16_t port=std::stoi(argv[2]);
TcpSocket sockfd;
sockfd.Socket();
sockfd.Bind(ip,port);
sockfd.Listen(10);
//客户端发起的连接很多,所以需要循环获取
while(1)
{
TcpSocket newsockfd;//这个新文件描述符是局部变量,循环一次结束就删除了
string ip;
uint16_t port;
bool ret=sockfd.Accept(&newsockfd,&ip,&port);
if(ret==false)
{
//不能让一个客户端连接出问题让程序崩溃退出
continue;
}
//创建子进程
creatwork(newsockfd);
}
return 0;
}
//父子进程代码共享,数据独有
//一旦创建了子进程,子进程会复制父进程,套接字父子进程各有一份
//子进程通过这个描述符与客户端通信
//父进程是不对该描述符操作,子进程将自己文件描述符关闭后不影响父进程,需要将父进程的描述符关闭
void creatwork(TcpSocket sock)
{
pid_t pid=fork();
if(pid>0)
{
sock.Close();
return;
}
else if(pid==0)
{
while(1)
{
string buf;
bool ret=sock.Recv(&buf);
cout<<"服务端接收的数据为:"<<buf<<endl;
buf.clear();
fflush(stdout);
cout<<"服务端发送的数据:";
cin>>buf;
sock.Send(buf);
if(ret==false)
{
sock.Close();
exit(-1);
}
}
}
exit(1);//子进程连接错误,或者收到数据错误,break跳出循环退出
//不能让子进程退出后运行和父进程一样的代码
}