目录
前言
1.接口介绍
2.编写服务器
3.编写客户端
4.编译链接
5.测试
6.总结
今天我们要介绍的是使用TCP协议实现数据通信,相比于之前写的UDP服务器实现数据信,在主体逻辑上并没有差别。客户端向服务器发送信息,服务器接受信息并回显,因为UDP是面向数据报,而TCP是面向连接的,所以在实现的时候接口上会有一些差别,下面,我们具体来看看UDP和TCP在编码的实现上有什么不同。
因为TCP是面向连接的,所以服务器创建完套接字,然后绑定成功后,将套接字设置为监听套接字
服务器启动之后,首先需要根据监听套接字建立连接,建立连接成功后返回一个新的文件描述符,后续的通信都是按照这个新的文件描述符按照读写文件的形式进行读写数据。
对于客户端来说创建完套接字之后,客户端启动之后首先需要建立连接
listen():设置sock为监听状态
#include
#include
int listen(int sockfd, int backlog);
sockfd:创建套接字的返回值
backlog:底层全连接队列的长度
accept():服务端建立连接
#include
#include
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd:监听套接字
struct sockaddr* addr:输出型参数,可以获取服务端的IP地址和port端口号
socklen_t* addrlen:结构体的大小
返回值:返回一个新打开的文件描述符
connect():客户端建立连接
#include
#include
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockfd:创建套接字返回值
struct sockaddr* addr:输出型参数,用来填写需要访问的服务端的IP地址和port端口号
socklen_t addrlen:结构体的大小
tcpServer.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include "log.hpp"
namespace server
{
using namespace std;
enum
{
USAGE_ERR = 1,
SOCKET_ERR,
BIND_ERR,
LISTEN_ERR
};
static const uint16_t gport = 8080;
static const int gback = 5;
class TcpServer
{
public:
TcpServer(const uint16_t &port = gport)
: _port(gport), _sock(-1)
{}
void InitServer()
{
_sock = socket(AF_INET, SOCK_STREAM, 0);
if (_sock < 0)
{
logMessage(FATAL, "create socket error");
exit(SOCKET_ERR);
}
logMessage(NORMAL, "create socket success");
// 绑定:
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind socket error");
exit(BIND_ERR);
}
logMessage(NORMAL, "bind socket success");
// 设置sock为监听状态:
if (listen(_sock, gback) < 0)
{
logMessage(FATAL, "listen socket error");
exit(LISTEN_ERR);
}
logMessage(NORMAL, "listen socket success");
}
void start()
{
for (;;)
{
// 建立连接:
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_sock, (struct sockaddr *)&peer, &len);
if (sock < 0)
{
logMessage(ERROR, "accept error, next");
continue;
}
logMessage(NORMAL, "accept a new link success");
std::cout << "sock: " << sock << std::endl;
//未来通信全部用sock,面向字节流的,后续全部都是文件操作:
serviceIO(sock);
close(sock);
}
}
void serviceIO(int sock)
{
char buffer[1024];
while(true)
{
ssize_t n = read(sock,buffer,sizeof(buffer)-1);
if(n > 0)
{
buffer[n] = 0;
cout << "recvice message: " << buffer << endl;
string outbuffer = buffer;
outbuffer += "[server echo]";
write(sock,outbuffer.c_str(),outbuffer.size());
}
else if(n == 0)
{
// 代表client退出
logMessage(NORMAL, "client quit, me too!");
break;
}
}
}
~TcpServer()
{}
private:
int _sock;
uint16_t _port;
};
}
tcpServer.cc:启动服务器
#include"tcpServer.hpp"
#include
using namespace server;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}
int main(int argc,char* argv[])
{
if(argc != 2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
unique_ptr tcs(new TcpServer(port));
tcs->InitServer();
tcs->start();
return 0;
}
tcpClient.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
namespace client
{
using namespace std;
class TcpClient
{
public:
TcpClient(const string& serverip,const uint16_t port)
:_serverip(serverip),_port(port),_sock(-1)
{}
void InitClient()
{
_sock = socket(AF_INET,SOCK_STREAM,0);
if(_sock < 0)
{
cerr << "create sock fail" << endl;
exit(-1);
}
}
void start()
{
//建立连接:
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(_port);
server.sin_addr.s_addr = inet_addr(_serverip.c_str());
if(connect(_sock,(struct sockaddr*)&server,sizeof(server)) != 0)
{
cerr << "connect fail" << endl;
}
else
{
string message;
while(true)
{
cout << "Please Enter: ";
getline(cin,message);
write(_sock,message.c_str(),message.size());
char buffer[1024];
int n = read(_sock,buffer,sizeof(buffer)-1);
if(n > 0)
{
buffer[n] = 0;
cout << "Server回复: " << buffer << endl;
}
else
{
break;
}
}
}
}
~TcpClient()
{
if(_sock >= 0)
close(_sock);
}
private:
string _serverip;
uint16_t _port;
int _sock;
};
} // namespace client
tcpClient.cc:启动客户端
#include"tcpClient.hpp"
#include
using namespace client;
static void Usage(string proc)
{
cout << "\nUsage:\n\t" << proc << " serverip serverport\n\n";
}
int main(int argc,char* argv[])
{
if(argc != 3)
{
Usage(argv[0]);
exit(-1);
}
uint16_t port = atoi(argv[2]);
string ip = argv[1];
unique_ptr tcc(new TcpClient(ip,port));
tcc->InitClient();
tcc->start();
return 0;
}
makefile:
.PHONY:all
all:tcpServer tcpClient
tcpServer:tcpServer.cc
g++ -o $@ $^ -std=c++11
tcpClient:tcpClient.cc
g++ -o $@ $^ -std=c++11
.PHONY:clean
clean:
rm tcpServer tcpClient
如图所示,服务端和客户端可以完成正常的数据通信了。
TCP协议和UDP协议在数据通信的实现中,除了一些接口使用的不同之外,其实并没有太大的不同,在之前说的UDP是面向数据报的而TCP是面向字节流的,这些特性又是如何体现的呢?关于这个问题,博主将在后面的文章中会为大家继续进行介绍。不要错过哦!