TCP通信是面向连接、面向字节流的,相对于UDP通信,实现起来步骤要略微多一点,但相对应的可靠性比较高。下面依次说明服务端和客户端的实现步骤并进行测试。
目录
一、服务端
1、创建通信套接字
站在OS的角度其实就相当于打开一个用于通信的文件,该文件对应的文件描述符就是通信套接字
// 1. 创建通信套接字
int server = socket(AF_INET,SOCK_STREAM,0);
if(server<0){
std::cerr<<"socket创建失败"<<std::endl;
return 1;
}
2、绑定IP地址和端口
这里就是相当于固定了住址一样,别人想要给你寄点啥,直接填这个住址就行
//2. 绑定ip和端口
struct sockaddr_in local;
memset(&local,0,sizeof(local));
local.sin_family = AF_INET; //协议家族
local.sin_port = htons(port); //绑定端口,接收发送给该端口的所有数据
local.sin_addr.s_addr = INADDR_ANY; //IP地址不限,不论哪个IP地址给服务端发数据,只要端口号对应,全部接收
if (bind(server,(struct sockaddr*)&local,sizeof(local))<0)
{
std::cerr<<"服务端绑定失败"<<std::endl;
return 2;
}
3、将套接字设置为监听状态
这里就相当于是,你家大门敞开,随时欢迎有人来访
//3. 将套接字设为监听状态,监听来自客户端的请求
int ret = listen(server,5);
if(ret < 0){
std::cerr<<"listen调用失败"<<std::endl;
return 3;
}
4、当有连接请求来临时,接受连接请求
当没有连接请求到来时,这里暂时设置为阻塞等待,也就是说,如果服务端没有收到连接请求,服务端会一直堵在accept函数这里,不会继续向下运行。
// 4. 通过死循环不断接受请求
for(;;){
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(server,(struct sockaddr*)&peer,&len);
if (new_sock<0){
continue;
}
std::cout<<"客户端已连接...."<<std::endl;
//连接以后,下面开始提供服务
}
5、提供服务
到这一步说明服务端和客户端 已经连接成功了,现在就需要从套接字中读取客户端的数据,然后做出响应。下面就以回显为例,当服务器接收到数据时,将收到的数据不做任何处理返回给客户端。
因为TCP是面向字节流的,而UDP是使用数据包传输的,所以TCP接收数据时,我们既可以使用read函数,也可以使用recv函数
for(;;){
//accept接收请求
//提供服务
while(1){
char buffer[1024];
memset(buffer,0,sizeof(buffer));
int s = read(new_sock,buffer,sizeof(buffer)-1); //读取客户端发来的数据
if(s>0){
buffer[s]=0;
std::cout<<"client# "<<buffer<<std::endl;
std::string msg = "服务端收到了客户端的消息: ";
msg.append(buffer);
write(new_sock,msg.c_str(),msg.size()); //将接收到的内容返回给客户端
}
else if(s==0){
std::cout<<"客户端已退出..."<<std::endl;
}
else{
std::cerr<<"读取出错..."<<std::endl;
break;
}
}
}
二、客户端
1、创建套接字
//1. 创建客户端套接字
int client = socket(AF_INET, SOCK_STREAM, 0);
if (client < 0)
{
std::cerr << "socket创建失败" << std::endl;
return 1;
}
2、绑定端口
客户端有这个步骤,但无需我们完成,这个任务就交给OS来做,这一点在udp测试的时候已经说明了
3、连接服务端
现在万事俱备,可以开始连接服务端了,我们需要准备的东西是 服务端的地址信息,因为既然要连接,那就需要知道对方的地址才能连接。
//2. 准备好服务端的地址信息
struct sockaddr_in server;
server.sin_family = AF_INET;
uint16_t port = 8880;
server.sin_port = htons(port);
server.sin_addr.s_addr = inet_addr("124.222.215.205");
//3. 连接服务端
if(connect(client,(struct sockaddr*)&server,sizeof(server))<0){
std::cerr<<"客户端连接失败"<<std::endl;
return 2;
}
4、发送数据
到这里的话,基本就算是连接成功了,我们这里就从键盘获取输入,然后发送给服务端。
std::cout<<"连接成功..."<<std::endl;
while (1)
{
char buffer[1024];
std::cout<<"client# "<<std::endl;
memset(buffer,0,sizeof(buffer));
fgets(buffer,sizeof(buffer),stdin); //从键盘获取输入
write(client,buffer,sizeof(buffer)); //发送给服务端
ssize_t s = read(client,buffer,sizeof(buffer)-1);
if(s>0){
buffer[s]=0;
std::cout<<"server# "<<buffer<<std::endl;
}
}
三、TCP通信测试
下面是tcp的通信结果,我们从服务端可以看出,OS确实给客户端随机分配了一个端口号。