作者: 华丞臧.
专栏:【网络】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注
)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 LeetCode刷题网站
对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识:
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
程序中对 sockaddr 参数是这样初始化的:
#include
#include
// 2.填充域
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
volatile bool quitSer = false;
void Usage(void *vgs)
{
std::cout << "Usage:./tcpserver port ip" << std::endl;
}
class server
{
public:
server(int port, std::string ip = "")
: sockfd_(-1)
, ip_(ip)
, port_(port)
{
}
~server()
{
}
public:
void init()
{}
void start()
{}
private:
int sockfd_;
uint16_t port_;
std::string ip_;
};
// ./tcpserver port ip
int main(int argc, char *argv[])
{
if (argc < 2 || argc > 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port = atoi(argv[1]);
std::string ip;
if (argc == 3)
ip = argv[2];
server tcpSer(port, ip);
tcpSer.init();
tcpSer.start();
return 0;
}
TCP套接字服务器端的 init()
,与UDP类似,只不过多了最后一步监听套接字,TCP套接字设置步骤如下:
void init()
{
// 1. 创建套接字
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_ < 0)
{
logMessage(FATAL, "socket:%s[%d]", strerror(errno), sockfd_);
exit(SOCK_ERR);
}
logMessage(DEBUG, "socket success..");
// 2.填充域
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
// 3. 绑定网络信息
if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
{
logMessage(FATAL, "bind:%s[%d]", strerror(errno), sockfd_);
exit(BIND_ERR);
}
logMessage(DEBUG, "bind success...");
// 4. 监听套接字
// 为什么要监听套接字? 因为TCP是面向连接的,在任何时候都可能请求连接
if (listen(sockfd_, 5) < 0)
{
logMessage(FATAL, "listen:%s[%d]", strerror(errno), sockfd_);
exit(LISTEN_ERR);
}
logMessage(DEBUG, "listen success...");
// 完成
}
在TCP套接字提供服务之前,需要使用accept获取连接,accept返回值是一个新的套接字文件描述符,使用该套接字文件描述符来进行网络通信。start()是server提供服务的接口,因此该函数必须是一个死循环(服务器都是在一个死循环当中)以给用户提供持续的服务;在start函数中,主要完成接收用户发送的消息并且将消息提取出来,其主要步骤如下图:
void start()
{
char inbuffer_[1024]; // 用来接收客户端发来的消息
// 提供服务
while (true)
{
quitSer = false;
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 5. 获取连接, accept 的返回值是一个新的 socketfd
int serviceSock = accept(sockfd_, (struct sockaddr *)&peer, &len);
if (serviceSock < 0)
{
// 获取链接失败
logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
continue;
}
while (!quitSer)
{
memset(inbuffer_, 0, sizeof(inbuffer_));
ssize_t s = recvfrom(serviceSock, inbuffer_, sizeof(inbuffer_) - 1, 0,
(struct sockaddr *)&peer, &len);
if (s > 0)
{
std::cout << s << std::endl;
// 接收成功
inbuffer_[s] = '\0';
}
else if (s == -1)
{
//
logMessage(WARINING, "recvfrom fialed:%s[%d]", strerror(errno), sockfd_);
continue;
}
uint16_t peerPort = ntohs(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
if(s > 0)
logMessage(NOTICE, "[%s:%d]# %s", peerIp.c_str(), peerPort, inbuffer_);
if (strcmp(inbuffer_, "quit") == 0)
{
quitSer = true;
}
else
{
sendto(serviceSock, inbuffer_, strlen(inbuffer_), 0,\
(const struct sockaddr *)&peer, sizeof(peer));
}
}
logMessage(DEBUG, "quit server...");
close(serviceSock);
}
}
TCP客户端与UDP不同的是,在进行网络通信之前需要对服务器发起连接请求,连接成功后才能进行网络通信。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "Log.hpp"
volatile bool quit = false;
static void Usage(std::string proc)
{
std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
<< std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
std::string serverIp = argv[1];
uint16_t serverPort = atoi(argv[2]);
// 1. 创建socket SOCK_STREAM
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0)
{
std::cerr << "socket: " << strerror(errno) << std::endl;
exit(SOCK_ERR);
}
// 2. connect,发起连接请求,你想谁发起请求呢??当然是向服务器发起请求喽
// 2.1 先填充需要连接的远端主机的基本信息
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverPort);
inet_aton(serverIp.c_str(), &server.sin_addr);
// 2.2 发起请求,connect 会自动帮我们进行bind!
if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0)
{
std::cerr << "connect: " << strerror(errno) << std::endl;
exit(CONN_ERR);
}
std::cout << "info : connect success: " << sock << std::endl;
std::string message;
while (!quit)
{
message.clear();
std::cout << "请输入你的消息>>> ";
std::getline(std::cin, message);
if (strcasecmp(message.c_str(), "quit") == 0)
quit = true;
ssize_t s = write(sock, message.c_str(), message.size());
logMessage(DEBUG, "write success...");
if (s > 0)
{
message.resize(1024);
ssize_t s = read(sock, (char *)(message.c_str()), 1024);
if (s > 0)
message[s] = 0;
std::cout << "Server Echo>>> " << message << std::endl;
}
else if (s <= 0)
{
break;
}
}
close(sock);
return 0;
}
由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。
注意: